test(chapter): [PKG-CHAPTER-HOVER-01] wait for stable fullscreen geometry

This commit is contained in:
Harvey Zhao committed 2026-09-14 18:28:07 +08:00
1 parent 492e58a345
commit d89728c0a6
14 files changed
+1797 -14

No files matched your search

+20
View File
@@ -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
+2 -4
View File
@@ -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()
+52
View File
@@ -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()
})
})
+15
View File
@@ -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 }
}