mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-17 01:56:16 -08:00
fix(iframe): [PKG-IFRAME-05] checkpoint player and editor integration
This commit is contained in:
1 parent
047e2df945
commit
b35183c7af
15 files changed
+2547
-14
No files matched your search
@@ -0,0 +1,72 @@
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { iframeCandidate } from '../helpers/iframe.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const candidate = await iframeCandidate()
|
||||
|
||||
test('Iframe docs editor Run releases the previous tool and its pending requests', async ({ page }, testInfo) => {
|
||||
await page.route('https://pagead2.googlesyndication.com/**', route => route.fulfill({ contentType: 'text/javascript', body: '' }))
|
||||
await page.route('**/uncompiled/artplayer-tool-iframe/index.js', route => route.fulfill({
|
||||
contentType: 'text/javascript',
|
||||
body: `${candidate.code}\nif (window === window.top) {
|
||||
window.demoTools = [];
|
||||
window.ArtplayerToolIframe = class extends window.ArtplayerToolIframe {
|
||||
constructor(option) { super(option); window.demoTools.push(this); }
|
||||
};
|
||||
}`,
|
||||
}))
|
||||
await page.goto('/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe')
|
||||
await expect.poll(() => page.evaluate(() => window.demoTools?.length)).toBe(1)
|
||||
const firstFrame = page.frames().find(frame => frame.url().endsWith('/iframe.html'))
|
||||
await expect.poll(() => firstFrame.evaluate(() => window.Artplayer?.instances[0]?.isReady)).toBe(true)
|
||||
await page.evaluate(() => {
|
||||
window.demoTools[0].commit(() => {
|
||||
const art = window.Artplayer.instances[0]
|
||||
art.fullscreenWeb = true
|
||||
})
|
||||
})
|
||||
await expect(page.locator('iframe')).toHaveClass('fullscreenWeb')
|
||||
await firstFrame.locator('.art-video-player').hover()
|
||||
await firstFrame.locator('.art-control-fullscreenWeb').click()
|
||||
await expect(page.locator('iframe')).not.toHaveClass('fullscreenWeb')
|
||||
await page.evaluate(() => {
|
||||
window.pendingSettlement = 'pending'
|
||||
window.demoTools[0].commit((resolve) => {
|
||||
window.finishPendingDemo = () => resolve(1)
|
||||
}).then(() => window.pendingSettlement = 'resolved', () => window.pendingSettlement = 'rejected')
|
||||
})
|
||||
await page.locator('.run').click()
|
||||
await expect.poll(() => page.evaluate(() => window.demoTools.length)).toBe(2)
|
||||
await expect.poll(() => page.evaluate(() => window.demoTools[0].destroyed)).toBe(true)
|
||||
await expect.poll(() => page.evaluate(() => window.pendingSettlement)).toBe('rejected')
|
||||
await expect(page.locator('iframe')).not.toHaveClass('fullscreenWeb')
|
||||
const secondFrame = page.frames().find(frame => frame.url().endsWith('/iframe.html'))
|
||||
await expect.poll(() => secondFrame.evaluate(() => window.Artplayer?.instances[0]?.isReady)).toBe(true)
|
||||
await page.locator('.run').click()
|
||||
await expect.poll(() => page.evaluate(() => window.demoTools.length)).toBe(3)
|
||||
expect(await page.evaluate(() => window.demoTools.map(tool => tool.destroyed))).toEqual([true, true, false])
|
||||
await page.evaluate(() => {
|
||||
const model = window.monaco.editor.getModels().find(model => model.getLanguageId() === 'javascript')
|
||||
model.setValue(`
|
||||
document.querySelector('.artplayer-app').innerHTML = '<div class="first"></div><div class="second"></div>';
|
||||
window.parentPlayers = ['.first', '.second'].map(container => new Artplayer({ container, url: '/test/pattern.mp4' }));
|
||||
`)
|
||||
const url = new URL(location.href)
|
||||
url.searchParams.delete('example')
|
||||
history.replaceState(null, '', url)
|
||||
})
|
||||
await page.locator('.run').click()
|
||||
await expect.poll(() => page.evaluate(() => window.Artplayer.instances.length)).toBe(2)
|
||||
expect(await page.evaluate(() => window.demoTools.every(tool => tool.destroyed))).toBe(true)
|
||||
await page.evaluate(() => {
|
||||
window.previousPlayers = window.parentPlayers
|
||||
window.monaco.editor.getModels().find(model => model.getLanguageId() === 'javascript').setValue('window.exampleFinished = true')
|
||||
})
|
||||
await page.locator('.run').click()
|
||||
await expect.poll(() => page.evaluate(() => window.exampleFinished)).toBe(true)
|
||||
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
|
||||
expect(await page.evaluate(() => window.previousPlayers.every(art => art.isDestroy))).toBe(true)
|
||||
await testInfo.attach('iframe-editor', { contentType: 'application/json', body: JSON.stringify({ candidateSha256: hash(candidate.code), files: Object.fromEntries(['docs/index.html', 'docs/iframe.html', 'docs/assets/example/iframe.js', 'docs/assets/js/common.js'].map(file => [file, hash(fs.readFileSync(file))])), scope: 'Actual docs index, local Monaco and Run button. Only the iframe constructor is observed through a test subclass; external advertising script is stubbed in this test only. No physical-device/BFCache claim.' }) })
|
||||
await page.evaluate(() => window.demoTools.forEach(tool => tool.destroy()))
|
||||
})
|
||||
@@ -0,0 +1,135 @@
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { iframeCandidate, iframeHistorical } from '../helpers/iframe.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const candidate = await iframeCandidate()
|
||||
const published = (await iframeHistorical()).find(item => item.name === 'published-artplayer-plugin-iframe.js')
|
||||
const demo = fs.readFileSync('docs/assets/example/iframe.js', 'utf8')
|
||||
const childHtml = fs.readFileSync('docs/iframe.html', 'utf8')
|
||||
const bridges = [[candidate, candidate], [published, candidate], [candidate, published], [published, published]]
|
||||
|
||||
for (const core of ['published-4.5.9', 'published', 'candidate']) {
|
||||
for (const relation of ['same-origin', 'cross-origin']) {
|
||||
for (const [parentTool, childTool] of bridges) {
|
||||
test(`Iframe player ${core}/${relation}/${parentTool.name}/${childTool.name}`, async ({ page, request }, testInfo) => {
|
||||
const coreResponse = await request.get(`/${core}/artplayer.js`)
|
||||
expect(coreResponse.ok()).toBe(true)
|
||||
const coreBytes = await coreResponse.body()
|
||||
await page.addInitScript(() => {
|
||||
window.mediaEvents = []
|
||||
window.documentWitness = Math.random().toString(36)
|
||||
for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'playing', 'pause', 'seeking', 'seeked', 'emptied', 'error']) {
|
||||
addEventListener(name, (event) => {
|
||||
if (event.target instanceof HTMLVideoElement)
|
||||
window.mediaEvents.push({ type: name, source: event.target.currentSrc, time: event.target.currentTime, error: event.target.error?.code })
|
||||
}, true)
|
||||
}
|
||||
})
|
||||
const bridge = implementation => `${implementation.code}\n${implementation === published ? 'window.ArtplayerToolIframe = window.ArtplayerPluginIframe;' : ''}`
|
||||
await page.route('**/uncompiled/artplayer-tool-iframe/index.js', route => route.fulfill({ contentType: 'text/javascript', body: bridge(childTool) }))
|
||||
await page.route('**/uncompiled/artplayer/index.js', route => route.fulfill({ contentType: 'text/javascript', body: coreBytes }))
|
||||
await page.goto('/test/player.html?core=published')
|
||||
await page.addStyleTag({ url: '/assets/css/style.css' })
|
||||
await page.evaluate(() => {
|
||||
document.body.innerHTML = '<div class="artplayer-app" style="width:640px;height:360px"></div>'
|
||||
window.messages = []
|
||||
})
|
||||
await page.addScriptTag({ content: bridge(parentTool) })
|
||||
const parentOrigin = new URL(page.url()).origin
|
||||
const childOrigin = relation === 'cross-origin' ? parentOrigin.replace('127.0.0.1', 'localhost') : parentOrigin
|
||||
const url = `${childOrigin}/iframe.html`
|
||||
const adaptedDemo = demo.replace('url: \'/iframe.html\'', `url: '${url}'`)
|
||||
expect(adaptedDemo).not.toBe(demo)
|
||||
await page.addScriptTag({ content: `${adaptedDemo}\nwindow.demoTool = iframe; window.demoFrame = $iframe; const originalCallback = iframe.messageCallback; iframe.message(function (packet) { originalCallback.call(this, packet); window.messages.push(packet); });` })
|
||||
let child
|
||||
try {
|
||||
await expect.poll(() => page.frames().some(frame => frame.url() === url)).toBe(true)
|
||||
child = page.frames().find(frame => frame.url() === url)
|
||||
await expect.poll(() => child.evaluate(() => {
|
||||
const art = window.Artplayer?.instances[0]
|
||||
return Boolean(art && art.video.readyState >= 2 && art.video.videoWidth > 0)
|
||||
})).toBe(true)
|
||||
const initial = await page.evaluate(() => window.demoTool.commit(() => {
|
||||
const art = window.Artplayer.instances[0]
|
||||
art.muted = true
|
||||
return { version: window.Artplayer.version, width: art.video.videoWidth, height: art.video.videoHeight, duration: art.duration, instances: window.Artplayer.instances.length, source: art.video.currentSrc }
|
||||
}))
|
||||
expect(initial.instances).toBe(1)
|
||||
expect(initial.source).toContain('/assets/sample/video.mp4')
|
||||
expect(initial.width).toBeGreaterThan(0)
|
||||
const play = await page.evaluate(() => window.demoTool.commit((resolve) => {
|
||||
window.Artplayer.instances[0].play().then(() => {
|
||||
const result = { playing: !window.Artplayer.instances[0].video.paused }
|
||||
resolve(result)
|
||||
}).catch((error) => {
|
||||
const result = { error: error.message }
|
||||
resolve(result)
|
||||
})
|
||||
}))
|
||||
expect(play).toEqual({ playing: true })
|
||||
await expect.poll(() => child.evaluate(() => window.Artplayer.instances[0].currentTime)).toBeGreaterThan(0.15)
|
||||
await child.locator('.art-video-player').hover()
|
||||
await child.locator('.art-control-fullscreenWeb').click()
|
||||
await expect(page.locator('iframe')).toHaveClass('fullscreenWeb')
|
||||
const fullscreen = await page.locator('iframe').boundingBox()
|
||||
expect(fullscreen.width).toBe(page.viewportSize().width)
|
||||
expect(fullscreen.height).toBe(page.viewportSize().height)
|
||||
await child.locator('.art-video-player').hover()
|
||||
await child.locator('.art-control-fullscreenWeb').click()
|
||||
await expect(page.locator('iframe')).not.toHaveClass('fullscreenWeb')
|
||||
expect(await page.evaluate(() => window.messages.filter(packet => packet.type === 'fullscreenWeb').map(packet => packet.data))).toEqual([true, false])
|
||||
await page.evaluate(() => window.demoTool.commit(() => {
|
||||
const art = window.Artplayer.instances[0]
|
||||
art.pause()
|
||||
art.currentTime = 1
|
||||
art.playbackRate = 1.25
|
||||
}))
|
||||
await expect.poll(() => child.evaluate(() => window.Artplayer.instances[0].video.seeking)).toBe(false)
|
||||
const seek = await child.evaluate(() => ({ time: window.Artplayer.instances[0].currentTime, rate: window.Artplayer.instances[0].video.playbackRate, paused: window.Artplayer.instances[0].video.paused }))
|
||||
expect(seek.time).toBeCloseTo(1, 1)
|
||||
expect(seek.rate).toBe(1.25)
|
||||
expect(seek.paused).toBe(true)
|
||||
const switched = await page.evaluate(() => window.demoTool.commit((resolve) => {
|
||||
const art = window.Artplayer.instances[0]
|
||||
art.switchUrl('/test/pattern.mp4').then(() => {
|
||||
const result = { source: art.video.currentSrc, width: art.video.videoWidth }
|
||||
resolve(result)
|
||||
}).catch((error) => {
|
||||
const result = { error: error.message }
|
||||
resolve(result)
|
||||
})
|
||||
}))
|
||||
expect(switched.error).toBeUndefined()
|
||||
expect(switched.source).toContain('/test/pattern.mp4')
|
||||
await expect.poll(() => child.evaluate(() => window.Artplayer.instances[0].video.readyState >= 2)).toBe(true)
|
||||
const events = await child.evaluate(() => window.mediaEvents)
|
||||
expect(events.some(event => event.type === 'playing')).toBe(true)
|
||||
expect(events.some(event => event.type === 'seeked')).toBe(true)
|
||||
expect(events.filter(event => event.type === 'error')).toEqual([])
|
||||
const destroyed = await page.evaluate(() => window.demoTool.commit(() => {
|
||||
window.Artplayer.instances[0].destroy()
|
||||
return { instances: window.Artplayer.instances.length, players: document.querySelectorAll('.art-video-player').length }
|
||||
}))
|
||||
expect(destroyed).toEqual({ instances: 0, players: 0 })
|
||||
const afterDestroy = await page.evaluate(() => window.demoTool.commit(() => {
|
||||
return 17
|
||||
}))
|
||||
expect(afterDestroy).toBe(17)
|
||||
await testInfo.attach('iframe-player', { contentType: 'application/json', body: JSON.stringify({ core, coreSha256: hash(coreBytes), parent: { name: parentTool.name, sha256: hash(parentTool.code) }, child: { name: childTool.name, sha256: hash(childTool.code) }, relation, initial, seek, switched, destroyed, events, demoSha256: hash(demo), adaptedDemoSha256: hash(adaptedDemo), childHtmlSha256: hash(childHtml), scope: 'Actual docs iframe.html, parent example and CSS in a minimal parent harness; only source URL and explicit test-only old global-name aliases are adapted. Actual media plays/seeks/switches, fullscreenWeb control sends messages and both core/tool lifetimes are tested independently. No complete Monaco/device/BFCache claim.' }) })
|
||||
}
|
||||
finally {
|
||||
if (child && !child.isDetached()) {
|
||||
await child.evaluate(() => {
|
||||
for (const art of [...(window.Artplayer?.instances || [])]) art.destroy()
|
||||
})
|
||||
}
|
||||
await page.evaluate(() => {
|
||||
window.demoTool?.destroy()
|
||||
window.demoFrame?.remove()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,11 @@ const vastCoreMember = `package/${vastCore.manifest.main}`
|
||||
const vastCoreBytes = readMember(await ensureArchive(vastCore), vastCoreMember)
|
||||
assert.equal(hash(vastCoreBytes), vastCore.files[vastCoreMember], 'Historical VAST core differs from frozen release')
|
||||
add('/published-5.1.7/artplayer.js', vastCoreBytes, { kind: 'npm-release', version: vastCore.version, integrity: vastCore.integrity, member: vastCoreMember })
|
||||
const iframeCore = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/iframe-core.json'), 'utf8')).release
|
||||
const iframeCoreMember = `package/${iframeCore.manifest.main}`
|
||||
const iframeCoreBytes = readMember(await ensureArchive(iframeCore), iframeCoreMember)
|
||||
assert.equal(hash(iframeCoreBytes), iframeCore.files[iframeCoreMember], 'Historical Iframe core differs from frozen release')
|
||||
add('/published-4.5.9/artplayer.js', iframeCoreBytes, { kind: 'npm-release', version: iframeCore.version, integrity: iframeCore.integrity, member: iframeCoreMember })
|
||||
add('/test/audio-tone.m4a', fs.readFileSync(path.join(workspace, 'test/browser/media/audio-tone.m4a')), { kind: 'generated-media', file: 'test/browser/media/audio-tone.m4a' })
|
||||
|
||||
const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.css': 'text/css', '.mp4': 'video/mp4', '.m4a': 'audio/mp4', '.webm': 'video/webm', '.vtt': 'text/vtt', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg' }
|
||||
@@ -138,7 +143,7 @@ const server = http.createServer((req, res) => {
|
||||
if (url.pathname === '/test/player.html') {
|
||||
const core = url.searchParams.get('core') || 'candidate'
|
||||
const chapter = url.searchParams.get('chapter') || 'candidate'
|
||||
assert(['candidate', 'published', 'published-4.5.5', 'published-5.1.7'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
|
||||
assert(['candidate', 'published', 'published-4.5.5', 'published-4.5.9', 'published-5.1.7'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
|
||||
const html = fs.readFileSync(path.join(workspace, 'test/browser/player.html'), 'utf8')
|
||||
.replace('__CORE__', core)
|
||||
.replace('__CHAPTER__', chapter)
|
||||
|
||||
Reference in new issue
Block a user