Files
ArtPlayer/test/browser/storage-performance.spec.js
T

114 lines
5.7 KiB
JavaScript

import { expect, test } from './fixtures.js'
for (let group = 0; group < 3; group++) {
test(`playback persistence measurements and fresh-state contracts group ${group + 1}`, async ({ page }, testInfo) => {
const runs = []
for (const core of group % 2 ? ['candidate', 'published'] : ['published', 'candidate']) {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
await page.evaluate(() => {
localStorage.setItem('artplayer_settings', JSON.stringify({ times: { another: 2 }, external: { retained: true }, volume: 0.7 }))
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true, autoPlayback: true, id: 'persistence-measurement' })
document.querySelector('#play').onclick = () => window.art.play()
})
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.playing && window.art.currentTime > 0.1)).toBe(true)
try {
const result = await page.evaluate(() => {
const art = window.art
const refresh = () => art.emit('video:timeupdate', new Event('timeupdate'))
for (let index = 0; index < 100; index++)
refresh()
// Timings run without replacing native Storage/JSON methods.
const samples = []
for (let sample = 0; sample < 5; sample++) {
const started = performance.now()
for (let index = 0; index < 100; index++)
refresh()
samples.push(performance.now() - started)
}
const original = { getItem: Storage.prototype.getItem, setItem: Storage.prototype.setItem, parse: JSON.parse, stringify: JSON.stringify }
const counts = { getItem: 0, setItem: 0, parse: 0, stringify: 0 }
Storage.prototype.getItem = function (...args) {
if (args[0] === art.storage.name)
counts.getItem++
return original.getItem.apply(this, args)
}
Storage.prototype.setItem = function (...args) {
if (args[0] === art.storage.name)
counts.setItem++
return original.setItem.apply(this, args)
}
JSON.parse = (...args) => {
counts.parse++
return original.parse(...args)
}
JSON.stringify = (...args) => {
counts.stringify++
return original.stringify(...args)
}
try {
for (let index = 0; index < 100; index++)
refresh()
}
finally {
Storage.prototype.getItem = original.getItem
Storage.prototype.setItem = original.setItem
JSON.parse = original.parse
JSON.stringify = original.stringify
}
// WebKit's native clock can advance between consecutive property reads.
const beforeRefresh = art.currentTime
refresh()
const afterRefresh = art.currentTime
const stored = art.storage.get()
const time = { beforeRefresh, afterRefresh }
const isolated = art.storage.get()
isolated.times.transient = 999
isolated.external.retained = false
const fresh = art.storage.get()
const calls = []
const get = art.storage.get
// A legal custom get can update unrelated settings between record read and save.
art.storage.get = function (key) {
calls.push(key ?? null)
const result = get.call(this, key)
if (key === 'times') {
const latest = get.call(this)
latest.external = { retained: true, revision: 'interleaved' }
localStorage.setItem(this.name, JSON.stringify(latest))
}
return result
}
try {
refresh()
}
finally {
art.storage.get = get
}
const merged = art.storage.get()
const pausedBefore = localStorage.getItem(art.storage.name)
art.pause()
refresh()
const pausedAfter = localStorage.getItem(art.storage.name)
return { samples, counts, time, stored, fresh, merged, calls, pausedBefore, pausedAfter }
})
runs.push({ core, group, result })
expect(result.stored.times['persistence-measurement']).toBeGreaterThanOrEqual(result.time.beforeRefresh)
expect(result.stored.times['persistence-measurement']).toBeLessThanOrEqual(result.time.afterRefresh)
expect(result.stored.times.another).toBe(2)
expect(result.stored.external).toEqual({ retained: true })
expect(result.fresh.times).not.toHaveProperty('transient')
expect(result.fresh.external).toEqual({ retained: true })
expect(result.merged.external).toEqual({ retained: true, revision: 'interleaved' })
expect(result.calls).toEqual(['times', null])
expect(result.pausedAfter).toBe(result.pausedBefore)
}
finally {
await page.evaluate(() => window.art.destroy(false))
await testInfo.attach(`persistence-${core}`, { contentType: 'application/json', body: JSON.stringify(runs.at(-1) || { core, group, failedBeforeMeasurements: true }) })
}
}
await testInfo.attach('persistence-paired-summary', { contentType: 'application/json', body: JSON.stringify({ runs, limitation: 'Actual video and native same-origin localStorage, followed by synthetic timeupdate dispatch while playing. 100 warmups and five 100-event timing samples without native method wrappers; a separate instrumented window reports Storage/JSON calls. A custom public get tests synchronous interleaving, not a concurrent multi-process storage race. No storage implementation or write cadence change is made.' }) })
})
}