mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
215 lines
8.3 KiB
JavaScript
215 lines
8.3 KiB
JavaScript
import { expect, test } from './fixtures.js'
|
|
|
|
async function setup(page, core, crossOrigin = false, control = false) {
|
|
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
|
await page.evaluate(({ crossOrigin, control }) => {
|
|
const url = new URL('/test/pattern.mp4', location.href)
|
|
if (crossOrigin)
|
|
url.hostname = 'localhost'
|
|
window.art = new window.Artplayer({ container: '.player', url: url.href, muted: true, screenshot: control })
|
|
}, { crossOrigin, control })
|
|
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
|
await page.evaluate(() => window.art.play())
|
|
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
|
|
await page.evaluate(() => window.art.pause())
|
|
}
|
|
|
|
for (const core of ['published', 'candidate']) {
|
|
test(`${core}: captures decoded PNG pixels and consumer-owned Blob URLs`, async ({ page }) => {
|
|
await setup(page, core)
|
|
const result = await page.evaluate(async () => {
|
|
const art = window.art
|
|
const data = await art.getDataURL()
|
|
const url = await art.getBlobUrl()
|
|
const image = new Image()
|
|
image.src = data
|
|
await image.decode()
|
|
const canvas = document.createElement('canvas')
|
|
canvas.width = image.width
|
|
canvas.height = image.height
|
|
const context = canvas.getContext('2d')
|
|
context.drawImage(image, 0, 0)
|
|
const pixels = context.getImageData(0, 0, image.width, image.height).data
|
|
const colors = new Set()
|
|
for (let index = 0; index < pixels.length; index += 4)
|
|
colors.add(`${pixels[index]},${pixels[index + 1]},${pixels[index + 2]}`)
|
|
const expected = [art.video.videoWidth, art.video.videoHeight]
|
|
const descriptors = ['getDataURL', 'getBlobUrl', 'screenshot'].map((name) => {
|
|
const d = Object.getOwnPropertyDescriptor(art, name)
|
|
return [d.writable, d.configurable, d.enumerable]
|
|
})
|
|
art.destroy()
|
|
const blob = await (await fetch(url)).blob()
|
|
URL.revokeObjectURL(url)
|
|
return { png: data.startsWith('data:image/png;base64,'), dimensions: [image.width, image.height], expected, colors: colors.size, blobType: blob.type, blobSize: blob.size, descriptors }
|
|
})
|
|
expect(result.png).toBe(true)
|
|
expect(result.dimensions).toEqual(result.expected)
|
|
expect(result.dimensions[0]).toBeGreaterThan(0)
|
|
expect(result.colors).toBeGreaterThan(10)
|
|
expect(result.blobType).toBe('image/png')
|
|
expect(result.blobSize).toBeGreaterThan(100)
|
|
expect(result.descriptors).toEqual(Array.from({ length: 3 }, () => [false, false, false]))
|
|
})
|
|
|
|
test(`${core}: screenshot preserves download filename, event data and Promise result`, async ({ page }) => {
|
|
await setup(page, core)
|
|
const downloadPromise = page.waitForEvent('download')
|
|
const result = await page.evaluate(async () => {
|
|
const art = window.art
|
|
const events = []
|
|
art.on('screenshot', value => events.push(value))
|
|
const screenshot = art.screenshot
|
|
const value = await screenshot('chosen.png')
|
|
art.destroy()
|
|
return { count: events.length, same: events[0] === value, png: value.startsWith('data:image/png;base64,') }
|
|
})
|
|
const download = await downloadPromise
|
|
expect(download.suggestedFilename()).toBe('chosen.png.png')
|
|
expect(result).toEqual({ count: 1, same: true, png: true })
|
|
await download.delete()
|
|
})
|
|
|
|
test(`${core}: actual cross-origin video rejects capture with SecurityError and notice`, async ({ page }) => {
|
|
await setup(page, core, true)
|
|
const result = await page.evaluate(async () => {
|
|
const art = window.art
|
|
const errors = []
|
|
for (const name of ['getDataURL', 'getBlobUrl', 'screenshot']) {
|
|
try {
|
|
await art[name]()
|
|
errors.push('resolved')
|
|
}
|
|
catch (error) {
|
|
errors.push(error.name)
|
|
}
|
|
}
|
|
const result = { errors, notice: art.notice.show, text: art.template.$noticeInner.textContent.length }
|
|
art.destroy()
|
|
return result
|
|
})
|
|
expect(result.errors).toEqual(['SecurityError', 'SecurityError', 'SecurityError'])
|
|
expect(result.notice).toBe(true)
|
|
expect(result.text).toBeGreaterThan(0)
|
|
})
|
|
|
|
test(`${core}: source replacement does not download a stale captured frame`, async ({ page }) => {
|
|
await setup(page, core)
|
|
const result = await page.evaluate(async () => {
|
|
const art = window.art
|
|
let events = 0
|
|
let downloads = 0
|
|
const original = HTMLAnchorElement.prototype.click
|
|
HTMLAnchorElement.prototype.click = () => {
|
|
downloads++
|
|
}
|
|
art.on('screenshot', () => {
|
|
events++
|
|
})
|
|
try {
|
|
const promise = art.screenshot()
|
|
art.url = '/test/pattern.mp4?replacement=1'
|
|
const result = { png: (await promise).startsWith('data:image/png;base64,'), events, downloads }
|
|
art.destroy()
|
|
return result
|
|
}
|
|
finally {
|
|
HTMLAnchorElement.prototype.click = original
|
|
}
|
|
})
|
|
const oldEffects = core === 'published' ? 1 : 0
|
|
expect(result).toEqual({ png: true, events: oldEffects, downloads: oldEffects })
|
|
})
|
|
|
|
test(`${core}: screenshot toolbar rejection has exact historical error accounting`, async ({ page }, testInfo) => {
|
|
await setup(page, core, false, true)
|
|
await page.evaluate((core) => {
|
|
window.captureFailures = []
|
|
const failure = new Error('controlled screenshot draw failure')
|
|
const original = HTMLCanvasElement.prototype.toDataURL
|
|
window.restoreCapture = () => {
|
|
HTMLCanvasElement.prototype.toDataURL = original
|
|
}
|
|
HTMLCanvasElement.prototype.toDataURL = () => {
|
|
throw failure
|
|
}
|
|
if (core === 'published') {
|
|
window.addEventListener('unhandledrejection', (event) => {
|
|
if (event.reason === failure) {
|
|
window.captureFailures.push(event.reason.message)
|
|
event.preventDefault()
|
|
}
|
|
})
|
|
}
|
|
window.art.notice.show = false
|
|
}, core)
|
|
await page.locator('.art-control-screenshot').click()
|
|
await expect.poll(() => page.evaluate(() => window.art.template.$noticeInner.textContent)).toBe('controlled screenshot draw failure')
|
|
if (core === 'published')
|
|
await expect.poll(() => page.evaluate(() => window.captureFailures.length)).toBe(1)
|
|
const known = await page.evaluate(() => {
|
|
window.restoreCapture()
|
|
window.art.destroy()
|
|
return window.captureFailures
|
|
})
|
|
expect(known).toEqual(core === 'published' ? ['controlled screenshot draw failure'] : [])
|
|
await testInfo.attach('known-screenshot-rejections', { contentType: 'application/json', body: JSON.stringify(known) })
|
|
})
|
|
}
|
|
|
|
test('candidate: null toBlob callback rejects and reports without a page error', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
expect(await page.evaluate(async () => {
|
|
const prototype = HTMLCanvasElement.prototype
|
|
const original = prototype.toBlob
|
|
prototype.toBlob = function (callback) {
|
|
queueMicrotask(() => callback(null))
|
|
}
|
|
try {
|
|
await window.art.getBlobUrl()
|
|
return 'resolved'
|
|
}
|
|
catch (error) {
|
|
return error.message
|
|
}
|
|
finally {
|
|
prototype.toBlob = original
|
|
window.art.destroy()
|
|
}
|
|
})).toBe('Unable to encode screenshot blob')
|
|
})
|
|
|
|
test('candidate: destroy during screenshot await suppresses the queued download and event', async ({ page }) => {
|
|
await setup(page, 'candidate')
|
|
expect(await page.evaluate(async () => {
|
|
const art = window.art
|
|
let events = 0
|
|
let downloads = 0
|
|
const original = HTMLAnchorElement.prototype.click
|
|
HTMLAnchorElement.prototype.click = () => {
|
|
downloads++
|
|
}
|
|
art.on('screenshot', () => {
|
|
events++
|
|
})
|
|
try {
|
|
const promise = art.screenshot()
|
|
art.destroy()
|
|
return { png: (await promise).startsWith('data:image/png;base64,'), events, downloads }
|
|
}
|
|
finally {
|
|
HTMLAnchorElement.prototype.click = original
|
|
}
|
|
})).toEqual({ png: true, events: 0, downloads: 0 })
|
|
})
|
|
|
|
test('candidate: cross-origin screenshot toolbar handles its internal rejected Promise', async ({ page }) => {
|
|
await setup(page, 'candidate', true, true)
|
|
await page.evaluate(() => {
|
|
window.art.notice.show = false
|
|
})
|
|
await page.locator('.art-control-screenshot').click()
|
|
await expect.poll(() => page.evaluate(() => window.art.notice.show)).toBe(true)
|
|
await page.evaluate(() => window.art.destroy())
|
|
})
|