This commit is contained in:
Chris Tate
2026-01-11 23:55:29 -06:00
parent 343aa1f723
commit d84a769949
6 changed files with 56 additions and 39 deletions
+8 -2
View File
@@ -383,7 +383,7 @@ async function handleNavigate(
async function handleClick(command: ClickCommand, browser: BrowserManager): Promise<Response> { async function handleClick(command: ClickCommand, browser: BrowserManager): Promise<Response> {
// Support both refs (@e1) and regular selectors // Support both refs (@e1) and regular selectors
const locator = browser.getLocator(command.selector); const locator = browser.getLocator(command.selector);
await locator.click({ await locator.click({
button: command.button, button: command.button,
clickCount: command.clickCount, clickCount: command.clickCount,
@@ -449,7 +449,13 @@ async function handleScreenshot(
} }
async function handleSnapshot( async function handleSnapshot(
command: Command & { action: 'snapshot'; interactive?: boolean; maxDepth?: number; compact?: boolean; selector?: string }, command: Command & {
action: 'snapshot';
interactive?: boolean;
maxDepth?: number;
compact?: boolean;
selector?: string;
},
browser: BrowserManager browser: BrowserManager
): Promise<Response<SnapshotData>> { ): Promise<Response<SnapshotData>> {
// Use enhanced snapshot with refs and optional filtering // Use enhanced snapshot with refs and optional filtering
+3 -1
View File
@@ -123,7 +123,9 @@ describe('BrowserManager', () => {
it('should set cookie with domain', async () => { it('should set cookie with domain', async () => {
const page = browser.getPage(); const page = browser.getPage();
const context = page.context(); const context = page.context();
await context.addCookies([{ name: 'domainCookie', value: 'domainValue', domain: 'example.com', path: '/' }]); await context.addCookies([
{ name: 'domainCookie', value: 'domainValue', domain: 'example.com', path: '/' },
]);
const cookies = await context.cookies(); const cookies = await context.cookies();
const testCookie = cookies.find((c) => c.name === 'domainCookie'); const testCookie = cookies.find((c) => c.name === 'domainCookie');
expect(testCookie?.value).toBe('domainValue'); expect(testCookie?.value).toBe('domainValue');
+1 -1
View File
@@ -94,7 +94,7 @@ export class BrowserManager {
if (!refData) return null; if (!refData) return null;
const page = this.getPage(); const page = this.getPage();
// Parse the selector and create locator // Parse the selector and create locator
if (refData.name) { if (refData.name) {
return page.getByRole(refData.role as any, { name: refData.name }); return page.getByRole(refData.role as any, { name: refData.name });
+4 -2
View File
@@ -33,7 +33,7 @@ export function getSession(): string {
function getPortForSession(session: string): number { function getPortForSession(session: string): number {
let hash = 0; let hash = 0;
for (let i = 0; i < session.length; i++) { for (let i = 0; i < session.length; i++) {
hash = ((hash << 5) - hash) + session.charCodeAt(i); hash = (hash << 5) - hash + session.charCodeAt(i);
hash |= 0; hash |= 0;
} }
// Port range 49152-65535 (dynamic/private ports) // Port range 49152-65535 (dynamic/private ports)
@@ -90,7 +90,9 @@ export function isDaemonRunning(session?: string): boolean {
* Get connection info for the current session * Get connection info for the current session
* Returns { type: 'unix', path: string } or { type: 'tcp', port: number } * Returns { type: 'unix', path: string } or { type: 'tcp', port: number }
*/ */
export function getConnectionInfo(session?: string): { type: 'unix'; path: string } | { type: 'tcp'; port: number } { export function getConnectionInfo(
session?: string
): { type: 'unix'; path: string } | { type: 'tcp'; port: number } {
const sess = session ?? currentSession; const sess = session ?? currentSession;
if (isWindows) { if (isWindows) {
return { type: 'tcp', port: getPortForSession(sess) }; return { type: 'tcp', port: getPortForSession(sess) };
+22 -12
View File
@@ -252,7 +252,9 @@ describe('parseCommand', () => {
}); });
it('should parse storage_get with specific key', () => { it('should parse storage_get with specific key', () => {
const result = parseCommand(cmd({ id: '1', action: 'storage_get', type: 'local', key: 'mykey' })); const result = parseCommand(
cmd({ id: '1', action: 'storage_get', type: 'local', key: 'mykey' })
);
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.command.key).toBe('mykey'); expect(result.command.key).toBe('mykey');
@@ -426,14 +428,16 @@ describe('parseCommand', () => {
}); });
it('should parse snapshot with all options', () => { it('should parse snapshot with all options', () => {
const result = parseCommand(cmd({ const result = parseCommand(
id: '1', cmd({
action: 'snapshot', id: '1',
interactive: true, action: 'snapshot',
compact: true, interactive: true,
maxDepth: 5, compact: true,
selector: '.content', maxDepth: 5,
})); selector: '.content',
})
);
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.command.interactive).toBe(true); expect(result.command.interactive).toBe(true);
@@ -487,7 +491,9 @@ describe('parseCommand', () => {
describe('scroll', () => { describe('scroll', () => {
it('should parse scroll command', () => { it('should parse scroll command', () => {
const result = parseCommand(cmd({ id: '1', action: 'scroll', direction: 'down', amount: 300 })); const result = parseCommand(
cmd({ id: '1', action: 'scroll', direction: 'down', amount: 300 })
);
expect(result.success).toBe(true); expect(result.success).toBe(true);
}); });
@@ -521,7 +527,9 @@ describe('parseCommand', () => {
}); });
it('should parse geolocation', () => { it('should parse geolocation', () => {
const result = parseCommand(cmd({ id: '1', action: 'geolocation', latitude: 37.7749, longitude: -122.4194 })); const result = parseCommand(
cmd({ id: '1', action: 'geolocation', latitude: 37.7749, longitude: -122.4194 })
);
expect(result.success).toBe(true); expect(result.success).toBe(true);
}); });
@@ -572,7 +580,9 @@ describe('parseCommand', () => {
}); });
it('should parse dialog accept with prompt text', () => { it('should parse dialog accept with prompt text', () => {
const result = parseCommand(cmd({ id: '1', action: 'dialog', response: 'accept', promptText: 'hello' })); const result = parseCommand(
cmd({ id: '1', action: 'dialog', response: 'accept', promptText: 'hello' })
);
expect(result.success).toBe(true); expect(result.success).toBe(true);
if (result.success) { if (result.success) {
expect(result.command.promptText).toBe('hello'); expect(result.command.promptText).toBe('hello');
+18 -21
View File
@@ -173,10 +173,10 @@ function processAriaTree(ariaTree: string, refs: RefMap, options: SnapshotOption
for (const line of lines) { for (const line of lines) {
const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/); const match = line.match(/^(\s*-\s*)(\w+)(?:\s+"([^"]*)")?(.*)$/);
if (!match) continue; if (!match) continue;
const [, , role, name, suffix] = match; const [, , role, name, suffix] = match;
const roleLower = role.toLowerCase(); const roleLower = role.toLowerCase();
if (INTERACTIVE_ROLES.has(roleLower)) { if (INTERACTIVE_ROLES.has(roleLower)) {
const ref = nextRef(); const ref = nextRef();
refs[ref] = { refs[ref] = {
@@ -184,12 +184,12 @@ function processAriaTree(ariaTree: string, refs: RefMap, options: SnapshotOption
role: roleLower, role: roleLower,
name, name,
}; };
let enhanced = `- ${role}`; let enhanced = `- ${role}`;
if (name) enhanced += ` "${name}"`; if (name) enhanced += ` "${name}"`;
enhanced += ` [ref=${ref}]`; enhanced += ` [ref=${ref}]`;
if (suffix && suffix.includes('[')) enhanced += suffix; if (suffix && suffix.includes('[')) enhanced += suffix;
result.push(enhanced); result.push(enhanced);
} }
} }
@@ -223,11 +223,7 @@ function getIndentLevel(line: string): number {
/** /**
* Process a single line: add ref if needed, filter if requested * Process a single line: add ref if needed, filter if requested
*/ */
function processLine( function processLine(line: string, refs: RefMap, options: SnapshotOptions): string | null {
line: string,
refs: RefMap,
options: SnapshotOptions
): string | null {
const depth = getIndentLevel(line); const depth = getIndentLevel(line);
// Check max depth // Check max depth
@@ -302,27 +298,27 @@ function processLine(
function compactTree(tree: string): string { function compactTree(tree: string): string {
const lines = tree.split('\n'); const lines = tree.split('\n');
const result: string[] = []; const result: string[] = [];
// Simple pass: keep lines that have content or refs // Simple pass: keep lines that have content or refs
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
// Always keep lines with refs // Always keep lines with refs
if (line.includes('[ref=')) { if (line.includes('[ref=')) {
result.push(line); result.push(line);
continue; continue;
} }
// Keep lines with text content (after :) // Keep lines with text content (after :)
if (line.includes(':') && !line.endsWith(':')) { if (line.includes(':') && !line.endsWith(':')) {
result.push(line); result.push(line);
continue; continue;
} }
// Check if this structural element has children with refs // Check if this structural element has children with refs
const currentIndent = getIndentLevel(line); const currentIndent = getIndentLevel(line);
let hasRelevantChildren = false; let hasRelevantChildren = false;
for (let j = i + 1; j < lines.length; j++) { for (let j = i + 1; j < lines.length; j++) {
const childIndent = getIndentLevel(lines[j]); const childIndent = getIndentLevel(lines[j]);
if (childIndent <= currentIndent) break; if (childIndent <= currentIndent) break;
@@ -331,12 +327,12 @@ function compactTree(tree: string): string {
break; break;
} }
} }
if (hasRelevantChildren) { if (hasRelevantChildren) {
result.push(line); result.push(line);
} }
} }
return result.join('\n'); return result.join('\n');
} }
@@ -359,17 +355,18 @@ export function parseRef(arg: string): string | null {
/** /**
* Get snapshot statistics * Get snapshot statistics
*/ */
export function getSnapshotStats(tree: string, refs: RefMap): { export function getSnapshotStats(
tree: string,
refs: RefMap
): {
lines: number; lines: number;
chars: number; chars: number;
tokens: number; tokens: number;
refs: number; refs: number;
interactive: number; interactive: number;
} { } {
const interactive = Object.values(refs).filter(r => const interactive = Object.values(refs).filter((r) => INTERACTIVE_ROLES.has(r.role)).length;
INTERACTIVE_ROLES.has(r.role)
).length;
return { return {
lines: tree.split('\n').length, lines: tree.split('\n').length,
chars: tree.length, chars: tree.length,