192 lines
7.1 KiB
JavaScript
192 lines
7.1 KiB
JavaScript
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)
|