up
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
const { mkdirSync } = require('node:fs')
|
||||
const { spawnSync } = require('node:child_process')
|
||||
mkdirSync('fixtures', { recursive: true })
|
||||
const jobs = [
|
||||
[
|
||||
'-f',
|
||||
'lavfi',
|
||||
'-i',
|
||||
'sine=frequency=1000:sample_rate=48000:duration=60',
|
||||
'-af',
|
||||
'volume=0.1',
|
||||
'-c:a',
|
||||
'pcm_s16le',
|
||||
'fixtures/audio.wav',
|
||||
],
|
||||
[
|
||||
'-f',
|
||||
'lavfi',
|
||||
'-i',
|
||||
'testsrc2=size=1280x720:rate=30:duration=60',
|
||||
'-f',
|
||||
'lavfi',
|
||||
'-i',
|
||||
'sine=frequency=1000:sample_rate=48000:duration=60',
|
||||
'-af',
|
||||
'volume=0.1',
|
||||
'-c:v',
|
||||
'libx264',
|
||||
'-preset',
|
||||
'fast',
|
||||
'-pix_fmt',
|
||||
'yuv420p',
|
||||
'-c:a',
|
||||
'aac',
|
||||
'-movflags',
|
||||
'+faststart',
|
||||
'-shortest',
|
||||
'fixtures/video.mp4',
|
||||
],
|
||||
]
|
||||
for (const args of jobs) {
|
||||
const result = spawnSync('ffmpeg', ['-hide_banner', '-loglevel', 'error', '-y', ...args], {
|
||||
stdio: 'inherit',
|
||||
})
|
||||
if (result.error)
|
||||
console.error('Установите ffmpeg с libx264 и добавьте в PATH:', result.error.message)
|
||||
if (result.error || result.status !== 0) process.exit(1)
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { app, BrowserWindow, screen } = require('electron')
|
||||
const root = require('node:path').resolve(__dirname, '..')
|
||||
app.setAppPath(root)
|
||||
app.setPath(
|
||||
'userData',
|
||||
require('node:fs').mkdtempSync(
|
||||
require('node:path').join(require('node:os').tmpdir(), 'screen-smoke-'),
|
||||
),
|
||||
)
|
||||
const delay = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
app.whenReady().then(() => {
|
||||
const original = screen.getAllDisplays.bind(screen)
|
||||
screen.getAllDisplays = () => [
|
||||
...original(),
|
||||
{
|
||||
...original()[0],
|
||||
id: 424242,
|
||||
label: 'Synthetic test display',
|
||||
bounds: { x: 1280, y: 0, width: 1280, height: 720 },
|
||||
size: { width: 1280, height: 720 },
|
||||
},
|
||||
]
|
||||
})
|
||||
require(root + '/dist-electron/electron/main.js')
|
||||
async function poll(check, label) {
|
||||
for (let i = 0; i < 150; i++) {
|
||||
const result = await check()
|
||||
if (result) return result
|
||||
await delay(100)
|
||||
}
|
||||
throw new Error('Timeout: ' + label)
|
||||
}
|
||||
app.whenReady().then(async () => {
|
||||
try {
|
||||
const control = await poll(() => BrowserWindow.getAllWindows()[0], 'control')
|
||||
control.webContents.on('console-message', (_event, details) =>
|
||||
console.log('renderer:', details.message),
|
||||
)
|
||||
await poll(
|
||||
async () =>
|
||||
!control.webContents.isLoading() &&
|
||||
(await control.webContents.executeJavaScript(
|
||||
'!!window.screenBridge && !!document.querySelector("h1")',
|
||||
)),
|
||||
'control UI',
|
||||
)
|
||||
const evaluate = (s) => control.webContents.executeJavaScript(s)
|
||||
assert.equal(await evaluate('typeof window.require'), 'undefined')
|
||||
await evaluate('window.screenBridge.show(424242)')
|
||||
const output = await poll(
|
||||
() => BrowserWindow.getAllWindows().find((w) => w !== control),
|
||||
'output',
|
||||
)
|
||||
output.webContents.on('console-message', (_event, details) =>
|
||||
console.log('output:', details.message),
|
||||
)
|
||||
await poll(
|
||||
async () =>
|
||||
!output.webContents.isLoading() &&
|
||||
(await output.webContents.executeJavaScript(
|
||||
'document.querySelector("video")?.readyState >= 1',
|
||||
)),
|
||||
'metadata',
|
||||
)
|
||||
const range = await output.webContents.executeJavaScript(
|
||||
`fetch('screen-app://app/media/video', {headers: {Range: 'bytes=100-199'}}).then(async r => ({status:r.status, size:(await r.arrayBuffer()).byteLength, range:r.headers.get('content-range')}))`,
|
||||
)
|
||||
assert.equal(range.status, 206)
|
||||
assert.equal(range.size, 100)
|
||||
const command = async (id, action, media = 'audio', position = 0) => {
|
||||
await evaluate(
|
||||
`window.screenBridge.command(${JSON.stringify({ id, action, media, position })})`,
|
||||
)
|
||||
return poll(async () => {
|
||||
const s = await evaluate('window.screenBridge.state()')
|
||||
if (s.status === 'error') throw new Error(s.error)
|
||||
return s.status !== 'pending' && s.command.id === id && s
|
||||
}, id)
|
||||
}
|
||||
assert.equal((await command('audio-play', 'play')).status, 'playing')
|
||||
assert.equal((await command('audio-pause', 'pause')).status, 'paused')
|
||||
assert.equal((await command('audio-stop', 'stop')).status, 'stopped')
|
||||
assert.equal((await command('video-play', 'play', 'video')).status, 'playing')
|
||||
await command('video-seek', 'seek', 'video', 30)
|
||||
const position = await output.webContents.executeJavaScript(
|
||||
'document.querySelector("video").currentTime',
|
||||
)
|
||||
assert.ok(position >= 29.9 && position < 35, 'video seek actual position')
|
||||
await command('video-pause', 'pause', 'video')
|
||||
assert.equal(
|
||||
await output.webContents.executeJavaScript('document.querySelector("audio").paused'),
|
||||
true,
|
||||
)
|
||||
assert.equal(
|
||||
await output.webContents.executeJavaScript('document.querySelector("video").paused'),
|
||||
true,
|
||||
)
|
||||
await assert.rejects(
|
||||
output.webContents.executeJavaScript(
|
||||
`window.screenBridge.command({id:'forbidden', action:'play', media:'audio', position:0})`,
|
||||
),
|
||||
)
|
||||
const network = (await evaluate('window.screenBridge.host()')).lan[0]
|
||||
assert.ok(network, 'LAN interface available for browser smoke')
|
||||
await evaluate(`window.screenBridge.lan(${JSON.stringify(network.address)})`)
|
||||
const host = await evaluate('window.screenBridge.host()')
|
||||
const phone = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: true,
|
||||
backgroundThrottling: false,
|
||||
partition: 'screen-smoke-phone',
|
||||
},
|
||||
})
|
||||
assert.equal((await fetch(host.url)).status, 200)
|
||||
await phone.webContents.session.setProxy({ mode: 'direct' })
|
||||
await phone.loadURL(host.url)
|
||||
const phoneEval = (s) => phone.webContents.executeJavaScript(s)
|
||||
await poll(async () => await phoneEval('!!document.querySelector("input")'), 'phone UI')
|
||||
assert.equal(await phoneEval('typeof window.screenBridge'), 'undefined')
|
||||
await phoneEval(
|
||||
`(() => {const input = document.querySelector('input'); input.value = ${JSON.stringify(host.code)}; input.dispatchEvent(new Event('input', {bubbles:true}));})()`,
|
||||
)
|
||||
await delay(100)
|
||||
await phoneEval(
|
||||
`Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Подключиться')).click()`,
|
||||
)
|
||||
const pending = await poll(
|
||||
async () => (await evaluate('window.screenBridge.host()')).pending[0],
|
||||
'pair request from phone UI',
|
||||
)
|
||||
await evaluate(`window.screenBridge.approve(${JSON.stringify(pending)})`)
|
||||
await poll(
|
||||
async () =>
|
||||
await phoneEval(
|
||||
`!Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Пуск с начала')).disabled`,
|
||||
),
|
||||
'phone connected',
|
||||
)
|
||||
await phoneEval(
|
||||
`Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Пуск с начала')).click()`,
|
||||
)
|
||||
await poll(async () => {
|
||||
const state = await evaluate('window.screenBridge.state()')
|
||||
return state.status === 'playing' && state.samples.some((s) => s.origin === 'phone')
|
||||
}, 'phone play')
|
||||
await command('computer-after-phone', 'pause')
|
||||
await evaluate('window.screenBridge.lan(null)')
|
||||
await poll(
|
||||
async () => await phoneEval(`document.body.textContent.includes('Нет связи')`),
|
||||
'phone revoked',
|
||||
)
|
||||
phone.close()
|
||||
output.close()
|
||||
await evaluate('window.screenBridge.show(424242)')
|
||||
const reopened = await poll(
|
||||
() => BrowserWindow.getAllWindows().find((w) => w !== control),
|
||||
'reopened output',
|
||||
)
|
||||
await poll(
|
||||
async () =>
|
||||
!reopened.webContents.isLoading() &&
|
||||
(await reopened.webContents.executeJavaScript('!!document.querySelector("video")')),
|
||||
'reopened ready',
|
||||
)
|
||||
assert.equal(
|
||||
await reopened.webContents.executeJavaScript('document.querySelector("video").paused'),
|
||||
true,
|
||||
)
|
||||
console.log(
|
||||
'SCREEN_SMOKE_PASS',
|
||||
JSON.stringify({
|
||||
range,
|
||||
seek: position,
|
||||
samples: (await evaluate('window.screenBridge.state()')).samples,
|
||||
electron: process.versions.electron,
|
||||
}),
|
||||
)
|
||||
app.exit(0)
|
||||
} catch (error) {
|
||||
console.error('SCREEN_SMOKE_FAIL', error)
|
||||
app.exit(1)
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
console.error('Global timeout')
|
||||
app.exit(1)
|
||||
}, 45000)
|
||||
Reference in New Issue
Block a user