Files
ArtPlayer/test/browser/media-events.spec.js
T

132 lines
6.5 KiB
JavaScript

import { expect, test } from './fixtures.js'
for (const core of ['published', 'candidate']) {
test(`${core}: HTTP failure retries the updated option URL with the original event and one ready`, async ({ page, request }, testInfo) => {
const caseId = encodeURIComponent(testInfo.testId)
await page.goto(`/test/player.html?core=${core}&chapter=published`)
await page.evaluate((id) => {
window.Artplayer.RECONNECT_SLEEP_TIME = 0
window.Artplayer.RECONNECT_TIME_MAX = 1
window.createPlayer(`/test/fail.mp4?case=${id}`)
const { art } = window
window.recovery = { attempts: [], ready: 0, identities: [], order: [] }
let original
art.on('video:error', (event) => {
original = event
window.recovery.order.push('native-error')
art.option.url = `/test/pattern.mp4?case=${id}`
})
art.on('error', (event, attempt) => {
window.recovery.attempts.push(attempt)
window.recovery.identities.push(event === original && event.target === art.video)
window.recovery.order.push('retry')
})
art.on('ready', () => {
window.recovery.ready++
window.recovery.order.push('ready')
})
}, caseId)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
const result = await page.evaluate(() => ({ ...window.recovery, path: new URL(window.art.video.currentSrc).pathname, duration: window.art.duration, mediaError: window.art.video.error?.code || 0 }))
expect(result.attempts).toEqual([1])
expect(result.identities).toEqual([true])
expect(result.ready).toBe(1)
expect(result.order).toEqual(['native-error', 'retry', 'ready'])
expect(result.path).toBe('/test/pattern.mp4')
expect(result.duration).toBeGreaterThan(0)
expect(result.mediaError).toBe(0)
const requests = await (await request.get(`/test/requests.json?case=${caseId}`)).json()
expect(requests.some(item => item.path === '/test/fail.mp4')).toBe(true)
expect(requests.some(item => item.path === '/test/pattern.mp4')).toBe(true)
await testInfo.attach('reconnect-http', { body: JSON.stringify({ result, requests }), contentType: 'application/json' })
await page.evaluate(() => window.art.destroy())
})
test(`${core}: replacing a failed source cancels candidate retries`, async ({ page }, testInfo) => {
const caseId = encodeURIComponent(testInfo.testId)
await page.goto(`/test/player.html?core=${core}&chapter=published`)
await page.evaluate((id) => {
window.Artplayer.RECONNECT_SLEEP_TIME = 0
window.Artplayer.RECONNECT_TIME_MAX = 1
window.createPlayer(`/test/fail.mp4?case=${id}`)
window.retries = 0
window.art.on('error', () => window.retries++)
window.art.once('video:error', () => {
window.art.url = `/test/pattern.mp4?case=${id}`
})
}, caseId)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
// The zero-delay retry's timer queue is observed after the replacement canplay.
const retries = await page.evaluate(async () => {
await new Promise(resolve => setTimeout(resolve, 0))
return window.retries
})
expect(retries).toBe(core === 'candidate' ? 0 : 1)
expect(await page.evaluate(() => window.art.video.error)).toBeNull()
await page.evaluate(() => window.art.destroy())
})
}
test('candidate: rejected real-media switch still allows current-source reconnect', async ({ page }, testInfo) => {
const caseId = encodeURIComponent(testInfo.testId)
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.evaluate(id => window.createPlayer(`/test/pattern.mp4?initial=${id}`), caseId)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.evaluate((id) => {
const { art, Artplayer } = window
Artplayer.RECONNECT_SLEEP_TIME = 0
Artplayer.RECONNECT_TIME_MAX = 1
window.switchRecovery = { rejected: false, sameError: false, attempts: [], restart: [] }
let original
art.on('video:error', (event) => {
original = event
art.option.url = `/test/pattern.mp4?recovered=${id}`
})
art.on('error', (event, count) => window.switchRecovery.attempts.push([event === original, count]))
art.on('restart', url => window.switchRecovery.restart.push(url))
art.switchUrl(`/test/fail.mp4?case=${id}`).catch((error) => {
window.switchRecovery.rejected = true
window.switchRecovery.sameError = error === original
})
}, caseId)
await expect.poll(() => page.evaluate(() => window.switchRecovery.restart.length)).toBe(1)
expect(await page.evaluate(() => window.switchRecovery)).toEqual({ rejected: true, sameError: true, attempts: [[true, 1]], restart: [`/test/pattern.mp4?recovered=${caseId}`] })
expect(await page.evaluate(() => window.art.video.error)).toBeNull()
await page.evaluate(() => window.art.destroy())
})
for (const core of ['published', 'candidate']) {
test(`${core}: destroying from first-ready controls prevents a candidate ghost ready event`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const result = await page.evaluate(() => {
const art = new window.Artplayer({ container: '.player', url: '' })
let ready = 0
art.on('ready', () => ready++)
art.on('control', () => art.destroy(false))
art.emit('video:canplay')
return { ready, isReady: art.isReady, isDestroy: art.isDestroy, instances: window.Artplayer.instances.length }
})
expect(result).toEqual({ ready: core === 'candidate' ? 0 : 1, isReady: core !== 'candidate', isDestroy: true, instances: 0 })
})
}
test.describe('mobile UA metadata path (not physical-device validation)', () => {
test.use({ userAgent: 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36' })
for (const core of ['published', 'candidate']) {
test(`${core}: metadata emits resize before mobile visibility changes`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const order = await page.evaluate(() => {
const art = new window.Artplayer({ container: '.player', url: '' })
const seen = []
art.on('resize', () => seen.push('resize'))
art.on('control', value => seen.push(['control', value]))
art.on('video:loadedmetadata', () => seen.push('metadata'))
art.emit('video:loadedmetadata')
art.destroy()
return seen
})
expect(order).toEqual(['resize', ['control', true], 'metadata'])
})
}
})