fix(ads): [PKG-ADS-05] preserve ownership and verify native browser lifecycle

This commit is contained in:
Harvey Zhao committed 2026-09-12 08:52:51 +08:00
1 parent fb1cfa701d
commit 29692d2815
22 files changed
+1060 -29

No files matched your search

+17
View File
@@ -50,6 +50,23 @@ accepted-with-scope;保留诊断并编译迁移示例,不把批准范围扩
`test/ads.test.js` 增加候选 `.default === factory` 及同步取消断言,覆盖三种产物。
这些检查不替代全部历史子路径、媒体/设备与发布验收。
## 05 真实 UI 与标签可见性
ads-ui.spec.js 以三个实际核心和三个浏览器引擎覆盖原生全屏、双实例媒体/静音/销毁
隔离、实际详情导航及390px视口布局。不把视口模拟称为真机。main/legacy各27项。
`yarn test:ads-native-visibility` 使用本机Chrome的独立profile和默认context,通过
connectOverCDP(noDefaults:true)避免框架强制可见。保留真实页面visibilitychange及
isTrusted,只禁用桌面窗口遮挡背景化以排除其他应用干扰。测试开始先切换到本页并
确认可见,再等待元数据;不放宽超时。三个核心分别验证HTML/视频广告后台倒计时
停止、返回恢复、再次后台销毁后不触发skip。视频后台是否暂停作为浏览器状态记录,
不假定不同浏览器策略相同。main/legacy、失败尝试和trace分别保存,见05变更记录。
脚本独占8084,报告使用独立目录;关闭自建浏览器和服务后才运行下一浏览器套件。
DevToolsActivePort刚创建时可能EBUSY/未写完,启动器仅对这两个已确认瞬态条件做
有界重读,不重启浏览器,也不读取用户profile。缺安装路径直接失败,支持显式
ARTPLAYER_NATIVE_CHROME。实际设备、最小化、全量分发仍是后续门槛。
## 边界与文件职责
- `test/helpers/ads.js`:来源加载、冻结 bundle 的 VM、可控时钟和最小 ArtPlayer 宿主。
@@ -0,0 +1,538 @@
{
"schemaVersion": 1,
"task": "PKG-ADS-05",
"status": "doing",
"sourceCommit": "fb1cfa70",
"inputs": [
{
"file": "package.json",
"sha256LF": "55b35b5738356fb88755ff55f7b3baa274ff78ed02dbd84888aac34eb490f4a3"
},
{
"file": "packages/artplayer-plugin-ads/src/session.ts",
"sha256LF": "c45b0c63870975cf8fbd6f93340427de387d9dc15c56dfb3ad2ddacad8380e63"
},
{
"file": "test/ads-lifecycle.test.js",
"sha256LF": "3411000b63e5bee5ec1f4a422d333e90a856d476eb9f544d5b1049dbd63fbbfd"
},
{
"file": "test/browser/ads-ui.spec.js",
"sha256LF": "90a215d534703aa69654a3258fa5a46c29c308645356348db0229e1bc950bf4f"
},
{
"file": "test/helpers/native-chrome.js",
"sha256LF": "4f486db36f3e5738ee835005b0bd073b3878507b6ea417a01e5663ab6954d93b"
},
{
"file": "refactor/scripts/ads-native-visibility.mjs",
"sha256LF": "7521c78c3874cdc4b5029fe85b00a9afc6b8a14fb71728d64e588717219f19d0"
}
],
"artifacts": [
{
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js",
"sha256": "1429541242f49c4d558eda242a63b6e57a876c2dba06ce9f631d71d315028023",
"bytes": 7279,
"copy": {
"file": "docs/compiled/artplayer-plugin-ads.js",
"sha256": "1429541242f49c4d558eda242a63b6e57a876c2dba06ce9f631d71d315028023",
"bytes": 7279
}
},
{
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.legacy.js",
"sha256": "4f64ac015e0850b71f51cf01d9fc6f238c95414730c71e91d1240c40e28499d8",
"bytes": 7718,
"copy": {
"file": "docs/compiled/artplayer-plugin-ads.legacy.js",
"sha256": "4f64ac015e0850b71f51cf01d9fc6f238c95414730c71e91d1240c40e28499d8",
"bytes": 7718
}
},
{
"file": "packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.mjs",
"sha256": "8ad7df8145b2514467cac0f733151619c36fa965e620df575c0779604372503f",
"bytes": 13067,
"copy": {
"file": "docs/compiled/artplayer-plugin-ads.mjs",
"sha256": "8ad7df8145b2514467cac0f733151619c36fa965e620df575c0779604372503f",
"bytes": 13067
}
}
],
"fixed": "Destroy before initialization no longer deletes a caller-owned undefined template.$ads descriptor. Reproduced first, then verified in source and all three generated artifacts.",
"node": {
"before": {
"file": "refactor/.cache/ads05-ownership-before.log",
"sha256": "05a9e9a3cfed36b11ea62c3695c3e6d4cebb08da03ed6832fd2d23c97110d9ce",
"bytes": 1308
},
"after": {
"file": "refactor/.cache/ads05-artifacts-node.log",
"sha256": "b89fdd0efe47c96c170ce3e6d97a47c919334832c7ef792359f0b5f41fa2dc1e",
"bytes": 21921
},
"passed": 194,
"failed": 0
},
"build": {
"file": "refactor/.cache/ads05-build.log",
"sha256": "be382dde6782d27827ddec67d4bd93b6e45e4ca6a88fad953316cb0c9ded107a",
"bytes": 1438
},
"browser": [
{
"format": "main",
"stats": {
"startTime": "2026-09-12T00:34:58.186Z",
"duration": 30596.369,
"expected": 27,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"report": {
"file": "refactor/.cache/ads05-ui-main-report.json",
"sha256": "dc641b693ded43d4bcd43e67c4772e0874243b8c50f1fddba22f5b5ee3ec26ea",
"bytes": 541550
},
"log": {
"file": "refactor/.cache/ads05-ui-main.log",
"sha256": "521308ceca482e1cb6584039c63e00601802a5f5ceb4bb140596969c94dfa9e1",
"bytes": 6563
},
"results": [
{
"file": "refactor/.cache/ads05-ui-main-results/.last-run.json",
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
"bytes": 45
}
]
},
{
"format": "legacy",
"stats": {
"startTime": "2026-09-12T00:44:58.841Z",
"duration": 25158.441,
"expected": 27,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"report": {
"file": "refactor/.cache/ads05-ui-legacy-report.json",
"sha256": "33ddb6f0f8ccb62af712c0fcd386e0aa86b88bd54aafc03b11e53c2ed9540ecc",
"bytes": 542840
},
"log": {
"file": "refactor/.cache/ads05-ui-legacy.log",
"sha256": "c1bd76322c2aeda4a348edb7b5953fd717efa9e53058a77110af2a19cc37c701",
"bytes": 6563
},
"results": [
{
"file": "refactor/.cache/ads05-ui-legacy-results/.last-run.json",
"sha256": "91d1c43004802cd49950d78eb11c8fa7d05da8ffffe219a8b13b2f561bc00903",
"bytes": 45
}
]
}
],
"native": [
{
"format": "main",
"browser": "152.0.7977.84",
"executable": "C:/Program Files/Google/Chrome/Application/chrome.exe",
"scope": "Native selected/background tabs in isolated installed Chrome. Desktop window occlusion excluded by --disable-backgrounding-occluded-windows; no focus or visibility property emulation. Does not prove minimized-window or mobile lifecycle behavior.",
"cases": [
{
"core": "published-4.5.5",
"media": "html",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": null,
"restoredVideo": null,
"destroyed": true
},
{
"core": "published-4.5.5",
"media": "video",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": {
"time": 0.428142,
"paused": true
},
"restoredVideo": {
"time": 2.350778,
"paused": false
},
"destroyed": true
},
{
"core": "published",
"media": "html",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": null,
"restoredVideo": null,
"destroyed": true
},
{
"core": "published",
"media": "video",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": {
"time": 0.423085,
"paused": true
},
"restoredVideo": {
"time": 2.34293,
"paused": false
},
"destroyed": true
},
{
"core": "candidate",
"media": "html",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": null,
"restoredVideo": null,
"destroyed": true
},
{
"core": "candidate",
"media": "video",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": {
"time": 0.431513,
"paused": true
},
"restoredVideo": {
"time": 2.356181,
"paused": false
},
"destroyed": true
}
],
"report": {
"file": "refactor/.cache/ads-native-visibility-KDPUQX/report.json",
"sha256": "73a9cba3b6cb6772cd89f83fe8318f942d9502f764b39bf91b0d25050aaaa6ff",
"bytes": 8852
},
"files": [
{
"file": "refactor/.cache/ads-native-visibility-KDPUQX/report.json",
"sha256": "73a9cba3b6cb6772cd89f83fe8318f942d9502f764b39bf91b0d25050aaaa6ff",
"bytes": 8852
},
{
"file": "refactor/.cache/ads-native-visibility-KDPUQX/server.log",
"sha256": "f8605bce374d9ef389168f86c0cb76d3c35d68089a3c609bb9634f17ce455e57",
"bytes": 43
},
{
"file": "refactor/.cache/ads-native-visibility-KDPUQX/trace.zip",
"sha256": "cf8dc9ae78d9cfc441aa5d9ad907cdf23cb76456ccda0f32200ec83fb9a172e3",
"bytes": 2383611
}
],
"log": {
"file": "refactor/.cache/ads05-native-main-video.log",
"sha256": "25249bdf4cb22e1277931a6393c9b036d593d694da210a057b249cf83137c598",
"bytes": 510
}
},
{
"format": "legacy",
"browser": "152.0.7977.84",
"executable": "C:/Program Files/Google/Chrome/Application/chrome.exe",
"scope": "Native selected/background tabs in isolated installed Chrome. Desktop window occlusion excluded by --disable-backgrounding-occluded-windows; no focus or visibility property emulation. Does not prove minimized-window or mobile lifecycle behavior.",
"cases": [
{
"core": "published-4.5.5",
"media": "html",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": null,
"restoredVideo": null,
"destroyed": true
},
{
"core": "published-4.5.5",
"media": "video",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": {
"time": 0.448822,
"paused": true
},
"restoredVideo": {
"time": 2.388307,
"paused": false
},
"destroyed": true
},
{
"core": "published",
"media": "html",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": null,
"restoredVideo": null,
"destroyed": true
},
{
"core": "published",
"media": "video",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": {
"time": 0.430689,
"paused": true
},
"restoredVideo": {
"time": 2.361555,
"paused": false
},
"destroyed": true
},
{
"core": "candidate",
"media": "html",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": null,
"restoredVideo": null,
"destroyed": true
},
{
"core": "candidate",
"media": "video",
"hiddenText": "30秒",
"events": [
{
"hidden": true,
"trusted": true
},
{
"hidden": false,
"trusted": true
}
],
"hiddenVideo": {
"time": 0.441339,
"paused": true
},
"restoredVideo": {
"time": 2.365354,
"paused": false
},
"destroyed": true
}
],
"report": {
"file": "refactor/.cache/ads-native-visibility-lSst7b/report.json",
"sha256": "dcd60b88cff93a9843819ce7ceb8aab71c43f58eddf57bcdd68a2b62c7e79e07",
"bytes": 8902
},
"files": [
{
"file": "refactor/.cache/ads-native-visibility-lSst7b/report.json",
"sha256": "dcd60b88cff93a9843819ce7ceb8aab71c43f58eddf57bcdd68a2b62c7e79e07",
"bytes": 8902
},
{
"file": "refactor/.cache/ads-native-visibility-lSst7b/server.log",
"sha256": "f8605bce374d9ef389168f86c0cb76d3c35d68089a3c609bb9634f17ce455e57",
"bytes": 43
},
{
"file": "refactor/.cache/ads-native-visibility-lSst7b/trace.zip",
"sha256": "890ef0d36e9f32b98590279281d732767f21a6b4c474be924adc7d7756a6c018",
"bytes": 2453467
}
],
"log": {
"file": "refactor/.cache/ads05-native-legacy-video.log",
"sha256": "6e01a308c3c218fff108f47be871b379c0d0c38279980540fbee3623ef351979",
"bytes": 510
}
}
],
"visual": {
"file": "refactor/.cache/ads05-narrow-review.png",
"sha256": "ce01253a61890514a79d66a693c8839a659038451f938b8995ed9d03435a91ca",
"bytes": 5387,
"inspected": true,
"note": "390px viewport, 360px player; translated close/countdown/details and fullscreen controls visible without overflow. Not a physical phone."
},
"ci": {
"file": "refactor/.cache/ads05-ci.log",
"sha256": "cb77116e93e65ef47e9118b3b6ffbcf1868f6b0c535593bea480a9a9d9031db3",
"bytes": 87478,
"groups": [
772,
14,
49
],
"passed": 835,
"productionTS": 269
},
"diagnosticAttempts": [
{
"file": "refactor/.cache/ads05-ui-source-report.json",
"sha256": "144a699807c807ee15e753ef2fd1a79939bceef959875da037600f6f91008db1",
"bytes": 533140
},
{
"file": "refactor/.cache/ads05-visibility-probe.json",
"sha256": "e8e8fec96c14429e03669fedef2c4cdb85de614d4e0aac7b8088e57643b91eef",
"bytes": 1356
},
{
"file": "refactor/.cache/ads05-visibility-chrome-probe.json",
"sha256": "436d44baa09185e3ea198cce9bda37c88fa2499ae77147f65fab910286827021",
"bytes": 541
},
{
"file": "refactor/.cache/ads05-native-cdp-probe.json",
"sha256": "f4ee12f981fdc5000ae43cd46844772898bed4eafcdc3843b09b71bdaf5086f4",
"bytes": 643
},
{
"file": "refactor/.cache/ads05-headed-install-script.log",
"sha256": "7a3bc4655c858bccff0130b36575da3dc10982a716bd269b49bdb49677eeb13c",
"bytes": 5297
},
{
"file": "refactor/.cache/ads-native-visibility-tJauB0/report.json",
"sha256": "6a04f099e92d8bba7396dc15955ff319487ebf16d6e551e22617ba86141eb65a",
"bytes": 6703
},
{
"file": "refactor/.cache/ads-native-visibility-ORaYRO/report.json",
"sha256": "d3601fd60d6da0b7178b8620c7ba3f6f684f192e915f19ece71c236c877b8a8d",
"bytes": 7109
},
{
"file": "refactor/.cache/ads-native-visibility-ghHsLL/report.json",
"sha256": "8aad442bf3f144d45deaa8ce134b8c2c6a6b2e7701cc8b1a1000250d26c3d295",
"bytes": 6379
},
{
"file": "refactor/.cache/ads-native-visibility-U9bOpa/report.json",
"sha256": "3839f6778d1ff6b3945b17a44baf6d68b50136b9f2c1ca37a5d1f16d7c9d5236",
"bytes": 6843
}
],
"remaining": [
"Physical mobile/Safari lifecycle and minimize/background policy are not covered by Chromium/Firefox/WebKit automation or native Chrome tab checks; task05 remains doing.",
"Complete historical package distribution and docs demo belong to task06.",
"VAST-only VPN script loading exception recorded after user correction; not applied to Ads.",
"No push, tag, merge or publish."
]
}
@@ -0,0 +1,55 @@
# PKG-ADS-05 页面与媒体组合:进行中
起点 fb1cfa70;本步仍 doing。Ads-04 的类型推导修正已由用户明确接受,范围不扩大。
2026-09-12,用户更正:可能被 VPN 规则阻止的是 VAST 脚本,若始终无法加载,可以
记录并跳过,由用户后续修复。授权范围仅 VAST 的 VPN 加载限制,不适用于 Ads,
也不将未执行检查记为通过。Ads 本地脚本加载成功,继续正常验收。
## 已修复与已验证
- 复现销毁前从未初始化广告时,调用方预设的 `template.$ads = undefined` 属性描述符
被误删。session 的清理增加 root 存在判断,只删除实际拥有的节点引用。
原失败见 `.cache/ads05-ownership-before.log`;当前源码及三格式194项Node测试通过,
见 `.cache/ads05-artifacts-node.log`。构建由仓库脚本生成。
- 新增 ads-ui.spec.js,三个核心(实际4.5.5、5.4.1、候选)与三个浏览器引擎,
验证原生全屏前初始化、外部退出、广告按钮切换、双实例真实视频/静音/销毁隔离、
实际详情popup与窄屏翻译控件布局。源码、正式main和legacy矩阵分别27项通过。
报告/完整results分别归档于 `.cache/ads05-ui-source-*`、`.cache/ads05-ui-main-*`、
`.cache/ads05-ui-legacy-*`。实际查看390px截图,控件和翻译文案无溢出;不是手机真机。
## 真实后台页面验证
无头三引擎和普通headed Playwright实例切换标签均保持 visible,不能算验收。
浏览器连接工具当前返回 nodeRepl.fetch request failed;完整测试Chromium下载多次
30秒超时,日志保留。未改变锁文件或浏览器版本声明。
本机Chrome152.0.7977.84以独立临时profile启动,通过connectOverCDP的noDefaults
关闭自动框架默认模拟。探针实际获得可信 hidden/visible 事件,见
`.cache/ads05-native-cdp-probe.json`。不会连接用户日常profile,不修改document.hidden。
新增可重跑 `yarn test:ads-native-visibility` 和 native-chrome helper:独占8084服务、
读取候选产物、保存trace及报告,最终关闭自建浏览器和服务,临时profile清理有目录校验。
首轮广告集成在4.5.5的isReady等待失败;报告和trace保留在
`.cache/ads-native-visibility-tJauB0/`,日志 `.cache/ads05-native-main.log`。
追加状态观测证实页面初始hidden、无元数据请求,不是脚本下载失败。显式切到测试页
后进一步确认桌面其他窗口遮挡影响;通过单一 --disable-backgrounding-occluded-windows
排除桌面遮挡,只测真实标签选择切换。noDefaults仍保留,不模拟focus或document.hidden。
Chrome刚创建DevToolsActivePort时曾出现EBUSY,启动器有界等待文件完整内容,不重启进程。
最终main/legacy各6组:4.5.5/5.4.1/候选核心分别使用HTML和视频广告,真实可信
hidden/visible事件、后台2200ms倒计时不变、返回后恢复倒计时,视频时间继续推进且
paused=false;再次后台销毁后回前台不出现广告或skip。报告包含浏览器版本、实际
核心manifest、广告产物SHA、各例隐藏/恢复媒体状态和trace。此范围不包含桌面
最小化和真实移动设备生命周期。
最终代码/产物指纹、原失败、main/legacy UI和原生Chrome结果、CI统计见
[验证证据](../baselines/ads-ui-visibility-validation.json)。源码和三产物Node194通过,
本地完整CI835项通过、269个生产TS严格检查、37份核心声明无漂移。
## 尚待完成
真机Safari/移动端和最小化等实际后台策略仍待对应设备证据,05继续doing。Ads核心
支持门槛、资源所有权和桌面UI具体问题已有闭环证据;媒体设备与完整分发不提前关闭。
全包发布、npm分发完整验收不属于本次通过结论。
本批以PKG-ADS-05独立本地检查点提交,保留明确未完成项,无推送或发布。
+6 -1
View File
@@ -4,6 +4,11 @@ BASE-08;2026-09-10;来源提交 ed293231。矩阵建立完成,矩阵内未
## 版本和分发维度
用户环境例外(2026-09-12):VAST 脚本可能被 VPN 规则阻止;若确认始终无法加载,
用户授权记录失败证据并跳过该项,留待用户后续修复,继续其他重构。用户已明确更正
对象为 VAST,不是 Ads。执行时保存实际 URL、错误及未验证范围;跳过不等于通过,
不扩大为其他 SDK、运行时缺陷或设备检查的豁免。
工作区版本不是发布证据。当前只冻结 core 5.4.0 和 chapter 1.1.0 的发布 tarball,核心工作区为 5.4.1;其余包的真实发布版本、旧核心支持窗口由各包 01 任务核实。历史最低浏览器/TS 版本尚未证明,不把本机版本设为用户最低版本。全部包仍按自身下一 major 准备,版本目标见 [版本策略](version-policy.md)。
| 检查 ID | 内容及执行方法 | 当前证据/状态 | 责任与发布影响 |
@@ -33,7 +38,7 @@ Node10 是 TS 模块解析模式名,不代表支持 Node.js 10。现代 es2020
| 包 / 工作区版本 | 能力与固定样本/资源要求 | 当前状态、待补环境 | 负责验证任务 |
| --- | --- | --- | --- |
| artplayer / 5.4.1 | 原生媒体、字幕、切源、控件/设置、全屏/PiP、SSR;ENV-MEDIA + VTT/SRT | 发布 5.4.0 已冻结;桌面部分基线,移动/原生全屏/PiP/多引擎待测 | CORE-07/16/17/18/22/23、ENG-05 |
| artplayer-plugin-ads / npm 1.0.6、工作区 2.1.0 未发布 | 五个 1.0.x 归档及实际核心 4.5.5 归档已冻结;HTML/视频、本片恢复、跳过、错误/销毁;本地短广告和主片 | 4.5.5/5.4.1/候选核心 × 发布/候选插件实际组合见 ads-validation.md;设备/隐藏页/全屏与完整分发仍待,不扩大到全部历史核心。无需借 IMA 结果替代自有逻辑 | PKG-ADS-01/02/03/05/06 |
| artplayer-plugin-ads / npm 1.0.6、工作区 2.1.0 未发布 | 五个 1.0.x 归档及实际核心 4.5.5 归档已冻结;HTML/视频、本片恢复、跳过、错误/销毁;本地短广告和主片 | 4.5.5/5.4.1/候选核心组合、三引擎原生全屏/双实例/UI的main和legacy各27项;本机Chrome152真实标签暂停/视频恢复/后台销毁各6项见ads-validation.md。实际设备/最小化及完整分发仍待,不扩大到全部历史核心 | PKG-ADS-01/02/03/05/06 |
| artplayer-plugin-ambilight / 1.1.0 | Canvas 采样、CORS、resize、后台/销毁;同源及受控跨源 MP4 | 待真实画布和资源测量 | PKG-AMBILIGHT-01/02/05/06 |
| artplayer-plugin-asr / 2.1.0 | PCM 音频输出/暂停/销毁;AudioContext + 调用方服务 | 待音频环境与调用方 WebSocket 验证;示例服务版本未知,不在 PR 调线上服务 | PKG-ASR-01/02/05/06、EX-03 |
| artplayer-plugin-audio-track / 1.1.0 | 独立外部 Audio 与主视频时钟同步;本地 MP4+AAC,偏移/切源/缓冲/失败样本 | 1.1.0 发布及调用契约见 [包基线](baselines/audio-track-contract.md);旧核心 5.4.0 为具体对照点,桌面/Safari 实测待完成 | PKG-AUDIO-01/02/05/06 |
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
状态:todo 135 / doing 4 / blocked 0 / done 78 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 134 / doing 5 / blocked 0 / done 78 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -260,7 +260,7 @@
| PKG-ADS-02 | artplayer-plugin-ads<br>建立特有行为与错误测试 | PKG-ADS-01, ENG-03, ENG-05 | 固定 npm 1.0.6 与工作区契约;HTML/视频/图片 HTML、跳过阈值、计时链、工厂复用、多实例、过早/重复方法、播放拒绝与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-ADS-03 | artplayer-plugin-ads<br>整理内部职责与资源 | PKG-ADS-02, CORE-10, CORE-13 | 配置/广告状态/计时/视图/主视频恢复分离,保留产品规则;资源与版本检查改用经验证的旧核心能力边界 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-ADS-04 | artplayer-plugin-ads<br>迁移自有源码和公开类型 | PKG-ADS-03, ENG-04, ENG-06, CORE-07 | 兼容真实 html/video/url/i18n、旧 export= 与工作区 source/type 类型接受面;时长 string/number 漂移与未实现别名单独处置 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-ADS-05 | artplayer-plugin-ads<br>验证新旧核心和组合 | PKG-ADS-04, CORE-22 | 本地广告 fixture、多实例、正常结束/跳过/销毁;核验历史关联核心 4.5.5、发布 5.4.1 与候选核心的实际归档/能力/媒体组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-ADS-05 | artplayer-plugin-ads<br>验证新旧核心和组合 | PKG-ADS-04, CORE-22 | 本地广告 fixture、多实例、正常结束/跳过/销毁;核验历史关联核心 4.5.5、发布 5.4.1 与候选核心的实际归档/能力/媒体组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
| PKG-ADS-06 | artplayer-plugin-ads<br>验证分发并同步文档 | PKG-ADS-05, ENG-07 | ads.js/README/声明一致;旧 require(pkg).default、原 dist 路径、当前 callable/ESM/legacy 的隔离 tarball 及浏览器验收 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
## 5 包迁移:artplayer-plugin-vast
@@ -488,3 +488,4 @@
- PKG-ADS-02: [记录](changes/2026-09-12-PKG-ADS-02-tests.md) [记录](ads-validation.md) [记录](baselines/ads-validation.json)
- PKG-ADS-03: [记录](changes/2026-09-12-PKG-ADS-03-lifecycle.md) [记录](ads-validation.md) [记录](baselines/ads-lifecycle-validation.json)
- PKG-ADS-04: [记录](changes/2026-09-12-PKG-ADS-04-types.md) [记录](baselines/ads-types-validation.json)
- PKG-ADS-05: [记录](changes/2026-09-12-PKG-ADS-05-browser.md) [记录](baselines/ads-ui-visibility-validation.json)
+11
View File
@@ -1,5 +1,16 @@
# 进度与证据
## 当前实施:Ads-05 页面与媒体组合
补上27项原生全屏/多实例真实视频/详情导航/窄屏布局验证,源码、正式main/legacy均通过。
修复销毁前未创建广告时误删调用方undefined模板属性;源码及三格式Node194项通过。
独立Chrome实际标签切换的main/legacy各6组通过,覆盖后台倒计时暂停、恢复后视频
继续播放和后台销毁。首轮失败已定位为初始隐藏/桌面遮挡及启动端口文件竞争,保留
原始失败,未放宽超时或模拟document.hidden。完整CI835项通过。
关闭具体核心兼容/生命周期/桌面UI问题,实际设备媒体策略仍待验;VAST脚本VPN例外
按用户更正单独登记。详见 [进行中记录](changes/2026-09-12-PKG-ADS-05-browser.md)。
当前217项:78 done、5 doing、134 todo。05仍未完成,本批独立检查点提交。
## 最新完成:Ads-04 类型与导入兼容
公开声明补齐 html/video/url/i18n,增加 CJS/ESM 类型桥和同一运行时的准确 /runtime
+3 -3
View File
@@ -143,7 +143,7 @@
| DASH-SEEK-01 | open / 已复现 | dash.js 4.5.2 空裁剪漏更新缓冲量,稳定暂停 seek 后调度停滞 | PKG-DASH-05, REVIEW-02 |
| ADS-TYPE-01 | accepted-with-scope / 已复现 | Ads 声明丢失真实 html/video/url,旧 totalDuration 为 string,当前 source/type 未实现 | PKG-ADS-02, PKG-ADS-04 |
| ADS-DIST-01 | open / 已复现 | Ads npm 1.0.6 default namespace 与未发布 2.1.0 callable/exports 分发不同 | PKG-ADS-04, PKG-ADS-06, REL-01 |
| ADS-CORE-01 | open / 源码/产物事实 | Ads 旧发布关联核心 4.5.5,当前源码新增 >=5 门槛和 document 事件桥依赖 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-LIFE-01 | open / 已复现 | Ads 计时链、重复/过早调用、工厂复用与销毁缺乏资源边界 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-CORE-01 | resolved / 源码/产物事实 | Ads 旧发布关联核心 4.5.5,当前源码新增 >=5 门槛和 document 事件桥依赖 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-LIFE-01 | resolved / 已复现 | Ads 计时链、重复/过早调用、工厂复用与销毁缺乏资源边界 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-MEDIA-01 | open / 已复现 | Ads metadata 与 play 拒绝、隐藏页媒体状态和内容恢复仍缺真实验证 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-UI-01 | open / 已复现 | Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| ADS-UI-01 | resolved / 已复现 | Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
+46 -11
View File
@@ -3034,7 +3034,7 @@
"id": "ADS-CORE-01",
"title": "Ads 旧发布关联核心 4.5.5,当前源码新增 >=5 门槛和 document 事件桥依赖",
"confirmation": "source-observed",
"status": "open",
"status": "resolved",
"owners": [
"PKG-ADS-02",
"PKG-ADS-03",
@@ -3046,16 +3046,27 @@
"packages/artplayer-plugin-ads/src/index.ts",
"refactor/baselines/ads-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
"packages/artplayer-plugin-ads/ARCHITECTURE.md",
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md"
],
"compatibleResolution": "先核验实际核心归档和能力,通过本地 fallback 保留原用户调用;不能用未发布源码门槛强制全部旧消费者升级,也不泛称全部 4.x 通过。",
"closureCriteria": "4.5.5 关联来源和实际可用组合明确,5.4.1/候选核心与原生 visibility/事件桥路径有真实浏览器证据。"
"closureCriteria": "4.5.5 关联来源和实际可用组合明确,5.4.1/候选核心与原生 visibility/事件桥路径有真实浏览器证据。",
"resolutionEvidence": [
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md",
"refactor/baselines/ads-lifecycle-validation.json",
"test/browser/ads-ui.spec.js",
"refactor/scripts/ads-native-visibility.mjs",
"test/ads-lifecycle.test.js"
],
"resolutionRationale": "实际4.5.5归档已冻结;候选入口按能力检查,没有>=5门槛。4.5.5/5.4.1/候选核心main/legacy的三引擎UI及原生Chrome标签可见性通过,覆盖原生监听和较新核心事件桥,不泛化为全部4.x。"
},
{
"id": "ADS-LIFE-01",
"title": "Ads 计时链、重复/过早调用、工厂复用与销毁缺乏资源边界",
"confirmation": "reproduced",
"status": "open",
"status": "resolved",
"owners": [
"PKG-ADS-02",
"PKG-ADS-03",
@@ -3070,10 +3081,21 @@
"refactor/baselines/ads-validation.json",
"refactor/baselines/ads-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
"packages/artplayer-plugin-ads/ARCHITECTURE.md",
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md"
],
"compatibleResolution": "先复现重复 play 的计时链、skip 后计时、destroy 后恢复与复用工厂 options;以实例所有权和幂等清理修正,不更改广告时长/跳过规则。",
"closureCriteria": "时钟/订阅/DOM 与主片恢复的成功、异常、重入、销毁、复用和多实例回归通过。"
"closureCriteria": "时钟/订阅/DOM 与主片恢复的成功、异常、重入、销毁、复用和多实例回归通过。",
"resolutionEvidence": [
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md",
"refactor/baselines/ads-lifecycle-validation.json",
"test/browser/ads-ui.spec.js",
"refactor/scripts/ads-native-visibility.mjs",
"test/ads-lifecycle.test.js"
],
"resolutionRationale": "03的可重跑重复/过早/复用/重入/拒绝修复与本批194项源码及三格式回归通过;真实双实例、后台销毁及自有资源清理通过。新增未创建root时误删调用方undefined属性的复现与修复,不触碰未拥有的引用。"
},
{
"id": "ADS-MEDIA-01",
@@ -3095,16 +3117,18 @@
"refactor/baselines/ads-validation.json",
"refactor/baselines/ads-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
"packages/artplayer-plugin-ads/ARCHITECTURE.md",
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md"
],
"compatibleResolution": "用本地媒体验证事件安装顺序、加载失败与拒绝、倒计时和广告播放的独立语义,恢复主片时不吞公共拒绝或在销毁后播放。",
"compatibleResolution": "用本地媒体验证事件安装顺序、加载失败与拒绝、倒计时和广告播放的独立语义,恢复主片时不吞公共拒绝或在销毁后播放。 05已验证桌面Chrome真实后台标签暂停/恢复、视频继续播放和后台销毁,main/legacy各6组合;最小化和实际移动端媒体策略仍缺设备证据,保持open。",
"closureCriteria": "实际广告/主片的加载、暂停/恢复、失败/换源/销毁和隐藏状态有新旧核心组合证据。"
},
{
"id": "ADS-UI-01",
"title": "Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验",
"confirmation": "reproduced",
"status": "open",
"status": "resolved",
"owners": [
"PKG-ADS-02",
"PKG-ADS-03",
@@ -3120,10 +3144,21 @@
"refactor/baselines/ads-validation.json",
"refactor/baselines/ads-lifecycle-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-03-lifecycle.md",
"packages/artplayer-plugin-ads/ARCHITECTURE.md"
"packages/artplayer-plugin-ads/ARCHITECTURE.md",
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md"
],
"compatibleResolution": "复现 playDuration<=0 文案可关闭但 isCanClose 尚为 false;按既有 demo 规则修复,验证全屏按钮和外部退出状态,保留 CSS/DOM 钩子。",
"closureCriteria": "零/正/不可跳阈值、真实点击、倒计时/详情/静音/全屏与外部状态组合均有回归。"
"closureCriteria": "零/正/不可跳阈值、真实点击、倒计时/详情/静音/全屏与外部状态组合均有回归。",
"resolutionEvidence": [
"refactor/baselines/ads-ui-visibility-validation.json",
"refactor/changes/2026-09-12-PKG-ADS-05-browser.md",
"refactor/baselines/ads-lifecycle-validation.json",
"test/browser/ads-ui.spec.js",
"refactor/scripts/ads-native-visibility.mjs",
"test/ads-lifecycle.test.js"
],
"resolutionRationale": "零/正/不可关闭阈值及错误输入由现有Node/媒体回归保持;main/legacy分别27项真实原生全屏(初始/外部退出/按钮)、静音、详情popup和窄屏翻译控件通过。截图已查看;手机设备策略仍由05媒体门槛承接。"
}
]
}
+141
View File
@@ -0,0 +1,141 @@
import assert from 'node:assert/strict'
import { spawn } from 'node:child_process'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { setTimeout as delay } from 'node:timers/promises'
import { expect } from '@playwright/test'
import { nativeChrome } from '../../test/helpers/native-chrome.js'
import { hash } from './releases.mjs'
async function main() {
const output = fs.mkdtempSync(path.resolve('refactor/.cache/ads-native-visibility-'))
const artifact = path.resolve(process.env.ARTPLAYER_ADS_ARTIFACT || 'packages/artplayer-plugin-ads/dist/artplayer-plugin-ads.js')
const code = fs.readFileSync(artifact, 'utf8')
const port = Number(process.env.ARTPLAYER_BROWSER_PORT || 8084)
const baseURL = `http://127.0.0.1:${port}`
const server = spawn(process.execPath, ['test/browser/server.mjs'], { windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'], env: process.env })
let serverLog = ''
server.stderr.on('data', (bytes) => {
serverLog += bytes
})
let native
let page
const report = { task: 'PKG-ADS-05', artifact, sha256: hash(code), noDefaults: true, scope: 'Native selected/background tabs in isolated installed Chrome. Desktop window occlusion excluded by --disable-backgrounding-occluded-windows; no focus or visibility property emulation. Does not prove minimized-window or mobile lifecycle behavior.', cases: [], passed: false }
try {
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('Owned browser server startup timed out')), 30000)
server.on('error', (error) => {
clearTimeout(timeout)
reject(error)
})
server.on('exit', (code) => {
clearTimeout(timeout)
reject(new Error(`Owned browser server exited: ${code}; ${serverLog}`))
})
server.stdout.on('data', (bytes) => {
serverLog += bytes
if (serverLog.includes('Browser test server:')) {
clearTimeout(timeout)
resolve()
}
})
})
report.manifest = await (await fetch(`${baseURL}/test/manifest.json`)).json()
native = await nativeChrome()
report.browser = native.browser.version()
report.executable = native.executable
page = native.context.pages()[0]
const errors = []
page.on('pageerror', error => errors.push(error.message))
await native.context.tracing.start({ screenshots: true, snapshots: true, sources: true })
for (const core of ['published-4.5.5', 'published', 'candidate']) {
for (const media of ['html', 'video']) {
report.currentCase = { core, media }
await page.goto(`${baseURL}/test/player.html?core=${core}`)
// A stock browser may open this owned tab behind a startup tab/window.
await page.bringToFront()
await expect.poll(() => page.evaluate(() => document.hidden)).toBe(false)
await page.addScriptTag({ content: code })
await page.evaluate((media) => {
window.visibilityEvents = []
document.addEventListener('visibilitychange', event => window.visibilityEvents.push({ hidden: document.hidden, trusted: event.isTrusted }))
window.adsSkips = 0
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, plugins: [window.artplayerPluginAds({ html: 'Native visibility ad', video: media === 'video' ? '/test/pattern.mp4' : '', muted: true, totalDuration: 30, playDuration: 0 })] })
window.art.on('artplayerPluginAds:skip', () => window.adsSkips++)
document.querySelector('#play').onclick = () => window.art.play()
}, media)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.locator('#play').click()
const countdown = page.locator('.artplayer-plugin-ads-countdown')
await expect(countdown).toBeVisible()
if (media === 'video')
await expect.poll(() => page.locator('.artplayer-plugin-ads-video').evaluate(video => video.currentTime)).toBeGreaterThan(0.2)
const foreground = await native.context.newPage()
await foreground.goto('data:text/html,<title>Ads visibility foreground</title>Foreground test tab')
await foreground.bringToFront()
await expect.poll(() => page.evaluate(() => document.hidden)).toBe(true)
const hiddenText = await countdown.textContent()
await delay(2200)
assert.equal(await countdown.textContent(), hiddenText, 'Countdown advanced in a genuinely hidden page')
const hiddenVideo = media === 'video' ? await page.locator('.artplayer-plugin-ads-video').evaluate(video => ({ time: video.currentTime, paused: video.paused })) : null
await page.bringToFront()
await expect.poll(() => page.evaluate(() => document.hidden)).toBe(false)
await expect.poll(() => countdown.textContent()).not.toBe(hiddenText)
let restoredVideo = null
if (media === 'video') {
await expect.poll(() => page.locator('.artplayer-plugin-ads-video').evaluate(video => video.currentTime)).toBeGreaterThan(hiddenVideo.time + 0.2)
restoredVideo = await page.locator('.artplayer-plugin-ads-video').evaluate(video => ({ time: video.currentTime, paused: video.paused }))
assert.equal(restoredVideo.paused, false, 'Ad video remained paused after the tab returned')
}
const events = await page.evaluate(() => window.visibilityEvents)
assert(events.some(event => event.hidden && event.trusted))
assert(events.some(event => !event.hidden && event.trusted))
await foreground.bringToFront()
await expect.poll(() => page.evaluate(() => document.hidden)).toBe(true)
await page.evaluate(() => window.art.destroy(false))
await page.bringToFront()
await delay(1200)
assert.equal(await page.locator('.artplayer-plugin-ads').count(), 0)
assert.equal(await page.evaluate(() => window.adsSkips), 0)
report.cases.push({ core, media, hiddenText, events, hiddenVideo, restoredVideo, destroyed: true })
await foreground.close()
}
}
assert.deepEqual(errors, [])
assert.equal(report.cases.length, 6)
report.passed = true
}
catch (error) {
report.error = error.stack
if (page) {
report.failureState = await page.evaluate(() => {
const video = window.art?.template.$video
return {
hidden: document.hidden,
focused: document.hasFocus(),
ready: window.art?.isReady,
visibility: window.visibilityEvents,
video: video && { source: video.currentSrc, src: video.src, ready: video.readyState, network: video.networkState, preload: video.preload, error: video.error?.message, paused: video.paused },
}
}).catch(() => null)
await page.screenshot({ path: path.join(output, 'failure.png') }).catch(() => {})
}
throw error
}
finally {
if (native) {
await native.context.tracing.stop({ path: path.join(output, 'trace.zip') }).catch(() => {})
await native.close()
}
server.kill()
fs.writeFileSync(path.join(output, 'server.log'), serverLog)
fs.writeFileSync(path.join(output, 'report.json'), `${JSON.stringify(report, null, 2)}\n`)
console.log(`Ads native visibility evidence: ${output}; passed=${report.passed}, cases=${report.cases.length}`)
}
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
+5 -2
View File
@@ -2610,11 +2610,14 @@
"PKG-ADS-04",
"CORE-22"
],
"status": "todo",
"status": "doing",
"risk": "H",
"deliverable": "本地广告 fixture、多实例、正常结束/跳过/销毁;核验历史关联核心 4.5.5、发布 5.4.1 与候选核心的实际归档/能力/媒体组合",
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
"evidence": []
"evidence": [
"changes/2026-09-12-PKG-ADS-05-browser.md",
"baselines/ads-ui-visibility-validation.json"
]
},
{
"id": "PKG-ADS-06",