mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 20:49:41 +02:00
Full product rename across 109 files (~834 occurrences): - Env vars: CLAUDEMAN_* → CODEMAN_* - Data dirs: ~/.claudeman/ → ~/.codeman/, ~/claudeman-cases/ → ~/codeman-cases/ - tmux prefix: claudeman- → codeman- - localStorage: claudeman-* → codeman-* - Package/CLI: claudeman → codeman - GitHub repo: Ark0N/Claudeman → Ark0N/Codeman - systemd service: claudeman-web → codeman-web - Class: ClaudemanApp → CodemanApp Migration infrastructure for seamless transition: - state-store.ts: auto-migrates data directories on startup - tmux-manager.ts: dual-prefix detection (legacy claudeman- sessions) - app.js: localStorage key migration (preserves old keys) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
591 lines
24 KiB
JavaScript
591 lines
24 KiB
JavaScript
/**
|
|
* Local Echo Overlay — Basic operation tests.
|
|
*
|
|
* Tests the most fundamental user scenarios:
|
|
* 1. Type → switch tab → come back → backspace works
|
|
* 2. Type → switch tab → come back → space doesn't erase text
|
|
* 3. Type → switch tab → come back → backspace + new text works
|
|
* 4. Overlay position stays consistent across renders
|
|
* 5. Overlay doesn't shift left during Ink redraws
|
|
* 6. Type → switch → back → rapid backspace+retype
|
|
* 7. Overlay covers flushed canvas text properly
|
|
*
|
|
* Usage: node test/local-echo-basic-test.mjs
|
|
*/
|
|
import { chromium } from 'playwright';
|
|
|
|
const PORT = 3000;
|
|
const BASE = `http://localhost:${PORT}`;
|
|
const SCREENSHOT_DIR = '/tmp/local-echo-basic';
|
|
|
|
async function createSession(name) {
|
|
const resp = await fetch(`${BASE}/api/sessions`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ mode: 'claude', name })
|
|
});
|
|
if (!resp.ok) throw new Error(`Failed to create '${name}': ${resp.status}`);
|
|
const data = await resp.json();
|
|
return data.session?.id || data.id;
|
|
}
|
|
|
|
async function deleteSession(id) {
|
|
await fetch(`${BASE}/api/sessions/${id}`, { method: 'DELETE' }).catch(() => {});
|
|
}
|
|
|
|
async function waitForReady(id, ms = 15000) {
|
|
const start = Date.now();
|
|
while (Date.now() - start < ms) {
|
|
const r = await fetch(`${BASE}/api/sessions/${id}`);
|
|
if (r.ok) { const d = await r.json(); if (d.status === 'idle' || d.status === 'busy') return true; }
|
|
await new Promise(r => setTimeout(r, 300));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function getState(page) {
|
|
return page.evaluate(() => {
|
|
const app = window.app;
|
|
if (!app) return null;
|
|
const ov = app._localEchoOverlay;
|
|
const buf = app.terminal?.buffer?.active;
|
|
return {
|
|
visible: ov?.overlay?.style.display !== 'none',
|
|
text: ov?.pendingText || '',
|
|
flushed: ov?._flushedOffset || 0,
|
|
flushedMap: app._flushedOffsets ? Object.fromEntries(app._flushedOffsets) : {},
|
|
enabled: app._localEchoEnabled,
|
|
activeId: app.activeSessionId,
|
|
// Overlay position and content
|
|
overlayLeft: ov?.overlay?.style.left,
|
|
overlayTop: ov?.overlay?.style.top,
|
|
children: (() => {
|
|
if (!ov?.overlay) return [];
|
|
return Array.from(ov.overlay.children).map(c => ({
|
|
text: Array.from(c.querySelectorAll('span')).map(s => s.textContent).join(''),
|
|
left: c.style.left,
|
|
top: c.style.top,
|
|
width: c.style.width,
|
|
bgColor: c.style.backgroundColor,
|
|
spanCount: c.querySelectorAll('span').length,
|
|
}));
|
|
})(),
|
|
// Prompt line from buffer
|
|
promptLine: (() => {
|
|
if (!buf) return null;
|
|
for (let row = app.terminal.rows - 1; row >= 0; row--) {
|
|
const line = buf.getLine(buf.viewportY + row);
|
|
if (!line) continue;
|
|
const text = line.translateToString(true);
|
|
if (text.includes('\u276f')) return { text, row, col: text.lastIndexOf('\u276f') };
|
|
}
|
|
return null;
|
|
})(),
|
|
};
|
|
});
|
|
}
|
|
|
|
async function focus(page) {
|
|
await page.evaluate(() => document.querySelector('.xterm-helper-textarea')?.focus());
|
|
await page.waitForTimeout(200);
|
|
}
|
|
|
|
async function switchTab(page, id) {
|
|
await page.evaluate((id) => document.querySelector(`.session-tab[data-id="${id}"]`)?.click(), id);
|
|
await page.waitForTimeout(1500);
|
|
}
|
|
|
|
async function waitForPrompt(page, timeoutMs = 10000) {
|
|
const start = Date.now();
|
|
while (Date.now() - start < timeoutMs) {
|
|
const state = await getState(page);
|
|
if (state?.promptLine) return true;
|
|
await page.waitForTimeout(200);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async function sc(page, name) {
|
|
const path = `${SCREENSHOT_DIR}/${name}.png`;
|
|
await page.screenshot({ path });
|
|
console.log(` 📸 ${path}`);
|
|
}
|
|
|
|
const R = [];
|
|
const pass = (n) => { R.push({n, s:'PASS'}); console.log(` ✅ ${n}`); };
|
|
const fail = (n, d) => { R.push({n, s:'FAIL', d}); console.log(` ❌ ${n} — ${d}`); };
|
|
|
|
async function main() {
|
|
const { mkdirSync } = await import('fs');
|
|
try { mkdirSync(SCREENSHOT_DIR, { recursive: true }); } catch {}
|
|
|
|
console.log('=== Local Echo — Basic Operation Tests ===\n');
|
|
|
|
const sA = await createSession('basic-A');
|
|
const sB = await createSession('basic-B');
|
|
console.log(` A: ${sA}\n B: ${sB}`);
|
|
if (!await waitForReady(sA) || !await waitForReady(sB)) {
|
|
await deleteSession(sA); await deleteSession(sB);
|
|
throw new Error('Sessions not ready');
|
|
}
|
|
console.log(' Ready\n');
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const ctx = await browser.newContext({
|
|
viewport: { width: 390, height: 844 },
|
|
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)',
|
|
isMobile: true, hasTouch: true,
|
|
});
|
|
const page = await ctx.newPage();
|
|
|
|
// Capture JS errors
|
|
const jsErrors = [];
|
|
page.on('pageerror', err => jsErrors.push(err.message));
|
|
|
|
try {
|
|
await page.goto(BASE, { waitUntil: 'domcontentloaded' });
|
|
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Enable local echo
|
|
await page.evaluate(() => {
|
|
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
|
s.localEchoEnabled = true;
|
|
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
|
window.app?._updateLocalEchoState?.();
|
|
});
|
|
|
|
// Select tab A, wait for prompt
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
if (!await waitForPrompt(page)) throw new Error('No prompt on A');
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 1: Type → switch → back → IMMEDIATE backspace
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('--- 1. Type → switch → back → immediate backspace ---');
|
|
await page.keyboard.type('hello world', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
let st = await getState(page);
|
|
console.log(` Typed: text="${st?.text}"`);
|
|
if (st?.text === 'hello world') pass('1a: Text buffered');
|
|
else fail('1a: Text buffered', `"${st?.text}"`);
|
|
await sc(page, '01-typed');
|
|
|
|
// Switch to B and back
|
|
await switchTab(page, sB);
|
|
st = await getState(page);
|
|
console.log(` On B: text="${st?.text}", flushedMap=${JSON.stringify(st?.flushedMap)}`);
|
|
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
|
|
// Check state BEFORE doing anything — flushed offset should be restored
|
|
st = await getState(page);
|
|
console.log(` Back on A: text="${st?.text}", flushed=${st?.flushed}`);
|
|
if (st?.flushed === 11) pass('1b: Flushed offset restored');
|
|
else fail('1b: Flushed offset', `expected 11, got ${st?.flushed}`);
|
|
|
|
// IMMEDIATELY backspace (don't wait for buffer load)
|
|
await page.keyboard.press('Backspace');
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After 1 backspace: text="${st?.text}", flushed=${st?.flushed}`);
|
|
if (st?.flushed === 10) pass('1c: Backspace decremented flushed');
|
|
else fail('1c: Backspace', `expected flushed=10, got ${st?.flushed}`);
|
|
await sc(page, '02-after-backspace');
|
|
|
|
// Backspace 5 more times
|
|
for (let i = 0; i < 5; i++) {
|
|
await page.keyboard.press('Backspace');
|
|
await page.waitForTimeout(50);
|
|
}
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After 6 total backspaces: flushed=${st?.flushed}`);
|
|
if (st?.flushed === 5) pass('1d: Multiple backspaces work');
|
|
else fail('1d: Multiple backspaces', `expected flushed=5, got ${st?.flushed}`);
|
|
|
|
// Clean up
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 2: Type → switch → back → IMMEDIATE space (don't erase)
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 2. Type → switch → back → immediate space ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
await page.keyboard.type('test input', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
// Switch and return
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
|
|
// Check flushed offset is restored
|
|
st = await getState(page);
|
|
console.log(` Back on A: flushed=${st?.flushed}`);
|
|
|
|
// Type a space immediately
|
|
await page.keyboard.type(' ', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After space: text="${st?.text}", flushed=${st?.flushed}`);
|
|
console.log(` Children: ${JSON.stringify(st?.children?.map(c => c.text))}`);
|
|
await sc(page, '03-after-space');
|
|
|
|
// The overlay should show the flushed text + space
|
|
if (st?.text === ' ' && st?.flushed > 0) pass('2a: Space typed with flushed offset');
|
|
else fail('2a: Space', `text="${st?.text}", flushed=${st?.flushed}`);
|
|
|
|
// The first child should contain the flushed text + space (not just space)
|
|
const child = st?.children?.[0];
|
|
if (child && child.text && child.text.length > 1) {
|
|
pass('2b: Overlay shows flushed text + space (not just space)');
|
|
console.log(` Overlay text: "${child.text}"`);
|
|
} else {
|
|
fail('2b: Overlay content', `child text="${child?.text}"`);
|
|
}
|
|
|
|
// Clean up
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 3: Type → switch → back → backspace some + type new text
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 3. Type → switch → back → backspace + retype ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
await page.keyboard.type('old text here', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
// Switch and return
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
await page.waitForTimeout(500); // Small wait for buffer
|
|
|
|
// Backspace 4 chars ("here")
|
|
for (let i = 0; i < 4; i++) {
|
|
await page.keyboard.press('Backspace');
|
|
await page.waitForTimeout(50);
|
|
}
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After 4 backspaces: flushed=${st?.flushed}, text="${st?.text}"`);
|
|
if (st?.flushed === 9) pass('3a: Backspace 4 leaves flushed=9');
|
|
else fail('3a: Backspace count', `expected flushed=9, got ${st?.flushed}`);
|
|
|
|
// Now type new text
|
|
await page.keyboard.type('new stuff', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After retype: text="${st?.text}", flushed=${st?.flushed}`);
|
|
console.log(` Overlay: ${JSON.stringify(st?.children?.map(c => c.text))}`);
|
|
await sc(page, '04-backspace-retype');
|
|
|
|
if (st?.text === 'new stuff') pass('3b: New text after backspace correct');
|
|
else fail('3b: New text', `"${st?.text}"`);
|
|
|
|
// Overlay should show flushed chars + new text
|
|
const overlayText3 = st?.children?.[0]?.text || '';
|
|
console.log(` Full overlay text: "${overlayText3}"`);
|
|
if (overlayText3.endsWith('new stuff') && overlayText3.length > 'new stuff'.length) {
|
|
pass('3c: Overlay combines flushed + new text');
|
|
} else {
|
|
fail('3c: Combined overlay', `"${overlayText3}"`);
|
|
}
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 4: Overlay position consistency
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 4. Overlay position consistency ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
|
|
// Type and record position
|
|
await page.keyboard.type('position test', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
const pos1 = {
|
|
overlayLeft: st?.overlayLeft,
|
|
overlayTop: st?.overlayTop,
|
|
childLeft: st?.children?.[0]?.left,
|
|
promptCol: st?.promptLine?.col,
|
|
};
|
|
console.log(` Initial position: overlay=(${pos1.overlayLeft}, ${pos1.overlayTop}), child left=${pos1.childLeft}, prompt col=${pos1.promptCol}`);
|
|
await sc(page, '05-position-1');
|
|
|
|
// Type more text — position should stay stable (left doesn't change)
|
|
await page.keyboard.type('!', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
const pos2 = {
|
|
overlayLeft: st?.overlayLeft,
|
|
overlayTop: st?.overlayTop,
|
|
childLeft: st?.children?.[0]?.left,
|
|
promptCol: st?.promptLine?.col,
|
|
};
|
|
console.log(` After more text: overlay=(${pos2.overlayLeft}, ${pos2.overlayTop}), child left=${pos2.childLeft}, prompt col=${pos2.promptCol}`);
|
|
|
|
if (pos1.overlayLeft === pos2.overlayLeft && pos1.overlayTop === pos2.overlayTop) {
|
|
pass('4a: Overlay container position stable');
|
|
} else {
|
|
fail('4a: Overlay shifted', `${pos1.overlayLeft},${pos1.overlayTop} → ${pos2.overlayLeft},${pos2.overlayTop}`);
|
|
}
|
|
|
|
if (pos1.childLeft === pos2.childLeft) {
|
|
pass('4b: Child left position stable');
|
|
} else {
|
|
fail('4b: Child left shifted', `${pos1.childLeft} → ${pos2.childLeft}`);
|
|
}
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 5: Switch → return → type → position matches prompt
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 5. Position after tab switch ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
|
|
// Record prompt position
|
|
st = await getState(page);
|
|
const promptCol5 = st?.promptLine?.col;
|
|
console.log(` Prompt col before: ${promptCol5}`);
|
|
|
|
await page.keyboard.type('before switch', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
const beforeLeft = st?.children?.[0]?.left;
|
|
console.log(` Overlay left before switch: ${beforeLeft}`);
|
|
await sc(page, '06-before-switch');
|
|
|
|
// Switch and return
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
await page.waitForTimeout(500);
|
|
|
|
// Type something
|
|
await page.keyboard.type(' more', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
const afterLeft = st?.children?.[0]?.left;
|
|
const promptCol5After = st?.promptLine?.col;
|
|
console.log(` Prompt col after: ${promptCol5After}`);
|
|
console.log(` Overlay left after switch+type: ${afterLeft}`);
|
|
await sc(page, '07-after-switch-type');
|
|
|
|
if (beforeLeft === afterLeft) {
|
|
pass('5a: Overlay position stable after tab switch');
|
|
} else {
|
|
fail('5a: Position shifted after switch', `${beforeLeft} → ${afterLeft}`);
|
|
}
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 6: Rapid backspace-and-retype after return
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 6. Rapid backspace-and-retype ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
await page.keyboard.type('rapid test', { delay: 20 });
|
|
await page.waitForTimeout(200);
|
|
|
|
// Switch and return
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
|
|
// Rapidly: backspace 4, type "done"
|
|
for (let i = 0; i < 4; i++) await page.keyboard.press('Backspace');
|
|
await page.keyboard.type('done', { delay: 10 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After rapid edit: text="${st?.text}", flushed=${st?.flushed}`);
|
|
await sc(page, '08-rapid-edit');
|
|
|
|
if (st?.text === 'done') pass('6a: Rapid backspace+retype text correct');
|
|
else fail('6a: Rapid edit text', `"${st?.text}"`);
|
|
|
|
if (st?.flushed === 6) pass('6b: Flushed offset correct after rapid backspace');
|
|
else fail('6b: Flushed offset', `expected 6, got ${st?.flushed}`);
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 7: Overlay background covers canvas text
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 7. Overlay covers canvas text ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
await page.keyboard.type('canvas cover', { delay: 30 });
|
|
await page.waitForTimeout(200);
|
|
|
|
// Flush by switching tabs
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
await page.waitForTimeout(500); // Wait for buffer
|
|
|
|
// Type a space — overlay should render flushed text + space
|
|
await page.keyboard.type('X', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
const overlayChild = st?.children?.[0];
|
|
console.log(` Overlay: visible=${st?.visible}, text="${overlayChild?.text}", width=${overlayChild?.width}, bg=${overlayChild?.bgColor}`);
|
|
await sc(page, '09-canvas-cover');
|
|
|
|
if (st?.visible && overlayChild?.text?.includes('canvas cover')) {
|
|
pass('7a: Overlay contains flushed text');
|
|
} else {
|
|
fail('7a: Flushed text in overlay', `"${overlayChild?.text}"`);
|
|
}
|
|
|
|
if (overlayChild?.bgColor) {
|
|
pass('7b: Overlay has background color');
|
|
} else {
|
|
fail('7b: No background color', `bg="${overlayChild?.bgColor}"`);
|
|
}
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 8: Backspace ALL flushed text back to zero
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 8. Full backspace through flushed text ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
await page.keyboard.type('abcde', { delay: 20 });
|
|
await page.waitForTimeout(200);
|
|
|
|
// Flush
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
|
|
st = await getState(page);
|
|
console.log(` Flushed: ${st?.flushed}`);
|
|
|
|
// Backspace ALL 5 chars + 2 extra (should be no-ops)
|
|
for (let i = 0; i < 7; i++) {
|
|
await page.keyboard.press('Backspace');
|
|
await page.waitForTimeout(40);
|
|
}
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` After full backspace: flushed=${st?.flushed}, text="${st?.text}", visible=${st?.visible}`);
|
|
await sc(page, '10-full-backspace');
|
|
|
|
if (st?.flushed === 0) pass('8a: Flushed offset back to 0');
|
|
else fail('8a: Flushed not zero', `${st?.flushed}`);
|
|
|
|
if (!st?.text && !st?.visible) pass('8b: Overlay hidden after full backspace');
|
|
else fail('8b: Overlay state', `text="${st?.text}", visible=${st?.visible}`);
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// TEST 9: Fresh type after full backspace
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- 9. Type fresh after full backspace ---');
|
|
await focus(page);
|
|
await waitForPrompt(page);
|
|
await page.keyboard.type('delete me', { delay: 20 });
|
|
await page.waitForTimeout(200);
|
|
|
|
// Flush
|
|
await switchTab(page, sB);
|
|
await switchTab(page, sA);
|
|
await focus(page);
|
|
|
|
// Delete all
|
|
for (let i = 0; i < 12; i++) {
|
|
await page.keyboard.press('Backspace');
|
|
await page.waitForTimeout(30);
|
|
}
|
|
await page.waitForTimeout(200);
|
|
|
|
// Now type fresh text
|
|
await page.keyboard.type('brand new', { delay: 30 });
|
|
await page.waitForTimeout(300);
|
|
|
|
st = await getState(page);
|
|
console.log(` Fresh type: text="${st?.text}", flushed=${st?.flushed}, visible=${st?.visible}`);
|
|
await sc(page, '11-fresh-type');
|
|
|
|
if (st?.text === 'brand new' && st?.flushed === 0) {
|
|
pass('9a: Clean fresh text after full delete');
|
|
} else {
|
|
fail('9a: Fresh text', `text="${st?.text}", flushed=${st?.flushed}`);
|
|
}
|
|
|
|
if (st?.visible) pass('9b: Overlay visible with fresh text');
|
|
else fail('9b: Overlay not visible');
|
|
|
|
await page.keyboard.press('Control+c');
|
|
await page.waitForTimeout(500);
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// JS ERRORS
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n--- JS Errors ---');
|
|
if (jsErrors.length === 0) {
|
|
pass('No JS errors');
|
|
} else {
|
|
for (const err of jsErrors) {
|
|
fail('JS Error', err.slice(0, 120));
|
|
}
|
|
}
|
|
|
|
await sc(page, '12-final');
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
// SUMMARY
|
|
// ══════════════════════════════════════════════════════════════
|
|
console.log('\n=== SUMMARY ===');
|
|
const passes = R.filter(r => r.s === 'PASS').length;
|
|
const fails = R.filter(r => r.s === 'FAIL').length;
|
|
console.log(` ${passes} passed, ${fails} failed`);
|
|
|
|
if (fails > 0) {
|
|
console.log('\n FAILURES:');
|
|
R.filter(r => r.s === 'FAIL').forEach(r => console.log(` ❌ ${r.n}: ${r.d}`));
|
|
}
|
|
|
|
} finally {
|
|
await browser.close();
|
|
console.log('\nCleaning up...');
|
|
await deleteSession(sA);
|
|
await deleteSession(sB);
|
|
}
|
|
}
|
|
|
|
main().catch(err => { console.error('Test failed:', err); process.exit(1); });
|