chore: move manual test scripts to test/manual/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-03-01 17:38:45 +01:00
co-authored by Claude Opus 4.6
parent 87abc0d301
commit 888a2d8e9a
10 changed files with 0 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
// Check ALL background colors in the xterm rendering stack
const bgInfo = await page.evaluate(() => {
const term = window.app?.terminal;
if (!term) return { error: 'no terminal' };
const el = term.element;
if (!el) return { error: 'no element' };
const results = {};
// Walk up from xterm-screen to find who provides the background
const screen = el.querySelector('.xterm-screen');
const viewport = el.querySelector('.xterm-viewport');
const rows = el.querySelector('.xterm-rows');
const elements = {
'.xterm (term.element)': el,
'.xterm-viewport': viewport,
'.xterm-screen': screen,
'.xterm-rows': rows,
};
// Also check parent elements
let parent = el.parentElement;
let depth = 0;
while (parent && depth < 5) {
elements[`parent-${depth} (${parent.tagName}.${parent.className?.split(' ')[0] || ''})`] = parent;
parent = parent.parentElement;
depth++;
}
for (const [name, elem] of Object.entries(elements)) {
if (!elem) { results[name] = 'not found'; continue; }
const cs = getComputedStyle(elem);
results[name] = {
background: cs.background?.slice(0, 80),
backgroundColor: cs.backgroundColor,
inlineStyle: elem.style.backgroundColor || elem.style.background || '(none)',
};
}
// Also check if xterm-viewport has inline style set by xterm.js
if (viewport) {
results['viewport-inline-full'] = viewport.style.cssText?.slice(0, 200);
}
// Theme config
results.theme = term.options?.theme;
// Overlay bg
results.overlayBg = window.app?._localEchoOverlay?._font?.backgroundColor;
return results;
});
console.log(JSON.stringify(bgInfo, null, 2));
await browser.close();
+85
View File
@@ -0,0 +1,85 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
// Sample the ACTUAL canvas pixel color at an empty area
const colorInfo = await page.evaluate(() => {
const term = window.app?.terminal;
if (!term) return { error: 'no terminal' };
// Find the WebGL canvas
const screen = term.element?.querySelector('.xterm-screen');
const canvases = screen?.querySelectorAll('canvas');
const results = {};
for (const canvas of canvases || []) {
const ctx = canvas.getContext('2d') || canvas.getContext('webgl') || canvas.getContext('webgl2');
const ctxType = ctx?.constructor?.name;
if (ctx && (ctxType === 'CanvasRenderingContext2D')) {
// 2D context — can read pixels directly
try {
const pixel = ctx.getImageData(10, 10, 1, 1).data;
results['2d'] = {
r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3],
hex: '#' + [pixel[0], pixel[1], pixel[2]].map(v => v.toString(16).padStart(2, '0')).join(''),
};
} catch (e) {
results['2d'] = { error: e.message };
}
} else if (ctx) {
// WebGL context — use readPixels
try {
const gl = ctx;
const pixel = new Uint8Array(4);
// Read from an empty area (bottom-left corner, row 0 col 0)
gl.readPixels(10, canvas.height - 10, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
results['webgl'] = {
r: pixel[0], g: pixel[1], b: pixel[2], a: pixel[3],
hex: '#' + [pixel[0], pixel[1], pixel[2]].map(v => v.toString(16).padStart(2, '0')).join(''),
};
} catch (e) {
results['webgl'] = { error: e.message };
}
}
}
// Also check what the terminal theme says
results.theme = {
background: term.options?.theme?.background,
foreground: term.options?.theme?.foreground,
};
// Check the overlay's configured background
const overlay = window.app?._localEchoOverlay;
results.overlayBg = overlay?._font?.backgroundColor;
// Check xterm-rows computed style
const rows = term.element?.querySelector('.xterm-rows');
if (rows) {
results.rowsBg = getComputedStyle(rows).backgroundColor;
}
// Check .xterm element bg
const xtermEl = term.element;
if (xtermEl) {
results.xtermBg = getComputedStyle(xtermEl).backgroundColor;
}
// Canvas count and types
results.canvasCount = canvases?.length;
results.canvasClasses = Array.from(canvases || []).map(c => c.className);
return results;
});
console.log(JSON.stringify(colorInfo, null, 2));
await browser.close();
console.log('Done');
+48
View File
@@ -0,0 +1,48 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
const firstTab = page.locator('.session-tab').first();
await firstTab.click();
await page.waitForTimeout(5000);
const info = await page.evaluate(() => {
const term = window.app?.terminal;
if (!term) return { error: 'no app.terminal' };
const core = term._core;
const renderService = core?._renderService;
const dims = renderService?.dimensions;
if (!dims) return { error: 'no dimensions', hasCore: !!core, hasRenderService: !!renderService };
return {
css: {
cell: dims.css?.cell,
char: dims.css?.char,
canvas: dims.css?.canvas,
},
device: {
cell: dims.device?.cell,
char: dims.device?.char,
canvas: dims.device?.canvas,
},
topLevelKeys: Object.keys(dims),
cssKeys: dims.css ? Object.keys(dims.css) : [],
deviceKeys: dims.device ? Object.keys(dims.device) : [],
cssAllDetail: dims.css ? Object.fromEntries(
Object.entries(dims.css).map(([k, v]) => [k, v && typeof v === 'object' ? { ...v } : v])
) : null,
deviceAllDetail: dims.device ? Object.fromEntries(
Object.entries(dims.device).map(([k, v]) => [k, v && typeof v === 'object' ? { ...v } : v])
) : null,
dpr: window.devicePixelRatio,
};
});
console.log(JSON.stringify(info, null, 2));
await browser.close();
+87
View File
@@ -0,0 +1,87 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true, deviceScaleFactor: 2 });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem('codeman-settings') || '{}');
settings.localEchoEnabled = true;
localStorage.setItem('codeman-settings', JSON.stringify(settings));
if (window.app?.updateLocalEchoState) window.app.updateLocalEchoState();
});
await page.waitForTimeout(500);
// Force render and capture zoomed area of just the overlay
const result = await page.evaluate(() => {
const overlay = window.app?._localEchoOverlay;
if (!overlay || !overlay._overlay) return { error: 'no overlay' };
overlay._lastPromptPos = { row: 5, col: 0 };
overlay._pendingText = 'h';
overlay._lastRenderKey = '';
overlay._render();
const container = overlay._overlay;
const lineDiv = container.querySelector('div');
const charSpan = container.querySelector('div > span');
// Get precise rects
const lineDivRect = lineDiv?.getBoundingClientRect();
const charSpanRect = charSpan?.getBoundingClientRect();
const screenRect = document.querySelector('.xterm-screen')?.getBoundingClientRect();
// Check what's right below the line div
const belowY = (lineDivRect?.bottom || 0) + 1;
const belowX = lineDivRect?.left || 0;
const elBelow = document.elementFromPoint(belowX, belowY);
return {
dpr: window.devicePixelRatio,
lineDiv: {
rect: lineDivRect,
bg: lineDiv?.style.backgroundColor,
height: lineDiv?.style.height,
},
charSpan: {
rect: charSpanRect,
transform: charSpan?.style.transform,
// Check if span bottom exceeds lineDiv bottom
extendsBelow: charSpanRect && lineDivRect ?
(charSpanRect.bottom - lineDivRect.bottom).toFixed(2) : 'N/A',
},
elBelow: elBelow ? {
tag: elBelow.tagName,
class: elBelow.className,
} : null,
// Check the exact background color of the canvas
canvasBg: document.querySelector('.xterm-screen')?.style.backgroundColor,
themeBg: window.app?.terminal?.options?.theme?.background,
overlayBg: lineDiv?.style.backgroundColor,
};
});
console.log(JSON.stringify(result, null, 2));
// Take tight screenshot around the first char area
const screenEl = page.locator('.xterm-screen').first();
const screenBox = await screenEl.boundingBox();
if (screenBox) {
// Crop to just the overlay area (row 5, first few columns)
const cellH = 19;
const promptRow = 5;
const cropY = screenBox.y + (promptRow - 1) * cellH;
const cropH = cellH * 3;
await page.screenshot({
path: '/tmp/first-char-zoomed.png',
clip: { x: screenBox.x, y: cropY, width: 200, height: cropH },
});
console.log('Zoomed screenshot saved');
}
await page.evaluate(() => { window.app?._localEchoOverlay?.clear(); });
await browser.close();
console.log('Done');
+116
View File
@@ -0,0 +1,116 @@
import { chromium } from 'playwright';
const SESSION_ID = '9270f4e4-84f7-414c-b901-837f12c30e59';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
page.on('console', msg => {
if (msg.type() === 'error') console.log('CONSOLE ERROR:', msg.text());
});
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(4000);
// Click on test session tab
const tab = page.locator(`.session-tab[data-id="${SESSION_ID}"]`);
if (await tab.isVisible({ timeout: 3000 }).catch(() => false)) {
await tab.click();
await page.waitForTimeout(2000);
} else {
console.log('Tab not found, using whatever is active');
}
await page.waitForTimeout(2000);
// Ensure local echo is OFF
const localEchoToggle = page.locator('#localEchoToggle');
const isChecked = await localEchoToggle.isChecked().catch(() => false);
console.log('Local echo initially:', isChecked);
if (isChecked) {
await localEchoToggle.click();
await page.waitForTimeout(500);
}
// Type with echo OFF
await page.locator('.xterm-helper-textarea').first().focus();
await page.waitForTimeout(300);
await page.keyboard.type('hello test', { delay: 80 });
await page.waitForTimeout(1000);
const termScreen = page.locator('.xterm-screen').first();
await termScreen.screenshot({ path: '/tmp/echo-off-term.png' });
console.log('Saved /tmp/echo-off-term.png');
// Clear
for (let i = 0; i < 10; i++) await page.keyboard.press('Backspace');
await page.waitForTimeout(500);
// Enable local echo
await localEchoToggle.click();
await page.waitForTimeout(500);
console.log('Local echo now:', await localEchoToggle.isChecked().catch(() => false));
// Type with echo ON
await page.locator('.xterm-helper-textarea').first().focus();
await page.waitForTimeout(300);
await page.keyboard.type('hello test', { delay: 80 });
await page.waitForTimeout(1500);
await termScreen.screenshot({ path: '/tmp/echo-on-term.png' });
console.log('Saved /tmp/echo-on-term.png');
await page.screenshot({ path: '/tmp/echo-on-full.png' });
// Inspect overlay DOM
const overlayInfo = await page.evaluate(() => {
const overlay = document.querySelector('.zl-overlay');
if (!overlay) return { found: false };
const firstDiv = overlay.querySelector('div');
const spans = overlay.querySelectorAll('span');
const firstCharSpan = Array.from(spans).find(s => s.textContent && s.textContent.trim());
return {
found: true,
display: overlay.style.display,
top: overlay.style.top,
containerOverflow: overlay.style.overflow,
containerHeight: overlay.style.height,
firstDiv: firstDiv ? {
height: firstDiv.style.height,
top: firstDiv.style.top,
overflow: firstDiv.style.overflow,
backgroundColor: firstDiv.style.backgroundColor,
} : null,
firstCharSpan: firstCharSpan ? {
text: firstCharSpan.textContent,
transform: firstCharSpan.style.transform,
top: firstCharSpan.style.top,
height: firstCharSpan.style.height,
lineHeight: firstCharSpan.style.lineHeight,
width: firstCharSpan.style.width,
} : null,
spanCount: spans.length,
};
});
console.log('\nOverlay info:', JSON.stringify(overlayInfo, null, 2));
// Cell dimensions
const cellInfo = await page.evaluate(() => {
if (window.app?.sessions) {
for (const [, s] of Object.entries(window.app.sessions)) {
const dims = s.terminal?._core?._renderService?.dimensions;
if (dims) return { css: dims.css?.cell, device_char: dims.device?.char };
}
}
return { error: 'not found' };
});
console.log('Cell dims:', JSON.stringify(cellInfo, null, 2));
// Cleanup
await page.evaluate(async (id) => {
await fetch('/api/sessions/' + id, { method: 'DELETE' });
}, SESSION_ID);
await browser.close();
console.log('\nDone');
+114
View File
@@ -0,0 +1,114 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
page.on('console', msg => console.log('CONSOLE:', msg.type(), msg.text()));
page.on('pageerror', err => console.log('PAGE ERROR:', err.message));
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(4000);
// Create a fresh session
const resp = await page.evaluate(async () => {
const r = await fetch('/api/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'ov-test', mode: 'claude' })
});
return r.json();
});
const sid = resp.session?.id;
console.log('Session:', sid?.slice(0, 8));
await page.waitForTimeout(3000);
// Click on it
await page.locator(`.session-tab[data-id="${sid}"]`).click();
await page.waitForTimeout(3000);
// Check for the local echo toggle
const toggle = page.locator('#localEchoToggle');
const toggleVisible = await toggle.isVisible().catch(() => false);
console.log('Toggle visible:', toggleVisible);
if (!toggleVisible) {
// Maybe need to open settings or it's somewhere else
console.log('Looking for local echo toggle...');
const all = await page.locator('input[type="checkbox"]').all();
for (const cb of all) {
const id = await cb.getAttribute('id');
const label = await cb.evaluate(el => el.parentElement?.textContent?.trim().slice(0, 30));
console.log(' checkbox:', id, label);
}
}
// Get cell dimensions first
const cellInfo = await page.evaluate(() => {
if (window.app?.sessions) {
for (const [id, s] of Object.entries(window.app.sessions)) {
const dims = s.terminal?._core?._renderService?.dimensions;
if (dims) return {
id: id.slice(0, 8),
cssCell: dims.css?.cell,
deviceChar: dims.device?.char,
actualFull: dims,
};
}
}
return { error: 'no terminal' };
});
console.log('Cell info:', JSON.stringify(cellInfo, null, 2));
// Focus terminal and type with echo OFF
const textarea = page.locator('.xterm-helper-textarea').first();
await textarea.focus();
await page.waitForTimeout(500);
await page.keyboard.type('hello test', { delay: 80 });
await page.waitForTimeout(1500);
await page.screenshot({ path: '/tmp/echo-off-full.png' });
console.log('Saved echo-off-full.png');
// Clear
for (let i = 0; i < 10; i++) await page.keyboard.press('Backspace');
await page.waitForTimeout(500);
// Enable local echo
if (toggleVisible) {
await toggle.click();
await page.waitForTimeout(500);
console.log('Toggled local echo ON');
}
// Type again
await textarea.focus();
await page.waitForTimeout(300);
await page.keyboard.type('hello test', { delay: 80 });
await page.waitForTimeout(1500);
await page.screenshot({ path: '/tmp/echo-on-full.png' });
console.log('Saved echo-on-full.png');
// Get overlay info
const oi = await page.evaluate(() => {
const o = document.querySelector('.zl-overlay');
if (!o) return { found: false, html: 'none' };
return {
found: true,
display: o.style.display,
top: o.style.top,
childCount: o.children.length,
innerHTML: o.innerHTML.slice(0, 500),
};
});
console.log('Overlay:', JSON.stringify(oi, null, 2));
// Cleanup
if (sid) {
await page.evaluate(async (id) => {
await fetch('/api/sessions/' + id, { method: 'DELETE' });
}, sid);
}
await browser.close();
console.log('Done');
+109
View File
@@ -0,0 +1,109 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
// Click first tab
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
// Get terminal and enable local echo via JS
const result = await page.evaluate(() => {
const term = window.app.terminal;
if (!term) return { error: 'no terminal' };
const dims = term._core._renderService.dimensions;
// Toggle local echo on via settings
const checkbox = document.getElementById('appSettingsLocalEcho');
return {
cellW: dims.css.cell.width,
cellH: dims.css.cell.height,
charTop: dims.device?.char?.top ?? 0,
charHeight: dims.device?.char?.height ?? 0,
dpr: window.devicePixelRatio,
fontSize: term.options.fontSize,
fontFamily: term.options.fontFamily,
localEchoCheckbox: !!checkbox,
localEchoChecked: checkbox?.checked,
};
});
console.log('Terminal info:', JSON.stringify(result, null, 2));
// Enable local echo via the app settings checkbox
await page.evaluate(() => {
const cb = document.getElementById('appSettingsLocalEcho');
if (cb && !cb.checked) {
cb.click();
cb.dispatchEvent(new Event('change', { bubbles: true }));
}
});
await page.waitForTimeout(500);
// Focus terminal and type
await page.locator('.xterm-helper-textarea').first().focus();
await page.waitForTimeout(300);
await page.keyboard.type('hello', { delay: 100 });
await page.waitForTimeout(1500);
// Take screenshots and analyze pixel positions
const pixelInfo = await page.evaluate(() => {
const overlay = document.querySelector('.zl-overlay');
if (!overlay) return { error: 'no overlay', overlayDisplay: overlay?.style?.display };
const spans = overlay.querySelectorAll('span');
const firstCharSpan = Array.from(spans).find(s => s.textContent === 'h');
if (!firstCharSpan) {
return { error: 'no h span', spanCount: spans.length, innerHTML: overlay.innerHTML.slice(0, 200) };
}
const spanRect = firstCharSpan.getBoundingClientRect();
const containerRect = overlay.getBoundingClientRect();
const lineDiv = firstCharSpan.parentElement;
const lineDivRect = lineDiv.getBoundingClientRect();
// Get the canvas position for comparison
const canvasEl = document.querySelector('.xterm-screen canvas');
const canvasRect = canvasEl?.getBoundingClientRect();
return {
span: {
top: spanRect.top,
bottom: spanRect.bottom,
height: spanRect.height,
relativeTop: spanRect.top - lineDivRect.top,
},
lineDiv: {
top: lineDivRect.top,
bottom: lineDivRect.bottom,
height: lineDivRect.height,
},
container: {
top: containerRect.top,
height: containerRect.height,
},
canvas: canvasRect ? {
top: canvasRect.top,
} : null,
spanStyle: {
transform: firstCharSpan.style.transform,
top: firstCharSpan.style.top,
height: firstCharSpan.style.height,
lineHeight: firstCharSpan.style.lineHeight,
},
};
});
console.log('Pixel info:', JSON.stringify(pixelInfo, null, 2));
// Screenshot the terminal area
const termScreen = page.locator('.xterm-screen').first();
await termScreen.screenshot({ path: '/tmp/overlay-test-zoomed.png' });
console.log('Screenshot saved to /tmp/overlay-test-zoomed.png');
await browser.close();
console.log('Done');
+69
View File
@@ -0,0 +1,69 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
// Enable local echo
await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('codeman-settings') || '{}');
s.localEchoEnabled = true;
localStorage.setItem('codeman-settings', JSON.stringify(s));
window.app?.updateLocalEchoState?.();
});
await page.waitForTimeout(500);
// Force render overlay with single char at a realistic prompt position
await page.evaluate(() => {
const ov = window.app?._localEchoOverlay;
if (!ov) return;
// Find actual bottom row for a realistic position
const buf = ov._terminal?.buffer?.active;
const row = buf ? Math.max(0, buf.baseY + ov._terminal.rows - 2) - buf.viewportY : 5;
ov._lastPromptPos = { row, col: 0 };
ov._pendingText = 'hello test';
ov._lastRenderKey = '';
ov._render();
});
await page.waitForTimeout(500);
// Take full-page and zoomed screenshots
await page.screenshot({ path: '/tmp/sc-full.png' });
// Zoom into overlay area — find where the overlay actually is
const overlayRect = await page.evaluate(() => {
const ov = window.app?._localEchoOverlay?._overlay;
if (!ov) return null;
const r = ov.getBoundingClientRect();
const lineDiv = ov.querySelector('div');
const lr = lineDiv?.getBoundingClientRect();
return {
container: { x: r.x, y: r.y, w: r.width, h: r.height },
lineDiv: lr ? { x: lr.x, y: lr.y, w: lr.width, h: lr.height } : null,
};
});
console.log('Overlay rect:', JSON.stringify(overlayRect));
if (overlayRect?.lineDiv) {
const ld = overlayRect.lineDiv;
// Capture area around the overlay: 1 row above, the overlay, 1 row below
const margin = 20; // ~1 cell height
await page.screenshot({
path: '/tmp/sc-overlay-zoomed.png',
clip: {
x: Math.max(0, ld.x - 5),
y: Math.max(0, ld.y - margin),
width: Math.min(400, ld.w),
height: ld.h + margin * 2 + 5,
},
});
console.log('Zoomed screenshot saved');
}
await page.evaluate(() => { window.app?._localEchoOverlay?.clear(); });
await browser.close();
console.log('Done');
+69
View File
@@ -0,0 +1,69 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
// Enable local echo and update state
await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem('codeman-settings') || '{}');
settings.localEchoEnabled = true;
localStorage.setItem('codeman-settings', JSON.stringify(settings));
if (window.app?.updateLocalEchoState) window.app.updateLocalEchoState();
});
await page.waitForTimeout(500);
// Force render with fake prompt position and inspect
const result = await page.evaluate(() => {
const overlay = window.app?._localEchoOverlay;
if (!overlay || !overlay._overlay) return { error: 'no overlay' };
const term = overlay._terminal;
const dims = term._core._renderService.dimensions;
const cellH = dims.css.cell.height;
const charTop = dims.device?.char?.top ?? 0;
const charHeight = dims.device?.char?.height ?? cellH;
// Calculate the correction
const cssCenter = (cellH - charHeight) / 2;
const correction = charTop - cssCenter;
// Force render
overlay._lastPromptPos = { row: 5, col: 0 };
overlay._pendingText = 'hello test';
overlay._lastRenderKey = '';
overlay._render();
const spans = overlay._overlay.querySelectorAll('span');
const firstChar = Array.from(spans).find(s => s.textContent === 'h');
return {
cellH, charTop, charHeight,
cssCenter,
correction: correction.toFixed(2),
firstChar: firstChar ? {
top: firstChar.style.top,
height: firstChar.style.height,
lineHeight: firstChar.style.lineHeight,
transform: firstChar.style.transform,
} : null,
};
});
console.log('Fix result:', JSON.stringify(result, null, 2));
// Take screenshot
await page.locator('.xterm-screen').first().screenshot({ path: '/tmp/fix-verify-term.png' });
console.log('Screenshot saved');
// Clean up
await page.evaluate(() => {
window.app?._localEchoOverlay?.clear();
});
await browser.close();
console.log('Done');
+61
View File
@@ -0,0 +1,61 @@
import { chromium } from 'playwright';
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1400, height: 900 } });
await page.goto('http://localhost:3099', { waitUntil: 'domcontentloaded' });
await page.waitForTimeout(6000);
await page.locator('.session-tab').first().click();
await page.waitForTimeout(4000);
await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem('codeman-settings') || '{}');
settings.localEchoEnabled = true;
localStorage.setItem('codeman-settings', JSON.stringify(settings));
if (window.app?.updateLocalEchoState) window.app.updateLocalEchoState();
});
const result = await page.evaluate(() => {
const overlay = window.app?._localEchoOverlay;
if (!overlay) return { error: 'no overlay' };
overlay._lastPromptPos = { row: 5, col: 0 };
overlay._pendingText = 'hello test';
overlay._lastRenderKey = '';
overlay._render();
const container = overlay._overlay;
const lineDiv = container.querySelector('div');
const charSpan = container.querySelector('div > span');
const lineDivRect = lineDiv?.getBoundingClientRect();
const charSpanRect = charSpan?.getBoundingClientRect();
return {
lineDiv: { bottom: lineDivRect?.bottom, height: lineDivRect?.height },
charSpan: {
bottom: charSpanRect?.bottom,
height: charSpanRect?.height,
transform: charSpan?.style.transform,
top: charSpan?.style.top,
lineHeight: charSpan?.style.lineHeight,
},
extendsBelow: charSpanRect && lineDivRect ?
(charSpanRect.bottom - lineDivRect.bottom).toFixed(4) : 'N/A',
};
});
console.log(JSON.stringify(result, null, 2));
// Screenshot
const screenEl = page.locator('.xterm-screen').first();
const screenBox = await screenEl.boundingBox();
if (screenBox) {
await page.screenshot({
path: '/tmp/no-overhang-zoomed.png',
clip: { x: screenBox.x, y: screenBox.y + 4 * 19, width: 200, height: 57 },
});
}
await page.evaluate(() => { window.app?._localEchoOverlay?.clear(); });
await browser.close();
console.log('Done');