Files
ArtPlayer/test/browser/slider-updates.spec.js

99 lines
5.7 KiB
JavaScript

import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { expect, test } from './fixtures.js'
const baseline = process.env.ARTPLAYER_SLIDER_BASELINE
const before = baseline ? fs.readFileSync(baseline, 'utf8') : undefined
const attributes = ['aria-disabled', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']
for (let group = 0; group < 3; group++) {
test(`slider DOM updates and duplicate-event timing group ${group + 1}`, async ({ page }, testInfo) => {
const results = []
const variants = before ? (group % 2 ? ['candidate', 'before'] : ['before', 'candidate']) : ['candidate']
for (const variant of variants) {
if (variant === 'before')
await page.route('**/candidate/artplayer.js', route => route.fulfill({ contentType: 'text/javascript', body: before }))
else
await page.unroute('**/candidate/artplayer.js')
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.evaluate(() => window.createPlayer('/test/pattern.mp4'))
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.2)
await page.evaluate(() => window.art.pause())
const result = await page.evaluate((attributes) => {
const art = window.art
const element = document.querySelector('.art-control-progress')
const refresh = () => art.emit('video:timeupdate', new Event('timeupdate'))
refresh()
const initial = Object.fromEntries(attributes.map(name => [name, element.getAttribute(name)]))
// Keep MutationObserver out of the timing loop: these are synthetic duplicate events.
for (let i = 0; i < 300; i++)
refresh()
const timings = []
for (let sample = 0; sample < 5; sample++) {
const started = performance.now()
for (let i = 0; i < 1000; i++)
refresh()
timings.push(performance.now() - started)
}
const observer = new MutationObserver(() => {})
observer.observe(element, { attributes: true, attributeFilter: attributes })
let events = 0
const count = () => events++
art.on('video:timeupdate', count)
for (let i = 0; i < 100; i++)
refresh()
const duplicates = observer.takeRecords().map(record => record.attributeName)
// External DOM changes must still be repaired on the next refresh.
element.setAttribute('aria-valuemax', '999')
element.removeAttribute('aria-disabled')
observer.takeRecords()
refresh()
const repaired = observer.takeRecords().map(record => record.attributeName)
const final = Object.fromEntries(attributes.map(name => [name, element.getAttribute(name)]))
observer.disconnect()
art.off('video:timeupdate', count)
window.sliderNativeUpdates = { updates: [], events: 0 }
const nativeObserver = new MutationObserver(records => window.sliderNativeUpdates.updates.push(...records.map(record => record.attributeName)))
nativeObserver.observe(element, { attributes: true, attributeFilter: attributes })
const nativeCount = () => window.sliderNativeUpdates.events++
art.on('video:timeupdate', nativeCount)
window.finishSliderNativeUpdates = () => {
window.sliderNativeUpdates.updates.push(...nativeObserver.takeRecords().map(record => record.attributeName))
nativeObserver.disconnect()
art.off('video:timeupdate', nativeCount)
return { ...window.sliderNativeUpdates, value: Number(element.getAttribute('aria-valuenow')), time: art.currentTime }
}
return { initial, final, timings, duplicates, repaired, events, time: art.currentTime, duration: art.duration }
}, attributes)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(result.time + 0.8)
await page.evaluate(() => window.art.pause())
const native = await page.evaluate(() => window.finishSliderNativeUpdates())
const record = { group, variant, before: variant === 'before' ? { file: baseline, sha256: hash(before) } : null, result, native }
results.push(record)
try {
expect(result.events).toBe(101)
expect(result.final).toEqual(result.initial)
expect(Number(result.final['aria-valuemax'])).toBe(result.duration)
expect(result.duplicates).toHaveLength(variant === 'before' ? 500 : 0)
expect(result.repaired).toEqual(variant === 'before' ? attributes : ['aria-disabled', 'aria-valuemax'])
expect(native.events).toBeGreaterThan(0)
expect(Math.abs(native.value - native.time)).toBeLessThan(0.3)
if (variant === 'candidate') {
expect(native.updates).not.toContain('aria-disabled')
expect(native.updates).not.toContain('aria-valuemin')
expect(native.updates).not.toContain('aria-valuemax')
expect(native.updates).toContain('aria-valuenow')
}
}
finally {
await page.evaluate(() => window.art.destroy(false))
await testInfo.attach(`slider-updates-${variant}`, { contentType: 'application/json', body: JSON.stringify(record) })
}
}
await testInfo.attach('slider-paired-summary', { contentType: 'application/json', body: JSON.stringify({ results, limitation: 'Three alternating same-engine groups when a baseline artifact is supplied; 300-event warmup plus five samples of 1000 synthetic paused timeupdate events, without a MutationObserver in the timed loop. Separate DOM operation counts and actual native playback checks. Not total playback FPS, input latency or full BASE-06 initialization/resource acceptance.' }) })
})
}