ci: [CI-BROWSER-01] separate source and installed browser scopes

This commit is contained in:
Harvey Zhao committed 2026-09-14 18:11:00 +08:00
1 parent 765ddb816f
commit 492e58a345
20 files changed
+1920 -15

No files matched your search

+7 -1
View File
@@ -142,8 +142,12 @@ jobs:
run: |
yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas 2>&1 | tee refactor/.cache/ci/browser-packages.log
node --input-type=module -e 'import fs from "node:fs"; const { output } = JSON.parse(fs.readFileSync("refactor/.cache/packages/latest.json")); fs.appendFileSync(process.env.GITHUB_ENV, `ARTPLAYER_BROWSER_ARTIFACTS=${output}/browser-artifacts.json\n`);'
- name: Run all source browser checks
if: ${{ !cancelled() }}
run: yarn test:browser:source 2>&1 | tee refactor/.cache/ci/browser-source.log
- name: Run installed browser checks
run: yarn test:browser 2>&1 | tee refactor/.cache/ci/browser.log
if: ${{ !cancelled() }}
run: yarn test:browser:installed 2>&1 | tee refactor/.cache/ci/browser-installed.log
- name: Verify iframe cached history and interrupted navigation
run: yarn test:iframe-history 2>&1 | tee refactor/.cache/ci/iframe-history.log
- name: Compare installed performance and verify resource cleanup
@@ -156,6 +160,8 @@ jobs:
path: |
refactor/.cache/ci/
refactor/.cache/browser/
refactor/.cache/browser-source/
refactor/.cache/browser-installed/
refactor/.cache/iframe-history/
refactor/.cache/react-consumer-*/
refactor/.cache/vue-consumer-*/
+6 -4
View File
@@ -40,12 +40,12 @@
"test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor,browser-validation}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
"build:all": "yarn ci:build && yarn lint",
"check:toolchain": "node scripts/check-toolchain.mjs",
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
"lint:fix": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor,browser-validation}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --fix",
"check:plan": "node refactor/scripts/plan.mjs --check",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js test/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js test/dev-server.test.js test/pages-artifact.test.js test/site-notices.test.js test/vconsole-lifecycle.test.js",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js test/declarations.test.js test/editor-types.test.js test/coverage.test.js test/performance-report.test.js test/media-gate.test.js test/ci-summary.test.js test/package-runtime.test.js test/library-build.test.js test/site-loading.test.js test/documentation-pipeline.test.js test/site-build.test.js test/site-markdown.test.js test/site-editor.test.js test/plugin-scaffold.test.js test/dev-server.test.js test/pages-artifact.test.js test/site-notices.test.js test/vconsole-lifecycle.test.js test/browser-validation.test.js",
"test:baseline": "node --test refactor/scripts/*.test.mjs",
"ci:check": "yarn check:toolchain --strict && yarn check:commits --report && yarn check:impact --report && yarn check:ci && yarn test:contracts && yarn check:contracts --report && yarn check:plan && yarn check:release-ledger && yarn lint && yarn typecheck:library && yarn typecheck:scaffold && yarn typecheck:docs-tools && yarn check:docs-smoke && yarn check:editor-types && yarn check:llm && yarn check:vconsole && yarn check:site-notices && yarn typecheck:site-assets && yarn check:site-assets && yarn check:types && yarn typecheck && yarn typecheck:react && yarn lint:react && yarn typecheck:vue && yarn lint:vue && yarn test",
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:vconsole && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports",
@@ -146,7 +146,9 @@
"test:site-notices": "node --test test/site-notices.test.js",
"build:vconsole": "node scripts/build-vconsole.mjs",
"check:vconsole": "node scripts/build-vconsole.mjs --check",
"test:vconsole": "node --test test/vconsole-lifecycle.test.js"
"test:vconsole": "node --test test/vconsole-lifecycle.test.js",
"test:browser:source": "node scripts/browser-check.mjs source",
"test:browser:installed": "node scripts/browser-check.mjs installed"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+10
View File
@@ -0,0 +1,10 @@
import assert from 'node:assert/strict'
import process from 'node:process'
import { defineConfig } from '@playwright/test'
import base from './playwright.config.js'
import { browserScopeConfig, installedPackages } from './scripts/browser-validation/scope.ts'
import { verifyInstalledArtifacts } from './scripts/installed-artifacts.mjs'
assert(process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Use yarn test:browser:installed with checked tarballs')
verifyInstalledArtifacts(process.cwd(), process.env.ARTPLAYER_BROWSER_ARTIFACTS, installedPackages)
export default defineConfig(base, browserScopeConfig('installed'))
+8
View File
@@ -0,0 +1,8 @@
import assert from 'node:assert/strict'
import process from 'node:process'
import { defineConfig } from '@playwright/test'
import base from './playwright.config.js'
import { browserScopeConfig } from './scripts/browser-validation/scope.ts'
assert(!process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Use yarn test:browser:source to isolate source fixtures')
export default defineConfig(base, browserScopeConfig('source'))
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,47 @@
# CI-BROWSER-01: 源码与已安装浏览器范围
## 已复现问题
nodejs.yml 为全部 browser spec 设置 ARTPLAYER_BROWSER_ARTIFACTS。HLS、DASH、
ASR/Audio 等明确只验证源码或指定 SDK 产物,主动拒绝把它们称为已安装包检查。
用实际四包 map 运行 HLS capability 的 Chromium 用例,beforeAll 在 guard 失败,
尚未测试功能;旧报告和 trace 保留。相同环境经新 source 入口清除继承 map 后,
实际 SDK 初始化/销毁用例通过。
## 改造与保持的范围
新增严格 TS 的 browser-validation/scope.ts,负责包/测试清单、输入与输出规则。
两个薄 Playwright config 复用原三引擎/重试/超时/server配置;browser-check.mjs
校验输入、分配子进程环境,保留真实退出码/终止信号和调用元数据。新增两条 Yarn
命令,原 test:browser 不变。没有改播放器/插件生产行为、公开 API、依赖或锁文件。
source 默认保留全部 149 文件/4413 个用例;这是实际 collection 的数量,不是
4413 项测试通过。installed 包含当前 core/chapter/Ambilight/Canvas 已支持的8个
文件/225个用例,启动前核对四包安装摘要及源码/构建输入,保留未覆盖生态门槛。
两个报告目录分开,并记录局部 --grep/--project 等参数,禁止重定向报告/config
或允许零测试成功。CI 的默认命令没有局部筛选。
CI 使用两个 !cancelled() 步骤,不设置 continue-on-error;源码失败后仍运行已安装
检查保留证据,失败步骤仍导致 job 失败。报告始终上传。静态反例保护两个步骤的
存在、顺序、失败传播和各自目录。没有改变远端设置或触发 Actions。
## 验证与限制
可重跑命令、输入及结果见 [机器记录](../baselines/browser-scope-validation.json)。
原混用红例、source新绿例、全量collection和installed实际执行分别保存,不能混为
一个通过率。runner控制面测试使用真实Node子进程fixture,验证0/17退出码与环境
隔离;它们不替代真实浏览器。严格TS、lint、工作流反例、汇总测试与actionlint
分别记录。本机Playwright不代表真实移动设备、macOS Safari或远端托管runner。
全量source的4413项耗时未测,browser-smoke目前60分钟预算是否足够仍未知;需要
CI-01根据完整实测确定分片/并发,CI-04核对实际Actions取消与上传。这次不声称
整个CI或全生态发布门槛已通过。回退本任务会恢复混用配置,应保留此缺陷记录。
installed完整225项本机执行为224通过/1失败,真实失败码1由launcher保留;失败是
Chromium旧核心+新Chapter原生全屏后的hover opacity。初期进度曾误报前两引擎全绿,
最终以完整报告纠正。标题先出现后清空、opacity下降到0,鼠标坐标与全屏截图已
留存;几何/指针时序只是待证假设,不称为生产bug已修复。新增
CHAPTER-FULLSCREEN-GEOMETRY-01与PKG-CHAPTER-HOVER-01处理,不放宽断言。
该产品/测试同步问题仍阻止相关章节/完整CI验收,但不是这次输入隔离修复失败。
最终source三引擎capability检查和installed三引擎Range/产物路由检查分别3通过;
它们验证当前入口,不覆盖或替代225项中的失败。控制面/工作流/汇总50项通过。
+7
View File
@@ -1,5 +1,12 @@
# PR 检查和 Pages 操作说明
浏览器CI现在分别执行 `yarn test:browser:source` 和
`yarn test:browser:installed`。前者清除继承的安装map并保留全部spec;后者要求已
核验的core/chapter/Ambilight/Canvas安装,执行明确列出的installed子集。两步不
吞失败,普通失败后仍保存另一范围的证据,分别上传browser-source/browser-installed。
入口与报告规则见[维护说明](../scripts/browser-validation/README.md)。source全量
耗时/60分钟预算、其余插件安装矩阵与远端运行仍待CI-01/CI-04,不计为已通过。
## 本地命令
使用 toolchain-setup.md 的 Node/Yarn 环境与冻结安装。
+9 -5
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 256 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 258 项,范围 22 个包及工作区/示例。
状态:todo 50 / doing 19 / blocked 0 / done 187 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 51 / doing 19 / blocked 0 / done 188 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -20,7 +20,7 @@
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06, PKG-ASR-07, PKG-ASR-08, PKG-ASR-09 |
| artplayer-plugin-audio-track | 1.1.0 | PKG-AUDIO-01, PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-04, PKG-AUDIO-05, PKG-AUDIO-06 |
| artplayer-plugin-auto-thumbnail | 1.1.0 | PKG-AUTO-THUMB-01, PKG-AUTO-THUMB-02, PKG-AUTO-THUMB-03, PKG-AUTO-THUMB-04, PKG-AUTO-THUMB-05, PKG-AUTO-THUMB-06, PKG-AUTO-THUMB-07, PKG-AUTO-THUMB-08, PKG-AUTO-THUMB-09, PKG-AUTO-THUMB-10, PKG-AUTO-THUMB-11 |
| artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06 |
| artplayer-plugin-chapter | 1.1.0 | PILOT-01, PKG-CHAPTER-01, PKG-CHAPTER-02, PKG-CHAPTER-03, PKG-CHAPTER-04, PKG-CHAPTER-05, PKG-CHAPTER-06, PKG-CHAPTER-HOVER-01 |
| artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 |
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12 |
| artplayer-plugin-danmuku-mask | 1.1.0 | PKG-MASK-01, PKG-MASK-02, PKG-MASK-03, PKG-MASK-04, PKG-MASK-05, PKG-MASK-06, ENG-LINT-01 |
@@ -96,7 +96,8 @@
| CI-01 | workspace<br>增强兼容矩阵、并发缓存与 CI 报告 | DOC-10, ENG-08, ENG-09, ENG-10 | OS/Node/TS/浏览器与影响范围矩阵、缓存、超时、汇总检查和 artifact 报告 | 固定安装、失败/取消不误报、核心影响全生态;检查只读,失败证据可追溯 | H | doing |
| CI-02 | workspace<br>分离并改进 GitHub Pages 部署 | DOC-10, ENG-02, SITE-03 | Pages artifact 部署配置、旧路径/域名核对、预检和迁移恢复指南 | 部署只取受信任已验证产物;本地实现可验收,远端 source/环境和实际部署状态单独登记 | H | done |
| CI-03 | workspace<br>建立 npm 分包候选与发布工作流 | DOC-10, CI-01, REL-08, REL-04 | 候选准备、精确 artifact 发布配置、OIDC 评估、版本/tag/registry 预检和部分失败恢复 | 不自动发布;明确逐包信任前置和 dry run 限制,不能重建未验证内容或重发冲突版本;按版本清单校验各包下一 major 和预发布/正式 tag,保留旧核心支持范围 | H | todo |
| CI-04 | workspace<br>验收 GitHub 流水线与远端发布准入 | CI-01, CI-02, CI-03, SITE-06 | 静态/干净环境检查、真实 PR 正反例、候选 dry run、required checks/Pages/npm 必需配置状态及运维指南 | 必要 Actions 证据和远端配置核对齐全;缺失保持未完成,真实 publish/deploy 仍在授权发布步骤执行 | H | todo |
| CI-04 | workspace<br>验收 GitHub 流水线与远端发布准入 | CI-01, CI-02, CI-03, SITE-06, CI-BROWSER-01 | 静态/干净环境检查、真实 PR 正反例、候选 dry run、required checks/Pages/npm 必需配置状态及运维指南 | 必要 Actions 证据和远端配置核对齐全;缺失保持未完成,真实 publish/deploy 仍在授权发布步骤执行 | H | todo |
| CI-BROWSER-01 | workspace<br>分离源码与已安装产物浏览器验证范围 | ENG-05, ENG-07 | 完整源码入口、明确已安装包子集、分开的报告目录与失败传播 | 混用输入旧红新绿;源码默认保留所有spec,已安装入口严格校验四包来源;两类报告都保留,源码失败不能误报全绿;不代表完整远端或全包验收 | M | done |
## 2.1 早期试点
@@ -144,7 +145,8 @@
| PKG-CHAPTER-03 | artplayer-plugin-chapter<br>以 TypeScript 拆分内部职责与资源 | PKG-CHAPTER-02, ENG-07 | 以严格 TS 拆分区间计算、进度 DOM、事件生命周期与样式注入;公开声明消费在 04 闭环 | 不依赖新核心方法或大范围设置重构;旧 API/事件和资源通过,必要适配限包内 | M | done |
| PKG-CHAPTER-04 | artplayer-plugin-chapter<br>迁移自有源码和公开类型 | PKG-CHAPTER-03, ENG-04, ENG-06 | 承接 03 的自有源码 TS 化,完善公开 Chapters/Option/Result 与 update 推导,关闭声明模块解析和旧消费差异 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;处理 BASE-TYPE-01/03,保持运行时和新旧 TS 模块解析消费 | M | done |
| PKG-CHAPTER-05 | artplayer-plugin-chapter<br>验证新旧核心和组合 | PKG-CHAPTER-04, CORE-22 | chapter + quality/thumbnail、移动和全屏进度场景 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | doing |
| PKG-CHAPTER-06 | artplayer-plugin-chapter<br>验证分发并同步文档 | PKG-CHAPTER-05, ENG-07 | chapter.js 示例、产物和变更记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
| PKG-CHAPTER-06 | artplayer-plugin-chapter<br>验证分发并同步文档 | PKG-CHAPTER-05, ENG-07, PKG-CHAPTER-HOVER-01 | chapter.js 示例、产物和变更记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo |
| PKG-CHAPTER-HOVER-01 | artplayer-plugin-chapter<br>定位并修复原生全屏章节悬停时序 | PKG-CHAPTER-04, CI-BROWSER-01 | 原生全屏动态几何与指针时序复现、针对根因的修复和真实浏览器回归 | 解释 Chromium 旧核心+新章节的 opacity 失败;保留标题、缩略图和全屏退出断言,不以重试或扩大等待掩盖;区分测试同步与生产缺陷 | M | todo |
## 5 包迁移:artplayer-plugin-ambilight
@@ -666,3 +668,5 @@
- PKG-JASSUB-10: [记录](changes/2026-09-14-PKG-JASSUB-10-render-resources.md) [记录](baselines/jassub-render-validation.json) [记录](baselines/jassub-render-patch.json)
- ENG-13: [记录](changes/2026-09-14-ENG-13-observation-window.md) [记录](baselines/observation-window-validation.json)
- SITE-VCONSOLE-01: [记录](changes/2026-09-14-SITE-VCONSOLE-01-lifecycle.md) [记录](baselines/vconsole-lifecycle-validation.json)
- CI-BROWSER-01: [记录](changes/2026-09-14-CI-BROWSER-01-scopes.md) [记录](baselines/browser-scope-validation.json)
- PKG-CHAPTER-HOVER-01: [记录](changes/2026-09-14-CI-BROWSER-01-scopes.md) [记录](baselines/browser-scope-validation.json)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## CI-BROWSER-01 源码/安装浏览器范围隔离完成
复现全局安装map使HLS源码用例beforeAll直接失败;新source入口清除继承map后实际
SDK检查通过。两个TS配置入口分别保留完整source与已核验四包installed子集,CLI
保留真实退出码,报告目录分开;CI普通失败后继续收集另一范围,仍让job失败。
collection为source149文件4413项、installed8文件225项;前者不是完整执行通过。
installed实际224通过/1失败:Chromium旧核心+新Chapter原生全屏hover opacity。
该失败码1、trace、截图与初期误报纠正均留档,新增CHAPTER-FULLSCREEN-GEOMETRY-01
和PKG-CHAPTER-HOVER-01,不放宽断言。最终source/installed定向各3项通过;控制面/
工作流/汇总50项、TS、lint、actionlint、固定工具链和冻结安装通过。
见[变更](changes/2026-09-14-CI-BROWSER-01-scopes.md)与
[证据](baselines/browser-scope-validation.json)。258项:188 done、19 doing、51 todo。
CI-01仍需全量source耗时/分片及全包installed覆盖,CI-04远端/取消仍未完成;没有
push/部署/发布。下一步处理Chapter原生全屏几何与hover同步的新复现。
## PKG-AMBILIGHT-05 已安装产物组合检查点(仍doing)
隔离构建/打包流程显式纳入 Ambilight/Canvas,仓库外离线安装与冻结重装后校验
+1
View File
@@ -271,3 +271,4 @@
| JASSUB-RENDER-RESOURCES-01 | resolved / 已复现 | Main-thread async rendering leaves received ImageBitmaps open when native canvas operations throw | PKG-JASSUB-10 |
| ENG-PERF-OBS-01 | resolved / 已复现 | A nominal 350 ms resource observation can record only 349 ms and fail the required minimum | ENG-13 |
| VCONSOLE-LIFECYCLE-01 | resolved / 已复现 | vConsole delayed log, panel and scroller work outlives its owner | SITE-07, SITE-05, SITE-VCONSOLE-01 |
| CHAPTER-FULLSCREEN-GEOMETRY-01 | open / 已复现 | Chromium 原生全屏后章节 hover 标题曾出现后又隐藏 | PKG-CHAPTER-05, PKG-CHAPTER-HOVER-01 |
+18
View File
@@ -6193,6 +6193,24 @@
"test/browser/vconsole-lifecycle.spec.js"
],
"resolutionRationale": "Runtime lifecycle regression is fixed and old/native evidence retained. Original notices remain distributed; the distinct missing license body/dependency notices remain open under VENDOR-07 and SITE-07, without treating this runtime fix as clearance."
},
{
"id": "CHAPTER-FULLSCREEN-GEOMETRY-01",
"title": "Chromium 原生全屏后章节 hover 标题曾出现后又隐藏",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-CHAPTER-05",
"PKG-CHAPTER-HOVER-01"
],
"evidence": [
"refactor/changes/2026-09-14-CI-BROWSER-01-scopes.md",
"refactor/baselines/browser-scope-validation.json",
"test/browser/chapter-combinations.spec.js"
],
"compatibleResolution": "核对原生全屏布局/controls transition 与鼠标坐标的时序;根据实际根因修复测试同步或生产实现,保持旧接口。",
"closureCriteria": "保留原失败并取得可解释的旧红新绿/稳定真实浏览器证据,不用重试、延时扩大或移除 opacity 断言关闭。",
"workspaceState": "Installed 225-case run with two workers: 224 pass, published 5.4.0 core + candidate Chapter Chromium fullscreen case fails at hoverChapter opacity. Text assertion initially passes, opacity falls from 0.279 to 0 and title clears. Trace mouse=(640,665.9921875); screenshot is fullscreen1280x720. Geometry transition is a hypothesis; production defect versus test synchronization remains unverified."
}
]
}
+11 -2
View File
@@ -83,7 +83,7 @@ export function validateCIWorkflow(source) {
assert(probe?.run === command && !Object.hasOwn(probe, 'if'), 'Run the installed consumer on the selected Node')
consumerIndex = index + 1
}
assert(browser.steps.findIndex(step => step.run?.startsWith('yarn test:browser ')) > consumerIndex, 'Browser tools must run after canonical Node restoration')
assert(browser.steps.findIndex(step => step.run?.startsWith('yarn test:browser:source ')) > consumerIndex, 'Browser tools must run after canonical Node restoration')
const reactIndex = browser.steps.findIndex(step => step.run === 'yarn test:react-consumer 2>&1 | tee refactor/.cache/ci/react-consumer.log')
assert(reactIndex > consumerIndex && !Object.hasOwn(browser.steps[reactIndex], 'if'), 'Run React installed consumers after restoring canonical Node')
assert(browser.steps.some(step => step.uses?.startsWith('actions/upload-artifact@') && step.if === 'always()' && step.with.path.split('\n').includes('refactor/.cache/react-consumer-*/')), 'Retain React consumer failure evidence')
@@ -91,9 +91,18 @@ export function validateCIWorkflow(source) {
assert(vueIndex > consumerIndex && !Object.hasOwn(browser.steps[vueIndex], 'if'), 'Run Vue installed consumers after restoring canonical Node')
assert(browser.steps.some(step => step.uses?.startsWith('actions/upload-artifact@') && step.if === 'always()' && step.with.path.split('\n').includes('refactor/.cache/vue-consumer-*/')), 'Retain Vue consumer failure evidence')
const extraIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas '))
const engineIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:browser '))
const sourceEngineIndex = browser.steps.findIndex(step => step.run === 'yarn test:browser:source 2>&1 | tee refactor/.cache/ci/browser-source.log')
const engineIndex = browser.steps.findIndex(step => step.run === 'yarn test:browser:installed 2>&1 | tee refactor/.cache/ci/browser-installed.log')
assert(extraIndex > Math.max(consumerIndex, reactIndex, vueIndex) && extraIndex < engineIndex, 'Prepare installed Ambilight/Canvas after consumer probes and before browser checks')
assert(!Object.hasOwn(browser.steps[extraIndex], 'if') && browser.steps[extraIndex].run.includes('GITHUB_ENV') && browser.steps[extraIndex].run.includes('ARTPLAYER_BROWSER_ARTIFACTS='), 'Always select the additional installed browser artifact map')
assert(sourceEngineIndex > extraIndex && sourceEngineIndex < engineIndex, 'Retain complete source checks before the additional installed suite')
for (const index of [sourceEngineIndex, engineIndex]) {
assert.equal(browser.steps[index].if, '${{ !cancelled() }}', 'Both browser scopes run after ordinary failures without masking the failed job')
assert(!browser.steps[index]['continue-on-error'], 'Browser scope failures must fail CI')
}
const browserUpload = browser.steps.find(step => step.uses?.startsWith('actions/upload-artifact@'))
for (const directory of ['refactor/.cache/browser-source/', 'refactor/.cache/browser-installed/'])
assert(browserUpload.if === 'always()' && browserUpload.with.path.split('\n').includes(directory), 'Retain independent source and installed reports even on failure')
const pages = workflow.jobs.checks.steps.find(step => step.uses?.startsWith('actions/upload-pages-artifact@'))
assert.equal(pages?.if, 'inputs.pages-artifact && github.ref == \'refs/heads/master\' && matrix.os == \'ubuntu-latest\'', 'Only one trusted matrix leg can prepare Pages')
const prepareIndex = workflow.jobs.checks.steps.findIndex(step => step.id === 'pages')
+5
View File
@@ -35,6 +35,11 @@ for (const [name, mutate] of [
['skipped installed consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.includes('--expected-node 22.12.0')).if = 'false'],
['missing browser plugin installation', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include=')).run = 'echo omitted'],
['skipped browser plugin installation', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:package --include=')).if = 'false'],
['missing source browser checks', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:browser:source ')).run = 'echo omitted'],
['filtered source browser checks', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:browser:source ')).run += ' --grep subset'],
['source failure prevents installed evidence', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:browser:installed ')).if = 'success()'],
['ignored installed failure', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:browser:installed '))['continue-on-error'] = true],
['missing separate source evidence', w => { const upload = w.jobs['browser-smoke'].steps.find(s => s.uses?.startsWith('actions/upload-artifact@')); upload.with.path = upload.with.path.replace('refactor/.cache/browser-source/\n', '') }],
['browser runtime not restored', w => w.jobs['browser-smoke'].steps.find(s => s.id === 'restore-canonical-node').with = { 'node-version': '22.12.0', 'package-manager-cache': false }],
['skipped React consumer', w => w.jobs['browser-smoke'].steps.find(s => s.run?.startsWith('yarn test:react-consumer')).if = 'false'],
['removed React consumer', w => w.jobs['browser-smoke'].steps = w.jobs['browser-smoke'].steps.filter(s => !s.run?.startsWith('yarn test:react-consumer'))],
+44 -2
View File
@@ -813,7 +813,8 @@
"CI-01",
"CI-02",
"CI-03",
"SITE-06"
"SITE-06",
"CI-BROWSER-01"
],
"status": "todo",
"risk": "H",
@@ -1452,7 +1453,8 @@
],
"dependsOn": [
"PKG-CHAPTER-05",
"ENG-07"
"ENG-07",
"PKG-CHAPTER-HOVER-01"
],
"status": "todo",
"risk": "M",
@@ -5347,6 +5349,46 @@
"changes/2026-09-14-SITE-VCONSOLE-01-lifecycle.md",
"baselines/vconsole-lifecycle-validation.json"
]
},
{
"id": "CI-BROWSER-01",
"phase": "2.2 GitHub CI/CD",
"title": "分离源码与已安装产物浏览器验证范围",
"scope": [
"workspace"
],
"dependsOn": [
"ENG-05",
"ENG-07"
],
"status": "done",
"risk": "M",
"deliverable": "完整源码入口、明确已安装包子集、分开的报告目录与失败传播",
"acceptance": "混用输入旧红新绿;源码默认保留所有spec,已安装入口严格校验四包来源;两类报告都保留,源码失败不能误报全绿;不代表完整远端或全包验收",
"evidence": [
"changes/2026-09-14-CI-BROWSER-01-scopes.md",
"baselines/browser-scope-validation.json"
]
},
{
"id": "PKG-CHAPTER-HOVER-01",
"phase": "5 包迁移:artplayer-plugin-chapter",
"title": "定位并修复原生全屏章节悬停时序",
"scope": [
"artplayer-plugin-chapter"
],
"dependsOn": [
"PKG-CHAPTER-04",
"CI-BROWSER-01"
],
"status": "todo",
"risk": "M",
"deliverable": "原生全屏动态几何与指针时序复现、针对根因的修复和真实浏览器回归",
"acceptance": "解释 Chromium 旧核心+新章节的 opacity 失败;保留标题、缩略图和全屏退出断言,不以重试或扩大等待掩盖;区分测试同步与生产缺陷",
"evidence": [
"changes/2026-09-14-CI-BROWSER-01-scopes.md",
"baselines/browser-scope-validation.json"
]
}
]
}
+30
View File
@@ -0,0 +1,30 @@
import { spawn } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { browserInvocation, installedPackages } from './browser-validation/scope.ts'
import { verifyInstalledArtifacts } from './installed-artifacts.mjs'
const invocation = browserInvocation(process.argv[2], process.argv.slice(3), process.env)
const artifacts = invocation.scope === 'installed'
? verifyInstalledArtifacts(process.cwd(), invocation.env.ARTPLAYER_BROWSER_ARTIFACTS, installedPackages)
: null
fs.mkdirSync(invocation.directory, { recursive: true })
fs.writeFileSync(path.join(invocation.directory, 'invocation.json'), `${JSON.stringify({ scope: invocation.scope, args: invocation.args, node: process.versions.node, artifactMap: invocation.env.ARTPLAYER_BROWSER_ARTIFACTS || null, inputs: artifacts?.inputs || [] }, null, 2)}\n`)
const child = spawn(process.execPath, ['node_modules/@playwright/test/cli.js', ...invocation.args], { env: invocation.env, stdio: 'inherit', windowsHide: true })
let spawnError
let interrupted
child.on('error', (error) => {
spawnError = error.message
console.error(error)
})
child.on('close', (code, signal) => {
fs.writeFileSync(path.join(invocation.directory, 'result.json'), `${JSON.stringify({ scope: invocation.scope, exitCode: code, signal, interrupted, spawnError, args: invocation.args }, null, 2)}\n`)
process.exitCode = interrupted || spawnError ? 1 : code ?? 1
})
for (const signal of ['SIGINT', 'SIGTERM']) {
process.on(signal, () => {
interrupted = signal
child.kill(signal)
})
}
+36
View File
@@ -0,0 +1,36 @@
# Browser validation scopes
`yarn test:browser:source` runs every `test/browser/**/*.spec.js` under the normal
three-engine configuration. The launcher clears only ARTPLAYER_BROWSER_ARTIFACTS
from the child environment, so an installed-core map inherited from CI cannot
mislabel source/explicit-vendor SDK tests. The caller's environment is untouched.
Other deliberate fixture switches retain their existing meaning.
`yarn test:browser:installed` requires ARTPLAYER_BROWSER_ARTIFACTS from
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas`.
Both launcher and config validate the selected four packages' installed hashes
and source/build freshness. `scope.ts` lists the currently supported installed
test files. This is a growing installed subset, not full ecosystem acceptance.
Source checks remain complete when the installed list grows.
`scope.ts` owns the typed scope, package/test roster, argument policy and report
paths. The two thin Playwright configs preserve the base engine, retry, timeout
and server settings. `../browser-check.mjs` owns preflight, child environment,
process exit/signal forwarding and invocation/result metadata. The existing
`yarn test:browser` entry remains available for established ad hoc commands.
Reports use `refactor/.cache/browser-source/` and `browser-installed/`; one scope
cannot overwrite the other's JSON, HTML, traces or screenshots. Archive existing
local reports before rerunning the same scope. Invocation JSON records arguments
and artifact identity; result JSON records the child's actual exit. Preflight
errors occur before starting Playwright and remain in the terminal/CI tee log.
Listing tests with `--list` is collection evidence only, not playback validation.
Local `--grep`, file names, workers and project options are supported for diagnosis;
config/output/report overrides and passing with zero tests are rejected.
CI executes both scopes with `if: !cancelled()` and no continue-on-error. An
ordinary source failure still permits installed evidence, while the failed step
keeps the job failed. Both report directories upload with always(). Node/runtime
and framework consumers keep their earlier fixed two-package scope. Full matrix
timing, remote cancellation behavior and runner execution remain CI-01/CI-04
acceptance items; local configuration checks do not establish those results.
+39
View File
@@ -0,0 +1,39 @@
import type { PlaywrightTestConfig } from '@playwright/test'
import assert from 'node:assert/strict'
export type BrowserScope = 'source' | 'installed'
export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas']
export const installedTests = [
'playback.spec.js',
'lifecycle.spec.js',
'initialization.spec.js',
'chapter.spec.js',
'chapter-combinations.spec.js',
'ambilight-lifecycle.spec.js',
'ambilight-proxy.spec.js',
'canvas-lifecycle.spec.js',
]
export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) {
assert(scope === 'source' || scope === 'installed', 'Choose source or installed browser scope')
assert(!args.some(arg => /^(?:--(?:config|reporter|output|pass-with-no-tests)(?:=|$)|-c)/.test(arg)), 'Do not override browser scope configuration or evidence output')
const env: NodeJS.ProcessEnv = { ...environment, ARTPLAYER_BROWSER_SCOPE: scope }
if (scope === 'source')
delete env.ARTPLAYER_BROWSER_ARTIFACTS
else
assert(env.ARTPLAYER_BROWSER_ARTIFACTS, 'Installed browser checks require ARTPLAYER_BROWSER_ARTIFACTS')
return { scope, env, args: ['test', `--config=playwright.${scope}.config.js`, ...args], directory: `refactor/.cache/browser-${scope}` }
}
export function browserScopeConfig(scope: BrowserScope): PlaywrightTestConfig {
const directory = `refactor/.cache/browser-${scope}`
return {
testMatch: scope === 'source' ? ['**/*.spec.js'] : installedTests.map(file => `**/${file}`),
outputDir: `${directory}/results`,
reporter: [
['list'],
['html', { outputFolder: `${directory}/html`, open: 'never' }],
['json', { outputFile: `${directory}/report.json` }],
],
}
}
+2 -1
View File
@@ -29,6 +29,7 @@
"build-i18n.js",
"build-docs.js",
"../packages/artplayer-vitepress/build/**/*.ts",
"build-vconsole.mjs"
"build-vconsole.mjs",
"browser-validation/**/*.ts"
]
}
+57
View File
@@ -0,0 +1,57 @@
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'
// eslint-disable-next-line test/no-import-node-test -- Validate real runner exit codes and scope boundaries.
import test from 'node:test'
import { browserInvocation, browserScopeConfig, installedTests } from '../scripts/browser-validation/scope.ts'
test('Source invocation clears inherited artifact selection without changing caller environment', () => {
const environment = { ARTPLAYER_BROWSER_ARTIFACTS: '/installed/map.json', KEEP: 'value' }
const source = browserInvocation('source', ['--project=chromium'], environment)
assert.equal(source.env.ARTPLAYER_BROWSER_ARTIFACTS, undefined)
assert.equal(source.env.KEEP, 'value')
assert.equal(environment.ARTPLAYER_BROWSER_ARTIFACTS, '/installed/map.json')
assert.deepEqual(browserScopeConfig('source').testMatch, ['**/*.spec.js'])
const installed = browserInvocation('installed', [], environment)
assert.equal(installed.env.ARTPLAYER_BROWSER_ARTIFACTS, environment.ARTPLAYER_BROWSER_ARTIFACTS)
assert.notEqual(source.directory, installed.directory)
assert(installedTests.includes('ambilight-proxy.spec.js'))
assert(installedTests.includes('canvas-lifecycle.spec.js'))
for (const file of installedTests)
assert(fs.existsSync(path.resolve('test/browser', file)))
})
test('Browser invocation rejects ambiguous scopes and output/configuration overrides', () => {
assert.throws(() => browserInvocation('unknown', [], {}), /Choose/)
assert.throws(() => browserInvocation('installed', [], {}), /require ARTPLAYER/)
for (const flag of ['--config=other.js', '--config', '-c', '-cother.js', '--reporter=json', '--output=elsewhere', '--pass-with-no-tests'])
assert.throws(() => browserInvocation('source', [flag], {}), /Do not override/)
})
for (const code of [0, 17]) {
test(`Browser runner preserves actual child exit ${code} and source input identity`, () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'artplayer-browser-runner-'))
try {
const cli = path.join(root, 'node_modules/@playwright/test/cli.js')
fs.mkdirSync(path.dirname(cli), { recursive: true })
fs.writeFileSync(cli, `require('node:assert/strict').equal(process.env.ARTPLAYER_BROWSER_ARTIFACTS, undefined); process.exit(${code});`)
const run = spawnSync(process.execPath, [path.resolve('scripts/browser-check.mjs'), 'source'], { cwd: root, env: { ...process.env, ARTPLAYER_BROWSER_ARTIFACTS: '/unwanted/map.json' }, encoding: 'utf8', windowsHide: true, timeout: 10000 })
assert.equal(run.status, code, run.stderr)
const directory = path.join(root, 'refactor/.cache/browser-source')
const invocation = JSON.parse(fs.readFileSync(path.join(directory, 'invocation.json')))
const result = JSON.parse(fs.readFileSync(path.join(directory, 'result.json')))
assert.equal(invocation.artifactMap, null)
assert.equal(result.exitCode, code)
assert.equal(result.scope, 'source')
assert(!fs.existsSync(path.join(root, 'refactor/.cache/browser-installed')))
}
finally {
assert.equal(path.dirname(root), path.resolve(os.tmpdir()))
assert(path.basename(root).startsWith('artplayer-browser-runner-'))
fs.rmSync(root, { recursive: true, force: true })
}
})
}
+9
View File
@@ -1,5 +1,14 @@
# Browser regression entry
CI uses `yarn test:browser:source` for the complete source suite and
`yarn test:browser:installed` for the reviewed installed-package subset. The source
launcher clears inherited ARTPLAYER_BROWSER_ARTIFACTS; the installed launcher and
config verify all required package hashes and build inputs. Reports live separately
under browser-source/ and browser-installed/. See
[scope maintenance](../../scripts/browser-validation/README.md) before adding an
installed test or interpreting `--list` output as execution evidence. Existing
`yarn test:browser` commands retain their ad hoc behavior and browser/ output.
`library-development.spec.js` starts the actual JS dev CLI and typed runner on an
atomically assigned fixture port. It checks generated TS/Less/SVG/inline-worker assets,
automatic reload on edits and recovery after a compiler error in all three