browser.ts
1 /** 2 * Pipeline step: navigate, click, type, wait, press, snapshot. 3 * Browser interaction primitives. 4 */ 5 6 import type { IPage } from '../../types.js'; 7 import { render } from '../template.js'; 8 9 import { isRecord } from '../../utils.js'; 10 11 export async function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 12 if (isRecord(params) && 'url' in params) { 13 const url = String(render(params.url, { args, data })); 14 await page!.goto(url, { waitUntil: params.waitUntil as 'load' | 'none' | undefined, settleMs: typeof params.settleMs === 'number' ? params.settleMs : undefined }); 15 } else { 16 const url = render(params, { args, data }); 17 await page!.goto(String(url)); 18 } 19 return data; 20 } 21 22 export async function stepClick(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 23 await page!.click(String(render(params, { args, data })).replace(/^@/, '')); 24 return data; 25 } 26 27 export async function stepType(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 28 if (isRecord(params)) { 29 const ref = String(render(params.ref ?? '', { args, data })).replace(/^@/, ''); 30 const text = String(render(params.text ?? '', { args, data })); 31 await page!.typeText(ref, text); 32 if (params.submit) await page!.pressKey('Enter'); 33 } 34 return data; 35 } 36 37 export async function stepWait(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 38 if (typeof params === 'number') await page!.wait(params); 39 else if (isRecord(params)) { 40 if ('text' in params) { 41 await page!.wait({ 42 text: String(render(params.text, { args, data })), 43 timeout: typeof params.timeout === 'number' ? params.timeout : undefined, 44 }); 45 } else if ('time' in params) await page!.wait(Number(params.time)); 46 } else if (typeof params === 'string') await page!.wait(Number(render(params, { args, data }))); 47 return data; 48 } 49 50 export async function stepPress(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 51 await page!.pressKey(String(render(params, { args, data }))); 52 return data; 53 } 54 55 export async function stepSnapshot(page: IPage | null, params: unknown, _data: unknown, _args: Record<string, unknown>): Promise<unknown> { 56 const opts = isRecord(params) ? params : {}; 57 return page!.snapshot({ 58 interactive: typeof opts.interactive === 'boolean' ? opts.interactive : false, 59 compact: typeof opts.compact === 'boolean' ? opts.compact : false, 60 maxDepth: typeof opts.max_depth === 'number' ? opts.max_depth : undefined, 61 raw: typeof opts.raw === 'boolean' ? opts.raw : false, 62 }); 63 } 64 65 export async function stepEvaluate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> { 66 const js = String(render(params, { args, data })); 67 let result: unknown = await page!.evaluate(js); 68 // Browser may return JSON as a string — auto-parse it 69 if (typeof result === 'string') { 70 const trimmed = result.trim(); 71 if ((trimmed.startsWith('[') && trimmed.endsWith(']')) || (trimmed.startsWith('{') && trimmed.endsWith('}'))) { 72 try { result = JSON.parse(trimmed); } catch {} 73 } 74 } 75 return result; 76 }