test(ci): [CI-01] verify installed Iframe tools across window boundaries

This commit is contained in:
Harvey Zhao committed 2026-09-15 04:34:28 +08:00
1 parent 4a9ef16b16
commit ce67eaa73e
19 files changed
+584 -28

No files matched your search

@@ -212,3 +212,28 @@ Physical devices, native Firefox/WebKit cached restoration and final distributio
remain separate gates. Desktop evidence does not establish the old helper or
package-name migration. See ADR-026 and the task 05 history checkpoint for the
exact fixture, automation limitations and source/main/legacy evidence.
## Installed browser checks
`yarn test:package --browser` includes this tool in the shared installed roster.
The pack checker verifies the real `artplayer-plugin-iframe@1.0.0` archive and
the frozen workspace independently. The tool's required dist/type filenames
come from its verified workspace manifest and files; the old plugin/helper
names are not silently treated as releases of the tool. Publishing or restoring
the predecessor package remains a separate distribution decision.
The five `test/browser/iframe*.spec.js` files use `iframeBrowserCandidate()` from
`test/helpers/iframe.js`. With an installed map it must load the verified tool
UMD, reject an explicit artifact or frozen-workspace override, and record the
package/file/archive/source identity for each candidate parent and child.
Without a map the same tests retain their source and historical controls.
`iframeCandidate()` keeps its existing unit/history semantics: history may
inherit a core-only map, which does not select a tool build. Do not merge these
two fixture entrypoints or claim the separate BFCache suite used an installed
tool when it used a source/explicit tool. Browser scope configuration rejects
diagnostic flags that would remove historical Iframe rows.
The installed tests cover protocol, peer/document boundaries, native navigation,
old/new player combinations and the real docs editor. They do not replace the
special `test:iframe-history` browser channel or physical-device evidence. See
`../../refactor/changes/2026-09-15-CI-01-iframe-installed.md` for measured results.
@@ -0,0 +1,355 @@
{
"schemaVersion": 1,
"task": "CI-01",
"date": "2026-09-15",
"sourceHead": "4a9ef16b168eecc1f39b08290415d76a25852314",
"candidate": "Working tree in the accompanying Iframe installed-browser checkpoint; executable fingerprints normalize LF",
"environment": {
"node": "24.21.0",
"yarn": "1.22.22",
"platform": "win32"
},
"inputs": [
{
"file": "scripts/package-check.mjs",
"sha256LF": "fbab857fd7565dad953b7c0387bd31bb19f986e1e5f6adc5525b2e76e25923b0"
},
{
"file": "scripts/browser-validation/scope.ts",
"sha256LF": "510e857438df5dcd410a4506f074b3864b8eb1e8f05a32acfbca1ff9056ddf3f"
},
{
"file": "test/helpers/iframe.js",
"sha256LF": "46549ef9b090cd69605582b6d011900ad60068b97ce75b0df821194319b1d93e"
},
{
"file": "test/package-check.test.js",
"sha256LF": "81717f7f9dfd46b99d90d84135cfb53181e3453ab9e3d18dd350c101d8781766"
},
{
"file": "test/browser-validation.test.js",
"sha256LF": "d1598e4393b4bb2e4d1e590b6c9744a47cf6aaff7c0086f9fb8d3eb38644cf8c"
},
{
"file": "test/browser/iframe.spec.js",
"sha256LF": "bfa55adb681caef7dcc2ede58f75230f1af281591a2868624522511fa9507baa"
},
{
"file": "test/browser/iframe-boundaries.spec.js",
"sha256LF": "11cce4e116abc0f0d34aa2e84c736071064e7ffaf403bfb294375d60e8c545d1"
},
{
"file": "test/browser/iframe-navigation.spec.js",
"sha256LF": "eae9fa7e840d79bd60b3ab9966517768d7975bc6988a7a6f4ef50723bc017309"
},
{
"file": "test/browser/iframe-player.spec.js",
"sha256LF": "09cd9b63f56b62d403cdfba187d4c854a25a922913623633efe843234667756c"
},
{
"file": "test/browser/iframe-editor.spec.js",
"sha256LF": "fe8865c82287001cc6432a6b9aece8359c1bd5a8298e2386b0db14eb3882b60a"
}
],
"packages": {
"directory": "refactor/.cache/packages/run-qBFP13",
"reportSha256": "58dbe13edcf4a8f529ee973868c6125cbf13b3cfd3a2515c17cf9b2c95c2cfa3",
"count": 19,
"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"
],
"tool": {
"name": "artplayer-tool-iframe",
"version": "1.1.0",
"archive": "artplayer-tool-iframe.tgz",
"sha256": "e8df09c3edd98de8fa0c731f2cf8769b243560a74d02bddd3e924f8079053365",
"files": {
"package/ARCHITECTURE.md": "dcb0d58fd02730a7a2cc620e4e7e934fae22dbbf85cc9a6eae21ee5ac2cba167",
"package/README.md": "ef5af60bcb831667a78cc3421f6e3c7b5d9a50b3d2e571c5cd02d46fc533e9ce",
"package/dist/artplayer-tool-iframe.js": "7408fceccb000e1b304abe30ac9bc364da7860c3896ad5aaa6af427804c237e6",
"package/dist/artplayer-tool-iframe.legacy.js": "4e8bd39cdc37e98bff9cd19b5e0c9453ebe5231264242fc1c573aa96edb5ea8a",
"package/dist/artplayer-tool-iframe.mjs": "ac4c5efa670c0429fc95bdab7902df60f5b5540f0dad9c7e65c2bd660796dc83",
"package/package.json": "0de9c86eeb3f9a2fb46f9297105b5beddb8d6164163ffaa9874cf5cd68ba7a58",
"package/types/artplayer-tool-iframe.d.cts": "66850135f45c81d4bd7a0de33b6a2f63a655de9000f2698f43660039727919f3",
"package/types/artplayer-tool-iframe.d.mts": "cd394fd68e61fab3c4474daab29852a3f87dd0086b007e7937106871272676fb",
"package/types/artplayer-tool-iframe.d.ts": "e760de22559caf7ffff48d7c5713485bf9f0ddbe876a93080295fb6106535bfa"
}
},
"exitCode": 0,
"wallSeconds": 118.17,
"genericConsumerScope": [
"artplayer",
"artplayer-plugin-chapter"
],
"baseline": "Verified npm artplayer-plugin-iframe@1.0.0 plus frozen artplayer-tool-iframe workspace at 40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f; distinct package identities"
},
"collection": {
"directory": "refactor/.cache/ci01-iframe-collection-report",
"reportSha256": "71d7ae92094a1cac3327619a0ac25c91a75c4d5bb6e3b8a9b4c0beee70e9108c",
"cases": 2406,
"files": 72,
"perEngine": {
"chromium": 802,
"firefox": 802,
"webkit": 802
},
"collectionOnly": true
},
"installed": {
"directory": "refactor/.cache/ci01-iframe-installed-report",
"reportSha256": "0b69259f917ad79a32b9ea65b9d7ca36d91941ea6bf42a2d83e8e6b1786f1c29",
"invocationSha256": "b383fcf488927f9a5bbaa57d1c4d24fb4b558351487ae1043a25918f3d8edf0e",
"args": [
"test",
"--config=playwright.installed.config.js",
"iframe",
"--workers=2"
],
"result": {
"scope": "installed",
"exitCode": 0,
"signal": null,
"args": [
"test",
"--config=playwright.installed.config.js",
"iframe",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T20:25:57.627Z",
"duration": 279343.054,
"expected": 426,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"browsers": {
"chromium": "153.0.8010.12",
"firefox": "155.0",
"webkit": "26.6"
},
"files": {
"chromium/iframe-boundaries.spec.js": {
"expected": 20,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"firefox/iframe-boundaries.spec.js": {
"expected": 20,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"webkit/iframe-boundaries.spec.js": {
"expected": 20,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"chromium/iframe-editor.spec.js": {
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"firefox/iframe-editor.spec.js": {
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"webkit/iframe-editor.spec.js": {
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"chromium/iframe-navigation.spec.js": {
"expected": 39,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"firefox/iframe-navigation.spec.js": {
"expected": 39,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"webkit/iframe-navigation.spec.js": {
"expected": 39,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"chromium/iframe-player.spec.js": {
"expected": 24,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"firefox/iframe-player.spec.js": {
"expected": 24,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"webkit/iframe-player.spec.js": {
"expected": 24,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"chromium/iframe.spec.js": {
"expected": 58,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"firefox/iframe.spec.js": {
"expected": 58,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"webkit/iframe.spec.js": {
"expected": 58,
"skipped": 0,
"unexpected": 0,
"flaky": 0
}
},
"casesWithCandidateIdentity": 264,
"identityRecords": 318,
"identityCountsByProject": {
"chromium": 106,
"firefox": 106,
"webkit": 106
},
"failures": []
},
"source": {
"directory": "refactor/.cache/ci01-iframe-source-report",
"reportSha256": "071cba96f1289cd4064bd777265e9c0c8ecab868c8cca77698f615a35236cb28",
"invocationSha256": "e2bd4beb7fee90c66918cd54cd84fcf479b88be3a7d51c01ea99816b90fb2cce",
"args": [
"test",
"--config=playwright.source.config.js",
"--project=chromium",
"--workers=2",
"--grep",
"(Iframe docs editor Run|Iframe player candidate/same-origin/candidate-source/candidate-source|Iframe document same-origin/attribute-navigation/candidate-source)"
],
"result": {
"scope": "source",
"exitCode": 0,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"--project=chromium",
"--workers=2",
"--grep",
"(Iframe docs editor Run|Iframe player candidate/same-origin/candidate-source/candidate-source|Iframe document same-origin/attribute-navigation/candidate-source)"
]
},
"stats": {
"startTime": "2026-09-14T20:31:13.013Z",
"duration": 34660.732,
"expected": 3,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"browsers": {
"chromium": "153.0.8010.12"
},
"files": {
"chromium/iframe-editor.spec.js": {
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"chromium/iframe-navigation.spec.js": {
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"chromium/iframe-player.spec.js": {
"expected": 1,
"skipped": 0,
"unexpected": 0,
"flaky": 0
}
},
"casesWithCandidateIdentity": 3,
"identityRecords": 4,
"identityCountsByProject": {
"chromium": 4
},
"failures": []
},
"verification": {
"tests": {
"passed": 113,
"failed": 0,
"durationMs": 3402.3125,
"log": "refactor/.cache/ci01-iframe-tests.log",
"logSha256": "cf9dfd8f04bbd1bd5b458fdba9f99661fce167917fd5683d4db8e4c5dee044d5"
},
"lint": {
"exitCode": 0,
"log": "refactor/.cache/ci01-iframe-lint.log",
"logSha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"libraryTypes": {
"exitCode": 0,
"log": "refactor/.cache/ci01-iframe-types.log",
"logSha256": "62ec4acffe7f959331ef7e06a10ebd9bc15263dc4c0624aedaed71e65e96c397"
},
"docsToolsTypes": {
"exitCode": 0,
"log": "refactor/.cache/ci01-iframe-docs-types.log",
"logSha256": "4ee15d1ddc61e077c26ded3c173c50a332ff4bc31c883febc247eba00f6826f3"
},
"toolchain": {
"exitCode": 0,
"log": "refactor/.cache/ci01-iframe-toolchain.log",
"logSha256": "0f342a63471393db876e60cb17b1e8ea4d21c2724848e8c50a05eba519ddcae6"
},
"ci": {
"exitCode": 0,
"log": "refactor/.cache/ci01-iframe-ci.log",
"logSha256": "2a42b55e8510c2b958e9dbd0cc99a359e6e80e6a1e1a3ddf0a27590ca3eb9741"
}
},
"limitations": [
"Collection is not a full run of all installed tests.",
"Candidate identity records can count both parent and child; they are not unique test counts. Pure historical rows preserve immutable predecessor/workspace inputs.",
"The separate BFCache/history server retains its source/explicit tool semantics and is not covered by this installed-tool claim.",
"No new npm package identity, helper alias, public API, type, dependency or version change; predecessor distribution decisions remain separate.",
"No remote OS matrix, physical-device or release-review/publication acceptance; CI-01 and PKG-IFRAME-05 remain in progress."
]
}
@@ -0,0 +1,69 @@
# CI-01 Iframe 安装产物与跨窗口组合
## 范围与兼容性
接续 `4a9ef16b168eecc1f39b08290415d76a25852314`。统一 browser 安装清单加入
artplayer-tool-iframe 和五个既有浏览器测试文件。生产源码、公开类型、API、
DOM/事件、依赖版本和实际发布入口没有变化,也没有新增测试重试或放宽等待。
这个包的真实 npm 前身是 artplayer-plugin-iframe@1.0.0,拥有独立的 plugin/
helper 文件;未发布工作区则使用 artplayer-tool-iframe。打包检查先核验真实
旧归档与冻结 Git 源文件,再从冻结 tool manifest/dist/types 提取四个必须
保留的工具文件名。不会伪造 tool 名下的旧 npm 版本,也不会把旧 helper 当成
新工具的等价入口。`historicalDistributionFiles()` 为此提供显式分支;其他
包继续核对同名 npm 分发。旧包名与 helper 的最终分发策略仍由原任务负责。
`iframeBrowserCandidate()` 在有安装 map 时通过共用 browserCandidate 核验
核心/Chapter/工具的新鲜度和安装文件,加载正常 UMD,拒绝显式 artifact 与
冻结工作区替换。父/子窗口候选分别附加安装成员、归档和源文件身份。纯历史
对照仍使用原来的固定 npm/工作区字节;source 入口清除继承的安装 map。
原来的 `iframeCandidate()` 保留 unit/history 的源码或显式产物语义。核对
调用方时确认 history 服务会继承只含核心的 map,因此不能直接让这个旧 helper
改读工具安装包。两种用途有独立入口和回归保护,history 的 BFCache 配置也
保持原样。安装范围拒绝 BASELINE、LIFECYCLE_ONLY、BOUNDARIES_ONLY 三个
诊断开关,防止候选被替换或历史行被删掉。源码诊断仍保留原功能。
## 实际验证
Windows、Node 24.21.0、Yarn 1.22.22;Chromium 153.0.8010.12、Firefox
155.0、WebKit 26.6。完整结果及输入指纹见
[机器证据](../baselines/ci-iframe-installed-validation.json)。
| 检查 | 实际结果 |
| --- | --- |
| `yarn test:package --browser` | 十九包正常构建、pack、隔离安装及离线冻结复装通过,118.17 秒;run-qBFP13 |
| installed 全量 collection | 72 文件、2,406 项,三个引擎各 802 项;只收集,不计为全量运行 |
| `yarn test:browser:installed iframe --workers=2` | 五文件、三个引擎 426/426 通过,279,343.054 ms;每个引擎 142 项,零 skip/失败/flaky/重试 |
| source 定向检查 | Chromium 的真实编辑器、属性导航、新核心新父/子工具组合 3/3 通过,34,660.732 ms |
| Node 单元与工程回归 | 113/113 通过,3,402.3125 ms;包含原 Iframe 行为/生命周期/边界/导航和打包/launcher 负例 |
| 工程检查 | 改动文件只读 ESLint、library/docs-tools 严格类型、固定工具链和 CI 契约通过 |
五文件每个引擎分别为:协议 58、边界 20、导航 39、播放器 24、编辑器 1。
实际协议包含新旧消息、同一时刻请求、销毁中请求及 clone 失败;边界/导航
覆盖同源、跨域、opaque sandbox、重定向、属性/子页面导航及历史对照。播放器
使用三代核心与新旧父/子工具,真实播放、seek、切源、全屏网页控制和销毁。
编辑器通过实际 docs index/Monaco/Run 检查清理;它仍使用既有观察子类和测试
广告脚本拦截,不扩展成生产行为。
安装用例有 264 项附带候选工具身份,共 318 条身份记录;父/子同时使用候选
时会记录两条,不能将身份条数当成用例数。其余 162 项为纯历史对照。源码
三个用例共有四条 source-build 身份,安装 map 为 null。通用 pack 的运行时/
类型消费仍仅 core/chapter:36 运行时、5 旧类型与 8 精确类型模式。Iframe
自己的公开类型证据保留在 PKG-IFRAME-04,本次不虚报全包通用类型覆盖。
报告在进程实际结束后归档为 `refactor/.cache/ci01-iframe-installed-report`、
`ci01-iframe-source-report` 和 `ci01-iframe-collection-report`;同前缀日志保存
真实终态输出。复跑先执行 test:package --browser、设置其 browser-artifacts.json,
再执行上述 installed 命令。源码定向正则见机器记录中的 args;全量 CI 不使用
这个定向正则。重跑前先归档同一 scope 的旧报告。
## 剩余门槛
CI-01、PKG-IFRAME-05 继续 doing;197/263 完成不变。此次没有重跑特殊 Chromium
channel 的 BFCache suite,也不声称 Firefox/WebKit 实际缓存恢复、物理设备、
旧包 helper 分发或发布复盘已完成。远端完整 OS × 引擎矩阵仍未运行。
没有 push、部署或 npm 发布。代码、测试、架构说明和证据随独立 CI-01 本地
检查点提交,之后执行提交审计。回退此提交只撤回安装验证接入及相关记录,
不会回退或改变已迁移的工具生产实现。
+7 -1
View File
@@ -5,10 +5,16 @@
核验的统一包清单,执行明确列出的 installed 子集;清单维护在
`scripts/browser-validation/scope.ts`。两步不
吞失败,普通失败后仍保存另一范围的证据,分别上传browser-source/browser-installed。
入口与报告规则见[维护说明](../scripts/browser-validation/README.md)。source全量
入口与报告规则见[维护说明](../scripts/browser-validation/README.md)。
目前按 OS × 引擎拆分播放作业,消费者另行执行;各作业的实际远端耗时/60分钟预算、
其余插件安装矩阵与远端运行仍待CI-01/CI-04,不计为已通过。
Iframe 接入后共同安装清单含十九包;新增五文件本地三引擎 426/426 通过,
完整 installed collection 为 72 文件/2,406 项,见
[安装记录](changes/2026-09-15-CI-01-iframe-installed.md)。原 browser-consumers 的
history 仍保留源码/显式工具加核心 map 的独立语义,不因新增安装清单而自动
宣称 BFCache 已使用安装工具验收。
## 本地命令
使用 toolchain-setup.md 的 Node/Yarn 环境与冻结安装。
+2 -2
View File
@@ -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)
- 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-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)
@@ -639,7 +639,7 @@
- PKG-IFRAME-02: [记录](changes/2026-09-12-PKG-IFRAME-02-behavior.md) [记录](baselines/iframe-behavior-validation.json)
- PKG-IFRAME-03: [记录](changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md) [记录](baselines/iframe-requests-checkpoint.json) [记录](changes/2026-09-12-PKG-IFRAME-03-boundaries-checkpoint.md) [记录](baselines/iframe-boundaries-checkpoint.json) [记录](iframe-message-boundary.md) [记录](changes/2026-09-13-PKG-IFRAME-03-navigation.md) [记录](baselines/iframe-navigation-validation.json) [记录](iframe-document-protocol.md)
- PKG-IFRAME-04: [记录](changes/2026-09-13-PKG-IFRAME-04-types.md) [记录](baselines/iframe-types-validation.json)
- PKG-IFRAME-05: [记录](changes/2026-09-13-PKG-IFRAME-05-integration.md) [记录](baselines/iframe-integration-validation.json) [记录](changes/2026-09-13-PKG-IFRAME-05-history.md) [记录](baselines/iframe-history-validation.json)
- PKG-IFRAME-05: [记录](changes/2026-09-13-PKG-IFRAME-05-integration.md) [记录](baselines/iframe-integration-validation.json) [记录](changes/2026-09-13-PKG-IFRAME-05-history.md) [记录](baselines/iframe-history-validation.json) [记录](changes/2026-09-15-CI-01-iframe-installed.md) [记录](baselines/ci-iframe-installed-validation.json)
- PKG-TOOL-THUMB-01: [记录](baselines/thumbnail-release.json) [记录](baselines/thumbnail-contract.md) [记录](changes/2026-09-13-PKG-TOOL-THUMB-01-contract.md) [记录](baselines/thumbnail-contract-validation.json)
- PKG-TOOL-THUMB-02: [记录](baselines/thumbnail-behavior-validation.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-02-behavior.md)
- PKG-TOOL-THUMB-03: [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-input-checkpoint.md) [记录](baselines/thumbnail-input-checkpoint.json) [记录](changes/2026-09-13-PKG-TOOL-THUMB-03-lifecycle.md) [记录](baselines/thumbnail-lifecycle-validation.json)
+9
View File
@@ -1,5 +1,14 @@
# 进度与证据
## CI-01 Iframe 安装包与真实跨窗口组合
统一清单现含十九包,保留 Iframe 真实旧 npm 包与冻结 tool 工作区的不同身份。
五文件三引擎 426/426 全部通过,零跳过/失败/重试;覆盖历史协议、窗口/文档
边界、导航、新旧核心/父子工具和实际编辑器。源码定向 3/3、单元及工程 113/113。
见[记录](changes/2026-09-15-CI-01-iframe-installed.md)。完整安装 collection
为 72 文件/2,406 项,并未全跑;197/263 完成。CI-01/IFRAME-05 继续 doing,
其余生态分发、特殊 BFCache、设备和远端矩阵门槛保留。
## CI-01 浏览器引擎与消费者作业拆分
播放检查改为 3 个 OS × 3 个引擎;Node/React/Vue/iframe history/性能每个 OS
+8 -4
View File
@@ -776,9 +776,11 @@
"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"
"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"
]
},
{
@@ -4080,7 +4082,9 @@
"changes/2026-09-13-PKG-IFRAME-05-integration.md",
"baselines/iframe-integration-validation.json",
"changes/2026-09-13-PKG-IFRAME-05-history.md",
"baselines/iframe-history-validation.json"
"baselines/iframe-history-validation.json",
"changes/2026-09-15-CI-01-iframe-installed.md",
"baselines/ci-iframe-installed-validation.json"
]
},
{
+9
View File
@@ -20,6 +20,15 @@ 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.
Iframe adds five installed files through `iframeBrowserCandidate()`. Its actual
published predecessor is `artplayer-plugin-iframe`; required current tool paths
are checked against the frozen workspace manifest rather than invented npm tool
contents. Candidate parent/child evidence includes installed provenance, while
historical rows retain their own names and bytes. Installed scope rejects the
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.
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,
+7 -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', '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']
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 installedTests = [
'playback.spec.js',
'lifecycle.spec.js',
@@ -71,6 +71,11 @@ export const installedTests = [
'danmuku-scheduler.spec.js',
'danmuku-stability.spec.js',
'danmuku-timing-diagnostic.spec.js',
'iframe.spec.js',
'iframe-boundaries.spec.js',
'iframe-navigation.spec.js',
'iframe-player.spec.js',
'iframe-editor.spec.js',
]
export function browserInvocation(scope: string, args: string[], environment: NodeJS.ProcessEnv) {
@@ -84,6 +89,7 @@ export function browserInvocation(scope: string, args: string[], environment: No
assert(env.ARTPLAYER_BROWSER_ARTIFACTS, 'Installed browser checks require ARTPLAYER_BROWSER_ARTIFACTS')
assert(env.ARTPLAYER_MB_BROWSER_CANDIDATE !== '1', 'Installed browser checks must retain MediaBunny historical controls')
assert(env.ARTPLAYER_MASK_PROFILE !== '1', 'Installed browser checks cannot enable Mask CPU profiling')
assert(!['ARTPLAYER_IFRAME_BASELINE', 'ARTPLAYER_IFRAME_LIFECYCLE_ONLY', 'ARTPLAYER_IFRAME_BOUNDARIES_ONLY'].some(name => env[name] === '1'), 'Installed browser checks must retain Iframe candidate and historical controls')
assert(!env.ARTPLAYER_DASH_DIAGNOSTIC_SDK || env.ARTPLAYER_DASH_DIAGNOSTIC_SDK === 'none', 'Installed browser checks require the unchanged DASH SDK')
assert(!['ARTPLAYER_DASH_DIAGNOSE_STALL', 'ARTPLAYER_DASH_DIAGNOSE_GETTER', 'ARTPLAYER_DASH_DIAGNOSE_METRICS'].some(name => env[name] === '1'), 'Installed browser checks cannot use DASH recovery diagnostics')
}
+18 -1
View File
@@ -43,6 +43,22 @@ export function checkFiles(manifest, files, historical = []) {
assert(!files.includes('package/tsconfig.json'), 'Implementation tsconfig leaked into package')
}
export function historicalDistributionFiles(name, { baseline, sources }) {
if (name !== 'artplayer-tool-iframe') {
assert.equal(baseline.release.name, name, 'Review renamed package distribution separately')
return Object.keys(baseline.release.files)
}
assert.equal(baseline.release.name, 'artplayer-plugin-iframe', 'Iframe keeps its distinct published predecessor')
const prefix = `packages/${name}/`
const manifest = JSON.parse(sources.get(`${prefix}package.json`))
assert.equal(manifest.name, name, 'Iframe distribution must use the frozen tool manifest')
const files = Object.keys(baseline.source).filter(file => file.startsWith(`${prefix}dist/`) || file.startsWith(`${prefix}types/`))
assert(files.length > 0 && files.every(file => sources.has(file)), 'Missing verified iframe workspace distribution')
const members = files.map(file => `package/${file.slice(prefix.length)}`)
checkFiles(manifest, members)
return members
}
export function packedFiles(archive) {
const args = { encoding: 'utf8', windowsHide: true }
const names = execFileSync('tar', ['-tzf', archive], args).trim().split(/\r?\n/)
@@ -99,6 +115,7 @@ export async function checkPackages({ release = false, include = [] } = {}) {
'artplayer-plugin-jassub': async () => (await import('../refactor/scripts/jassub-contract.mjs')).verifyJassubContract(),
'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-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(),
@@ -108,7 +125,7 @@ export async function checkPackages({ release = false, include = [] } = {}) {
for (const name of include) {
assert(Object.hasOwn(supported, name), `Additional package needs a reviewed contract: ${name}`)
const contract = await supported[name]()
historicalFiles.set(name, Object.keys(contract.baseline.release.files))
historicalFiles.set(name, historicalDistributionFiles(name, contract))
}
const names = [...defaultNames, ...include]
const parent = path.join(workspace, 'refactor/.cache/packages')
+5
View File
@@ -39,6 +39,11 @@ test('Installed validation rejects diagnostic SDK substitutions and recovery pro
const profile = { ...base, ARTPLAYER_MASK_PROFILE: '1' }
assert.throws(() => browserInvocation('installed', [], profile), /Mask CPU profiling/)
assert.equal(browserInvocation('source', [], profile).env.ARTPLAYER_MASK_PROFILE, '1')
for (const name of ['ARTPLAYER_IFRAME_BASELINE', 'ARTPLAYER_IFRAME_LIFECYCLE_ONLY', 'ARTPLAYER_IFRAME_BOUNDARIES_ONLY']) {
const env = { ...base, [name]: '1' }
assert.throws(() => browserInvocation('installed', [], env), /retain Iframe candidate and historical controls/)
assert.equal(browserInvocation('source', [], env).env[name], '1')
}
for (const mode of ['upstream4', 'bufferlevel4']) {
const env = { ...base, ARTPLAYER_DASH_DIAGNOSTIC_SDK: mode }
assert.throws(() => browserInvocation('installed', [], env), /unchanged DASH SDK/)
+3 -3
View File
@@ -1,9 +1,9 @@
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { iframeCandidate, iframeHistorical } from '../helpers/iframe.js'
import { iframeBrowserCandidate, iframeHistorical } from '../helpers/iframe.js'
import { expect, test } from './fixtures.js'
const candidate = await iframeCandidate()
const candidate = await iframeBrowserCandidate()
const old = (await iframeHistorical()).filter(item => ['published-artplayer-plugin-iframe.js', 'workspace.js'].includes(item.name))
const cases = ['same-origin', 'cross-origin', 'opaque-origin', 'redirect'].flatMap(relation => ['parent-guard', 'child-guard', 'malformed'].map(scenario => ({ relation, scenario, parent: candidate, child: candidate })))
if (process.env.ARTPLAYER_IFRAME_BOUNDARIES_ONLY !== '1') {
@@ -139,7 +139,7 @@ for (const item of cases) {
}
const packets = await page.evaluate(() => window.packets)
expect(packets.filter(packet => packet.fromChild).every(packet => packet.origin === eventOrigin)).toBe(true)
await testInfo.attach('iframe-boundary', { contentType: 'application/json', body: JSON.stringify({ relation: item.relation, scenario: item.scenario, parent: { name: item.parent.name, sha256: hash(item.parent.code) }, child: { name: item.child.name, sha256: hash(item.child.code) }, parentOrigin, initialUrl, childUrl, eventOrigin, redirects, result, packets, scope: 'Actual window peers and native source/origin. Redirect is an HTTP 302 from the local test server; opaque peer is sandbox allow-scripts. Mixed-version rows cover normal wire compatibility only, not security of an unchanged historical peer. No player/media integration.' }) })
await testInfo.attach('iframe-boundary', { contentType: 'application/json', body: JSON.stringify({ relation: item.relation, scenario: item.scenario, parent: { name: item.parent.name, sha256: hash(item.parent.code), provenance: item.parent.provenance || null }, child: { name: item.child.name, sha256: hash(item.child.code), provenance: item.child.provenance || null }, parentOrigin, initialUrl, childUrl, eventOrigin, redirects, result, packets, scope: 'Actual window peers and native source/origin. Redirect is an HTTP 302 from the local test server; opaque peer is sandbox allow-scripts. Mixed-version rows cover normal wire compatibility only, not security of an unchanged historical peer. No player/media integration.' }) })
}
finally {
await page.evaluate(() => {
+3 -3
View File
@@ -1,9 +1,9 @@
import fs from 'node:fs'
import { hash } from '../../refactor/scripts/releases.mjs'
import { iframeCandidate } from '../helpers/iframe.js'
import { iframeBrowserCandidate } from '../helpers/iframe.js'
import { expect, test } from './fixtures.js'
const candidate = await iframeCandidate()
const candidate = await iframeBrowserCandidate()
test('Iframe docs editor Run releases the previous tool and its pending requests', async ({ page }, testInfo) => {
await page.route('https://pagead2.googlesyndication.com/**', route => route.fulfill({ contentType: 'text/javascript', body: '' }))
@@ -67,6 +67,6 @@ test('Iframe docs editor Run releases the previous tool and its pending requests
await expect.poll(() => page.evaluate(() => window.exampleFinished)).toBe(true)
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
expect(await page.evaluate(() => window.previousPlayers.every(art => art.isDestroy))).toBe(true)
await testInfo.attach('iframe-editor', { contentType: 'application/json', body: JSON.stringify({ candidateSha256: hash(candidate.code), files: Object.fromEntries(['docs/index.html', 'docs/iframe.html', 'docs/assets/example/iframe.js', 'docs/assets/js/common.js'].map(file => [file, hash(fs.readFileSync(file))])), scope: 'Actual docs index, local Monaco and Run button. Only the iframe constructor is observed through a test subclass; external advertising script is stubbed in this test only. No physical-device/BFCache claim.' }) })
await testInfo.attach('iframe-editor', { contentType: 'application/json', body: JSON.stringify({ candidateSha256: hash(candidate.code), provenance: candidate.provenance || null, files: Object.fromEntries(['docs/index.html', 'docs/iframe.html', 'docs/assets/example/iframe.js', 'docs/assets/js/common.js'].map(file => [file, hash(fs.readFileSync(file))])), scope: 'Actual docs index, local Monaco and Run button. Only the iframe constructor is observed through a test subclass; external advertising script is stubbed in this test only. No physical-device/BFCache claim.' }) })
await page.evaluate(() => window.demoTools.forEach(tool => tool.destroy()))
})
+4 -4
View File
@@ -1,9 +1,9 @@
import fs from 'node:fs'
import { hash } from '../../refactor/scripts/releases.mjs'
import { iframeCandidate, iframeHistorical } from '../helpers/iframe.js'
import { iframeBrowserCandidate, iframeHistorical } from '../helpers/iframe.js'
import { expect, test } from './fixtures.js'
const implementation = await iframeCandidate()
const implementation = await iframeBrowserCandidate()
const scenarios = ['inject-before-load', 'duplicate-inject', 'attribute-navigation', 'child-navigation', 'rapid-navigation', 'same-source-reload', 'fragment-navigation', 'attribute-fragment', 'stale-packets', 'history-back', 'srcdoc-navigation']
const childHtml = fs.readFileSync(new URL('./iframe-boundary-child.html', import.meta.url), 'utf8')
@@ -222,7 +222,7 @@ for (const relation of ['same-origin', 'cross-origin', 'opaque-origin']) {
}
const timeline = await page.evaluate(() => window.timeline)
expect(await page.evaluate(() => window.callbacks.some(packet => packet.type === 'artplayer-tool-iframe:session'))).toBe(false)
await testInfo.attach('iframe-navigation', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), relation, scenario, parentOrigin, childOrigin, result, timeline, scope: 'Actual native frame navigation and postMessage; history-back records actual pageshow.persisted rather than assuming bfcache. Delayed-load barrier forces injection before load. Stale-packet tests replay old metadata from the controlled current frame to isolate document filtering. No media/player integration.' }) })
await testInfo.attach('iframe-navigation', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), provenance: implementation.provenance || null, relation, scenario, parentOrigin, childOrigin, result, timeline, scope: 'Actual native frame navigation and postMessage; history-back records actual pageshow.persisted rather than assuming bfcache. Delayed-load barrier forces injection before load. Stale-packet tests replay old metadata from the controlled current frame to isolate document filtering. No media/player integration.' }) })
}
finally {
releaseBarrier?.()
@@ -278,7 +278,7 @@ for (const historical of (await iframeHistorical()).filter(item => ['published-a
expect(result.states.new.value).toBe('2')
expect(result.states.old.error).toBe('The iframe document has changed')
expect(result.pending).toBe(0)
await testInfo.attach('iframe-navigation', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), relation, scenario: 'legacy-child-source-change', child: { name: historical.name, sha256: hash(historical.code) }, parentOrigin, childOrigin, result, scope: 'Actual source attribute navigation with an immutable historical child. No document metadata or internal child navigation protection is claimed for an unchanged legacy peer.' }) })
await testInfo.attach('iframe-navigation', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), provenance: implementation.provenance || null, relation, scenario: 'legacy-child-source-change', child: { name: historical.name, sha256: hash(historical.code) }, parentOrigin, childOrigin, result, scope: 'Actual source attribute navigation with an immutable historical child. No document metadata or internal child navigation protection is claimed for an unchanged legacy peer.' }) })
}
finally {
await page.evaluate(() => {
+3 -3
View File
@@ -1,9 +1,9 @@
import fs from 'node:fs'
import { hash } from '../../refactor/scripts/releases.mjs'
import { iframeCandidate, iframeHistorical } from '../helpers/iframe.js'
import { iframeBrowserCandidate, iframeHistorical } from '../helpers/iframe.js'
import { expect, test } from './fixtures.js'
const candidate = await iframeCandidate()
const candidate = await iframeBrowserCandidate()
const published = (await iframeHistorical()).find(item => item.name === 'published-artplayer-plugin-iframe.js')
const demo = fs.readFileSync('docs/assets/example/iframe.js', 'utf8')
const childHtml = fs.readFileSync('docs/iframe.html', 'utf8')
@@ -116,7 +116,7 @@ for (const core of ['published-4.5.9', 'published', 'candidate']) {
return 17
}))
expect(afterDestroy).toBe(17)
await testInfo.attach('iframe-player', { contentType: 'application/json', body: JSON.stringify({ core, coreSha256: hash(coreBytes), parent: { name: parentTool.name, sha256: hash(parentTool.code) }, child: { name: childTool.name, sha256: hash(childTool.code) }, relation, initial, seek, switched, destroyed, events, demoSha256: hash(demo), adaptedDemoSha256: hash(adaptedDemo), childHtmlSha256: hash(childHtml), scope: 'Actual docs iframe.html, parent example and CSS in a minimal parent harness; only source URL and explicit test-only old global-name aliases are adapted. Actual media plays/seeks/switches, fullscreenWeb control sends messages and both core/tool lifetimes are tested independently. No complete Monaco/device/BFCache claim.' }) })
await testInfo.attach('iframe-player', { contentType: 'application/json', body: JSON.stringify({ core, coreSha256: hash(coreBytes), parent: { name: parentTool.name, sha256: hash(parentTool.code), provenance: parentTool.provenance || null }, child: { name: childTool.name, sha256: hash(childTool.code), provenance: childTool.provenance || null }, relation, initial, seek, switched, destroyed, events, demoSha256: hash(demo), adaptedDemoSha256: hash(adaptedDemo), childHtmlSha256: hash(childHtml), scope: 'Actual docs iframe.html, parent example and CSS in a minimal parent harness; only source URL and explicit test-only old global-name aliases are adapted. Actual media plays/seeks/switches, fullscreenWeb control sends messages and both core/tool lifetimes are tested independently. No complete Monaco/device/BFCache claim.' }) })
}
finally {
if (child && !child.isDetached()) {
+3 -3
View File
@@ -1,9 +1,9 @@
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { iframeCandidate, iframeHistorical } from '../helpers/iframe.js'
import { iframeBrowserCandidate, iframeHistorical } from '../helpers/iframe.js'
import { expect, test } from './fixtures.js'
const candidate = { ...await iframeCandidate(), lifecycle: true }
const candidate = { ...await iframeBrowserCandidate(), lifecycle: true }
const implementations = process.env.ARTPLAYER_IFRAME_LIFECYCLE_ONLY === '1'
? [candidate]
: [...(await iframeHistorical()).filter(item => item.global !== 'ArtplayerHelperIframe'), candidate]
@@ -213,7 +213,7 @@ for (const implementation of implementations) {
}
const packets = await page.evaluate(() => window.packets)
expect(packets.filter(packet => packet.fromChild).every(packet => packet.origin === childOrigin)).toBe(true)
await testInfo.attach('iframe-behavior', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), relation, scenario, parentOrigin, childOrigin, foreignOrigin, result, packets, lifecycleAcceptance: Boolean(implementation.lifecycle), scope: 'Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet.' }) })
await testInfo.attach('iframe-behavior', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), provenance: implementation.provenance || null, relation, scenario, parentOrigin, childOrigin, foreignOrigin, result, packets, lifecycleAcceptance: Boolean(implementation.lifecycle), scope: 'Actual browser iframe WindowProxy/source/origin and postMessage. Local route fulfills immutable library bytes. Date.now is controlled for collision and distinct clone-failure IDs. Only the deliberate child error/DataCloneError is intercepted. Historical defects are asserted for historical rows; lifecycle acceptance requires cleanup and absence of uncaught clone errors. Navigation and trust are not candidate acceptance yet.' }) })
}
finally {
await page.evaluate(() => {
+14 -2
View File
@@ -1,14 +1,26 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import process from 'node:process'
import vm from 'node:vm'
import { verifyIframeContract } from '../../refactor/scripts/iframe-contract.mjs'
import { readMember } from '../../refactor/scripts/releases.mjs'
import { hash, readMember } from '../../refactor/scripts/releases.mjs'
import { browserCandidate } from './browser-candidate.js'
import { compilePackage } from './load.js'
export async function iframeCandidate() {
if (process.env.ARTPLAYER_IFRAME_BASELINE === '1')
return (await iframeHistorical()).find(item => item.name === 'workspace.js')
return { name: process.env.ARTPLAYER_IFRAME_ARTIFACT ? 'candidate-artifact' : 'candidate-source', global: 'ArtplayerToolIframe', namespace: false, code: process.env.ARTPLAYER_IFRAME_ARTIFACT ? fs.readFileSync(process.env.ARTPLAYER_IFRAME_ARTIFACT, 'utf8') : await compilePackage('artplayer-tool-iframe', 'umd') }
const artifact = process.env.ARTPLAYER_IFRAME_ARTIFACT
const code = artifact ? fs.readFileSync(artifact, 'utf8') : await compilePackage('artplayer-tool-iframe', 'umd')
return { name: artifact ? 'candidate-artifact' : 'candidate-source', global: 'ArtplayerToolIframe', namespace: false, code, provenance: { kind: artifact ? 'explicit-artifact' : 'source-build', file: artifact || null, sha256: hash(code) } }
}
export async function iframeBrowserCandidate() {
if (!process.env.ARTPLAYER_BROWSER_ARTIFACTS)
return iframeCandidate()
assert(process.env.ARTPLAYER_IFRAME_BASELINE !== '1', 'Installed Iframe cannot use the frozen workspace')
const loaded = await browserCandidate('artplayer-tool-iframe', process.env.ARTPLAYER_IFRAME_ARTIFACT)
return { name: 'candidate-installed', global: 'ArtplayerToolIframe', namespace: false, ...loaded }
}
export async function iframeHistorical() {
+32 -1
View File
@@ -4,8 +4,9 @@ import path from 'node:path'
import process from 'node:process'
// eslint-disable-next-line test/no-import-node-test -- This fixture exercises the repository's Node test runner.
import { test } from 'node:test'
import { verifyIframeContract } from '../refactor/scripts/iframe-contract.mjs'
import { installedPackages } from '../scripts/browser-validation/scope.ts'
import { checkFiles, checkPackages, packageOptions, publishedConsumer } from '../scripts/package-check.mjs'
import { checkFiles, checkPackages, historicalDistributionFiles, packageOptions, publishedConsumer } from '../scripts/package-check.mjs'
import { removeConsumer, runtimeConsumer } from '../scripts/package-consumer.mjs'
import { emitterContracts } from './contracts/emitter.js'
import { ambilightCandidate } from './helpers/ambilight.js'
@@ -13,6 +14,7 @@ import { autoThumbnailCandidate } from './helpers/auto-thumbnail.js'
import { browserCandidate } from './helpers/browser-candidate.js'
import { canvasCandidate } from './helpers/canvas.js'
import { dpipCandidate } from './helpers/dpip.js'
import { iframeBrowserCandidate, iframeCandidate } from './helpers/iframe.js'
import { mbCandidate } from './helpers/mediabunny.js'
import { multipleSubtitlesCandidate } from './helpers/multiple-subtitles.js'
import { vttThumbnailCandidate } from './helpers/vtt-thumbnail.js'
@@ -30,10 +32,30 @@ test('Additional browser packages cannot be mistaken for full release consumer a
await assert.rejects(checkPackages({ release: true, include: ['artplayer-plugin-ambilight'] }), /not full release acceptance/)
})
test('Iframe installation preserves verified tool paths without inventing a renamed npm archive', async () => {
const contract = await verifyIframeContract()
const expected = ['package/dist/artplayer-tool-iframe.js', 'package/dist/artplayer-tool-iframe.legacy.js', 'package/dist/artplayer-tool-iframe.mjs', 'package/types/artplayer-tool-iframe.d.ts']
assert.deepEqual(historicalDistributionFiles('artplayer-tool-iframe', contract).sort(), expected)
assert.throws(() => historicalDistributionFiles('artplayer-unreviewed-name', contract), /renamed package/)
for (const member of expected) {
const file = member.replace('package/', 'packages/artplayer-tool-iframe/')
const sources = new Map(contract.sources)
sources.delete(file)
assert.throws(() => historicalDistributionFiles('artplayer-tool-iframe', { ...contract, sources }), /Missing verified iframe/)
const baseline = structuredClone(contract.baseline)
delete baseline.source[file]
assert.throws(() => historicalDistributionFiles('artplayer-tool-iframe', { ...contract, baseline }), /Missing .* file/)
}
const baseline = structuredClone(contract.baseline)
baseline.release.name = 'artplayer-tool-iframe'
assert.throws(() => historicalDistributionFiles('artplayer-tool-iframe', { ...contract, baseline }), /distinct published predecessor/)
})
test('Explicit installed plugin maps never fall back to source or frozen workspace', async () => {
const keys = ['ARTPLAYER_BROWSER_ARTIFACTS', 'ARTPLAYER_AMBILIGHT_BASELINE', 'ARTPLAYER_CANVAS_BASELINE', 'ARTPLAYER_DPIP_BASELINE', 'ARTPLAYER_DPIP_ARTIFACT', 'ARTPLAYER_VTT_THUMBNAIL_BASELINE', 'ARTPLAYER_VTT_THUMBNAIL_ARTIFACT', 'ARTPLAYER_MULTIPLE_SUBTITLES_ARTIFACT']
keys.push('ARTPLAYER_AUTO_THUMBNAIL_BASELINE', 'ARTPLAYER_AUTO_THUMBNAIL_ARTIFACT')
keys.push('ARTPLAYER_MB_BASELINE', 'ARTPLAYER_MB_ARTIFACT')
keys.push('ARTPLAYER_IFRAME_BASELINE', 'ARTPLAYER_IFRAME_ARTIFACT')
const previous = Object.fromEntries(keys.map(key => [key, process.env[key]]))
try {
process.env.ARTPLAYER_BROWSER_ARTIFACTS = path.resolve('refactor/.cache/absent-installed-plugin-map.json')
@@ -49,6 +71,15 @@ test('Explicit installed plugin maps never fall back to source or frozen workspa
delete process.env.ARTPLAYER_AUTO_THUMBNAIL_ARTIFACT
delete process.env.ARTPLAYER_MB_BASELINE
delete process.env.ARTPLAYER_MB_ARTIFACT
delete process.env.ARTPLAYER_IFRAME_BASELINE
delete process.env.ARTPLAYER_IFRAME_ARTIFACT
await assert.rejects(iframeBrowserCandidate(), { code: 'ENOENT' })
assert.equal((await iframeCandidate()).provenance.kind, 'source-build', 'Existing unit/history helpers do not consume the core-only map as a tool artifact')
process.env.ARTPLAYER_IFRAME_BASELINE = '1'
await assert.rejects(iframeBrowserCandidate(), /cannot use the frozen workspace/)
delete process.env.ARTPLAYER_IFRAME_BASELINE
process.env.ARTPLAYER_IFRAME_ARTIFACT = 'override.js'
await assert.rejects(iframeBrowserCandidate(), /cannot override artplayer-tool-iframe artifact/)
await assert.rejects(mbCandidate(), { code: 'ENOENT' })
process.env.ARTPLAYER_MB_BASELINE = '1'
await assert.rejects(mbCandidate(), /cannot use the frozen workspace/)
+8
View File
@@ -1,5 +1,13 @@
# Installed package checks
`historicalDistributionFiles()` in `scripts/package-check.mjs` distinguishes a
same-name npm release from the Iframe package rename. Iframe verifies the real
plugin archive plus the frozen tool workspace, then requires all four original
tool dist/type paths. Missing verified workspace members fail packing; legacy
plugin/helper filenames remain a separate distribution gate. This does not
change the generic core/chapter runtime/type consumer scope. The shared browser
roster now also checks the installed Iframe tool in five browser test files.
For independent core/plugin rollback, run `yarn test:package` then
`yarn test:rollback`. The seven-step rehearsal restores frozen consumer locks,
checks complete installed file inventories and runtime entrypoints, and saves