Files
evening_detective_screen/scripts/smoke.cjs
T
2026-10-17 19:08:09 +07:00

248 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
let targetDisplay
let isSyntheticDisplay = false
const boundsBeforeFullscreen = new Map()
const fullscreenEntered = new Set()
app.on('browser-window-created', (_event, window) => {
window.on('enter-full-screen', () => fullscreenEntered.add(window.id))
let requestedBounds
const setBounds = window.setBounds.bind(window)
window.setBounds = (bounds, ...args) => {
requestedBounds = { ...bounds }
return setBounds(bounds, ...args)
}
const setFullScreen = window.setFullScreen.bind(window)
window.setFullScreen = (flag) => {
if (flag) boundsBeforeFullscreen.set(window.id, requestedBounds)
return setFullScreen(flag)
}
})
app.whenReady().then(() => {
const original = screen.getAllDisplays.bind(screen)
const displays = original()
targetDisplay = displays.find((display) => display.id !== screen.getPrimaryDisplay().id)
if (!targetDisplay) {
isSyntheticDisplay = true
targetDisplay = {
...displays[0],
id: 424242,
label: 'Synthetic test display',
bounds: { x: 1280, y: 0, width: 1280, height: 720 },
size: { width: 1280, height: 720 },
}
screen.getAllDisplays = () => [...original(), targetDisplay]
}
})
let offlinePackage
const seeded = (async () => {
const { PackageStore } = require(root + '/dist-electron/electron/package-store.js')
const { buildScreenPackage } = require(root + '/dist-electron/electron/screen-package.js')
const bytes = require('node:fs').readFileSync(root + '/fixtures/audio.wav')
const pkg = buildScreenPackage('https://offline.invalid', 7, {
game: {
game: {
ID: 7,
name: 'Offline smoke',
scenario: { story: { introduction: { audio: 'audio.wav' } } },
},
},
plan: {},
music: {},
})
const store = new PackageStore(require('node:path').join(app.getPath('userData'), 'packages'), {
fetch: async () =>
new Response(bytes, {
headers: { 'Content-Type': 'audio/wav', 'Content-Length': String(bytes.length) },
}),
})
await store.initialize()
offlinePackage = await store.download(pkg)
})()
seeded.catch((error) => {
console.error('PACKAGE_SEED_FAIL', error)
app.exit(1)
})
// Регистрация протокола выполняется до ready; открытие хранилища ждёт seed.
const whenReady = app.whenReady.bind(app)
app.whenReady = () => whenReady().then(() => seeded)
require(root + '/dist-electron/electron/main.js')
app.whenReady = whenReady
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)
await seeded
const asset = offlinePackage.files[0]
const localURL = `screen-app://app/packages/${offlinePackage.key}/${asset.id}`
const offline = await evaluate(`(async () => {
const response = await fetch(${JSON.stringify(localURL)}, { headers: { Range: 'bytes=0-43' } })
const size = (await response.arrayBuffer()).byteLength
const audio = new Audio(${JSON.stringify(localURL)})
await new Promise((resolve, reject) => { audio.onloadedmetadata = resolve; audio.onerror = () => reject(new Error('Offline audio failed')) })
await audio.play()
const playing = !audio.paused
const duration = audio.duration
audio.pause()
return { status: response.status, size, duration, playing }
})()`)
assert.equal(offline.status, 206)
assert.equal(offline.size, 44)
assert.ok(offline.duration > 0)
assert.equal(offline.playing, true)
assert.equal(await evaluate('typeof window.require'), 'undefined')
await evaluate(`window.screenBridge.show(${targetDisplay.id})`)
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',
)
if (process.platform === 'linux')
assert.equal(app.commandLine.getSwitchValue('ozone-platform'), 'x11')
await poll(() => boundsBeforeFullscreen.has(output.id), 'output fullscreen requested')
assert.deepEqual(boundsBeforeFullscreen.get(output.id), targetDisplay.bounds)
if (!isSyntheticDisplay) {
await poll(() => fullscreenEntered.has(output.id), 'output entered fullscreen')
assert.ok(output.isFullScreen())
assert.equal(screen.getDisplayMatching(output.getBounds()).id, targetDisplay.id)
}
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)
}
await evaluate(
`Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('Пуск с начала')).click()`,
)
await poll(async () => {
const state = await evaluate('window.screenBridge.state()')
if (state.status === 'error') throw new Error(state.error)
return state.status === 'playing'
}, 'computer UI play')
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})`,
),
)
assert.equal(
await evaluate("document.body.textContent.includes('Телефон по локальной сети')"),
false,
)
assert.deepEqual(Object.keys(await evaluate('window.screenBridge.host()')), ['displays'])
assert.equal(await evaluate('typeof window.screenBridge.lan'), 'undefined')
assert.equal(await evaluate('typeof window.screenBridge.approve'), 'undefined')
output.close()
await evaluate(`window.screenBridge.show(${targetDisplay.id})`)
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',
)
await poll(() => boundsBeforeFullscreen.has(reopened.id), 'reopened fullscreen requested')
assert.deepEqual(boundsBeforeFullscreen.get(reopened.id), targetDisplay.bounds)
if (!isSyntheticDisplay) {
await poll(() => fullscreenEntered.has(reopened.id), 'reopened entered fullscreen')
assert.ok(reopened.isFullScreen())
assert.equal(screen.getDisplayMatching(reopened.getBounds()).id, targetDisplay.id)
}
assert.equal(
await reopened.webContents.executeJavaScript('document.querySelector("video").paused'),
true,
)
console.log(
'SCREEN_SMOKE_PASS',
JSON.stringify({
range,
offlinePackage: offline,
display: {
id: targetDisplay.id,
bounds: targetDisplay.bounds,
synthetic: isSyntheticDisplay,
},
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)