mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09:42 +02:00
chore: move manual test scripts to test/manual/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
@@ -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');
|
||||
@@ -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();
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
Reference in New Issue
Block a user