diff --git a/packages/artplayer-plugin-vast/ARCHITECTURE.md b/packages/artplayer-plugin-vast/ARCHITECTURE.md index 73ab420f4..cb476b4c2 100644 --- a/packages/artplayer-plugin-vast/ARCHITECTURE.md +++ b/packages/artplayer-plugin-vast/ARCHITECTURE.md @@ -134,3 +134,29 @@ countdown already has an accessible skip name; visibility alone is insufficient. The local XML server permits only IMA origins on that fixture route. Native SDK timeouts remain failures even when a late ad subsequently plays or skips correctly. Physical device validation remains05 and full distribution06. + +## Complete installed bundles + +The shared `yarn test:package --browser` roster includes VAST. Its checked +archive retains the real npm 1.0.0 dist/type paths; the same-name contract also +verifies the frozen SDK archives. Generic package runtime/type fixtures still +cover core/chapter only. Use the separate VAST type consumers for VAST claims. + +`vast-package.spec.js` uses `browserCandidate()` to select a normal source build +or the verified installed UMD, including the actual glomex implementation. It +holds/aborts native SDK script requests to verify concurrent load rejection, +fresh attempts and listener cleanup. A second case destroys the host before +fulfilling the script with an inert readiness sentinel, which intentionally has +no IMA player APIs: late construction or callbacks must not run. Main content +still decodes after SDK failure. These are loader/lifetime tests, not Google IMA +ad playback; `vast.spec.js` remains a separate controlled SDK boundary suite. + +`yarn test:vast-native` defaults to the existing source build. With +`ARTPLAYER_BROWSER_ARTIFACTS` set, its config requires verified core, Chapter and +VAST installations, and all three native test files load the complete installed +bundle. They preserve real remote IMA, published plugin controls, timeouts and +zero retries. Input provenance is attached before SDK initialization, including +for tests that fail before ad playback. This optional integration run stays +outside normal PR checks. See the +[installed VAST record](../../refactor/changes/2026-09-15-CI-01-vast-installed.md) +for actual results and the remaining external SDK/device/distribution gates. diff --git a/playwright.vast-native.config.js b/playwright.vast-native.config.js index af862677a..7b925b28f 100644 --- a/playwright.vast-native.config.js +++ b/playwright.vast-native.config.js @@ -1,9 +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 { verifyInstalledArtifacts } from './scripts/installed-artifacts.mjs' -assert(!process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Native VAST source validation does not accept unrelated artifact maps') +if (process.env.ARTPLAYER_BROWSER_ARTIFACTS) + verifyInstalledArtifacts(process.cwd(), process.env.ARTPLAYER_BROWSER_ARTIFACTS, ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-vast']) export default defineConfig(base, { testMatch: ['**/vast.native.js', '**/vast-recovery.native.js', '**/vast-skip.native.js'], timeout: 30000, diff --git a/refactor/baselines/ci-vast-installed-validation.json b/refactor/baselines/ci-vast-installed-validation.json new file mode 100644 index 000000000..54c56eb9e --- /dev/null +++ b/refactor/baselines/ci-vast-installed-validation.json @@ -0,0 +1,384 @@ +{ + "schemaVersion": 1, + "task": "CI-01", + "date": "2026-09-15", + "sourceHead": "ce67eaa73ec177b23de9a4c7e27ba99fdf458ae9", + "candidate": "Working tree included in the accompanying CI-01 complete VAST bundle checkpoint; code fingerprints normalize LF", + "environment": { + "node": "24.21.0", + "yarn": "1.22.22", + "platform": "win32" + }, + "inputs": [ + { + "file": "scripts/package-check.mjs", + "sha256LF": "61529f6d109d6581ead9f32883b4082a02cd00c9bc6c396b1339a8c30530de09" + }, + { + "file": "scripts/browser-validation/scope.ts", + "sha256LF": "746f751b2e1c40f7f2f5a8aafd40ff9c47066fc5716984d2feeda167ecfc41fc" + }, + { + "file": "playwright.vast-native.config.js", + "sha256LF": "c1b8f0db85602adc6eda12460af1b0740ad380abceb59d565f12bbbf39ed7135" + }, + { + "file": "test/browser-validation.test.js", + "sha256LF": "08b2772f18adc640adce948d00e9de64cb18e5e4f4135286ec62971521f2331f" + }, + { + "file": "test/browser/vast-package.spec.js", + "sha256LF": "60fad56473db0b3c7bc062342ced578d9176ab0f449c871dc5a2a54b001c9888" + }, + { + "file": "test/browser/vast.native.js", + "sha256LF": "9141ecf01a3c8474225c750f3024f2bbd1222bc6fa0b6446d8fa183680d294a5" + }, + { + "file": "test/browser/vast-recovery.native.js", + "sha256LF": "4c1969596742b72b04ad505175073bed5802ac1420922128fd0e1ddcceb11804" + }, + { + "file": "test/browser/vast-skip.native.js", + "sha256LF": "cfad59626989094aa92e40d4282d410c4596bf21dd2c908923992a9414b2fb4e" + } + ], + "packages": { + "directory": "refactor/.cache/packages/run-yqnfgx", + "reportSha256": "ea83dc2b4e3f66a247e1e700a4115ec0231e97ab2984c74593aef10c1e49f463", + "count": 20, + "names": [ + "artplayer", + "artplayer-plugin-chapter", + "artplayer-plugin-ambilight", + "artplayer-proxy-canvas", + "artplayer-plugin-document-pip", + "artplayer-plugin-ads", + "artplayer-plugin-audio-track", + "artplayer-plugin-vtt-thumbnail", + "artplayer-plugin-multiple-subtitles", + "artplayer-plugin-hls-control", + "artplayer-plugin-dash-control", + "artplayer-plugin-auto-thumbnail", + "artplayer-plugin-asr", + "artplayer-plugin-chromecast", + "artplayer-proxy-mediabunny", + "artplayer-plugin-jassub", + "artplayer-plugin-danmuku", + "artplayer-plugin-danmuku-mask", + "artplayer-tool-iframe", + "artplayer-plugin-vast" + ], + "vast": { + "name": "artplayer-plugin-vast", + "version": "1.2.0", + "archive": "artplayer-plugin-vast.tgz", + "sha256": "e765c2809cec4494d62811c9d2cd96ea8a3091e4cc75162ece31586b95f5f2d7", + "files": { + "package/ARCHITECTURE.md": "918c8f66ed6010c0b2fb8ca70ccc62101fcc126962829347eb3718242375d0f3", + "package/README.md": "7ec086f19c899e315efcb136e037a02d951b0395d5f0ce239817be87ea4ab4e6", + "package/dist/artplayer-plugin-vast.js": "cebaf6a44f80079841205498c4f855ba4e5c804ae7958c06eadda524d728e079", + "package/dist/artplayer-plugin-vast.legacy.js": "8ebb4ebdd4c0fc5ddc467d5ee15b18ab7e5c078c15bbfd37dc33aa89eac1de92", + "package/dist/artplayer-plugin-vast.mjs": "a63e6e8b27032ea690fe740e9ec0a98646346f1fcd3622740f1f5363f234a72b", + "package/package.json": "d359f65c20ca85939a15d4502fdafd6258401aa4a3e421314e1275fe7102826e", + "package/types/artplayer-plugin-vast.d.ts": "51257361f482f026a9b5ba84ca85d320d9e1457a34d0d259ac3c7b46c4fbd29c", + "package/types/runtime-api.d.ts": "4634b65f2d1bbd6973a40390f3217251b69fc5167198a8409d9fed48500e106d", + "package/types/runtime.d.cts": "cead9984e0e2644e67cc687380ed40aed0d561b9d6f9636bea3b98d71522b13e", + "package/types/runtime.d.mts": "5abf25cba3dfaaea2652c0d3721f58ad243eb4d3a2fef80f5d27eb978973b586", + "package/types/runtime.d.ts": "cead9984e0e2644e67cc687380ed40aed0d561b9d6f9636bea3b98d71522b13e" + } + }, + "wallSeconds": 113.47, + "exitCode": 0, + "genericConsumerScope": [ + "artplayer", + "artplayer-plugin-chapter" + ] + }, + "collection": { + "directory": "refactor/.cache/ci01-vast-collection-report", + "reportSha256": "98f1803d4c78173a843e35f3be2889253469fb7bdbcce9f02da93e23849b0b5a", + "count": 2442, + "files": 73, + "collectionOnly": true + }, + "source": { + "directory": "refactor/.cache/ci01-vast-source-report", + "reportSha256": "a2e75d564fbd87c467aef377e5c486e5db983633ee816f8575dcf4797607e8ea", + "invocation": { + "scope": "source", + "args": [ + "test", + "--config=playwright.source.config.js", + "vast-package.spec.js", + "--workers=2" + ], + "node": "24.21.0", + "artifactMap": null, + "installedPackages": 0 + }, + "result": { + "scope": "source", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.source.config.js", + "vast-package.spec.js", + "--workers=2" + ] + }, + "stats": { + "startTime": "2026-09-14T20:39:43.739Z", + "duration": 25485.434999999998, + "expected": 36, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "counts": { + "chromium/vast-package.spec.js": { + "expected": 12, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "firefox/vast-package.spec.js": { + "expected": 12, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "webkit/vast-package.spec.js": { + "expected": 12, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + } + }, + "browserVersions": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "sdkVersions": [], + "identityCounts": { + "chromium/source-build": 12, + "firefox/source-build": 12, + "webkit/source-build": 12 + }, + "failures": [], + "invocationSha256": "28d87cf83591b7927e472c05ddd2422b4a7f93d2af69845b6b53cbbe1768d2d7" + }, + "installed": { + "directory": "refactor/.cache/ci01-vast-installed-report", + "reportSha256": "a0b7608243bee0371a0d0de83fba648c10fa6b0ee5eb3a06debde84016973b42", + "invocation": { + "scope": "installed", + "args": [ + "test", + "--config=playwright.installed.config.js", + "vast-package.spec.js", + "--workers=2" + ], + "node": "24.21.0", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-yqnfgx\\browser-artifacts.json", + "installedPackages": 20 + }, + "result": { + "scope": "installed", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "vast-package.spec.js", + "--workers=2" + ] + }, + "stats": { + "startTime": "2026-09-14T20:42:41.950Z", + "duration": 23818.322999999997, + "expected": 36, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "counts": { + "chromium/vast-package.spec.js": { + "expected": 12, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "firefox/vast-package.spec.js": { + "expected": 12, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "webkit/vast-package.spec.js": { + "expected": 12, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + } + }, + "browserVersions": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "sdkVersions": [], + "identityCounts": { + "chromium/installed": 12, + "firefox/installed": 12, + "webkit/installed": 12 + }, + "failures": [], + "invocationSha256": "4284fe5664b42cc83055f595a22f0ccb452fe9a44981bcc2f79430f5d7816dfc" + }, + "native": { + "directory": "refactor/.cache/ci01-vast-native-report", + "reportSha256": "e65f83cc260088fcb62f9b05795991c7734b74e5d05ad3d0cc190f15461dc788", + "invocation": { + "scope": "installed-native", + "command": "yarn test:vast-native", + "head": "ce67eaa73ec177b23de9a4c7e27ba99fdf458ae9", + "startedAt": "2026-09-14T20:44:01.4909743Z", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-yqnfgx\\browser-artifacts.json" + }, + "result": { + "exitCode": 0, + "finishedAt": "2026-09-14T20:48:44.1700556Z" + }, + "stats": { + "startTime": "2026-09-14T20:44:02.273Z", + "duration": 281695.31499999994, + "expected": 39, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "counts": { + "chromium/vast-recovery.native.js": { + "expected": 2, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "firefox/vast-recovery.native.js": { + "expected": 2, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "webkit/vast-recovery.native.js": { + "expected": 2, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "chromium/vast-skip.native.js": { + "expected": 2, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "firefox/vast-skip.native.js": { + "expected": 2, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "webkit/vast-skip.native.js": { + "expected": 2, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "chromium/vast.native.js": { + "expected": 9, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "firefox/vast.native.js": { + "expected": 9, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + }, + "webkit/vast.native.js": { + "expected": 9, + "unexpected": 0, + "skipped": 0, + "flaky": 0 + } + }, + "browserVersions": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "sdkVersions": [ + "3.789.0" + ], + "identityCounts": { + "chromium/installed": 10, + "firefox/installed": 10, + "webkit/installed": 10, + "chromium/published": 3, + "firefox/published": 3, + "webkit/published": 3 + }, + "failures": [] + }, + "verification": { + "tests": { + "passed": 94, + "failed": 0, + "durationMs": 3676.2619, + "log": "refactor/.cache/ci01-vast-tests.log", + "logSha256": "aeae1681eef5ab7b20fead22cf3f8a15cc0ebf84025735555abc1cd8b1a2e542" + }, + "lint": { + "exitCode": 0, + "log": "refactor/.cache/ci01-vast-lint-complete.log", + "logSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "libraryTypes": { + "exitCode": 0, + "log": "refactor/.cache/ci01-vast-library-types.log", + "logSha256": "647c339f0e832948a0fc7393cfeb4ff4e677c8df7b449abbdf3f519523fd782c" + }, + "docsTypes": { + "exitCode": 0, + "log": "refactor/.cache/ci01-vast-docs-types.log", + "logSha256": "6dc9f3b0cbfb35e5b93dc8c9ec04cd2d64146a65c7f581614bcb289777b56795" + }, + "ci": { + "exitCode": 0, + "log": "refactor/.cache/ci01-vast-ci.log", + "logSha256": "ae8f7eb4d69bd9bdded1bfd024dbc81778a9f5746ea59e8348416ee5f4117fd8" + }, + "toolchain": { + "exitCode": 0, + "log": "refactor/.cache/ci01-vast-toolchain.log", + "logSha256": "e826888421079163997eb19d528152df86b03b7d0507877e099e569b461814fb" + }, + "impact": { + "exitCode": 0, + "log": "refactor/.cache/ci01-vast-impact.log", + "logSha256": "4aaebfd09336848065473b9db6e05b17da44d5810422144e0a5ac7341155089d" + } + }, + "limitations": [ + "The PR loader suite uses the complete glomex bundle but controls network failure/readiness. Its sentinel has no IMA player API and is not advertising playback.", + "The original vast.spec.js continues to substitute glomex and is not represented as installed complete-bundle coverage.", + "Only the separate native run uses the real remote IMA SDK. Current successful runs do not close earlier intermittent SDK9000 or historical WebKit first-frame failures.", + "Published plugin controls retain frozen npm1.0.0 bytes; their existing fixture explicitly cleans up SDK resources after observation.", + "Generic pack runtime/type consumers remain core/chapter only. VAST root/legacy/runtime installed type compatibility was separately verified in PKG-VAST-04.", + "No physical-device evidence, remote CI run, full ecosystem acceptance, publication, or closure of CI-01/PKG-VAST-05." + ] +} diff --git a/refactor/changes/2026-09-15-CI-01-vast-installed.md b/refactor/changes/2026-09-15-CI-01-vast-installed.md new file mode 100644 index 000000000..8fb677324 --- /dev/null +++ b/refactor/changes/2026-09-15-CI-01-vast-installed.md @@ -0,0 +1,73 @@ +# CI-01 VAST 完整安装包与真实 IMA 验证 + +## 实现范围 + +接续 `ce67eaa73ec177b23de9a4c7e27ba99fdf458ae9`。统一 browser 安装清单加入 +VAST,正常构建/pack/隔离安装二十包;校验真实 npm 1.0.0 的 dist/types 路径 +和冻结 SDK 归档。没有修改生产源码、公开接口/声明、依赖版本或 npm 入口。 + +原 `vast.spec.js` 通过替换 glomex 验证插件逻辑,继续作为源码范围的受控测试。 +新增 `vast-package.spec.js` 加载完整普通构建,包括真实 glomex 实现,分别在 +三代核心、两种兼容模式、三个引擎下验证两个边界: + +- 两个并发注册共享一次真实 script 请求;网络失败传递同一个原生 error Event, + 两个注册均拒绝、各自释放 destroy listener、回调不执行。新注册产生第二次 + SDK 请求,失败后仍释放监听;主片继续实际解码和播放。 +- SDK 请求尚未完成时销毁核心,然后返回只含 readiness 标记的脚本。这个哨兵 + 没有 IMA 播放器 API,迟到的结果不能创建 session 或调用用户回调。 + +这些是完整 glomex 的加载/销毁验证;受控网络和哨兵不计为 Google 广告播放。 +两种模式继续使用同一个完整包,已批准的 npm 默认与 workspace-1.2 行为保留。 + +独立 `test:vast-native` 默认仍构建源码;显式给出安装 map 后,config 核验核心、 +Chapter、VAST 文件及构建新鲜度,三份 native 测试直接加载安装 UMD。真实旧 npm +插件仍使用固定归档。初始化前附加来源,失败在播放之前发生时也能核对候选。 +没有替换 AdsLoader/AdsManager、修改 SDK 超时、添加重试或把 native 测试移入 +普通 PR 的受控范围。 + +## 实测 + +Windows、Node 24.21.0、Yarn 1.22.22;Chromium 153.0.8010.12、Firefox +155.0、WebKit 26.6;本轮真实 Google IMA 为 3.789.0。原始报告目录、输入摘要、 +逐文件结果和源码/安装/历史身份见 +[机器证据](../baselines/ci-vast-installed-validation.json)。 + +| 检查 | 实际结果 | +| --- | --- | +| `yarn test:package --browser` | 二十包正常构建/打包/隔离安装/离线冻结复装通过;run-yqnfgx,113.47 秒 | +| installed 全量 collection | 73 文件、2,442 项;仅清单收集,不是全量运行 | +| source 完整 SDK 加载边界 | 36/36,25,485.435 ms,三个引擎各 12 项 | +| installed 完整 SDK 加载边界 | 36/36,23,818.323 ms,三个引擎各 12 项 | +| installed-map 真实 IMA 专项 | 39/39,281,695.315 ms;零失败、skip、flaky 或重试 | +| 单元/工程检查 | 94/94,3,676.2619 ms,包含原 VAST 导出/兼容/生命周期回归和 native 配置的无 map/坏 map 验证 | +| 静态检查 | 改动文件只读 ESLint、library/docs-tools 严格类型、CI/影响范围和固定工具链通过 | + +真实专项包含三个引擎各 13 项:三代核心 × 两种候选模式和一个历史插件共 +9 项播放/主片恢复,加 2 项真实 303 恢复/重建/活跃广告销毁、2 项 playUrl/SDK +跳过按钮。全组 30 个安装候选、9 个历史对照,每个引擎分别 10/3;候选均附带 +核验过的安装身份。实际广告解码、SDK 事件顺序、点击可用的跳过按钮和恢复主片 +仍由原断言判定。本轮加载正常,没有启用用户许可的 VPN/脚本加载跳过例外。 + +这次 native 全组通过是本次安装候选证据,不是对过去间歇 SDK9000/迟到广告和 +旧插件 WebKit 首帧失败的生产修复。旧报告和开放风险保留;没有循环执行相同 +失败直到变绿。加载边界的首次 lint 两个多语句行已格式修正,原 lint 日志保留。 + +通用 pack 中 36 运行时、5 旧类型、8 精确类型模式仍仅覆盖 core/Chapter;VAST +自己的根/legacy/runtime 十四组安装类型证据继续引用 PKG-VAST-04,不虚报为 +本次全包类型重跑。源码加载测试的 map 为 null;安装加载和 native 专项分别 +核验同一 run-yqnfgx 的完整 UMD,不使用外部化 glomex 的测试构建。 + +## 重跑和剩余工作 + +先执行 test:package --browser,设置其 ARTPLAYER_BROWSER_ARTIFACTS,然后运行 +`yarn test:browser:installed vast-package.spec.js --workers=2` 和独立的 +`yarn test:vast-native`。后者包含真实远端依赖;移除 map 才回到原源码模式。 +源码对照使用 `yarn test:browser:source vast-package.spec.js --workers=2`。 +三个入口保持各自报告目录,重跑前先归档旧证据。此次原始报告已在进程终态后 +归档为 `refactor/.cache/ci01-vast-{source,installed,native,collection}-report`; +native 的实际调用/退出记录另存同前缀 invocation/result JSON。 + +CI-01、PKG-VAST-05 仍 doing;197/263 完成不变。物理 iOS/Android、SDK 间歇 +行为、完整分发/远端 CI 与发布复盘仍未完成。工作区保持已批准的公开兼容边界, +没有发布、部署、推送或修改浏览器/VPN 设置。代码、测试、架构和证据随独立 +CI-01 本地提交,随后运行提交审计。 diff --git a/refactor/ci-setup.md b/refactor/ci-setup.md index ea24c10fe..14273f5b2 100644 --- a/refactor/ci-setup.md +++ b/refactor/ci-setup.md @@ -1,5 +1,9 @@ # PR 检查和 Pages 操作说明 +最新统一安装清单含二十包。VAST 的完整 glomex 加载边界进入普通 installed +范围;真实 IMA 仍由独立 test:vast-native 按显式安装 map 验证,本地 39/39 +通过不表示远端 CI 已覆盖真实广告。见[记录](changes/2026-09-15-CI-01-vast-installed.md)。 + 浏览器CI现在分别执行 `yarn test:browser:source` 和 `yarn test:browser:installed`。前者清除继承的安装map并保留全部spec;后者要求已 核验的统一包清单,执行明确列出的 installed 子集;清单维护在 diff --git a/refactor/plan.md b/refactor/plan.md index 573a5ee6d..4db863681 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -509,7 +509,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) [记录](changes/2026-09-15-CI-01-ads-installed.md) [记录](baselines/ci-ads-installed-validation.json) [记录](changes/2026-09-15-CI-01-subtitles-installed.md) [记录](baselines/ci-subtitles-installed-validation.json) [记录](changes/2026-09-15-CI-01-adaptive-installed.md) [记录](baselines/ci-adaptive-installed-validation.json) [记录](changes/2026-09-15-CI-01-asr-cast-installed.md) [记录](baselines/ci-asr-cast-installed-validation.json) [记录](changes/2026-09-15-CI-01-mediabunny-installed.md) [记录](baselines/ci-mediabunny-installed-validation.json) [记录](changes/2026-09-15-CI-01-jassub-installed.md) [记录](baselines/ci-jassub-installed-validation.json) [记录](changes/2026-09-15-CI-01-danmuku-installed.md) [记录](baselines/ci-danmuku-installed-validation.json) [记录](changes/2026-09-15-CI-01-browser-matrix.md) [记录](baselines/ci-browser-matrix-validation.json) [记录](changes/2026-09-15-CI-01-iframe-installed.md) [记录](baselines/ci-iframe-installed-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) [记录](changes/2026-09-15-CI-01-subtitles-installed.md) [记录](baselines/ci-subtitles-installed-validation.json) [记录](changes/2026-09-15-CI-01-adaptive-installed.md) [记录](baselines/ci-adaptive-installed-validation.json) [记录](changes/2026-09-15-CI-01-asr-cast-installed.md) [记录](baselines/ci-asr-cast-installed-validation.json) [记录](changes/2026-09-15-CI-01-mediabunny-installed.md) [记录](baselines/ci-mediabunny-installed-validation.json) [记录](changes/2026-09-15-CI-01-jassub-installed.md) [记录](baselines/ci-jassub-installed-validation.json) [记录](changes/2026-09-15-CI-01-danmuku-installed.md) [记录](baselines/ci-danmuku-installed-validation.json) [记录](changes/2026-09-15-CI-01-browser-matrix.md) [记录](baselines/ci-browser-matrix-validation.json) [记录](changes/2026-09-15-CI-01-iframe-installed.md) [记录](baselines/ci-iframe-installed-validation.json) [记录](changes/2026-09-15-CI-01-vast-installed.md) [记录](baselines/ci-vast-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) @@ -603,7 +603,7 @@ - PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md) - PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md) [记录](changes/2026-09-14-PKG-VAST-03-compatibility.md) [记录](baselines/vast-compatibility-validation.json) - PKG-VAST-04: [记录](changes/2026-09-14-PKG-VAST-04-types.md) [记录](baselines/vast-types-validation.json) [记录](vast-type-decision.md) [记录](vast-validation.md) -- PKG-VAST-05: [记录](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md) [记录](baselines/vast-native-validation.json) [记录](changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md) [记录](baselines/vast-skip-validation.json) +- PKG-VAST-05: [记录](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md) [记录](baselines/vast-native-validation.json) [记录](changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md) [记录](baselines/vast-skip-validation.json) [记录](changes/2026-09-15-CI-01-vast-installed.md) [记录](baselines/ci-vast-installed-validation.json) - PKG-CAST-01: [记录](baselines/chromecast-release.json) [记录](baselines/chromecast-contract.md) [记录](baselines/chromecast-contract-validation.json) [记录](changes/2026-09-13-PKG-CAST-01-contracts.md) - PKG-CAST-02: [记录](changes/2026-09-13-PKG-CAST-02-failures.md) [记录](baselines/chromecast-failures-validation.json) - PKG-CAST-03: [记录](changes/2026-09-13-PKG-CAST-03-runtime.md) [记录](baselines/chromecast-runtime-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index d764e5489..e7c035992 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,14 @@ # 进度与证据 +## CI-01 VAST 完整安装包与真实 IMA + +二十包安装准备通过。VAST 完整 glomex 加载边界在源码、安装范围各 36/36; +真实 IMA 3.789.0 专项 39/39(30 安装候选、9 历史对照),三个引擎均无失败/ +跳过/重试,覆盖播放、303 恢复、重建、活跃广告销毁与真实跳过按钮。单元/工程 +94/94,见[记录](changes/2026-09-15-CI-01-vast-installed.md)。完整 installed +清单现为 73 文件/2,442 项,本轮未全跑。197/263 完成;CI-01/VAST-05 仍 doing, +以前的间歇失败、物理设备、其余分发和远端 CI 门槛保留。 + ## CI-01 Iframe 安装包与真实跨窗口组合 统一清单现含十九包,保留 Iframe 真实旧 npm 包与冻结 tool 工作区的不同身份。 diff --git a/refactor/risks.json b/refactor/risks.json index dc731104d..fac0bc005 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -779,11 +779,13 @@ "refactor/changes/2026-09-14-PKG-VAST-05-native-checkpoint.md", "refactor/baselines/vast-native-validation.json", "refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md", - "refactor/baselines/vast-skip-validation.json" + "refactor/baselines/vast-skip-validation.json", + "refactor/changes/2026-09-15-CI-01-vast-installed.md", + "refactor/baselines/ci-vast-installed-validation.json" ], "compatibleResolution": "@glomex/vast-ima-player loadImaSdk loads external IMA; preserve event/content/ad state and teardown, do not equate mocks with vendor service acceptance.", "closureCriteria": "固定 SDK/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。", - "workspaceState": "Real IMA3.789.0 executes with immutable npm1.0.0/candidate bundles: final27 desktop combinations22 passed5 failed; error303 recovery/recreation/active-ad destruction6 passed. Initial27 were23 passed4 failed and are preserved. SDK9000/late loading and historical WebKit ad-frame failures remain open. IAB SDK200 but bridge document ERR_BLOCKED_BY_CLIENT; physical devices/playUrl/skip not accepted. No VPN exception or timeout widening/retry-to-green. Native playUrl/IMA skip UI adds5/6 passes (one9000 failure despite later skip), shared server12/12 passes. Historical WebKit trace contains real ad frames; three fixed raw-media diagnostic runs passed without changing the assertion, so the original intermittent failure is not closed." + "workspaceState": "Real IMA3.789.0 executes with immutable npm1.0.0/candidate bundles: final27 desktop combinations22 passed5 failed; error303 recovery/recreation/active-ad destruction6 passed. Initial27 were23 passed4 failed and are preserved. SDK9000/late loading and historical WebKit ad-frame failures remain open. IAB SDK200 but bridge document ERR_BLOCKED_BY_CLIENT; physical devices/playUrl/skip not accepted. No VPN exception or timeout widening/retry-to-green. Native playUrl/IMA skip UI adds5/6 passes (one9000 failure despite later skip), shared server12/12 passes. Historical WebKit trace contains real ad frames; three fixed raw-media diagnostic runs passed without changing the assertion, so the original intermittent failure is not closed. CI-01 complete installed bundle checkpoint: source/installed native-script loader and cancellation controls each36/36; real remote IMA3.789.0 across three desktop engines39/39 (30 installed candidate,9 published controls), including303 recovery/recreation/active-ad destroy and actual skip UI. No retries/skips or VPN exception. This new successful run does not close earlier intermittent9000/late-ad/historical WebKit observations, physical-device or full distribution gates." }, { "id": "SDK-08", @@ -3350,11 +3352,13 @@ "refactor/changes/2026-09-14-PKG-VAST-05-native-checkpoint.md", "refactor/baselines/vast-native-validation.json", "refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md", - "refactor/baselines/vast-skip-validation.json" + "refactor/baselines/vast-skip-validation.json", + "refactor/changes/2026-09-15-CI-01-vast-installed.md", + "refactor/baselines/ci-vast-installed-validation.json" ], "compatibleResolution": "Reproduce pending SDK/callback destruction, errors, duplicate IDs and stale ad events before adding compatible cleanup and state ownership.", "closureCriteria": "Tests prove cleanup during load/callback rejection/core destruction and distinguish explicit plugin recreation from terminal core destruction; vendor media restoration is separately verified.", - "workspaceState": "Task03 controlled82 Node/162 browser checks remain valid; task05 adds6 real IMA303 recovery/recreation/active-ad core destruction passes across three engines/two modes. Native full combination failures, devices and remaining ad lifecycle paths keep this risk open; no claim of complete vendor/device acceptance. Native playUrl/IMA skip UI adds5/6 passes (one9000 failure despite later skip), shared server12/12 passes. Historical WebKit trace contains real ad frames; three fixed raw-media diagnostic runs passed without changing the assertion, so the original intermittent failure is not closed." + "workspaceState": "Task03 controlled82 Node/162 browser checks remain valid; task05 adds6 real IMA303 recovery/recreation/active-ad core destruction passes across three engines/two modes. Native full combination failures, devices and remaining ad lifecycle paths keep this risk open; no claim of complete vendor/device acceptance. Native playUrl/IMA skip UI adds5/6 passes (one9000 failure despite later skip), shared server12/12 passes. Historical WebKit trace contains real ad frames; three fixed raw-media diagnostic runs passed without changing the assertion, so the original intermittent failure is not closed. CI-01 complete installed bundle checkpoint: source/installed native-script loader and cancellation controls each36/36; real remote IMA3.789.0 across three desktop engines39/39 (30 installed candidate,9 published controls), including303 recovery/recreation/active-ad destroy and actual skip UI. No retries/skips or VPN exception. This new successful run does not close earlier intermittent9000/late-ad/historical WebKit observations, physical-device or full distribution gates." }, { "id": "VAST-DIST-01", diff --git a/refactor/tasks.json b/refactor/tasks.json index bd9a824ab..85ccf934b 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -780,7 +780,9 @@ "changes/2026-09-15-CI-01-browser-matrix.md", "baselines/ci-browser-matrix-validation.json", "changes/2026-09-15-CI-01-iframe-installed.md", - "baselines/ci-iframe-installed-validation.json" + "baselines/ci-iframe-installed-validation.json", + "changes/2026-09-15-CI-01-vast-installed.md", + "baselines/ci-vast-installed-validation.json" ] }, { @@ -3163,7 +3165,9 @@ "changes/2026-09-14-PKG-VAST-05-native-checkpoint.md", "baselines/vast-native-validation.json", "changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md", - "baselines/vast-skip-validation.json" + "baselines/vast-skip-validation.json", + "changes/2026-09-15-CI-01-vast-installed.md", + "baselines/ci-vast-installed-validation.json" ] }, { diff --git a/refactor/vast-validation.md b/refactor/vast-validation.md index a333b43d3..f73de01d8 100644 --- a/refactor/vast-validation.md +++ b/refactor/vast-validation.md @@ -109,7 +109,10 @@ Google的[AdsRequest接口](https://developers.google.com/interactive-media-ads/ 广告解码截图、主片播放位置及事件;远端SDK版本会变,结果仅代表对应运行环境。 三核心固定为npm5.1.7、npm5.4.0、候选5.4.1;实际npm1.0.0插件完整bundle通过 -归档校验后直接加载,候选两种模式使用同一生产构建配置的内存bundle。历史插件的 +归档校验后直接加载,候选两种模式默认使用同一生产构建配置的内存bundle。 +设置 ARTPLAYER_BROWSER_ARTIFACTS 后改为核验并加载安装包;config 要求核心、 +Chapter、VAST 的安装和源码新鲜度证据,三个 native 文件都保留早期输入身份附件。 +历史插件的 SDK清理由夹具在证据后显式补做,不能据DOM被核心移除就认为旧插件拥有SDK清理。 源代码组合通过不等于最终tarball通过,也不等于iOS/Android物理设备通过。 @@ -125,3 +128,9 @@ SDK清理由夹具在证据后显式补做,不能据DOM被核心移除就认 最终5/6(另1项9000失败)、共享服务12/12及固定WebKit诊断3/3见 [增补记录](changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md)。媒体采样仅用于 记录,未放宽原首帧断言;三次通过不能覆盖先前间歇失败。 + +统一安装清单中的 `vast-package.spec.js` 只覆盖完整打包 glomex 的脚本加载、 +并发拒绝、重试请求和销毁边界。它通过网络拦截控制失败,迟到成功仅提供空的 +IMA readiness 哨兵;不会执行广告。原 `vast.spec.js` 继续替换 glomex 边界, +不纳入完整安装 bundle 声明。真实广告仍必须单独执行上述 native 入口,并按 +实际 SDK/网络结果记录。见[安装检查点](changes/2026-09-15-CI-01-vast-installed.md)。 diff --git a/scripts/browser-validation/README.md b/scripts/browser-validation/README.md index 6376747dc..35cb13e3d 100644 --- a/scripts/browser-validation/README.md +++ b/scripts/browser-validation/README.md @@ -29,6 +29,14 @@ Iframe baseline/lifecycle-only/boundaries-only diagnostic flags. The separate history server still uses `iframeCandidate()` and may receive a core-only map; its source/explicit tool selection has not become installed-tool acceptance. +VAST's installed entry is `vast-package.spec.js`: complete glomex bundle, +controlled native SDK script failure/readiness, registration cleanup and actual +main-content decoding. The original `vast.spec.js` substitutes the glomex boundary +and remains source-only. Real Google IMA execution stays in `test:vast-native`; +that separate config now accepts a verified core/Chapter/VAST installation map +and all native candidate files record the installed identity before initialization. +Loader tests cannot stand in for native advertising, devices or SDK availability. + 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, diff --git a/scripts/browser-validation/scope.ts b/scripts/browser-validation/scope.ts index ee9562a2a..eebedd692 100644 --- a/scripts/browser-validation/scope.ts +++ b/scripts/browser-validation/scope.ts @@ -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', 'artplayer-plugin-ads', 'artplayer-plugin-audio-track', 'artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-multiple-subtitles', 'artplayer-plugin-hls-control', 'artplayer-plugin-dash-control', 'artplayer-plugin-auto-thumbnail', 'artplayer-plugin-asr', 'artplayer-plugin-chromecast', 'artplayer-proxy-mediabunny', 'artplayer-plugin-jassub', 'artplayer-plugin-danmuku', 'artplayer-plugin-danmuku-mask', 'artplayer-tool-iframe'] +export const installedPackages = ['artplayer', 'artplayer-plugin-chapter', 'artplayer-plugin-ambilight', 'artplayer-proxy-canvas', 'artplayer-plugin-document-pip', 'artplayer-plugin-ads', 'artplayer-plugin-audio-track', 'artplayer-plugin-vtt-thumbnail', 'artplayer-plugin-multiple-subtitles', 'artplayer-plugin-hls-control', 'artplayer-plugin-dash-control', 'artplayer-plugin-auto-thumbnail', 'artplayer-plugin-asr', 'artplayer-plugin-chromecast', 'artplayer-proxy-mediabunny', 'artplayer-plugin-jassub', 'artplayer-plugin-danmuku', 'artplayer-plugin-danmuku-mask', 'artplayer-tool-iframe', 'artplayer-plugin-vast'] export const installedTests = [ 'playback.spec.js', 'lifecycle.spec.js', @@ -76,6 +76,7 @@ export const installedTests = [ 'iframe-navigation.spec.js', 'iframe-player.spec.js', 'iframe-editor.spec.js', + 'vast-package.spec.js', ] export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) { diff --git a/scripts/package-check.mjs b/scripts/package-check.mjs index 9742ad930..f801d1e8e 100644 --- a/scripts/package-check.mjs +++ b/scripts/package-check.mjs @@ -116,6 +116,7 @@ export async function checkPackages({ release = false, include = [] } = {}) { 'artplayer-plugin-danmuku': async () => (await import('../refactor/scripts/danmuku-contract.mjs')).verifyDanmukuContract(), 'artplayer-plugin-danmuku-mask': async () => (await import('../refactor/scripts/danmuku-mask-contract.mjs')).verifyDanmukuMaskContract(), 'artplayer-tool-iframe': async () => (await import('../refactor/scripts/iframe-contract.mjs')).verifyIframeContract(), + 'artplayer-plugin-vast': async () => (await import('../refactor/scripts/vast-contract.mjs')).verifyVastContract(), 'artplayer-plugin-audio-track': async () => (await import('../refactor/scripts/audio-contract.mjs')).verifyAudioContract(), 'artplayer-plugin-vtt-thumbnail': async () => (await import('../refactor/scripts/vtt-thumbnail-contract.mjs')).verifyVttThumbnailContract(), 'artplayer-plugin-multiple-subtitles': async () => (await import('../refactor/scripts/multiple-subtitles-contract.mjs')).verifyMultipleSubtitlesContract(), diff --git a/test/browser-validation.test.js b/test/browser-validation.test.js index 9969aca43..efd3feef3 100644 --- a/test/browser-validation.test.js +++ b/test/browser-validation.test.js @@ -24,6 +24,19 @@ test('Source invocation clears inherited artifact selection without changing cal assert(fs.existsSync(path.resolve('test/browser', file))) }) +test('Native VAST keeps its source default and rejects an invalid explicit installation before browser launch', () => { + const env = { ...process.env } + delete env.ARTPLAYER_BROWSER_ARTIFACTS + const command = 'const { default: config } = await import("./playwright.vast-native.config.js"); if (config.timeout !== 30000 || config.workers !== 1) throw new Error("Native execution policy changed");' + const source = spawnSync(process.execPath, ['--input-type=module', '-e', command], { encoding: 'utf8', env, windowsHide: true }) + assert.equal(source.status, 0, source.stderr) + const missing = path.resolve('refactor/.cache/absent-vast-native-artifact-map.json') + assert(!fs.existsSync(missing)) + const installed = spawnSync(process.execPath, ['--input-type=module', '-e', command], { encoding: 'utf8', env: { ...env, ARTPLAYER_BROWSER_ARTIFACTS: missing }, windowsHide: true }) + assert.equal(installed.status, 1) + assert.match(installed.stderr, /ENOENT/) +}) + test('Browser invocation rejects ambiguous scopes and output/configuration overrides', () => { assert.throws(() => browserInvocation('unknown', [], {}), /Choose/) assert.throws(() => browserInvocation('installed', [], {}), /require ARTPLAYER/) diff --git a/test/browser/vast-package.spec.js b/test/browser/vast-package.spec.js new file mode 100644 index 000000000..899304b45 --- /dev/null +++ b/test/browser/vast-package.spec.js @@ -0,0 +1,105 @@ +import { browserCandidate } from '../helpers/browser-candidate.js' +import { expect, test } from './fixtures.js' + +const candidate = await browserCandidate('artplayer-plugin-vast') +const sdkUrl = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js' + +async function setup(page, core, compatibility, testInfo) { + const requests = [] + await page.route(sdkUrl, route => requests.push(route)) + await page.goto(`/test/player.html?core=${core}`) + await page.addScriptTag({ content: candidate.code }) + await page.evaluate((compatibility) => { + window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true }) + window.vastCallbacks = 0 + window.vastListeners = new Set() + window.vastSettlements = [] + const on = window.art.on + const off = window.art.off + window.art.on = function (name, listener) { + if (name === 'destroy') + window.vastListeners.add(listener) + return on.call(this, name, listener) + } + window.art.off = function (name, listener) { + if (name === 'destroy') + window.vastListeners.delete(listener) + return off.call(this, name, listener) + } + window.registerVast = () => { + const plugin = window.artplayerPluginVast(() => window.vastCallbacks++, { compatibility }) + return window.art.plugins.add(plugin).then( + () => window.vastSettlements.push({ status: 'resolved' }), + error => window.vastSettlements.push({ status: 'rejected', error }), + ) + } + if (window.artplayerPluginVast.default !== window.artplayerPluginVast) + throw new Error('Default factory alias changed') + }, compatibility) + await testInfo.attach('vast-package-inputs', { contentType: 'application/json', body: JSON.stringify({ core, compatibility: compatibility || 'npm-default', provenance: candidate.provenance, sdkUrl, scope: 'Complete normal bundle including glomex. Native script requests are held/failed or fulfilled with an inert readiness sentinel; no real Google IMA/ad playback claim.' }) }) + return requests +} + +test.afterEach(async ({ page }, testInfo) => { + const state = await page.evaluate(() => ({ callbacks: window.vastCallbacks, listeners: window.vastListeners?.size, destroyed: window.art?.isDestroy, settlements: window.vastSettlements?.map(({ status, error }) => ({ status, errorType: error?.type })), scripts: [...document.scripts].map(script => script.src).filter(src => src.includes('imasdk.googleapis.com')) })) + await testInfo.attach('vast-package-state', { contentType: 'application/json', body: JSON.stringify(state) }) + await page.evaluate(() => { + if (window.art && !window.art.isDestroy) + window.art.destroy() + }) +}) + +for (const core of ['published-5.1.7', 'published', 'candidate']) { + for (const compatibility of [undefined, 'workspace-1.2']) { + test(`VAST package ${core}/${compatibility || 'npm-default'}: shared SDK failure rejects both registrations and permits another request`, async ({ page }, testInfo) => { + const requests = await setup(page, core, compatibility, testInfo) + await page.evaluate(() => { + void window.registerVast() + void window.registerVast() + }) + await expect.poll(() => requests.length).toBe(1) + expect(await page.evaluate(() => window.vastListeners.size)).toBe(2) + await requests[0].abort('failed') + await expect.poll(() => page.evaluate(() => window.vastSettlements.length)).toBe(2) + expect(await page.evaluate(() => ({ statuses: window.vastSettlements.map(item => item.status), sameError: window.vastSettlements[0].error === window.vastSettlements[1].error, errorType: window.vastSettlements[0].error.type, callbacks: window.vastCallbacks, listeners: window.vastListeners.size }))).toEqual({ statuses: ['rejected', 'rejected'], sameError: true, errorType: 'error', callbacks: 0, listeners: 0 }) + await page.evaluate(() => { + void window.registerVast() + }) + await expect.poll(() => requests.length).toBe(2) + await requests[1].abort('failed') + await expect.poll(() => page.evaluate(() => window.vastSettlements.length)).toBe(3) + expect(await page.evaluate(() => ({ status: window.vastSettlements[2].status, callbacks: window.vastCallbacks, listeners: window.vastListeners.size, registered: Boolean(window.art.plugins.artplayerPluginVast) }))).toEqual({ status: 'rejected', callbacks: 0, listeners: 0, registered: false }) + await expect(page.locator('.player video')).toHaveCount(1) + await page.locator('video').evaluate(video => video.play()) + await expect.poll(() => page.locator('video').evaluate(video => video.currentTime)).toBeGreaterThan(0.2) + const pixel = await page.locator('video').evaluate((video) => { + const canvas = document.createElement('canvas') + canvas.width = 80 + canvas.height = 45 + const context = canvas.getContext('2d') + context.drawImage(video, 0, 0, 80, 45) + return [...context.getImageData(30, 2, 1, 1).data] + }) + expect(pixel[0]).toBeGreaterThan(180) + expect(pixel[1]).toBeGreaterThan(180) + expect(pixel[2]).toBeLessThan(80) + await testInfo.attach('vast-package-content-frame', { contentType: 'application/json', body: JSON.stringify({ pixel }) }) + }) + + test(`VAST package ${core}/${compatibility || 'npm-default'}: destroy before SDK readiness suppresses late callbacks and construction`, async ({ page }, testInfo) => { + const requests = await setup(page, core, compatibility, testInfo) + await page.evaluate(() => { + void window.registerVast() + }) + await expect.poll(() => requests.length).toBe(1) + await page.evaluate(() => window.art.destroy()) + await expect(page.locator('.player')).toBeEmpty() + expect(await page.evaluate(() => window.vastListeners.size)).toBe(0) + await requests[0].fulfill({ contentType: 'text/javascript', body: 'window.google = { ima: { fixtureReady: true } };' }) + await expect.poll(() => page.evaluate(() => window.vastSettlements.map(item => item.status))).toEqual(['resolved']) + expect(await page.evaluate(() => ({ callbacks: window.vastCallbacks, listeners: window.vastListeners.size, destroyed: window.art.isDestroy, sentinel: window.google.ima.fixtureReady }))).toEqual({ callbacks: 0, listeners: 0, destroyed: true, sentinel: true }) + await expect(page.locator('.player')).toBeEmpty() + expect(requests).toHaveLength(1) + }) + } +} diff --git a/test/browser/vast-recovery.native.js b/test/browser/vast-recovery.native.js index 05eb626fa..3957dac03 100644 --- a/test/browser/vast-recovery.native.js +++ b/test/browser/vast-recovery.native.js @@ -1,14 +1,15 @@ 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' -// eslint-disable-next-line antfu/no-top-level-await -- Bundle the real SDK once before test discovery. -const code = await compilePackage('artplayer-plugin-vast', 'umd') +// eslint-disable-next-line antfu/no-top-level-await -- Select the complete source or installed SDK bundle before discovery. +const { code, provenance } = await browserCandidate('artplayer-plugin-vast') for (const compatibility of [undefined, 'workspace-1.2']) { test(`real IMA ${compatibility || 'npm-default'} recovers from empty VAST, recreates and destroys an active ad`, async ({ page }, testInfo) => { await page.goto('/test/player.html?core=candidate') await page.addScriptTag({ content: code }) + await testInfo.attach('native-vast-inputs', { contentType: 'application/json', body: JSON.stringify({ provenance, sourceSha256: hash(code), compatibility: compatibility || 'npm-default', scope: 'Complete glomex bundle with real remote Google IMA; no SDK substitution.' }) }) await page.evaluate(async (compatibility) => { window.createPlayer('/assets/sample/video.mp4') window.recoveryEvents = [] @@ -65,7 +66,7 @@ for (const compatibility of [undefined, 'workspace-1.2']) { expect(state.terminalInit).toBeNull() expect(state.events.filter(event => event.type === 'AdError')).toHaveLength(1) await expect(page.locator('[id^="art-"]')).toHaveCount(0) - await testInfo.attach('native-vast-recovery', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(code), compatibility: compatibility || 'npm-default', failure, ...state }) }) + await testInfo.attach('native-vast-recovery', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(code), provenance, compatibility: compatibility || 'npm-default', failure, ...state }) }) await testInfo.attach('native-vast-recovery.xml', { contentType: 'application/xml', body: await page.evaluate(() => window.recoveryXml) }) }) } diff --git a/test/browser/vast-skip.native.js b/test/browser/vast-skip.native.js index a24cf8532..76c47d5b6 100644 --- a/test/browser/vast-skip.native.js +++ b/test/browser/vast-skip.native.js @@ -1,16 +1,17 @@ import fs from 'node:fs' 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' import { skippableVast } from './vast-fixture.mjs' -// eslint-disable-next-line antfu/no-top-level-await -- Native SDK bundle is shared by test discovery. -const code = await compilePackage('artplayer-plugin-vast', 'umd') +// eslint-disable-next-line antfu/no-top-level-await -- Select the complete source or installed SDK bundle before discovery. +const { code, provenance } = await browserCandidate('artplayer-plugin-vast') for (const compatibility of [undefined, 'workspace-1.2']) { test(`real IMA ${compatibility || 'npm-default'} requests playUrl and skips through the SDK button`, async ({ page }, testInfo) => { await page.goto('/test/player.html?core=candidate') await page.addScriptTag({ content: code }) + await testInfo.attach('native-vast-inputs', { contentType: 'application/json', body: JSON.stringify({ provenance, sourceSha256: hash(code), compatibility: compatibility || 'npm-default', scope: 'Complete glomex bundle with real remote Google IMA; no SDK substitution.' }) }) await page.evaluate(async (compatibility) => { window.createPlayer('/assets/sample/video.mp4') window.skipEvents = [] @@ -62,7 +63,7 @@ for (const compatibility of [undefined, 'workspace-1.2']) { const state = await page.evaluate(() => ({ events: window.skipEvents, time: window.art.currentTime, paused: window.art.template.$video.paused, imaVersion: window.google.ima.VERSION })) expect(state.paused).toBe(false) expect(state.events.filter(event => event.type === 'AdError' || event.type === 'AdComplete')).toEqual([]) - await testInfo.attach('native-vast-skip', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(code), xmlSha256: hash(xml), mediaSha256: hash(fs.readFileSync('test/browser/media/pattern.mp4')), compatibility: compatibility || 'npm-default', beforeAd, ...state }) }) + await testInfo.attach('native-vast-skip', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(code), provenance, xmlSha256: hash(xml), mediaSha256: hash(fs.readFileSync('test/browser/media/pattern.mp4')), compatibility: compatibility || 'npm-default', beforeAd, ...state }) }) await page.evaluate(() => window.art.destroy()) await expect(page.locator('[id^="art-"]')).toHaveCount(0) }) diff --git a/test/browser/vast.native.js b/test/browser/vast.native.js index e5f23b716..d476f3286 100644 --- a/test/browser/vast.native.js +++ b/test/browser/vast.native.js @@ -1,11 +1,11 @@ import fs from 'node:fs' import { hash, readMember } from '../../refactor/scripts/releases.mjs' import { verifyVastContract } from '../../refactor/scripts/vast-contract.mjs' -import { compilePackage } from '../helpers/load.js' +import { browserCandidate } from '../helpers/browser-candidate.js' import { expect, test } from './fixtures.js' -// eslint-disable-next-line antfu/no-top-level-await -- Compile the real bundled SDK before test discovery. -const code = await compilePackage('artplayer-plugin-vast', 'umd') +// eslint-disable-next-line antfu/no-top-level-await -- Select the complete source or installed SDK bundle before discovery. +const { code, provenance } = await browserCandidate('artplayer-plugin-vast') // eslint-disable-next-line antfu/no-top-level-await -- Verify immutable published bytes before native test discovery. const contract = await verifyVastContract() const historicalCode = readMember(contract.archives.get('artplayer-plugin-vast@1.0.0'), 'package/dist/artplayer-plugin-vast.js').toString() @@ -38,7 +38,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) { test(`real IMA ${core} / ${implementation} plays local VAST media and resumes content`, async ({ page }, testInfo) => { await page.goto(`/test/player.html?core=${core}`) await page.addScriptTag({ content: bundle }) - await testInfo.attach('native-vast-inputs', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(bundle), sdk: historical ? 'immutable npm1.0.0 bundled SDK; real remote Google IMA' : '@glomex/vast-ima-player@1.21.2 bundled; real remote Google IMA', media: { file: mediaFile, sha256: hash(fs.readFileSync(mediaFile)), contentFile: 'docs/assets/sample/video.mp4', contentSha256: hash(fs.readFileSync('docs/assets/sample/video.mp4')) }, implementation, core, compatibility: compatibility || 'npm-default' }) }) + await testInfo.attach('native-vast-inputs', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(bundle), provenance: historical ? { kind: 'published', name: 'artplayer-plugin-vast', version: '1.0.0' } : provenance, sdk: historical ? 'immutable npm1.0.0 bundled SDK; real remote Google IMA' : '@glomex/vast-ima-player@1.21.2 bundled; real remote Google IMA', media: { file: mediaFile, sha256: hash(fs.readFileSync(mediaFile)), contentFile: 'docs/assets/sample/video.mp4', contentSha256: hash(fs.readFileSync('docs/assets/sample/video.mp4')) }, implementation, core, compatibility: compatibility || 'npm-default' }) }) await page.evaluate(({ compatibility, historical }) => { window.nativeVastHistorical = historical window.nativeVastEvents = []