
OpenScreen 的 2 套 Vitest 配置:让录屏导出管线在 jsdom 与无头 Chromium 里分别跑起测试【免费下载链接】android-async-httpAn asynchronous, callback-based Http client for Android built on top of Apaches HttpClient libraries.项目地址: https://gitcode.com/gh_mirrors/an/android-async-httpOpenScreen 是一个基于 Electron React 的录屏与视频编辑工具,导出管线依赖 WebCodecs、MediaRecorder、OffscreenCanvas 等真实浏览器 API,纯 Node 环境验证不了。它的 Vitest 测试体系给出 2 套配置:jsdom 跑单元测试,无头 Chromium 跑浏览器测试。判断标准:哪些代码进 jsdom,哪些必须进真浏览器划分只靠一个问题:被测代码是否触碰 jsdom 无法实现的浏览器 API。碰了走浏览器测试,没碰留在 jsdom 单元测试里。两套配置按文件后缀把用例切成互斥的两组,任何一次vitest调用只会命中其中一组:判断依据配置文件文件后缀运行命令纯逻辑:几何计算、数据转换、i18n 键值覆盖,不依赖真实浏览器 APIvitest.config.ts*.test.ts(x)、*.spec.ts(x)npm run test依赖 WebCodecs、OffscreenCanvas、WebGL 等 jsdom 缺失的 APIvitest.browser.config.ts*.browser.test.ts(x)npm run test:browser互斥不是靠口头约定,而是写死在配置里:单元测试一侧的exclude显式排掉src/**/*.browser.test.{ts,tsx},所以npm run test永远不会误跑浏览器用例。单元测试一侧:先讲场景,再拆配置 三类交给 jsdom 的代码纯几何计算:导出画布上屏幕画面与摄像头窗口的布局函数computeCompositeLayout,输入三个尺寸、输出坐标矩形,天然适合断言;i18n 键值覆盖:遍历 13 个语言包,逐个断言 tutorial 帮助文案的键存在、是字符串且非空;Electron 主进程逻辑:include同时覆盖src与electron,electron/ipc/下RecordingStreamRegistry的流式落盘逻辑就在这套配置里测。配置拆解:environment、include 与别名import path from node:path; import { defineConfig } from vitest/config; export default defineConfig({ test: { globals: true, environment: jsdom, include: [{src,electron}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}], exclude: [src/**/*.browser.test.{ts,tsx}], }, resolve: { alias: { : path.resolve(__dirname, src), }, }, });拆开看三处:environment: jsdom:用模拟 DOM 承接全部纯逻辑测试,全程无浏览器进程参与;include/exclude:匹配两个目录下的*.test.*、*.spec.*,再由exclude把浏览器用例剔出去,完成与另一套配置的隔离;别名指向src/,与浏览器侧保持一致,写import { SUPPORTED_LOCALES } from /i18n/config而不是一串../../。globals: true虽然开着,测试文件里仍显式import { describe, expect, it } from vitest,两种写法并存,没有强求统一。文件放置约定,加一个布局计算例子测试文件与源码同目录放置,src/lib/compositeLayout.ts旁边就是compositeLayout.test.ts;也可以收进同级__tests__/子目录集中管理,如src/i18n/__tests__/。布局计算是最典型的 jsdom 单元测试对象,下面的例子只验一条边界——摄像头窗口不得越出画布:import { describe, expect, it } from vitest; import { computeCompositeLayout } from ./compositeLayout; describe(computeCompositeLayout, () { it(摄像头窗口不得越出画布右缘与下缘, () { const layout computeCompositeLayout({ canvasSize: { width: 1920, height: 1080 }, screenSize: { width: 1920, height: 1080 }, webcamSize: { width: 1280, height: 720 }, }); const rect layout!.webcamRect!; expect(rect.x rect.width).toBeLessThanOrEqual(1920); expect(rect.y rect.height).toBeLessThanOrEqual(1080); }); });同一文件里还有更多断言:预设值被钳制在 10–50 区间、横竖屏画布下摄像头窗口面积保持一致、圆形蒙版强制宽高相等。写同类测试时,一条边界一个 it 是最省心的组织方式。浏览器测试一侧:真 Chromium 接手 jsdom 做不到的事jsdom 覆盖不到的 APIjsdom 里没有VideoDecoder/VideoEncoder,没有MediaRecorder,没有OffscreenCanvas,更没有 WebGL 上下文。围绕这些 API 的录屏采集、软解码、Pixi.js 渲染,以及最终产出真实Blob的导出流程,都必须放进真浏览器验证——WebCodecs 测试全部落在这一侧,也就是 Vitest 浏览器测试。配置:每个参数为什么存在import path from node:path; import { playwright } from vitest/browser-playwright; import { defineConfig } from vitest/config; export default defineConfig({ test: { include: [src/**/*.browser.test.{ts,tsx}], browser: { enabled: true, provider: playwright({ launch: { // 无头 CI 没有 GPU,启用 SwiftShader 软件渲染 args: [--enable-unsafe-swiftshader, --use-glswiftshader], }, }), headless: true, instances: [{ browser: chromium }], }, testTimeout: 120_000, hookTimeout: 30_000, }, resolve: { alias: { : path.resolve(__dirname, src), }, }, assetsInclude: [**/*.webm], });浏览器侧配置不长(vitest.browser.config.ts),但每个参数都有存在的理由:️SwiftShader 参数解读:--enable-unsafe-swiftshader与--use-glswiftshader让 Chromium 在无 GPU 时退回 CPU 渲染。Pixi.js 硬性依赖 WebGL,CI 机器普遍没有显卡,缺这两个参数渲染直接失败;本地有 GPU 的环境可以省,CI 不行;超时:testTimeout: 120_000给单个测试留 120 秒,hookTimeout: 30_000给 hook 留 30 秒。真实导出动辄几十秒,默认值不够用;用例侧则用小画布、低码率固件把耗时压回去,两边配合;assetsInclude: [**/*.webm]:声明.webm为可导入静态资源,配合?url后缀,dev server 会给出一个真实 Chromium 能用video/fetch拉取的 URL,这一步 jsdom 做不到;headless: trueinstances: [{ browser: chromium }]:Playwright 驱动无头 Chromium 测试,不起窗口,渲染有 SwiftShader 兜底。 固件:tests/fixtures/ 与 ?url 导入sample.webm、sample-inflated-duration.webm等媒体固件统一放在 tests/fixtures/,导入方式固定为:import sampleVideoUrl from ../../../tests/fixtures/sample.webm?url;示例:320×180 小画布导出 MP4import { describe, expect, it } from vitest; import sampleVideoUrl from ../../../tests/fixtures/sample.webm?url; import { VideoExporter } from ./videoExporter; describe(VideoExporter, () { it(从真实 webm 固件导出 MP4, async () { const exporter new VideoExporter({ videoUrl: sampleVideoUrl, width: 320, height: 180, frameRate: 15, bitrate: 1_000_000, wallpaper: #1a1a2e, zoomRegions: [], showShadow: false, shadowIntensity: 0, showBlur: false, cropRegion: { x: 0, y: 0, width: 1, height: 1 }, }); const result await exporter.export(); expect(result.success, result.error).toBe(true); expect(result.blob).toBeInstanceOf(Blob); }); });参数不是随手取的:导出耗时与画布面积、帧率、码率成正比,320×180、15 fps、1 Mbps 是能验证产物、又不拖慢 CI的取值。用例文件没有止步于Blob 存在,还做了三处深化:MP4 魔数校验:把 blob 解码为字节,检查bytes.slice(4, 8)解码后等于ftyp,确认产物是可识别的 MP4 容器而非空壳;进度事件断言:通过onProgress回调收集ExportProgress事件,断言存在phase finalizing的事件且末位percentage为 100,把 UI 进度反馈也纳入验证;负向路径:传入不存在的壁纸/wallpapers/does-not-exist.jpg,断言导出以BackgroundLoadError拒绝且错误对象携带出错的url,钉死加载失败不静默回退黑底的行为。GIF 导出用例走同样套路,另用/^GIF8[79]a/校验文件头。本地运行:从零到跑通按顺序三步:一次性npm run test:browser:install——Vitest 不自带浏览器,这个脚本(底层是playwright install --with-deps chromium-headless-shell)把 Chromium headless shell 连同系统依赖一次下好,同一台机器只需执行一次;日常单元测试:npm run test跑一轮,npm run test:watch开监听;日常浏览器测试:npm run test:browser。四个脚本入口都在package.json里,命名即分工。收尾jsdom 一侧守逻辑正确性,无头 Chromium 一侧守真实产出物,文件后缀决定归属。配置分别落在vitest.config.ts与vitest.browser.config.ts,固件统一放tests/fixtures/。动手前先问一句:这段代码碰不碰真实浏览器 API,答案直接决定文件后缀。【免费下载链接】android-async-httpAn asynchronous, callback-based Http client for Android built on top of Apaches HttpClient libraries.项目地址: https://gitcode.com/gh_mirrors/an/android-async-http创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考