mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-10 21:06:15 -08:00
test(vast): [PKG-VAST-05] checkpoint real IMA playback and recovery evidence
This commit is contained in:
1 parent
e5c73fa620
commit
75da50a454
14 files changed
+3052
-12
No files matched your search
+2
-1
@@ -150,7 +150,8 @@
|
||||
"test:browser:source": "node scripts/browser-check.mjs source",
|
||||
"test:browser:installed": "node scripts/browser-check.mjs installed",
|
||||
"test:vast-types": "node --test refactor/scripts/vast-types.test.mjs",
|
||||
"test:vast-types-package": "node refactor/scripts/vast-package-types.mjs"
|
||||
"test:vast-types-package": "node refactor/scripts/vast-package-types.mjs",
|
||||
"test:vast-native": "playwright test --config playwright.vast-native.config.js"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -122,4 +122,10 @@ runtime consumers and editor generation in TS5.9.3/4.3.5. `yarn build:ts artplay
|
||||
regenerates the root-compatible editor global. Frozen workspace editor tests continue to
|
||||
exercise the SDK declaration bundler; they are labelled historical. Public migration
|
||||
is documented in [the accepted type decision](../../refactor/vast-type-decision.md).
|
||||
Actual IMA/media/device validation remains05 and full distribution06.
|
||||
`yarn test:vast-native` separately loads the real remote Google IMA SDK with local
|
||||
VAST XML and video assets. It runs outside the default PR suite because SDK/network
|
||||
availability is an explicit environment gate. No SDK boundary is substituted.
|
||||
Reports include bundle/media hashes, XML, SDK/browser versions, event order, decoded
|
||||
ad frames and resumed content state. See the validation record for actual results;
|
||||
adding this command does not establish device or final distribution acceptance.
|
||||
Physical device validation remains05 and full distribution06.
|
||||
@@ -0,0 +1,17 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import process from 'node:process'
|
||||
import { defineConfig } from '@playwright/test'
|
||||
import base from './playwright.config.js'
|
||||
|
||||
assert(!process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Native VAST source validation does not accept unrelated artifact maps')
|
||||
export default defineConfig(base, {
|
||||
testMatch: ['**/vast.native.js', '**/vast-recovery.native.js'],
|
||||
timeout: 30000,
|
||||
workers: 1,
|
||||
outputDir: 'refactor/.cache/vast-native/results',
|
||||
reporter: [
|
||||
['list'],
|
||||
['html', { outputFolder: 'refactor/.cache/vast-native/html', open: 'never' }],
|
||||
['json', { outputFile: 'refactor/.cache/vast-native/report.json' }],
|
||||
],
|
||||
})
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,67 @@
|
||||
# PKG-VAST-05:真实 IMA 桌面验证检查点
|
||||
|
||||
任务仍为doing,本次不是完成提交。前置HEAD为e5c73fa62;已批准的VAST运行时和
|
||||
类型方案保持不变,没有修改生产源码、公开声明、依赖或分发产物。
|
||||
|
||||
## 实际交付
|
||||
|
||||
- 增加`yarn test:vast-native`,使用已有Playwright、固定Node/Yarn与8084隔离服务。
|
||||
不增加依赖;独立config避免默认PR受控测试隐式依赖外部SDK,单worker、零重试。
|
||||
- `vast.native.js`用实际npm1.0.0完整bundle和候选两种模式,在npm核心5.1.7、
|
||||
npm5.4.0、候选5.4.1上执行广告插播。历史bundle先校验归档,候选使用正常构建配置。
|
||||
断言主片暂停、真实广告首帧/时间推进、完成/恢复事件、主片续播及容器移除。
|
||||
- `vast-recovery.native.js`让真实IMA处理空VAST响应,核验303错误后的主片恢复,
|
||||
显式销毁/重建广告会话,第二次广告实际解码后核心销毁,检查容器与终止状态。
|
||||
- XML只控制测试输入和本地媒体/跟踪图片;没有替换SDK、广告事件或video。
|
||||
包架构、测试维护说明、环境矩阵、任务与风险台账同步。
|
||||
|
||||
## 终止进程后的结果
|
||||
|
||||
完整摘要、原报告SHA256、测试/源码/媒体指纹和逐例结果见
|
||||
[vast-native-validation.json](../baselines/vast-native-validation.json)。Windows上实测
|
||||
Chromium153.0.8010.12、Firefox155.0、WebKit26.6,远端IMA均观测为3.789.0。
|
||||
|
||||
| 批次 | 结果 | 边界 |
|
||||
| --- | --- | --- |
|
||||
| Chromium候选两模式前贴片探针 | 2通过 | 真实广告解码/完成,主片从起点播放 |
|
||||
| 首轮插播27组合 | 23通过、4失败 | 1项首帧断言竞态,3项SDK9000超时;完整保留 |
|
||||
| 错误恢复/重建/活动广告销毁 | 6通过 | 三引擎、候选核心、两模式;真实303及第二段广告解码 |
|
||||
| 修正夹具后27组合 | 22通过、5失败 | Chromium9/9、Firefox5/9、WebKit8/9;不是全绿 |
|
||||
|
||||
最终5项失败:Firefox旧插件+5.1.7和5.4.0核心在7秒注册等待内未就绪;其中一项
|
||||
在失败后采集状态时才ready,不能倒算通过。Firefox候选默认+5.4.0核心收到SDK9000
|
||||
后广告仍迟到播放;旧插件+候选核心收到9000但未在等待窗口开始广告。WebKit旧插件+
|
||||
候选核心已收到AdStarted,却未在首帧等待中取得有尺寸且时间推进的广告video。
|
||||
这些失败需要后续分层取证,不能笼统归为VPN,也不能凭其他轮通过把它们关闭。
|
||||
|
||||
初次插播夹具直接new Artplayer却点击了只在createPlayer中绑定的按钮,2项失败后
|
||||
中断(exit1),报告/日志保留在`vast05-native-unwired-play-report`和
|
||||
`vast05-native-matrix-first.log`。修正为使用现有createPlayer。首帧检查改为等待
|
||||
实际尺寸和时间推进,未提高7秒断言预算;主片改用90秒本地样本,使冷加载和插播
|
||||
不会混入短片自然结束路径。未改变SDK自身5秒/10秒预算,也没有新增重试或skip。
|
||||
一次进度消息误用未完整读取的尾部计数报24/27,已当场更正;本记录使用最终JSON。
|
||||
|
||||
## 内置浏览器的真实示例
|
||||
|
||||
Chrome库存连接失败后使用已授权iab fallback,打开8082编译VAST示例并点击播放。
|
||||
UA报告Chrome152.0.0.0,Browser.getVersion不受此接口支持,不能捏造更精确版本。
|
||||
主片画面实际解码640宽、播放至29.745651秒;页面插件注册且IMA3.789.0已加载。
|
||||
Network证据中`https://imasdk.googleapis.com/js/sdkloader/ima3.js`返回200,
|
||||
`http://imasdk.googleapis.com/js/core/bridge3.789.0_en.html`文档请求被
|
||||
`net::ERR_BLOCKED_BY_CLIENT`拦截,请求/失败ID匹配。因此原线上标签的广告播放
|
||||
不计通过。事件缓冲报告truncated,不能宣称捕获全部请求或据此确认VPN根因。
|
||||
没有禁用保护、修改用户网络设置或使用VAST VPN例外;测试页暂停后关闭。
|
||||
|
||||
## 剩余门槛与回退
|
||||
|
||||
专项只读lint、plan生成/检查、risk-register生成/检查通过,风险台账和浏览器验证
|
||||
runner的6项工程测试通过。严格工具链检查通过Node24.21.0/Yarn1.22.22及22个
|
||||
workspace;首次直接node调用被正确拒绝(缺Yarn调用上下文),改用固定Yarn
|
||||
入口验证通过。git diff --check通过;生产源码、dist、docs编译副本及锁文件无差异。
|
||||
|
||||
SDK-07和VAST-LIFE-01保持open;05保持doing,06尚待完整分发。下一步针对实际
|
||||
超时、迟到广告和WebKit旧插件首帧问题取证,补playUrl/skip与物理iOS/Android
|
||||
生命周期验证。远端CI、版本与三轮发布复盘是独立门槛。桌面WebKit不代替Apple真机。
|
||||
|
||||
本次仅新增可重跑验证与记录,可单独回退本检查点;前两个已批准兼容性提交不受影响。
|
||||
未push、未发布npm。提交后继续运行每任务提交审计;不把该检查点算为新增完成任务。
|
||||
@@ -56,7 +56,7 @@ Node10 是 TS 模块解析模式名,不代表支持 Node.js 10。现代 es2020
|
||||
| artplayer-plugin-hls-control / 1.1.0 | hls.js 清晰度/音轨自动手动切换;主/子列表与拓扑变化 | 1.5.17 在 Chromium/Firefox 本地双档位/音轨基线通过;Windows WebKit 无 MSE,14 播放用例 skipped;Safari/native/worker 仍待验,见 hls-validation.md | PKG-HLS-01/02/05/06、EX-03 |
|
||||
| artplayer-plugin-jassub / 1.1.0 | ASS、worker/WASM/font 加载、切源/销毁;短 ASS+明确来源字体 | 内嵌资源哈希已有;版本对应、跨源/worker 限制、实际渲染待 | PKG-JASSUB-01/02/05/06 |
|
||||
| artplayer-plugin-multiple-subtitles / 1.2.0 | VTT 合并/排序/解析错误、URL 回收;双语言/空/坏 VTT | parser 来源头已观察;特有行为和浏览器字幕待 | PKG-MULTI-SUB-01/02/05/06 |
|
||||
| artplayer-plugin-vast / 1.2.0 | Glomex/Google IMA、广告错误/恢复、重复初始化;供应商测试广告标签 | 依赖版本已记录;实际 IMA 网络/广告流程待,PR 用受控 adapter | PKG-VAST-01/02/05/06 |
|
||||
| artplayer-plugin-vast / npm1.0.0、工作区1.2.0未发布 | Glomex/Google IMA、广告错误/恢复、重复初始化;本地VAST与真实IMA3.789.0 | 三引擎最终27组合22通过5失败,错误恢复/重建/活动广告销毁6通过;iab桥接文档被拦截、主片正常。物理设备及SDK/历史失败仍待,详见vast-native-validation.json | PKG-VAST-01/02/05/06 |
|
||||
| artplayer-plugin-vtt-thumbnail / 1.1.0 | WebVTT 时间和 sprite xywh、图片失败/跨源;本地 VTT+sprite | 05 检查点已有五核心桌面像素/Chapter/原生和网页全屏、切源及模拟触摸;1.0.1 控件重名历史失败和真机/分发仍开放,见 vtt-thumbnail-combinations.json | PKG-VTT-THUMB-01/02/05/06 |
|
||||
| artplayer-proxy-canvas / 1.1.0 | video-like 属性/事件、Canvas 绘制、seek/销毁;MP4 | shim 与实际媒体顺序、跨源/移动待 | PKG-CANVAS-01/02/05/06 |
|
||||
| artplayer-proxy-mediabunny / 1.2.0 | Mediabunny 1.56.1 当前解析;WebCodecs/AudioContext、HLS/轨道拓扑、AV 同步 | 库依赖不等于 codec 可用;准确 codec/profile、设备能力、长播放待 | PKG-MB-01/02/07/08/09/10 |
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 262 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 47 / doing 20 / blocked 0 / done 195 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 46 / doing 21 / blocked 0 / done 195 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -298,7 +298,7 @@
|
||||
| PKG-VAST-02 | artplayer-plugin-vast<br>建立特有行为与错误测试 | PKG-VAST-01, ENG-03, ENG-05 | SDK 失败、重复初始化、广告事件、内容恢复和销毁竞态;发布 id/$container/eager 与工作区 lazy/config 覆盖、callback拒绝、加载中销毁、同毫秒多实例与晚到事件 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-VAST-03 | artplayer-plugin-vast<br>整理内部职责与资源 | PKG-VAST-02, CORE-10, CORE-13 | SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07, PKG-VAST-07 | IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-VAST-05 | artplayer-plugin-vast<br>验证新旧核心和组合 | PKG-VAST-04, CORE-22 | 官方测试广告或受控 SDK 环境,错误后主视频状态恢复;核验真实5.1.7/5.4.1/候选核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-VAST-05 | artplayer-plugin-vast<br>验证新旧核心和组合 | PKG-VAST-04, CORE-22 | 真实IMA广告播放、错误后主视频恢复、会话重建与销毁;核验npm5.1.7/npm5.4.0/候选5.4.1核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
|
||||
| PKG-VAST-06 | artplayer-plugin-vast<br>验证分发并同步文档 | PKG-VAST-05, ENG-07 | vast.js、SDK 资源、声明消费和独立版本记录;核验实际发布 namespace default、历史深路径、ESM和准确声明,版本按政策2.0.0由REL-09统一处理 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
| PKG-VAST-07 | artplayer-plugin-vast<br>恢复旧发布VAST工厂default调用入口 | PKG-VAST-01, PKG-VAST-02 | 保留当前直接工厂并增加default自身别名,验证四类分发调用及异步注册,初始化决策独立保留 | 旧产物default用例失败,新源码/实际main/legacy/ESM通过;真实核心三浏览器控制SDK注册验证、模块文档/构建/测试入口同步;不冒充namespace反射、声明和IMA验收 | M | done |
|
||||
|
||||
@@ -602,6 +602,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)
|
||||
- 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)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-VAST-05 真实 IMA 桌面验证检查点,仍进行中
|
||||
|
||||
新增独立`test:vast-native`,真实远端IMA3.789.0与本地广告媒体,不替换SDK。
|
||||
两模式Chromium前贴片2通过;首轮27组合23通过4失败,修正夹具后的27组合22通过
|
||||
5失败;另外三引擎错误303恢复、会话重建及活动广告销毁6通过。完整失败均保留,
|
||||
没有放宽断言/SDK超时或重复直到全绿。候选最终18项中17通过,历史9项中5通过。
|
||||
|
||||
iab真实示例主片解码播放正常,IMA主脚本200,但bridge文档被ERR_BLOCKED_BY_CLIENT
|
||||
拦截,广告播放不计通过,也不推断VPN原因。物理设备、playUrl/skip、SDK迟到/超时
|
||||
及WebKit历史首帧仍需验证,05/SDK-07/LIFE保持开放;262项为195 done、21 doing、
|
||||
46 todo。详见[检查点](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md)和
|
||||
[逐例证据](baselines/vast-native-validation.json)。本地检查点提交不代表完成或发布。
|
||||
|
||||
## PKG-VAST-04 公开类型协调及安装消费者完成
|
||||
|
||||
用户单独确认保留npm1.0.0根类型、工作区TS用法迁移/runtime。根/legacy声明原文
|
||||
|
||||
+8
-4
@@ -775,11 +775,13 @@
|
||||
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
|
||||
"refactor/baselines/vast-contract-validation.json",
|
||||
"refactor/vast-validation.md",
|
||||
"refactor/baselines/vast-behavior-validation.json"
|
||||
"refactor/baselines/vast-behavior-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-VAST-05-native-checkpoint.md",
|
||||
"refactor/baselines/vast-native-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": "VAST 1.0.0 and SDK 1.21.0/1.21.2 archives verified; Google IMA remote execution remains unverified. User allows recording/skipping only persistent VAST script loading failures caused by VPN rules, not Ads or other VAST code/type failures. This exception has not been used."
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"id": "SDK-08",
|
||||
@@ -3331,11 +3333,13 @@
|
||||
"refactor/baselines/vast-lifecycle-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-VAST-03-lifecycle.md",
|
||||
"refactor/changes/2026-09-14-PKG-VAST-03-compatibility.md",
|
||||
"refactor/baselines/vast-compatibility-validation.json"
|
||||
"refactor/baselines/vast-compatibility-validation.json",
|
||||
"refactor/changes/2026-09-14-PKG-VAST-05-native-checkpoint.md",
|
||||
"refactor/baselines/vast-native-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 runtime ownership and both approved modes pass82 Node and162 real-browser wrapper checks (99 candidate,63 historical), with controlled SDK only. Eager callback errors/cancellation, rollback and explicit recreation are covered. Risk remains open for actual vendor media restoration in task05; no VPN exception was used."
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"id": "VAST-DIST-01",
|
||||
|
||||
+6
-3
@@ -3134,11 +3134,14 @@
|
||||
"PKG-VAST-04",
|
||||
"CORE-22"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "doing",
|
||||
"risk": "H",
|
||||
"deliverable": "官方测试广告或受控 SDK 环境,错误后主视频状态恢复;核验真实5.1.7/5.4.1/候选核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收",
|
||||
"deliverable": "真实IMA广告播放、错误后主视频恢复、会话重建与销毁;核验npm5.1.7/npm5.4.0/候选5.4.1核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收",
|
||||
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-VAST-05-native-checkpoint.md",
|
||||
"baselines/vast-native-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-VAST-06",
|
||||
|
||||
@@ -94,3 +94,26 @@ VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
|
||||
|
||||
只有实际VAST外部脚本持续因VPN无法加载时,可按用户授权记录并跳过该网络验证;
|
||||
它不豁免类型、生命周期或其他包。本次未用该例外。05/06和SDK-07继续开放。
|
||||
|
||||
## 真实 Google IMA 专项入口
|
||||
|
||||
固定工具链运行 `yarn test:vast-native`;可使用 `--project=chromium` 缩小复现。
|
||||
`playwright.vast-native.config.js` 复用8084隔离服务、单worker、零重试,输出
|
||||
`refactor/.cache/vast-native/`。此目录在进程终止后归档,再开始下一轮。`.native.js`
|
||||
命名避免在默认PR受控测试中隐式请求远端SDK;后续CI/发布环境需明确执行此命令。
|
||||
|
||||
测试保留实际Glomex打包代码,由其正常加载Google IMA;手写本地VAST响应仅指定
|
||||
测试广告媒体和本地跟踪图片,不替换AdsLoader、AdsManager、事件或媒体实现。
|
||||
Google的[AdsRequest接口](https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/interface/google.ima.AdsRequestInterface)
|
||||
提供adsResponse作为广告输入。每项附件记录真实执行的输入、SDK版本、浏览器版本、
|
||||
广告解码截图、主片播放位置及事件;远端SDK版本会变,结果仅代表对应运行环境。
|
||||
|
||||
三核心固定为npm5.1.7、npm5.4.0、候选5.4.1;实际npm1.0.0插件完整bundle通过
|
||||
归档校验后直接加载,候选两种模式使用同一生产构建配置的内存bundle。历史插件的
|
||||
SDK清理由夹具在证据后显式补做,不能据DOM被核心移除就认为旧插件拥有SDK清理。
|
||||
源代码组合通过不等于最终tarball通过,也不等于iOS/Android物理设备通过。
|
||||
|
||||
`vast-recovery.native.js`补充候选核心两模式的真实303错误恢复、显式会话重建及
|
||||
广告播放中核心销毁。当前结果见[05检查点](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md):
|
||||
最终组合22/27,恢复6/6;SDK超时/迟到、旧插件首帧及真机未关闭。独立入口失败应
|
||||
保留为失败,不能用默认受控套件通过抵消,也不能为稳定CI添加宽泛skip。
|
||||
@@ -1,5 +1,11 @@
|
||||
# Browser regression entry
|
||||
|
||||
`yarn test:vast-native` is the explicit external Google IMA suite; it is separate
|
||||
from default `*.spec.js` PR checks. It uses local VAST/media inputs and real SDK
|
||||
loading, playback, content restoration and destruction. See
|
||||
[VAST validation](../../refactor/vast-validation.md) for reports and outstanding
|
||||
network/device failures; the current matrix is not fully passing.
|
||||
|
||||
`canvas-dpip.spec.js` tests actual native windows with the Canvas video and subtitle
|
||||
track, continued playback, paused cue seeking and close/native-close/destroy.
|
||||
Chromium/Firefox native outcomes and WebKit API-unavailable outcomes must be
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { compilePackage } from '../helpers/load.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')
|
||||
|
||||
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 page.evaluate(async (compatibility) => {
|
||||
window.createPlayer('/assets/sample/video.mp4')
|
||||
window.recoveryEvents = []
|
||||
window.observeRecoveryPlayer = (player) => {
|
||||
for (const type of ['AdError', 'AdStarted', 'AdContentPauseRequested', 'AdContentResumeRequested', 'MediaResumed']) {
|
||||
player.addEventListener(type, event => window.recoveryEvents.push({ type, at: performance.now(), time: window.art.currentTime, paused: window.art.template.$video.paused, code: event.detail?.error?.errorCode, message: event.detail?.error?.message }))
|
||||
}
|
||||
}
|
||||
await window.art.plugins.add(window.artplayerPluginVast((context) => {
|
||||
window.recoveryContext = context
|
||||
window.observeRecoveryPlayer(context.init())
|
||||
}, compatibility ? { compatibility } : undefined))
|
||||
}, compatibility)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.5)
|
||||
await page.evaluate(() => {
|
||||
document.querySelector('#play').onclick = () => window.recoveryContext.playRes('<VAST version="3.0"></VAST>')
|
||||
})
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.recoveryEvents.filter(event => event.type === 'AdError').length)).toBe(1)
|
||||
const failure = await page.evaluate(() => ({ events: window.recoveryEvents, time: window.art.currentTime }))
|
||||
expect(failure.events.find(event => event.type === 'AdError').code).toBe(303)
|
||||
await expect(page.locator('[id^="art-"]')).toBeHidden()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(failure.time + 0.2)
|
||||
expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(false)
|
||||
await page.evaluate(() => {
|
||||
const context = window.recoveryContext
|
||||
window.firstRecoveryContainer = context.container
|
||||
window.art.plugins.artplayerPluginVast.destroy()
|
||||
window.observeRecoveryPlayer(context.init())
|
||||
const origin = location.origin
|
||||
window.recoveryXml = `<?xml version="1.0"?><VAST version="3.0"><Ad id="recovery"><InLine>
|
||||
<AdSystem version="1">ArtPlayer test fixture</AdSystem><AdTitle>Recovery validation</AdTitle>
|
||||
<Impression><![CDATA[${origin}/assets/vast/nonlinear-320x50.png?tracking=recovery]]></Impression>
|
||||
<Creatives><Creative><Linear><Duration>00:00:04</Duration><MediaFiles>
|
||||
<MediaFile delivery="progressive" type="video/mp4" width="640" height="360"><![CDATA[${origin}/assets/vast/linear-video.mp4]]></MediaFile>
|
||||
</MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>`
|
||||
document.querySelector('#play').onclick = () => context.playRes(window.recoveryXml)
|
||||
})
|
||||
expect(await page.evaluate(() => window.firstRecoveryContainer.isConnected)).toBe(false)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.recoveryEvents.filter(event => event.type === 'AdStarted').length)).toBe(1)
|
||||
await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('video')].some(video => video.currentSrc.includes('/assets/vast/linear-video.mp4') && video.videoWidth > 0 && video.currentTime > 0))).toBe(true)
|
||||
await expect(page.locator('[id^="art-"]')).toBeVisible()
|
||||
await testInfo.attach('native-vast-recovery-ad.png', { contentType: 'image/png', body: await page.screenshot() })
|
||||
const state = await page.evaluate(() => {
|
||||
const container = window.recoveryContext.container
|
||||
window.art.destroy()
|
||||
return { oldConnected: window.firstRecoveryContainer.isConnected, activeConnected: container.isConnected, container: window.recoveryContext.container, terminalInit: window.recoveryContext.init(), events: window.recoveryEvents, imaVersion: window.google.ima.VERSION }
|
||||
})
|
||||
expect(state.oldConnected).toBe(false)
|
||||
expect(state.activeConnected).toBe(false)
|
||||
expect(state.container).toBeNull()
|
||||
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.xml', { contentType: 'application/xml', body: await page.evaluate(() => window.recoveryXml) })
|
||||
})
|
||||
}
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
const state = await page.evaluate(() => ({ events: window.recoveryEvents, imaVersion: window.google?.ima?.VERSION, time: window.art?.currentTime, paused: window.art?.template.$video.paused }))
|
||||
await testInfo.attach('native-vast-recovery-final', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
await page.evaluate(() => window.art?.destroy())
|
||||
})
|
||||
@@ -0,0 +1,105 @@
|
||||
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 { 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 -- 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()
|
||||
const mediaFile = 'docs/assets/vast/linear-video.mp4'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
const state = await page.evaluate(() => ({
|
||||
registration: window.nativeVastRegistration,
|
||||
error: window.nativeVastError,
|
||||
imaVersion: window.google?.ima?.VERSION,
|
||||
events: window.nativeVastEvents,
|
||||
videos: [...document.querySelectorAll('video')].map(video => ({ src: video.currentSrc, time: video.currentTime, duration: video.duration, width: video.videoWidth, height: video.videoHeight, paused: video.paused, ended: video.ended })),
|
||||
containerConnected: window.nativeVastContext?.container?.isConnected,
|
||||
coreVersion: window.Artplayer?.version,
|
||||
}))
|
||||
await testInfo.attach('native-vast-state', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
await page.evaluate(() => window.art?.destroy())
|
||||
await page.evaluate(() => {
|
||||
if (window.nativeVastHistorical)
|
||||
window.nativeVastContext?.imaPlayer?.destroy()
|
||||
})
|
||||
})
|
||||
|
||||
for (const core of ['published-5.1.7', 'published', 'candidate']) {
|
||||
for (const implementation of ['npm-default', 'workspace-1.2', 'published-1.0.0']) {
|
||||
const compatibility = implementation === 'workspace-1.2' ? implementation : undefined
|
||||
const historical = implementation === 'published-1.0.0'
|
||||
const bundle = historical ? historicalCode : code
|
||||
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 page.evaluate(({ compatibility, historical }) => {
|
||||
window.nativeVastHistorical = historical
|
||||
window.nativeVastEvents = []
|
||||
window.nativeVastRegistration = 'pending'
|
||||
window.createPlayer('/assets/sample/video.mp4')
|
||||
const factory = window.artplayerPluginVast.default || window.artplayerPluginVast
|
||||
window.art.plugins.add(factory((context) => {
|
||||
window.nativeVastContext = context
|
||||
const player = context.init?.() || context.imaPlayer
|
||||
for (const type of ['AdStarted', 'AdComplete', 'AdAllAdsCompleted', 'AdContentPauseRequested', 'AdContentResumeRequested', 'AdError', 'MediaResumed']) {
|
||||
player.addEventListener(type, (event) => {
|
||||
window.nativeVastEvents.push({ type, at: performance.now(), contentTime: window.art.currentTime, contentPaused: window.art.template.$video.paused, error: event.detail?.error && { code: event.detail.error.errorCode, vastCode: event.detail.error.vastErrorCode, message: event.detail.error.message } })
|
||||
})
|
||||
}
|
||||
}, compatibility ? { compatibility } : undefined)).then(() => {
|
||||
window.nativeVastRegistration = 'ready'
|
||||
}).catch((error) => {
|
||||
window.nativeVastRegistration = 'failed'
|
||||
window.nativeVastError = String(error)
|
||||
})
|
||||
}, { compatibility, historical })
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVastRegistration)).not.toBe('pending')
|
||||
expect(await page.evaluate(() => window.nativeVastRegistration)).toBe('ready')
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.5)
|
||||
await page.evaluate(() => {
|
||||
const origin = location.origin
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<VAST version="3.0"><Ad id="artplayer-local"><InLine>
|
||||
<AdSystem version="1">ArtPlayer test fixture</AdSystem><AdTitle>Local SDK validation</AdTitle>
|
||||
<Impression><![CDATA[${origin}/assets/vast/nonlinear-320x50.png?tracking=impression]]></Impression>
|
||||
<Creatives><Creative><Linear><Duration>00:00:04</Duration><MediaFiles>
|
||||
<MediaFile delivery="progressive" type="video/mp4" width="640" height="360"><![CDATA[${origin}/assets/vast/linear-video.mp4]]></MediaFile>
|
||||
</MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>`
|
||||
window.nativeVastXml = xml
|
||||
document.querySelector('#play').onclick = () => window.nativeVastContext.playRes(xml)
|
||||
})
|
||||
const beforeAd = await page.evaluate(() => window.art.currentTime)
|
||||
await testInfo.attach('native-vast-response.xml', { contentType: 'application/xml', body: await page.evaluate(() => window.nativeVastXml) })
|
||||
await page.locator('#play').click()
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVastEvents.some(event => event.type === 'AdStarted'))).toBe(true)
|
||||
await expect(page.locator('[id^="art-"]')).toBeVisible()
|
||||
await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('video')].some(video => video.currentSrc.includes('/assets/vast/linear-video.mp4') && video.videoWidth > 0 && video.videoHeight > 0 && video.currentTime > 0))).toBe(true)
|
||||
const decodedAd = await page.evaluate(() => [...document.querySelectorAll('video')].map(video => ({ src: video.currentSrc, width: video.videoWidth, height: video.videoHeight, time: video.currentTime })).filter(video => video.src.includes('/assets/vast/linear-video.mp4')))
|
||||
expect(decodedAd.some(video => video.width > 0 && video.height > 0)).toBe(true)
|
||||
expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true)
|
||||
await testInfo.attach('native-vast-ad.png', { contentType: 'image/png', body: await page.screenshot() })
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVastEvents.some(event => event.type === 'AdComplete'))).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.nativeVastEvents.some(event => event.type === 'AdContentResumeRequested'))).toBe(true)
|
||||
await expect(page.locator('[id^="art-"]')).toBeHidden()
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(beforeAd + 0.2)
|
||||
expect(await page.evaluate(() => window.nativeVastEvents.filter(event => event.type === 'AdError'))).toEqual([])
|
||||
const resumed = await page.evaluate(() => ({ time: window.art.currentTime, paused: window.art.template.$video.paused, width: window.art.template.$video.videoWidth, events: window.nativeVastEvents }))
|
||||
expect(resumed.paused).toBe(false)
|
||||
expect(resumed.width).toBeGreaterThan(0)
|
||||
expect(resumed.events.filter(event => event.type === 'AdStarted')).toHaveLength(1)
|
||||
expect(resumed.events.filter(event => event.type === 'AdComplete')).toHaveLength(1)
|
||||
expect(resumed.events.findIndex(event => event.type === 'AdStarted')).toBeLessThan(resumed.events.findIndex(event => event.type === 'AdComplete'))
|
||||
await testInfo.attach('native-vast-resumed', { contentType: 'application/json', body: JSON.stringify({ beforeAd, decodedAd, ...resumed }) })
|
||||
await page.evaluate(() => window.art.destroy())
|
||||
await expect(page.locator('[id^="art-"]')).toHaveCount(0)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user