From d89728c0a6a41cd895070a749ca5c7b2a3951ca7 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 18:28:07 +0800 Subject: [PATCH] test(chapter): [PKG-CHAPTER-HOVER-01] wait for stable fullscreen geometry --- .../artplayer-plugin-chapter/ARCHITECTURE.md | 9 + .../baselines/chapter-hover-validation.json | 1608 +++++++++++++++++ ...026-09-14-PKG-CHAPTER-HOVER-01-geometry.md | 57 + refactor/plan.md | 6 +- refactor/progress.md | 15 + refactor/risk-table.md | 2 +- refactor/risks.json | 11 +- refactor/tasks.json | 6 +- scripts/browser-validation/scope.ts | 1 + scripts/tsconfig.docs.json | 3 +- test/browser/README.md | 20 + test/browser/chapter-combinations.spec.js | 6 +- test/browser/chapter-hover.spec.js | 52 + test/browser/chapter-hover.ts | 15 + 14 files changed, 1797 insertions(+), 14 deletions(-) create mode 100644 refactor/baselines/chapter-hover-validation.json create mode 100644 refactor/changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md create mode 100644 test/browser/chapter-hover.spec.js create mode 100644 test/browser/chapter-hover.ts diff --git a/packages/artplayer-plugin-chapter/ARCHITECTURE.md b/packages/artplayer-plugin-chapter/ARCHITECTURE.md index bbb79f33d..3a54b7132 100644 --- a/packages/artplayer-plugin-chapter/ARCHITECTURE.md +++ b/packages/artplayer-plugin-chapter/ARCHITECTURE.md @@ -103,3 +103,12 @@ bridges; typesVersions provides the legacy subpath to old resolvers. All bridges refer to one contract and runtime import paths stay unchanged. The frozen published baseline is under refactor/baselines; never overwrite it with candidate behavior. Physical device, full editor and final release checks remain separate tasks. + +The combination suite uses `test/browser/chapter-hover.ts` to wait for Playwright +actionability/stable geometry before reading progress coordinates. Fullscreen state +alone does not mean the bottom controls have finished their CSS transition. +`chapter-hover.spec.js` exercises this boundary with an intentional native Web +Animation after entering real fullscreen. It preserves title/opacity/hit-test +assertions and uses actual installed artifacts in the installed browser scope. +`ARTPLAYER_CHAPTER_HOVER_BASELINE=1` restores the old immediate coordinate read for +diagnosis only. This helper corrects test synchronization, not player behavior. diff --git a/refactor/baselines/chapter-hover-validation.json b/refactor/baselines/chapter-hover-validation.json new file mode 100644 index 000000000..7b33790c1 --- /dev/null +++ b/refactor/baselines/chapter-hover-validation.json @@ -0,0 +1,1608 @@ +{ + "schemaVersion": 1, + "task": "PKG-CHAPTER-HOVER-01", + "date": "2026-09-14", + "node": "24.21.0", + "yarn": "1.22.22", + "conclusion": "The test read coordinates during progress motion. A native-animation control reproduces stale pointer coordinates with unchanged installed player bytes; awaiting actionability before reading coordinates fixes that control. The original failure has the same disappearing title and transitional pointer geometry; the exact original scheduling is not replayed.", + "scope": "Test synchronization only; no production runtime, public API, DOM/CSS, or installed package bytes changed.", + "limitations": [ + "Controlled Web Animation is intentional fault injection, not a claim about native transition duration.", + "Desktop browser engines and touch emulation are not physical devices.", + "CHAPTER-TIMING-01 and Chapter final integration/distribution tasks remain open.", + "No remote CI, full source-suite run, or npm publication." + ], + "reports": [ + { + "directory": "refactor/.cache/ci-browser-installed-full-report", + "purpose": "original installed integration failure", + "reportSha256": "b3220ff7b8e29b796f428b0295507fe22ad9033ad9f5627898d6ee0d33cb3011", + "stats": { + "startTime": "2026-09-14T10:00:23.507Z", + "duration": 161182.12099999998, + "expected": 224, + "skipped": 0, + "unexpected": 1, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 1, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "--workers=2" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.js", + "sha256": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [ + { + "title": "published core + candidate chapter: fullscreen keeps hover geometry and exits cleanly", + "browser": "chromium", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCSS\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.art-chapter-title')\nExpected: \u001b[32m\"1\"\u001b[39m\nReceived: \u001b[31m\"0\"\u001b[39m\nTimeout: 7000ms\n\nCall log:\n\u001b[2m - Expect \"toHaveCSS\" locator('.art-chapter-title') with timeout 7000ms\u001b[22m\n\u001b[2m - waiting for locator('.art-chapter-title')\u001b[22m\n\u001b[2m - locator resolved to
\u001b[22m\n\u001b[2m - unexpected value \"0.279014\"\u001b[22m\n\u001b[2m - locator resolved to
\u001b[22m\n\u001b[2m - unexpected value \"0.00171983\"\u001b[22m\n\u001b[2m 16 × locator resolved to
\u001b[22m\n\u001b[2m - unexpected value \"0\"\u001b[22m\n\n\n 43 | await page.mouse.move(x, box.y + box.height / 2)\n 44 | await expect(page.locator('.art-chapter-title')).toHaveText(text)\n> 45 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n | ^\n 46 | await expect(page.locator('.art-control-thumbnails')).toBeVisible()\n 47 | await expect.poll(() => page.evaluate(() => window.art.controls.thumbnails.style.backgroundImage)).not.toBe('')\n 48 | const layout = await page.evaluate(() => {\n at hoverChapter (D:\\github\\ArtPlayer\\test\\browser\\chapter-combinations.spec.js:45:52)\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-combinations.spec.js:180:33" + ] + } + ], + "observations": [] + }, + { + "directory": "refactor/.cache/chapter-hover-motion-red-report", + "purpose": "immediate coordinate read; controlled native animation", + "reportSha256": "c1c991888e83d82e5077542fedb901e1ac2c2e1f3ffcde70ac26d85328d0c989", + "stats": { + "startTime": "2026-09-14T10:14:21.083Z", + "duration": 17075.045, + "expected": 2, + "skipped": 0, + "unexpected": 4, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 1, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-hover.spec.js", + "--workers=1" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.js", + "sha256": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [ + { + "title": "published: chapter hover waits for a moving fullscreen progress bar", + "browser": "chromium", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | await expect(page.locator('.art-chapter-title')).toHaveText('Middle')\n 36 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n 37 | await page.locator('.art-control-fullscreen').click()\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "candidate: chapter hover waits for a moving fullscreen progress bar", + "browser": "chromium", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | await expect(page.locator('.art-chapter-title')).toHaveText('Middle')\n 36 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n 37 | await page.locator('.art-control-fullscreen').click()\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "published: chapter hover waits for a moving fullscreen progress bar", + "browser": "firefox", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | await expect(page.locator('.art-chapter-title')).toHaveText('Middle')\n 36 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n 37 | await page.locator('.art-control-fullscreen').click()\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "candidate: chapter hover waits for a moving fullscreen progress bar", + "browser": "firefox", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | await expect(page.locator('.art-chapter-title')).toHaveText('Middle')\n 36 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n 37 | await page.locator('.art-control-fullscreen').click()\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + } + ], + "observations": [ + { + "browser": "chromium", + "core": "published", + "pointer": { + "box": { + "x": 10, + "y": 590.340576171875, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 594.340576171875 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "pointer": { + "box": { + "x": 10, + "y": 590.3410034179688, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 594.3410034179688 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "pointer": { + "box": { + "x": 10, + "y": 640.004150390625, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 644.004150390625 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "pointer": { + "box": { + "x": 10, + "y": 638.3360595703125, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 642.3360595703125 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "pointer": { + "box": { + "x": 10, + "y": 592.2999877929688, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 596.2999877929688 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "pointer": { + "box": { + "x": 10, + "y": 592.2000122070312, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 596.2000122070312 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + }, + { + "directory": "refactor/.cache/chapter-hover-motion-green-report", + "purpose": "stable actionability; controlled native animation", + "reportSha256": "0edded4ca02b494404f978b971a5bdd445ca21beead3bc4f5cb788114f5d821b", + "stats": { + "startTime": "2026-09-14T10:18:16.301Z", + "duration": 19123.172, + "expected": 6, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-hover.spec.js", + "--workers=1" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.js", + "sha256": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [], + "observations": [ + { + "browser": "chromium", + "core": "published", + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + }, + { + "directory": "refactor/.cache/chapter-hover-combinations-main-report", + "purpose": "main installed chapter combinations and motion", + "reportSha256": "681214ef1a9fc81c65f56e70c396a920600a0109b271b6496c7233b33023472d", + "stats": { + "startTime": "2026-09-14T10:19:06.290Z", + "duration": 85751.219, + "expected": 66, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-combinations.spec.js", + "test/browser/chapter-hover.spec.js", + "--workers=2" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.js", + "sha256": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [], + "observations": [ + { + "browser": "chromium", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + }, + { + "directory": "refactor/.cache/chapter-hover-combinations-legacy-report", + "purpose": "legacy installed chapter combinations and motion", + "reportSha256": "8a80d8ed7a880988696b2407cc0d45964205b09befcd0687e1eb43ead63237c8", + "stats": { + "startTime": "2026-09-14T10:21:21.189Z", + "duration": 88985.99799999999, + "expected": 66, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-combinations.spec.js", + "test/browser/chapter-hover.spec.js", + "--workers=2" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts-legacy.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.legacy.js", + "sha256": "9bde35a6d64234cae15300e37b03204c075ef26126058f48bb3bf97d6e002449", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.legacy.js", + "sha256": "dade919ed044cbf46ac16e505f538f86a86edccf177da2e14d79d111559d4bef", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.legacy.js", + "sha256": "c3ed1a54e44a0202811d70378cb19edf87b1ebd010d05fe96ebe64d0b4301d5d", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.legacy.js", + "sha256": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [], + "observations": [ + { + "browser": "chromium", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + }, + { + "directory": "refactor/.cache/chapter-hover-bounds-red-report", + "purpose": "baseline with explicit final rectangle bounds", + "reportSha256": "daf08a260b28e8fc4d4cab736e8c267eaab61bd0717ce7db275488cfbe300371", + "stats": { + "startTime": "2026-09-14T10:25:19.790Z", + "duration": 19375.537, + "expected": 0, + "skipped": 0, + "unexpected": 6, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 1, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-hover.spec.js", + "--workers=1" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.js", + "sha256": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [ + { + "title": "published: chapter hover waits for a moving fullscreen progress bar", + "browser": "chromium", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, baseline: process.env.ARTPLAYER_CHAPTER_HOVER_BASELINE === '1', pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | expect(pointer.x).toBeGreaterThanOrEqual(layout.x)\n 36 | expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width)\n 37 | expect(pointer.y).toBeGreaterThanOrEqual(layout.y)\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "candidate: chapter hover waits for a moving fullscreen progress bar", + "browser": "chromium", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, baseline: process.env.ARTPLAYER_CHAPTER_HOVER_BASELINE === '1', pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | expect(pointer.x).toBeGreaterThanOrEqual(layout.x)\n 36 | expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width)\n 37 | expect(pointer.y).toBeGreaterThanOrEqual(layout.y)\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "published: chapter hover waits for a moving fullscreen progress bar", + "browser": "firefox", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, baseline: process.env.ARTPLAYER_CHAPTER_HOVER_BASELINE === '1', pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | expect(pointer.x).toBeGreaterThanOrEqual(layout.x)\n 36 | expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width)\n 37 | expect(pointer.y).toBeGreaterThanOrEqual(layout.y)\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "candidate: chapter hover waits for a moving fullscreen progress bar", + "browser": "firefox", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n 32 | }, pointer)\n 33 | await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, baseline: process.env.ARTPLAYER_CHAPTER_HOVER_BASELINE === '1', pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) })\n> 34 | expect(layout.pointerInside).toBe(true)\n | ^\n 35 | expect(pointer.x).toBeGreaterThanOrEqual(layout.x)\n 36 | expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width)\n 37 | expect(pointer.y).toBeGreaterThanOrEqual(layout.y)\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:34:34" + ] + }, + { + "title": "published: chapter hover waits for a moving fullscreen progress bar", + "browser": "webkit", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m647\u001b[39m\nReceived: \u001b[31m599\u001b[39m\n\n 35 | expect(pointer.x).toBeGreaterThanOrEqual(layout.x)\n 36 | expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width)\n> 37 | expect(pointer.y).toBeGreaterThanOrEqual(layout.y)\n | ^\n 38 | expect(pointer.y).toBeLessThanOrEqual(layout.y + layout.height)\n 39 | await expect(page.locator('.art-chapter-title')).toHaveText('Middle')\n 40 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:37:23" + ] + }, + { + "title": "candidate: chapter hover waits for a moving fullscreen progress bar", + "browser": "webkit", + "status": "failed", + "errors": [ + "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m647\u001b[39m\nReceived: \u001b[31m606.2999877929688\u001b[39m\n\n 35 | expect(pointer.x).toBeGreaterThanOrEqual(layout.x)\n 36 | expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width)\n> 37 | expect(pointer.y).toBeGreaterThanOrEqual(layout.y)\n | ^\n 38 | expect(pointer.y).toBeLessThanOrEqual(layout.y + layout.height)\n 39 | await expect(page.locator('.art-chapter-title')).toHaveText('Middle')\n 40 | await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1')\n at D:\\github\\ArtPlayer\\test\\browser\\chapter-hover.spec.js:37:23" + ] + } + ], + "observations": [ + { + "browser": "chromium", + "core": "published", + "baseline": true, + "pointer": { + "box": { + "x": 10, + "y": 590.336669921875, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 594.336669921875 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "baseline": true, + "pointer": { + "box": { + "x": 10, + "y": 590.4988403320312, + "width": 1260, + "height": 7.6875 + }, + "x": 640, + "y": 594.3425903320312 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "baseline": true, + "pointer": { + "box": { + "x": 10, + "y": 638.3333129882812, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 642.3333129882812 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "baseline": true, + "pointer": { + "box": { + "x": 10, + "y": 640.12939453125, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 644.12939453125 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": false, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "baseline": true, + "pointer": { + "box": { + "x": 10, + "y": 595, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 599 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "baseline": true, + "pointer": { + "box": { + "x": 10, + "y": 602.2999877929688, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 606.2999877929688 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + }, + { + "directory": "refactor/.cache/chapter-hover-bounds-main-report", + "purpose": "main stable pointer with explicit final rectangle bounds", + "reportSha256": "ab3c0db9168e03d88f45a01dcd467e2c9a719f4177155af9c500f91bf7b7f942", + "stats": { + "startTime": "2026-09-14T10:26:20.236Z", + "duration": 19154.688, + "expected": 6, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-hover.spec.js", + "--workers=1" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.js", + "sha256": "9148a369636f814b03e6a1ed2a7db222a31b159f93e8a94d52c5a9dde2dfe6a6", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.js", + "sha256": "179c3ea90ddc85da27b46f000110e32d464dfa5c8c394e98316dc1b75d6f2a72", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.js", + "sha256": "21ee5227f26cfff6e82ee6932f6eafc9741fa3be0ec2f5252a47b53ceea14599", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.js", + "sha256": "a901e5c5fc00a64a6c2ae9d4b2b846950412710ec9672d30640d40987d4f9e73", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [], + "observations": [ + { + "browser": "chromium", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + }, + { + "directory": "refactor/.cache/chapter-hover-bounds-legacy-report", + "purpose": "legacy stable pointer with explicit final rectangle bounds", + "reportSha256": "350b280112c6959d7faba35e5dc8ef6f7e7dffa55d9a36b91e80a51e5439c624", + "stats": { + "startTime": "2026-09-14T10:27:06.527Z", + "duration": 18531.621, + "expected": 6, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "result": { + "scope": "installed", + "exitCode": 0, + "signal": null, + "args": [ + "test", + "--config=playwright.installed.config.js", + "test/browser/chapter-hover.spec.js", + "--workers=1" + ] + }, + "invocation": { + "scope": "installed", + "artifactMap": "D:\\github\\ArtPlayer\\refactor\\.cache\\packages\\run-IIEOt4\\browser-artifacts-legacy.json", + "node": "24.21.0", + "inputs": [ + { + "name": "artplayer", + "member": "package/dist/artplayer.legacy.js", + "sha256": "9bde35a6d64234cae15300e37b03204c075ef26126058f48bb3bf97d6e002449", + "archiveSha256": "a4e53c0c08850e02585c025581af30c8a91f3c8871471375d2228ffc246fd454" + }, + { + "name": "artplayer-plugin-chapter", + "member": "package/dist/artplayer-plugin-chapter.legacy.js", + "sha256": "dade919ed044cbf46ac16e505f538f86a86edccf177da2e14d79d111559d4bef", + "archiveSha256": "9c0a399b4b48db3f30bef69bfedd24245e9328f39151b1e1f670c4d357f4fb05" + }, + { + "name": "artplayer-plugin-ambilight", + "member": "package/dist/artplayer-plugin-ambilight.legacy.js", + "sha256": "c3ed1a54e44a0202811d70378cb19edf87b1ebd010d05fe96ebe64d0b4301d5d", + "archiveSha256": "a72d5805ca34ebcad615cfd155bafdbe8ddbdeb03e10a154142f8927be80f3b7" + }, + { + "name": "artplayer-proxy-canvas", + "member": "package/dist/artplayer-proxy-canvas.legacy.js", + "sha256": "052263fdd7ab657a0183d874a706c2cf82527233c5e60b58158caa0677718b97", + "archiveSha256": "09936e35e18188cab011f933d8063cac5e0f491a1960353ca9d5c65b9b42b65d" + } + ] + }, + "browsers": { + "chromium": "153.0.8010.12", + "firefox": "155.0", + "webkit": "26.6" + }, + "failures": [], + "observations": [ + { + "browser": "chromium", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "chromium", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 640, + "y": 651 + }, + "layout": { + "x": 10, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "firefox", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8 + }, + "x": 683, + "y": 699 + }, + "layout": { + "x": 10, + "y": 695, + "width": 1346, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "published", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + }, + { + "browser": "webkit", + "core": "candidate", + "baseline": false, + "pointer": { + "box": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8 + }, + "x": 631, + "y": 651 + }, + "layout": { + "x": 1, + "y": 647, + "width": 1260, + "height": 8, + "pointerInside": true, + "title": "Middle" + }, + "motion": "controlled native Web Animation after actual fullscreen; no media or hit-test mocks" + } + ] + } + ] +} diff --git a/refactor/changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md b/refactor/changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md new file mode 100644 index 000000000..a2e8112af --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md @@ -0,0 +1,57 @@ +# PKG-CHAPTER-HOVER-01 全屏悬停测试同步 + +## 问题与边界 + +CI-BROWSER-01 的已安装四包检查曾实际出现 224 通过、1 失败:Chromium 中 +5.4.0 核心搭配候选 Chapter,原生全屏后的章节标题先通过文本断言,再隐藏, +opacity 从 0.279 降到 0。原始报告、trace 和截图保留在 +`refactor/.cache/ci-browser-installed-full-report`,不改写为通过。 + +旧测试在 `art.fullscreen === true` 后立即读取进度条 boundingBox 并移动鼠标。 +核心 bottom.less 的进度条/控件有 transform transition;全屏布尔状态不代表 +几何已稳定。原 trace 的鼠标位置为 (640, 665.9921875),终态截图的进度条已 +移动。仅靠后来通过不能证明原因,因此另建受控移动实验。 + +## 实现与责任 + +- `test/browser/chapter-hover.ts` 统一几何读取:先通过 Playwright locator 的 + `hover({ trial: true })` actionability/stability 检查,再读取 box 并实际移动鼠标。 + trial 不发送实际 hover;没有增加固定 sleep、断言超时或 test retries。 +- `chapter-combinations.spec.js` 复用该函数;标题、透明度、缩略图、边界及全屏 + 退出断言保持原样。 +- `chapter-hover.spec.js` 在真实原生全屏中用原生 Web Animation 让进度条移动 + 600ms,结束后通过真实 hit-test 验证指针仍在进度条内,再验证 Middle/opacity。 + 这是故意注入的几何移动,不是宣称产品 transition 实际持续 600ms。 +- `ARTPLAYER_CHAPTER_HOVER_BASELINE=1` 可恢复旧的立即读取路径,保留可重跑的 + 反例。默认必须使用稳定路径;诊断模式的失败不能豁免正式验收。 +- 新 TS helper 纳入严格工具类型检查;安装浏览器范围显式加入该回归文件。 + +API-08 仅涉及测试如何操作 DOM;生产源码、公开接口/事件/声明/CSS 和安装包 +字节都未修改。包架构文档记录入口和边界。本任务不需要重建生产运行时或引入依赖。 + +## 证据与限制 + +完整结果、报告摘要、浏览器版本、实际安装入口及 SHA 见 +[chapter-hover-validation.json](../baselines/chapter-hover-validation.json)。 +先前立即读取路径在受控移动中 4 失败、2 通过;等待稳定后同样 6 项全部通过。 +Chromium/Firefox 的失败是最终指针已离开移动后的进度条。首版 WebKit 旧路径 +通过,但审查附件发现其 hit-test 与最终矩形不一致。因此补上最终 x/y 矩形范围 +断言:最终 baseline 三引擎 6 项全部失败;默认稳定路径在 main/legacy 各 6 项 +通过。首版的 2 项通过仍留档,不能把只依赖 hit-test 的结果当作几何正确。 +原有组合与首版新增移动回归在 main/legacy 各 66 项 +通过,均无重试、跳过或失败。严格 TS、专项 ESLint、根 lint(0 错误/1 个既有 +warning)、4 项 browser runner 测试和固定工具链检查通过。 + +实验确认旧测试的几何竞态及针对该机制的修正,原失败符合相同症状;不能重新播放 +原 run 的精确调度,也不能据此声称所有全屏生产缺陷已排除。新旧包字节保持一致, +本次修复归类为测试同步问题。CHAPTER-TIMING-01 的 WebKit restart 观察耗时、 +真机、完整发布/分发验收仍由 Chapter-05/06 负责。 + +验证使用 Node 24.21.0、Yarn 1.22.22、实际安装产物及桌面三引擎。没有远端 CI、 +真机、全量 source suite 或 npm 发布;不把模拟触摸称为手机证据。 + +## 回退与接续 + +诊断时设置 baseline 环境变量并运行 README 所列命令,可比较真实指针与终态 +矩形。交付回退可还原本任务的 helper、回归测试、scope 和文档/状态;不要删除 +历史失败报告或把 opacity 断言放宽。后续 UI/CSS 改动须继续运行这两个浏览器文件。 diff --git a/refactor/plan.md b/refactor/plan.md index 30946350e..85e4be106 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -4,7 +4,7 @@ 基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 258 项,范围 22 个包及工作区/示例。 -状态:todo 51 / doing 19 / blocked 0 / done 188 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 +状态:todo 50 / doing 19 / blocked 0 / done 189 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。 前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。 @@ -146,7 +146,7 @@ | PKG-CHAPTER-04 | artplayer-plugin-chapter
迁移自有源码和公开类型 | PKG-CHAPTER-03, ENG-04, ENG-06 | 承接 03 的自有源码 TS 化,完善公开 Chapters/Option/Result 与 update 推导,关闭声明模块解析和旧消费差异 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容;处理 BASE-TYPE-01/03,保持运行时和新旧 TS 模块解析消费 | M | done | | PKG-CHAPTER-05 | artplayer-plugin-chapter
验证新旧核心和组合 | PKG-CHAPTER-04, CORE-22 | chapter + quality/thumbnail、移动和全屏进度场景 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | M | doing | | PKG-CHAPTER-06 | artplayer-plugin-chapter
验证分发并同步文档 | PKG-CHAPTER-05, ENG-07, PKG-CHAPTER-HOVER-01 | chapter.js 示例、产物和变更记录 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | M | todo | -| PKG-CHAPTER-HOVER-01 | artplayer-plugin-chapter
定位并修复原生全屏章节悬停时序 | PKG-CHAPTER-04, CI-BROWSER-01 | 原生全屏动态几何与指针时序复现、针对根因的修复和真实浏览器回归 | 解释 Chromium 旧核心+新章节的 opacity 失败;保留标题、缩略图和全屏退出断言,不以重试或扩大等待掩盖;区分测试同步与生产缺陷 | M | todo | +| PKG-CHAPTER-HOVER-01 | artplayer-plugin-chapter
定位并修复原生全屏章节悬停时序 | PKG-CHAPTER-04, CI-BROWSER-01 | 原生全屏动态几何与指针时序复现、针对根因的修复和真实浏览器回归 | 解释 Chromium 旧核心+新章节的 opacity 失败;保留标题、缩略图和全屏退出断言,不以重试或扩大等待掩盖;区分测试同步与生产缺陷 | M | done | ## 5 包迁移:artplayer-plugin-ambilight @@ -669,4 +669,4 @@ - ENG-13: [记录](changes/2026-09-14-ENG-13-observation-window.md) [记录](baselines/observation-window-validation.json) - SITE-VCONSOLE-01: [记录](changes/2026-09-14-SITE-VCONSOLE-01-lifecycle.md) [记录](baselines/vconsole-lifecycle-validation.json) - CI-BROWSER-01: [记录](changes/2026-09-14-CI-BROWSER-01-scopes.md) [记录](baselines/browser-scope-validation.json) -- PKG-CHAPTER-HOVER-01: [记录](changes/2026-09-14-CI-BROWSER-01-scopes.md) [记录](baselines/browser-scope-validation.json) +- PKG-CHAPTER-HOVER-01: [记录](changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md) [记录](baselines/chapter-hover-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index a6502a187..00f858166 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,20 @@ # 进度与证据 +## PKG-CHAPTER-HOVER-01 全屏悬停测试同步完成 + +原始 installed 224/1 失败保持归档。受控原生进度条移动实验确认立即读取坐标 +可让指针落在终态进度条外:首版旧步骤4失败/2通过,等待actionability后6通过。 +审查发现WebKit hit-test与最终矩形不一致,补强数值边界后baseline六项全部失败, +默认路径main/legacy各六项通过;保留首版报告,不把hit-test等同于几何正确。 +共享TS helper先等待几何稳定再读取box;保留文字、opacity、缩略图、边界和 +全屏退出断言,不更改生产代码或超时。完整main/legacy各66通过,实际安装 +字节、新旧核心/插件组合及三桌面引擎均有报告;原run精确调度不能重放。 +严格TS、专项lint、根lint(0错误/1既有warning)、4项runner测试、工具链通过。 +见[变更](changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md)和 +[证据](baselines/chapter-hover-validation.json)。258项:189 done、19 doing、50 todo。 +CHAPTER-FULLSCREEN-GEOMETRY-01按测试同步关闭;CHAPTER-TIMING-01、真机及 +Chapter-05/06仍未完成。没有远端CI、push或发布;本任务独立本地提交后审计。 + ## CI-BROWSER-01 源码/安装浏览器范围隔离完成 复现全局安装map使HLS源码用例beforeAll直接失败;新source入口清除继承map后实际 diff --git a/refactor/risk-table.md b/refactor/risk-table.md index fb6ccbd20..5311959b9 100644 --- a/refactor/risk-table.md +++ b/refactor/risk-table.md @@ -271,4 +271,4 @@ | JASSUB-RENDER-RESOURCES-01 | resolved / 已复现 | Main-thread async rendering leaves received ImageBitmaps open when native canvas operations throw | PKG-JASSUB-10 | | ENG-PERF-OBS-01 | resolved / 已复现 | A nominal 350 ms resource observation can record only 349 ms and fail the required minimum | ENG-13 | | VCONSOLE-LIFECYCLE-01 | resolved / 已复现 | vConsole delayed log, panel and scroller work outlives its owner | SITE-07, SITE-05, SITE-VCONSOLE-01 | -| CHAPTER-FULLSCREEN-GEOMETRY-01 | open / 已复现 | Chromium 原生全屏后章节 hover 标题曾出现后又隐藏 | PKG-CHAPTER-05, PKG-CHAPTER-HOVER-01 | +| CHAPTER-FULLSCREEN-GEOMETRY-01 | resolved / 已复现 | Chromium 原生全屏后章节 hover 标题曾出现后又隐藏 | PKG-CHAPTER-05, PKG-CHAPTER-HOVER-01 | diff --git a/refactor/risks.json b/refactor/risks.json index 728660614..205d52cec 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -6198,7 +6198,7 @@ "id": "CHAPTER-FULLSCREEN-GEOMETRY-01", "title": "Chromium 原生全屏后章节 hover 标题曾出现后又隐藏", "confirmation": "reproduced", - "status": "open", + "status": "resolved", "owners": [ "PKG-CHAPTER-05", "PKG-CHAPTER-HOVER-01" @@ -6210,7 +6210,14 @@ ], "compatibleResolution": "核对原生全屏布局/controls transition 与鼠标坐标的时序;根据实际根因修复测试同步或生产实现,保持旧接口。", "closureCriteria": "保留原失败并取得可解释的旧红新绿/稳定真实浏览器证据,不用重试、延时扩大或移除 opacity 断言关闭。", - "workspaceState": "Installed 225-case run with two workers: 224 pass, published 5.4.0 core + candidate Chapter Chromium fullscreen case fails at hoverChapter opacity. Text assertion initially passes, opacity falls from 0.279 to 0 and title clears. Trace mouse=(640,665.9921875); screenshot is fullscreen1280x720. Geometry transition is a hypothesis; production defect versus test synchronization remains unverified." + "workspaceState": "Shared typed hover helper waits for actionability before coordinate sampling. Production package bytes are unchanged; report-bound old/new core/plugin combinations cover main and legacy on three desktop engines. Physical devices and Chapter-05/06 acceptance remain open.", + "resolutionEvidence": [ + "refactor/changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md", + "refactor/baselines/chapter-hover-validation.json", + "test/browser/chapter-hover.ts", + "test/browser/chapter-hover.spec.js" + ], + "resolutionRationale": "The immediate bounding-box read permits stale pointer coordinates while progress transforms move. Initial hit-test control gives 4 failures/2 passes before and 6 passes after waiting for locator stability. WebKit hit tests disagree with the final rectangle, so additional explicit bounds assertions were added: baseline 6 failures, stable main/legacy 6 passes each. Complete main/legacy chapter combination suites retain title, opacity, thumbnails and fullscreen assertions (66 passes each). This closes the identified test synchronization race, not all possible production fullscreen defects or CHAPTER-TIMING-01. Original 224/1 failure remains archived; its exact scheduler cannot be replayed." } ] } diff --git a/refactor/tasks.json b/refactor/tasks.json index 6b66ff158..b667357c4 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -5381,13 +5381,13 @@ "PKG-CHAPTER-04", "CI-BROWSER-01" ], - "status": "todo", + "status": "done", "risk": "M", "deliverable": "原生全屏动态几何与指针时序复现、针对根因的修复和真实浏览器回归", "acceptance": "解释 Chromium 旧核心+新章节的 opacity 失败;保留标题、缩略图和全屏退出断言,不以重试或扩大等待掩盖;区分测试同步与生产缺陷", "evidence": [ - "changes/2026-09-14-CI-BROWSER-01-scopes.md", - "baselines/browser-scope-validation.json" + "changes/2026-09-14-PKG-CHAPTER-HOVER-01-geometry.md", + "baselines/chapter-hover-validation.json" ] } ] diff --git a/scripts/browser-validation/scope.ts b/scripts/browser-validation/scope.ts index f8e76119e..d7958d874 100644 --- a/scripts/browser-validation/scope.ts +++ b/scripts/browser-validation/scope.ts @@ -9,6 +9,7 @@ export const installedTests = [ 'initialization.spec.js', 'chapter.spec.js', 'chapter-combinations.spec.js', + 'chapter-hover.spec.js', 'ambilight-lifecycle.spec.js', 'ambilight-proxy.spec.js', 'canvas-lifecycle.spec.js', diff --git a/scripts/tsconfig.docs.json b/scripts/tsconfig.docs.json index a39c4eb2f..8e96574ed 100644 --- a/scripts/tsconfig.docs.json +++ b/scripts/tsconfig.docs.json @@ -30,6 +30,7 @@ "build-docs.js", "../packages/artplayer-vitepress/build/**/*.ts", "build-vconsole.mjs", - "browser-validation/**/*.ts" + "browser-validation/**/*.ts", + "../test/browser/chapter-hover.ts" ] } diff --git a/test/browser/README.md b/test/browser/README.md index ccfce263e..de332a768 100644 --- a/test/browser/README.md +++ b/test/browser/README.md @@ -1,5 +1,25 @@ # Browser regression entry +Chapter pointer geometry is shared by `chapter-hover.ts` and the combination +suite. It awaits locator actionability with `hover({ trial: true })` before +reading coordinates; `art.fullscreen` can become true while controls still move. +The motion regression enters native fullscreen and adds a controlled 600ms Web +Animation to reproduce stale coordinates. It checks the final hit target, chapter +text and opacity without changing production CSS or increasing assertion timeouts. +It also requires pointer x/y to lie within the final inner rectangle; WebKit's +initial hit-test alone reported a hit outside that recorded rectangle. +Use `ARTPLAYER_CHAPTER_HOVER_BASELINE=1` to rerun the old immediate-read path; +this diagnostic intentionally fails on affected engines and is not an acceptance +mode. Both files belong to source and installed scopes. For installed runs: + +```sh +# Set ARTPLAYER_BROWSER_ARTIFACTS to a verified installation map first. +yarn test:browser:installed test/browser/chapter-combinations.spec.js test/browser/chapter-hover.spec.js --workers=2 +``` + +See `refactor/baselines/chapter-hover-validation.json` for the original failure, +controlled red/green observations and actual execution limits. + CI uses `yarn test:browser:source` for the complete source suite and `yarn test:browser:installed` for the reviewed installed-package subset. The source launcher clears inherited ARTPLAYER_BROWSER_ARTIFACTS; the installed launcher and diff --git a/test/browser/chapter-combinations.spec.js b/test/browser/chapter-combinations.spec.js index 1dc2884e1..3bffca5a3 100644 --- a/test/browser/chapter-combinations.spec.js +++ b/test/browser/chapter-combinations.spec.js @@ -1,4 +1,5 @@ import { devices } from '@playwright/test' +import { hoverProgress } from './chapter-hover.ts' import { expect, test } from './fixtures.js' async function openChapter(page, core, chapter) { @@ -37,10 +38,7 @@ async function openChapter(page, core, chapter) { } async function hoverChapter(page, percentage, text) { - const inner = page.locator('.art-control-progress-inner') - const box = await inner.boundingBox() - const x = Math.round(box.x + box.width * percentage) - await page.mouse.move(x, box.y + box.height / 2) + const { box, x } = await hoverProgress(page, percentage) await expect(page.locator('.art-chapter-title')).toHaveText(text) await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1') await expect(page.locator('.art-control-thumbnails')).toBeVisible() diff --git a/test/browser/chapter-hover.spec.js b/test/browser/chapter-hover.spec.js new file mode 100644 index 000000000..c9e05abc1 --- /dev/null +++ b/test/browser/chapter-hover.spec.js @@ -0,0 +1,52 @@ +import { hoverProgress } from './chapter-hover.ts' +import { expect, test } from './fixtures.js' + +for (const core of ['published', 'candidate']) { + test(`${core}: chapter hover waits for a moving fullscreen progress bar`, async ({ page }, testInfo) => { + await page.goto(`/test/player.html?core=${core}&chapter=candidate`) + await page.evaluate(() => { + window.art = new window.Artplayer({ + container: '.player', + url: '/test/pattern.mp4', + muted: true, + fullscreen: true, + plugins: [window.artplayerPluginChapter({ chapters: [{ start: 0, end: 2, title: 'Opening' }, { start: 2, end: 6, title: 'Middle' }, { start: 6, end: Infinity, title: 'Ending' }] })], + }) + }) + await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true) + await page.locator('.art-control-fullscreen').click() + await expect.poll(() => page.evaluate(() => window.art.fullscreen)).toBe(true) + await page.evaluate(async () => { + window.progressAnimation = document.querySelector('.art-progress').animate([ + { transform: 'translateY(-60px)' }, + { transform: 'translateY(0)' }, + ], { duration: 600, easing: 'linear' }) + await window.progressAnimation.ready + }) + const pointer = await hoverProgress(page, 0.5) + await page.evaluate(() => window.progressAnimation.finished) + const layout = await page.evaluate(({ x, y }) => { + const element = document.querySelector('.art-control-progress-inner') + const rect = element.getBoundingClientRect() + return { x: rect.x, y: rect.y, width: rect.width, height: rect.height, pointerInside: !!document.elementFromPoint(x, y)?.closest('.art-progress'), title: document.querySelector('.art-chapter-title').textContent } + }, pointer) + await testInfo.attach('chapter-hover-motion', { contentType: 'application/json', body: JSON.stringify({ core, baseline: process.env.ARTPLAYER_CHAPTER_HOVER_BASELINE === '1', pointer, layout, motion: 'controlled native Web Animation after actual fullscreen; no media or hit-test mocks' }) }) + expect(layout.pointerInside).toBe(true) + expect(pointer.x).toBeGreaterThanOrEqual(layout.x) + expect(pointer.x).toBeLessThanOrEqual(layout.x + layout.width) + expect(pointer.y).toBeGreaterThanOrEqual(layout.y) + expect(pointer.y).toBeLessThanOrEqual(layout.y + layout.height) + await expect(page.locator('.art-chapter-title')).toHaveText('Middle') + await expect(page.locator('.art-chapter-title')).toHaveCSS('opacity', '1') + await page.locator('.art-control-fullscreen').click() + await expect.poll(() => page.evaluate(() => window.art.fullscreen)).toBe(false) + await page.evaluate(() => window.art.destroy()) + }) +} + +test.afterEach(async ({ page }) => { + await page.evaluate(() => { + if (window.art && !window.art.isDestroy) + window.art.destroy() + }) +}) diff --git a/test/browser/chapter-hover.ts b/test/browser/chapter-hover.ts new file mode 100644 index 000000000..e0b0a7603 --- /dev/null +++ b/test/browser/chapter-hover.ts @@ -0,0 +1,15 @@ +import type { Page } from '@playwright/test' +import assert from 'node:assert/strict' +import process from 'node:process' + +export async function hoverProgress(page: Page, percentage: number) { + const inner = page.locator('.art-control-progress-inner') + if (process.env.ARTPLAYER_CHAPTER_HOVER_BASELINE !== '1') + await inner.hover({ trial: true }) + const box = await inner.boundingBox() + assert(box, 'Chapter progress must have a visible box') + const x = Math.round(box.x + box.width * percentage) + const y = box.y + box.height / 2 + await page.mouse.move(x, y) + return { box, x, y } +}