const assert = require('node:assert/strict'); const { chromium, expect } = require('@playwright/test'); (async () => { const base = new URL(process.argv[2]); const sessionId = process.argv[3]; assert.equal(base.hostname, '127.0.0.1'); assert.match(sessionId, /^s-[a-f0-9]{32}$/); const browser = await chromium.launch({ executablePath: process.env.CHROMIUM_EXECUTABLE, args: ['--no-sandbox', '--disable-dev-shm-usage'], }); try { const page = await browser.newPage(); const errors = []; page.on('pageerror', error => errors.push(error.message)); await page.route('**/*', route => new URL(route.request().url()).origin === base.origin ? route.continue() : route.abort()); await page.addInitScript(() => { const NativeWebSocket = window.WebSocket; window.fixtureSockets = []; window.WebSocket = class extends NativeWebSocket { constructor(...args) { super(...args); window.fixtureSockets.push(this); } }; }); const text = () => page.evaluate(() => { const buffer = window.fixtureTerminal?.buffer.active; return buffer ? Array.from({ length: buffer.length }, (_, i) => buffer.getLine(i)?.translateToString()).join('\n') : ''; }); for (const width of [1440, 390]) { await page.setViewportSize({ width, height: 844 }); await page.goto(base.href); await page.getByRole('button', { name: 'Open terminal', exact: true }).click(); await expect.poll(text).toContain('D09-LINE-0000'); await expect.poll(text).toContain('D09-LINE-0099'); await expect(page.getByRole('combobox', { name: 'Terminal session' })).toHaveValue(sessionId); await page.locator('.xterm-helper-textarea').focus(); // No contiguous expected marker in the echoed command: prove execution. await page.keyboard.type(`printf 'NATIVE-%s-INPUT\\n' '${width}'`); await page.keyboard.press('Enter'); await expect.poll(text).toContain(`NATIVE-${width}-INPUT`); const before = await page.evaluate(() => window.fixtureSockets.length); await page.evaluate(() => window.fixtureSockets.at(-1).close()); await expect.poll(() => page.evaluate(() => window.fixtureSockets.length), { timeout: 8000 }).toBe(before + 1); await expect.poll(() => page.evaluate(() => window.fixtureSockets.at(-1).readyState)).toBe(1); // A retained old buffer must not make a failed reconnect look successful. await page.locator('.xterm-helper-textarea').focus(); await page.keyboard.type(`printf 'REJOIN-%s-DONE\\n' '${width}'`); await page.keyboard.press('Enter'); await expect.poll(text).toContain(`REJOIN-${width}-DONE`); assert.equal((await text()).split('D09-LINE-0000').length - 1, 1); await page.evaluate(() => window.fixtureTerminal.scrollToTop()); await expect(page.locator('.xterm-rows')).toContainText('D09-LINE-0000'); await page.screenshot({ path: `/out/actual-terminal-history-${width}.png` }); await page.getByRole('button', { name: 'Close', exact: true }).click(); const sessions = await page.evaluate(async () => { const response = await fetch('/api/terminal/sessions'); if (!response.ok) throw new Error(`session inventory failed: ${response.status}`); return response.json(); }); assert.equal(sessions.length, 1, 'browser reconnect must not create another session'); assert.equal(sessions[0].id, sessionId); } assert.deepEqual(errors, []); console.log('PASS real CLIPopup/xterm -> actual authenticated handler -> private tmux: typed command, history, same-session WebSocket reconnect and visible1440/390 history; synthetic owner bootstrap only'); } finally { await browser.close(); } })().catch(error => { console.error(error); process.exitCode = 1; });