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-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-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-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)
+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)
新增显式截图模式,由 Node pngjs@7.0.0 解码实际页面 PNG,不复制转移画布。
+3 -1
View File
@@ -2874,7 +2874,9 @@
"evidence": [
"refactor/baselines/chapter-combinations-checkpoint.json",
"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": "记录原生事件和绝对/相对时序,进一步区分媒体驱动耗时、夹具和核心源操作;未证明原因前不改核心、不扩大超时或重复到绿灯。",
"closureCriteria": "确认先前超时的来源并有对应可靠回归;后续单次通过不足以关闭。"
+3 -1
View File
@@ -1434,7 +1434,9 @@
"acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成",
"evidence": [
"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.
The fixture's automatic failure screenshot runs after cleanup and may show an
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'])
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'])
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('#pause').onclick = () => window.art.pause()
})
@@ -62,8 +62,30 @@ async function hoverChapter(page, percentage, text) {
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) => {
const state = await page.evaluate(() => ({
at: performance.now(),
timeOrigin: performance.timeOrigin,
qualitySelectedAt: window.chapterQualitySelectedAt,
events: window.chapterEvents,
nativeEvents: window.chapterMediaEvents,
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'])
await page.locator('.art-control-quality').hover()
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 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)
const restoredTime = await page.evaluate(() => window.art.currentTime)
const historicalReset = core === 'published' && browserName === 'webkit'