| 1 | // Opt in only on a disposable interactive Windows runner. Real UIA and screen |
| 2 | // APIs operate on our own WinForms fixture; no user app or raw input is touched. |
| 3 | import { test } from 'node:test'; |
| 4 | import assert from 'node:assert/strict'; |
| 5 | import fs from 'node:fs'; |
| 6 | import os from 'node:os'; |
| 7 | import path from 'node:path'; |
| 8 | import crypto from 'node:crypto'; |
| 9 | import { spawn } from 'node:child_process'; |
| 10 | import { fileURLToPath } from 'node:url'; |
| 11 | import win32 from '../src/backends/win32.mjs'; |
| 12 | |
| 13 | test('Windows desktop: observe, set Unicode value, invoke and independently verify the selected window', { |
| 14 | skip: process.platform !== 'win32' || process.env.CU_WINDOWS_DESKTOP_TESTS !== '1', timeout: 90_000, |
| 15 | }, async t => { |
| 16 | const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cu-win-desktop-')); |
| 17 | const title = `CU fixture ${crypto.randomUUID()}`; |
| 18 | const receipt = path.join(dir, 'state.json'); |
| 19 | const child = spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-File', fileURLToPath(new URL('./fixtures/windows-desktop.ps1', import.meta.url)), title, receipt], { stdio: ['ignore', 'ignore', 'pipe'], windowsHide: false }); |
| 20 | let errors = ''; child.stderr.on('data', chunk => { errors += chunk; }); |
| 21 | const exited = new Promise(resolve => child.once('exit', resolve)); |
| 22 | t.after(async () => { if (child.exitCode === null) child.kill(); await exited; fs.rmSync(dir, { recursive: true, force: true }); }); |
| 23 | async function until(check) { |
| 24 | const deadline = Date.now() + 15_000; |
| 25 | while (Date.now() < deadline) { |
| 26 | let result; try { result = check(); } catch {} |
| 27 | if (result) return result; |
| 28 | assert.equal(child.exitCode, null, errors); |
| 29 | await new Promise(resolve => setTimeout(resolve, 100)); |
| 30 | } |
| 31 | assert.fail(`fixture deadline: ${errors}`); |
| 32 | } |
| 33 | const read = () => JSON.parse(fs.readFileSync(receipt, 'utf8')); |
| 34 | await until(() => read().text === 'initial'); |
| 35 | const backend = win32.create(); |
| 36 | const displays = await backend.list_displays(); |
| 37 | assert.ok(displays.length >= 1); |
| 38 | t.diagnostic(JSON.stringify({fixture:read(), windows:await backend.list_windows()})); |
| 39 | const state = await backend.get_app_state({ app_ref: { name: title }, detail: 'full' }); |
| 40 | const edit = state.elements.find(e => e.role === 'Edit'); |
| 41 | const button = state.elements.find(e => e.label === 'Apply fixture'); |
| 42 | assert.ok(edit, JSON.stringify(state)); assert.ok(button, JSON.stringify(state)); |
| 43 | assert.equal(edit.value, 'initial'); |
| 44 | const target = el => ({ ...el, app_ref: { name: title }, windowIndex: 0 }); |
| 45 | const text = "A漢😀 O'Brien"; |
| 46 | assert.equal((await backend.set_value({ target: target(edit), value: text })).verified, true); |
| 47 | await until(() => read().text === text); |
| 48 | await backend.perform_action({ target: target(button), action: 'Invoke' }); |
| 49 | await until(() => read().clicks === 1); |
| 50 | await assert.rejects(backend.set_value({ target: { ...target(edit), runtime_id: [123456789] }, value: 'wrong' }), /element_stale/); |
| 51 | await assert.rejects(backend.set_value({ target: { ...target(edit), window_runtime_id: [123456789] }, value: 'wrong' }), /element_stale/); |
| 52 | assert.equal(read().text, text); assert.equal(read().clicks, 1); |
| 53 | const shot = await backend.screenshot({ display: 1, path: path.join(dir, 'screen.png') }); |
| 54 | const png = fs.readFileSync(shot.file); |
| 55 | assert.equal(png.subarray(1, 4).toString(), 'PNG'); |
| 56 | assert.equal(png.readUInt32BE(16), shot.pixels.w); assert.equal(png.readUInt32BE(20), shot.pixels.h); |
| 57 | assert.deepEqual(shot.points, displays[0].points); |
| 58 | const region = [shot.points.x + 1, shot.points.y + 1, 20, 20]; |
| 59 | const crop = await backend.screenshot({ region, path: path.join(dir, 'crop.png') }); |
| 60 | assert.deepEqual(crop.points, { x: region[0], y: region[1], w: 20, h: 20 }); |
| 61 | t.diagnostic('Real WinForms UIA value/invoke/read-back, stale identity refusal, screen and region capture passed; no raw input or packaged Engine claim.'); |
| 62 | }); |
| 63 |