test(chapter): [PKG-CHAPTER-05] correlate restart event and observation timing

This commit is contained in:
Harvey Zhao committed 2026-09-14 03:36:06 +08:00
1 parent 814bdc11db
commit e93d3cbb67
8 files changed
+2880 -6

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,49 @@
# PKG-CHAPTER-05 历史超时与事件观测
任务仍为 doing。本轮接续已提交的组合检查点,未修改生产源码、类型、产物或依赖。
没有把已通过的桌面模拟宣称为物理设备验收。
## 历史 trace 的新发现
读取原 `chapter05-quality-ready-results` 中 candidate core + candidate chapter 的
WebKit 失败 trace,核对实际 before/after 调用,而非只看测试错误摘要:
| 操作 | 耗时 | 返回值 |
| --- | --- | --- |
| 选择 Quality A | 582.473 ms | 点击完成 |
| 检查当前 URL | 38.196 ms | true |
| 第一次读取 restart 数组 | 3.373 ms | 空数组 |
| 第二次读取 restart 数组 | 9956.244 ms | 目标 URL 已在数组中 |
最后一次 evaluate 超过原 7000 ms 轮询期限,之后的 afterEach 才得到恢复位置和
readyState=4。这不是持续快速轮询七秒都得到空数组。旧 trace 没有为 restart
和原生媒体事件记录页面时钟,仍无法判断事件发生在该调用的哪一时刻,或区分
媒体驱动、页面主线程、协议传输、trace 快照开销。不能据此关闭风险,更不能仅
凭类似十秒停顿把它和 JASSUB 的 Firefox 问题认定为同一根因。
原 trace ZIP/member hash、准确时点和序列化返回值保存在
[机器记录](../baselines/chapter-timing-observations.json)。原始 ZIP 未改写。
## 测试改进与本轮验证
`chapter-combinations.spec.js` 现在记录公共事件与原生事件的 performance.now,
清晰度选择前的时间点,以及每次完成的 restart 读取的 Node 起点、往返耗时、
页面时间和 performance.timeOrigin。future trace 可以对齐两端时钟,分离已发生
但晚观察到的事件和真正晚发生的事件。原目标 URL、恢复位置、菜单、章节及
缩略图断言不变,超时没有增加,测试未启用重试。
固定 Node 24.21.0 / Yarn 1.22.22,执行:
```sh
yarn test:browser test/browser/chapter-combinations.spec.js --grep 'quality switch' --workers=1
```
三浏览器、四组新旧核心/章节组合共 12 项通过。两个已发布核心的 WebKit 用例
仍是历史位置归零观察,候选核心仍要求恢复原位置;不能将这两项统计为修复。
本轮使用浏览器服务的源码构建,报告保存实际资源 hash。定向只读 ESLint 通过。
没有重新运行章节全量、fullscreen/touch 全部矩阵或 tarball 安装验收。
本轮通过仅验证新增观测没有破坏既有组合断言,不解释历史停顿。CHAPTER-TIMING-01
保持 open,05/06 和物理设备门槛仍未完成。下一步可继续独立插件实施;若再次出现
停顿,先对照页面事件时点和往返耗时,避免直接修改核心或用更长等待掩盖它。
回退本提交只移除观测及记录;不 push/publish。
+1 -1
View File
@@ -500,7 +500,7 @@
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json) - PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
- PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json) - PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json)
- PKG-CHAPTER-04: [记录](changes/2026-09-10-PKG-CHAPTER-04-public-types.md) [记录](baselines/chapter-types-validation.json) - PKG-CHAPTER-04: [记录](changes/2026-09-10-PKG-CHAPTER-04-public-types.md) [记录](baselines/chapter-types-validation.json)
- PKG-CHAPTER-05: [记录](changes/2026-09-12-PKG-CHAPTER-05-combinations.md) [记录](baselines/chapter-combinations-checkpoint.json) - PKG-CHAPTER-05: [记录](changes/2026-09-12-PKG-CHAPTER-05-combinations.md) [记录](baselines/chapter-combinations-checkpoint.json) [记录](changes/2026-09-14-PKG-CHAPTER-05-timing.md) [记录](baselines/chapter-timing-observations.json)
- PKG-AMBILIGHT-01: [记录](changes/2026-09-12-PKG-AMBILIGHT-01-contract.md) [记录](baselines/ambilight-contract.md) [记录](baselines/ambilight-release.json) [记录](baselines/ambilight-contract-validation.json) - PKG-AMBILIGHT-01: [记录](changes/2026-09-12-PKG-AMBILIGHT-01-contract.md) [记录](baselines/ambilight-contract.md) [记录](baselines/ambilight-release.json) [记录](baselines/ambilight-contract-validation.json)
- PKG-AMBILIGHT-02: [记录](changes/2026-09-12-PKG-AMBILIGHT-02-tests.md) [记录](baselines/ambilight-behavior-validation.json) [记录](ambilight-validation.md) - PKG-AMBILIGHT-02: [记录](changes/2026-09-12-PKG-AMBILIGHT-02-tests.md) [记录](baselines/ambilight-behavior-validation.json) [记录](ambilight-validation.md)
- PKG-AMBILIGHT-03: [记录](changes/2026-09-12-PKG-AMBILIGHT-03-lifecycle.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-lifecycle-validation.json) - PKG-AMBILIGHT-03: [记录](changes/2026-09-12-PKG-AMBILIGHT-03-lifecycle.md) [记录](ambilight-validation.md) [记录](baselines/ambilight-lifecycle-validation.json)
+10
View File
@@ -1,5 +1,15 @@
# 进度与证据 # 进度与证据
## PKG-CHAPTER-05 时序取证继续(doing)
复核旧失败 ZIP:一次 restart 数组读取耗时 9956.244 ms,返回时已含目标 URL;
不能只凭原超时文本认定七秒都未触发 restart。新增公共/原生事件页面时点与
restart 读取往返耗时,三浏览器四组质量/章节/缩略图组合十二项通过,保留两项
已发布核心 WebKit 位置归零观察。无生产或依赖变更,CHAPTER-TIMING-01 和物理
设备缺口保持未关闭,05/06 未完成。详见[记录](changes/2026-09-14-PKG-CHAPTER-05-timing.md)
及[trace 摘要](baselines/chapter-timing-observations.json)。VAST 的初始化兼容
选择已再次列为待答复事项,尚未变更默认行为;其他独立实施继续。
## PKG-JASSUB-09 合成截图验证(doing) ## PKG-JASSUB-09 合成截图验证(doing)
新增显式截图模式,由 Node pngjs@7.0.0 解码实际页面 PNG,不复制转移画布。 新增显式截图模式,由 Node pngjs@7.0.0 解码实际页面 PNG,不复制转移画布。
+3 -1
View File
@@ -2874,7 +2874,9 @@
"evidence": [ "evidence": [
"refactor/baselines/chapter-combinations-checkpoint.json", "refactor/baselines/chapter-combinations-checkpoint.json",
"test/browser/chapter-combinations.spec.js", "test/browser/chapter-combinations.spec.js",
"refactor/changes/2026-09-12-PKG-CHAPTER-05-combinations.md" "refactor/changes/2026-09-12-PKG-CHAPTER-05-combinations.md",
"refactor/changes/2026-09-14-PKG-CHAPTER-05-timing.md",
"refactor/baselines/chapter-timing-observations.json"
], ],
"compatibleResolution": "记录原生事件和绝对/相对时序,进一步区分媒体驱动耗时、夹具和核心源操作;未证明原因前不改核心、不扩大超时或重复到绿灯。", "compatibleResolution": "记录原生事件和绝对/相对时序,进一步区分媒体驱动耗时、夹具和核心源操作;未证明原因前不改核心、不扩大超时或重复到绿灯。",
"closureCriteria": "确认先前超时的来源并有对应可靠回归;后续单次通过不足以关闭。" "closureCriteria": "确认先前超时的来源并有对应可靠回归;后续单次通过不足以关闭。"
+3 -1
View File
@@ -1434,7 +1434,9 @@
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成", "acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
"evidence": [ "evidence": [
"changes/2026-09-12-PKG-CHAPTER-05-combinations.md", "changes/2026-09-12-PKG-CHAPTER-05-combinations.md",
"baselines/chapter-combinations-checkpoint.json" "baselines/chapter-combinations-checkpoint.json",
"changes/2026-09-14-PKG-CHAPTER-05-timing.md",
"baselines/chapter-timing-observations.json"
] ]
}, },
{ {
+8
View File
@@ -217,3 +217,11 @@ hashes identify the actual selected ASS, not the white baseline. Screenshots are
attached at named phases; errors from individual polling attempts are retained. attached at named phases; errors from individual polling attempts are retained.
The fixture's automatic failure screenshot runs after cleanup and may show an The fixture's automatic failure screenshot runs after cleanup and may show an
empty player; use the named pre-cleanup screenshots for visual claims. empty player; use the named pre-cleanup screenshots for visual claims.
Chapter quality combinations attach `chapter-restart-observations`: completed
read calls include host wall time/elapsed time and browser monotonic time/origin.
Public/native chapter events also carry browser timestamps. Preserve those clocks
when diagnosing a timeout: the historical failure includes a single ~10-second
evaluate returning an already-recorded restart, not seven seconds of fast empty
reads. These observations do not expand the timeout or waive the open timing and
physical-device acceptance in PKG-CHAPTER-05.
+26 -3
View File
@@ -26,9 +26,9 @@ async function openChapter(page, core, chapter) {
] })], ] })],
}) })
for (const name of ['restart', 'seek', 'fullscreen', 'fullscreenWeb']) for (const name of ['restart', 'seek', 'fullscreen', 'fullscreenWeb'])
window.art.on(name, value => window.chapterEvents.push({ name, value })) window.art.on(name, value => window.chapterEvents.push({ name, value, at: performance.now() }))
for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'seeking', 'seeked', 'playing', 'pause', 'error']) for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'seeking', 'seeked', 'playing', 'pause', 'error'])
window.art.video.addEventListener(name, () => window.chapterMediaEvents.push({ name, time: window.art.currentTime, source: window.art.video.currentSrc, paused: window.art.video.paused, readyState: window.art.video.readyState })) window.art.video.addEventListener(name, () => window.chapterMediaEvents.push({ name, at: performance.now(), time: window.art.currentTime, source: window.art.video.currentSrc, paused: window.art.video.paused, readyState: window.art.video.readyState }))
document.querySelector('#play').onclick = () => window.art.play() document.querySelector('#play').onclick = () => window.art.play()
document.querySelector('#pause').onclick = () => window.art.pause() document.querySelector('#pause').onclick = () => window.art.pause()
}) })
@@ -62,8 +62,30 @@ async function hoverChapter(page, percentage, text) {
return { box, x, layout } return { box, x, layout }
} }
async function waitForChapterRestart(page, testInfo) {
const calls = []
try {
await expect.poll(async () => {
const started = Date.now()
const state = await page.evaluate(() => ({
at: performance.now(),
timeOrigin: performance.timeOrigin,
events: window.chapterEvents.filter(event => event.name === 'restart').map(event => event.value),
}))
calls.push({ started, elapsed: Date.now() - started, ...state })
return state.events
}).toEqual(['/test/pattern.mp4?quality=A'])
}
finally {
await testInfo.attach('chapter-restart-observations', { contentType: 'application/json', body: JSON.stringify(calls) })
}
}
test.afterEach(async ({ page }, testInfo) => { test.afterEach(async ({ page }, testInfo) => {
const state = await page.evaluate(() => ({ const state = await page.evaluate(() => ({
at: performance.now(),
timeOrigin: performance.timeOrigin,
qualitySelectedAt: window.chapterQualitySelectedAt,
events: window.chapterEvents, events: window.chapterEvents,
nativeEvents: window.chapterMediaEvents, nativeEvents: window.chapterMediaEvents,
time: window.art?.currentTime, time: window.art?.currentTime,
@@ -121,9 +143,10 @@ for (const core of ['published', 'candidate']) {
expect(await page.locator('.art-chapter .art-progress-played').evaluateAll(nodes => [nodes[0].style.width, nodes[2].style.width])).toEqual(['100%', '0px']) expect(await page.locator('.art-chapter .art-progress-played').evaluateAll(nodes => [nodes[0].style.width, nodes[2].style.width])).toEqual(['100%', '0px'])
await page.locator('.art-control-quality').hover() await page.locator('.art-control-quality').hover()
await expect(page.locator('.art-control-quality .art-selector-list')).toHaveCSS('opacity', '1') await expect(page.locator('.art-control-quality .art-selector-list')).toHaveCSS('opacity', '1')
await page.evaluate(() => window.chapterQualitySelectedAt = performance.now())
await page.locator('.art-control-quality .art-selector-item').filter({ hasText: 'Quality A' }).click() await page.locator('.art-control-quality .art-selector-item').filter({ hasText: 'Quality A' }).click()
await expect.poll(() => page.evaluate(() => window.art.url.includes('quality=A'))).toBe(true) await expect.poll(() => page.evaluate(() => window.art.url.includes('quality=A'))).toBe(true)
await expect.poll(() => page.evaluate(() => window.chapterEvents.filter(event => event.name === 'restart').map(event => event.value))).toEqual(['/test/pattern.mp4?quality=A']) await waitForChapterRestart(page, testInfo)
await expect.poll(() => page.evaluate(() => !window.art.video.seeking && window.art.video.readyState >= 2)).toBe(true) await expect.poll(() => page.evaluate(() => !window.art.video.seeking && window.art.video.readyState >= 2)).toBe(true)
const restoredTime = await page.evaluate(() => window.art.currentTime) const restoredTime = await page.evaluate(() => window.art.currentTime)
const historicalReset = core === 'published' && browserName === 'webkit' const historicalReset = core === 'published' && browserName === 'webkit'