test(ci): [CI-01] checkpoint verified Ads installed browser coverage

This commit is contained in:
Harvey Zhao committed 2026-09-15 01:31:28 +08:00
1 parent 18215b97c0
commit a03e5acc50
18 files changed
+2939 -25

No files matched your search

+1 -1
View File
@@ -140,7 +140,7 @@ jobs:
# These extra installed packages are required by the browser helpers.
# Other ecosystem installation/SDK gates remain tracked by CI-01.
run: |
yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip 2>&1 | tee refactor/.cache/ci/browser-packages.log
yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip,artplayer-plugin-ads 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() }}
@@ -104,6 +104,15 @@ regressions; `yarn test:browser test/browser/ads.spec.js` covers real local medi
cores. `yarn typecheck` checks the strict package configuration. `yarn build artplayer-plugin-ads`
generates main/legacy/ESM through the repository build. Do not hand-edit dist or copied docs assets.
The CI installed browser subset also runs ads.spec.js and ads-ui.spec.js. Their
shared browser-candidate helper requires a fresh, hash-verified Ads tarball when
ARTPLAYER_BROWSER_ARTIFACTS is set; it rejects ARTPLAYER_ADS_ARTIFACT overrides
and never falls back to source on an invalid installation. Each candidate case
attaches its installed file/archive identity. Without a map, existing explicit
artifact or source-build selection remains available. See the repository
scripts/browser-validation/README.md for the six-package preparation command.
This integration does not complete the separate Ads type, visibility or device gates.
Tests distinguish actual media decoding from injected errors/controlled clocks. Baseline and
open release gates live in [Ads validation](../../refactor/ads-validation.md). Public declaration
reconciliation is recorded in PKG-ADS-04; complete historical distribution and demo validation
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,55 @@
# CI-01 接入 Ads 安装产物与完整六包回归
安装范围由五包扩展到六包,新增 Ads 的真实媒体和 UI 用例。整个 installed 入口
运行 426 项,425 通过、1 项 WebKit 章节清晰度切换失败,退出码 1;不是绿色 CI。
Ads 两文件在三个引擎中共 171 项全部通过,其中 108 项使用已校验的候选安装包,
其余保留真实旧版 Ads 对照。CI-01 与各包最终设备/分发门槛继续开放。
## 实现与边界
- package-check 的额外包名单接入 Ads 已有冻结契约,先核验五份历史归档、30 个
成员及来源,再按现有独立源码构建、pack、安装、冻结复装和完整成员校验流程执行。
- browser-validation 的统一名单、两个 Ads spec、CI 准备命令和工作流校验同步。
Node/类型通用消费者仍只覆盖 core/chapter,不把浏览器扩展冒充全包类型验收。
- 新增 test/helpers/browser-candidate.js:安装 map 存在时验证来源/产物并读取实际
安装文件,拒绝同时指定单包路径,失败不退回源码。无 map 时保留显式产物与
源码构建入口。候选附件记录文件、tarball、源码指纹;旧版附件保留原发布输入。
- 缺失 map 和冲突覆盖的 Node 反例已加入现有测试。没有修改 Ads/核心生产代码、
packageManager、锁文件、测试超时或断言。没有新增依赖。
维护入口为 [浏览器范围说明](../../scripts/browser-validation/README.md)、
[安装检查](../../test/package/README.md) 和 Ads ARCHITECTURE.md。
## 实测结果
Windows x64,Node 24.21.0、Yarn 1.22.22;Chromium 153.0.8010.12、Firefox
155.0、WebKit 26.6。包源码 HEAD 为 18215b97c0674667a5c451b341e922ed39f6b872,
本轮未提交的测试/工具差异另以指纹绑定。run-s8MkZT 的六包安装通过,通用
core/chapter 36 运行时、5 旧类型模式、8 精确类型模式通过。
`yarn test:browser:installed --workers=2` 无文件、引擎或 grep 过滤,无重试,
Playwright 耗时 432.371 秒。13 文件共 426 项:425 通过、1 失败、0 跳过。
Canvas/PiP 包含 12 项原生窗口播放及 6 项 WebKit 不支持记录;不能把后者算成
原生能力通过。Ads 解码、静音、倒计时、恢复主片、失败/拒绝、原生全屏、多实例
和真实 popup 用例均通过。没有执行移动后台/最小化或 IMA/VAST 验收。
源码入口另定向执行 Chromium 的 candidate core/candidate Ads 真实视频用例:
1/1 通过。启动时故意继承 installed map,source 入口清除它;附件确认为
source-build。这证明本次修改保留该开发路径,不代表重跑全部 source 测试。
Node 工程测试 46/46、scoped ESLint、library 严格类型、工作流契约、actionlint
1.7.12(未调用 shellcheck/pyflakes)与严格工具链通过。远端 Actions 尚未运行。
结果、输入附件、命令与日志摘要见 [机器证据](../baselines/ci-ads-installed-validation.json)。
## 保留的章节失败
失败是 WebKit 的 candidate core/candidate chapter 清晰度切换,在等待
`!video.seeking && video.readyState >= 2` 时超时。restart 在页面 3552 ms 已触发,
第一轮 seeked 在 3597 ms;trace 中第一次读取返回 false,第二次调用耗时
9939.104 ms 后仍返回 false。之后状态附件记录第二轮 seeking、canplay,媒体没有
error。它不同于之前“没及时观察到 restart”的具体阶段,不能据此直接断言同一
根因、网络失败或 Ads 回归。调用延迟及底层媒体/渲染原因仍待 PKG-CHAPTER-05
调查,补入 CHAPTER-TIMING-01,保持 open。
原始报告/trace 保存在 ci01-ads-installed-report;没有通过重复、增大超时或跳过
制造全绿。撤销本检查点可恢复旧五包范围,不涉及生产源码回退。未推送或发布。
+1 -1
View File
@@ -2,7 +2,7 @@
浏览器CI现在分别执行 `yarn test:browser:source` 和
`yarn test:browser:installed`。前者清除继承的安装map并保留全部spec;后者要求已
核验的core/chapter/Ambilight/Canvas/Document PiP安装,执行明确列出的installed子集。两步不
核验的core/chapter/Ambilight/Canvas/Document PiP/Ads安装,执行明确列出的installed子集。两步不
吞失败,普通失败后仍保存另一范围的证据,分别上传browser-source/browser-installed。
入口与报告规则见[维护说明](../scripts/browser-validation/README.md)。source全量
耗时/60分钟预算、其余插件安装矩阵与远端运行仍待CI-01/CI-04,不计为已通过。
+1 -1
View File
@@ -508,7 +508,7 @@
- ENG-09: [记录](changes/2026-09-12-ENG-09-integration.md) [记录](baselines/engineering-integration.json)
- ENG-10: [记录](changes/2026-09-10-ENG-10-test-reliability.md) [记录](test-reliability.md)
- ENG-11: [记录](build-analysis.md) [记录](baselines/bundle-attribution.json) [记录](changes/2026-09-11-ENG-11-build-analysis.md)
- CI-01: [记录](ci-setup.md) [记录](changes/2026-09-13-CI-01-matrix-summary.md) [记录](baselines/ci-matrix-validation.json) [记录](changes/2026-09-13-CI-01-node-consumers.md) [记录](baselines/node-consumer-validation.json) [记录](changes/2026-09-14-CI-01-installed-full.md) [记录](baselines/ci-installed-full-validation.json) [记录](changes/2026-09-14-CI-01-source-chromium.md) [记录](baselines/ci-source-chromium-validation.json)
- CI-01: [记录](ci-setup.md) [记录](changes/2026-09-13-CI-01-matrix-summary.md) [记录](baselines/ci-matrix-validation.json) [记录](changes/2026-09-13-CI-01-node-consumers.md) [记录](baselines/node-consumer-validation.json) [记录](changes/2026-09-14-CI-01-installed-full.md) [记录](baselines/ci-installed-full-validation.json) [记录](changes/2026-09-14-CI-01-source-chromium.md) [记录](baselines/ci-source-chromium-validation.json) [记录](changes/2026-09-15-CI-01-ads-installed.md) [记录](baselines/ci-ads-installed-validation.json)
- CI-02: [记录](changes/2026-09-14-CI-02-pages-artifact.md) [记录](baselines/pages-artifact-validation.json) [记录](pages-deployment.md)
- PILOT-01: [记录](changes/2026-09-10-PILOT-01-chapter.md) [记录](baselines/pilot-validation.json)
- CORE-01: [记录](changes/2026-09-10-CORE-01-typed-utils.md) [记录](baselines/core-utils-validation.json)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## CI-01 Ads 接入安装矩阵,完整回归保留章节失败
六包统一 installed 范围实测 426 项:425 通过、1 项 WebKit 章节切画质等待失败;
新增 Ads 171 项全过,108 项确认加载已验证候选 tarball。源码模式定向真实播放
1/1、Node 工程 46/46、类型/CI 静态检查通过。失败 trace 的媒体状态读取耗时
9.94 秒,原因未确认,CHAPTER-TIMING-01 保持 open;没有扩大超时或重跑覆盖。
详见[记录](changes/2026-09-15-CI-01-ads-installed.md)。CI-01 继续 doing,
完成数保持 196/262;下一步调查章节读取延迟并继续其余包安装覆盖。
## MOD-01 Bun 隔离安装评估完成,保留 Yarn
固定 Bun 1.4.2 与 Yarn 1.22.22 在仓库外同提交安装。Bun 初次冻结迁移失败,
+5 -2
View File
@@ -2916,10 +2916,13 @@
"test/browser/chapter-combinations.spec.js",
"refactor/changes/2026-09-12-PKG-CHAPTER-05-combinations.md",
"refactor/changes/2026-09-14-PKG-CHAPTER-05-timing.md",
"refactor/baselines/chapter-timing-observations.json"
"refactor/baselines/chapter-timing-observations.json",
"refactor/baselines/ci-ads-installed-validation.json",
"refactor/changes/2026-09-15-CI-01-ads-installed.md"
],
"compatibleResolution": "记录原生事件和绝对/相对时序,进一步区分媒体驱动耗时、夹具和核心源操作;未证明原因前不改核心、不扩大超时或重复到绿灯。",
"closureCriteria": "确认先前超时的来源并有对应可靠回归;后续单次通过不足以关闭。"
"closureCriteria": "确认先前超时的来源并有对应可靠回归;后续单次通过不足以关闭。",
"workspaceState": "Six-package installed run: WebKit candidate/core Chapter failed after restart while waiting for non-seeking readyState>=2. A trace evaluation took 9939.104 ms and returned false; initial seeked had already fired, followed later by another seeking/canplay. This is a different observed phase from the previous restart timeout; common root cause remains unconfirmed. Keep both failures and investigate without relaxing timeouts."
},
{
"id": "DASH-SDK-01",
+2 -2
View File
@@ -90,10 +90,10 @@ export function validateCIWorkflow(source) {
const vueIndex = browser.steps.findIndex(step => step.run === 'yarn test:vue-consumer 2>&1 | tee refactor/.cache/ci/vue-consumer.log')
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,artplayer-plugin-document-pip '))
const extraIndex = browser.steps.findIndex(step => step.run?.startsWith('yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip,artplayer-plugin-ads '))
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/Document PiP after consumer probes and before browser checks')
assert(extraIndex > Math.max(consumerIndex, reactIndex, vueIndex) && extraIndex < engineIndex, 'Prepare installed Ambilight/Canvas/Document PiP/Ads 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]) {
+3 -1
View File
@@ -762,7 +762,9 @@
"changes/2026-09-14-CI-01-installed-full.md",
"baselines/ci-installed-full-validation.json",
"changes/2026-09-14-CI-01-source-chromium.md",
"baselines/ci-source-chromium-validation.json"
"baselines/ci-source-chromium-validation.json",
"changes/2026-09-15-CI-01-ads-installed.md",
"baselines/ci-ads-installed-validation.json"
]
},
{
+19 -2
View File
@@ -14,12 +14,21 @@ also retains explicit historical and committed-artifact cases; its name does not
promise that every dependency is rebuilt from current source in every test.
`yarn test:browser:installed` requires ARTPLAYER_BROWSER_ARTIFACTS from
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip`.
Both launcher and config validate the selected five packages' installed hashes
`yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip,artplayer-plugin-ads`.
Both launcher and config validate the selected six 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.
Ads media/lifecycle and UI files use `test/helpers/browser-candidate.js` to select
the verified installed plugin whenever an artifact map is present. An additional
ARTPLAYER_ADS_ARTIFACT override is rejected instead of mixing candidates. Missing,
stale or changed installations fail before source compilation; each candidate
case attaches installed file, archive and source identities. Explicit published
Ads/core controls still use their frozen old inputs. Without a map the same files
keep source or explicitly selected artifact behavior. This does not cover VAST,
Ads editor types, the separate native-visibility suite or physical devices.
`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,
@@ -40,6 +49,14 @@ per-engine/file counts and limitations; this is not full ecosystem or remote CI.
Local `--grep`, file names, workers and project options are supported for diagnosis;
config/output/report overrides and passing with zero tests are rejected.
The six-package Ads expansion ran 426 cases across 13 files: 425 passed and one
WebKit candidate Chapter quality-readiness check failed. All 171 Ads cases passed,
including 108 with verified candidate tarballs; this is not a green full suite.
The trace includes a 9.94-second media-state evaluation and remains open under
CHAPTER-TIMING-01. A separate targeted Chromium source-build Ads case passed.
See `../../refactor/baselines/ci-ads-installed-validation.json`; old reports and
the actual exit 1 remain intact, without retries or increased timeouts.
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
+3 -1
View File
@@ -2,7 +2,7 @@ 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', 'artplayer-plugin-document-pip']
export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas', 'artplayer-plugin-document-pip', 'artplayer-plugin-ads']
export const installedTests = [
'playback.spec.js',
'lifecycle.spec.js',
@@ -15,6 +15,8 @@ export const installedTests = [
'canvas-lifecycle.spec.js',
'canvas-subtitles.spec.js',
'canvas-dpip.spec.js',
'ads.spec.js',
'ads-ui.spec.js',
]
export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) {
+1
View File
@@ -76,6 +76,7 @@ export async function checkPackages({ release = false, include = [] } = {}) {
'artplayer-plugin-ambilight': async () => (await import('../refactor/scripts/ambilight-contract.mjs')).verifyAmbilightContract(),
'artplayer-proxy-canvas': async () => (await import('../refactor/scripts/canvas-contract.mjs')).verifyCanvasContract(),
'artplayer-plugin-document-pip': async () => (await import('../refactor/scripts/dpip-contract.mjs')).verifyDpipContract(),
'artplayer-plugin-ads': async () => (await import('../refactor/scripts/ads-contract.mjs')).verifyAdsContract(),
}
assert(new Set(include).size === include.length, 'Duplicate additional package')
const historicalFiles = new Map()
+6 -6
View File
@@ -1,20 +1,20 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let candidate
let provenance
test.beforeAll(async () => {
candidate = process.env.ARTPLAYER_ADS_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_ADS_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-ads', 'umd')
const input = await browserCandidate('artplayer-plugin-ads', process.env.ARTPLAYER_ADS_ARTIFACT)
candidate = input.code
provenance = input.provenance
})
async function setup(page, core, testInfo, option = {}) {
await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: candidate })
await testInfo.attach('ads-ui-inputs', { contentType: 'application/json', body: JSON.stringify({ core, artifact: process.env.ARTPLAYER_ADS_ARTIFACT || 'workspace source build', sha256: hash(candidate) }) })
await testInfo.attach('ads-ui-inputs', { contentType: 'application/json', body: JSON.stringify({ core, artifact: provenance, sha256: hash(candidate) }) })
await page.evaluate((option) => {
window.adsEvents = []
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, mutex: false, plugins: [window.artplayerPluginAds({ html: 'ad', totalDuration: 60, playDuration: 0, ...option })] })
+6 -5
View File
@@ -2,12 +2,13 @@ import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { browserCandidate } from '../helpers/browser-candidate.js'
import { expect, test } from './fixtures.js'
let published
let candidate
let release
let provenance
test.beforeAll(async () => {
release = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/ads-release.json', import.meta.url))).release
@@ -15,9 +16,9 @@ test.beforeAll(async () => {
const bytes = readMember(await ensureArchive(release), member)
assert.equal(hash(bytes), release.files[member])
published = bytes.toString()
candidate = process.env.ARTPLAYER_ADS_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_ADS_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-ads', 'umd')
const input = await browserCandidate('artplayer-plugin-ads', process.env.ARTPLAYER_ADS_ARTIFACT)
candidate = input.code
provenance = input.provenance
})
test.afterEach(async ({ page }, testInfo) => {
@@ -45,7 +46,7 @@ async function openAds(page, core, plugin, option, testInfo, start = true) {
await page.goto(`/test/player.html?core=${core}`)
const code = plugin === 'published' ? published : candidate
await page.addScriptTag({ content: code })
await testInfo.attach('ads-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin, release: plugin === 'published' ? release : undefined, sha256: hash(code), artifact: plugin === 'candidate' ? process.env.ARTPLAYER_ADS_ARTIFACT || 'workspace source build' : undefined }) })
await testInfo.attach('ads-inputs', { contentType: 'application/json', body: JSON.stringify({ core, plugin, release: plugin === 'published' ? release : undefined, sha256: hash(code), artifact: plugin === 'candidate' ? provenance : undefined }) })
await page.evaluate((option) => {
window.adsEvents = []
window.art = new window.Artplayer({
+22
View File
@@ -0,0 +1,22 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { hash } from '../../refactor/scripts/releases.mjs'
import { verifyInstalledArtifacts } from '../../scripts/installed-artifacts.mjs'
import { compilePackage } from './load.js'
const root = fileURLToPath(new URL('../../', import.meta.url))
export async function browserCandidate(name, artifact) {
const map = process.env.ARTPLAYER_BROWSER_ARTIFACTS
if (map) {
assert(!artifact, `Installed browser checks cannot override ${name} artifact`)
const { inputs } = verifyInstalledArtifacts(root, map, ['artplayer', 'artplayer-plugin-chapter', name])
const input = inputs.find(input => input.name === name)
const code = fs.readFileSync(input.file, 'utf8')
return { code, provenance: { kind: 'installed', ...input } }
}
const code = artifact ? fs.readFileSync(artifact, 'utf8') : await compilePackage(name, 'umd')
return { code, provenance: { kind: artifact ? 'explicit-artifact' : 'source-build', file: artifact || null, sha256: hash(code) } }
}
+3
View File
@@ -8,6 +8,7 @@ import { checkFiles, checkPackages, publishedConsumer } from '../scripts/package
import { removeConsumer, runtimeConsumer } from '../scripts/package-consumer.mjs'
import { emitterContracts } from './contracts/emitter.js'
import { ambilightCandidate } from './helpers/ambilight.js'
import { browserCandidate } from './helpers/browser-candidate.js'
import { canvasCandidate } from './helpers/canvas.js'
import { dpipCandidate } from './helpers/dpip.js'
@@ -28,6 +29,8 @@ test('Explicit installed plugin maps never fall back to source or frozen workspa
await assert.rejects(ambilightCandidate(), { code: 'ENOENT' })
await assert.rejects(canvasCandidate(), { code: 'ENOENT' })
await assert.rejects(dpipCandidate(), { code: 'ENOENT' })
await assert.rejects(browserCandidate('artplayer-plugin-ads'), { code: 'ENOENT' })
await assert.rejects(browserCandidate('artplayer-plugin-ads', 'override.js'), /cannot override artplayer-plugin-ads artifact/)
process.env.ARTPLAYER_AMBILIGHT_BASELINE = '1'
process.env.ARTPLAYER_CANVAS_BASELINE = '1'
process.env.ARTPLAYER_DPIP_BASELINE = '1'
+3 -3
View File
@@ -9,9 +9,9 @@ step through `ARTPLAYER_BROWSER_ARTIFACTS`. See
browser scope and the remaining package-specific release gates.
Run `yarn test:package` with the pinned Node and Yarn after a frozen install. The
default consumer scope is core and chapter. For Ambilight/Canvas/Document PiP browser work,
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip`.
These three additional packages have reviewed published contracts and use the same
default consumer scope is core and chapter. For Ambilight/Canvas/Document PiP/Ads browser work,
run `yarn test:package --include=artplayer-plugin-ambilight,artplayer-proxy-canvas,artplayer-plugin-document-pip,artplayer-plugin-ads`.
These four additional packages have reviewed published contracts and use the same
source snapshot/build/pack/offline-install/frozen-reinstall pipeline. Installed
files are copied only after archive hash checks. The report explicitly separates
the core/chapter runtime/type scope from the additional browser package scope;