Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/playwright-core/src/tools/cli-client/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,12 @@ export async function program(options?: { embedderVersion?: string}) {
return;
}
if (args.annotate) {
const dashboard = spawn(process.execPath, daemonArgs, { detached: true, stdio: 'ignore' });
dashboard.unref();
const annotate = spawn(process.execPath, [...daemonArgs, '--annotate'], { stdio: 'inherit' });
await new Promise<void>(resolve => annotate.on('exit', () => resolve()));
const entry = registry.entry(clientInfo, sessionName);
if (!entry)
output.errorBrowserNotOpenForTool(sessionName);
args.raw = true;
const text = await runInSession(entry, clientInfo, args, output);
output.toolResult(text);
return;
}
const foreground = args.port !== undefined;
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/tools/cli-client/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ playwright-cli video-start video.webm
playwright-cli video-chapter "Chapter Title" --description="Details" --duration=2000
playwright-cli video-stop

# launch the dashboard with annotation prompt to ask the user for input
# launch the dashboard for UI review / design feedback — user annotates the page, you receive the annotated screenshot, snapshot, and notes
playwright-cli show --annotate

# generate a Playwright locator for an element from its ref or selector
Expand Down Expand Up @@ -367,7 +367,7 @@ playwright-cli close

## Example: Interactive session

Ask the user to annotate the UI. User can provide contextual tasks or ask contextual questions using annotations:
Ask the user for UI review or design feedback. The user draws boxes on the live page and types comments; you receive the annotated screenshot, the snapshot of the marked region, and the user's notes. Use this whenever the user asks for "UI review", "design feedback", or to "ask the user what they think / want / mean":

```bash
playwright-cli open https://example.com
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -973,14 +973,15 @@ const dashboardShow = declareCommand({
name: 'show',
description: 'Show Playwright Dashboard',
category: 'devtools',
raw: true,
args: z.object({}),
options: z.object({
port: numberArg.optional().describe('Start as a blocking HTTP server on this port (use 0 for a random port)'),
host: z.string().optional().describe('Host to bind to when using --port (defaults to localhost)'),
annotate: z.boolean().optional().describe('Switch the dashboard into annotation mode.'),
kill: z.boolean().optional().describe('Kill the dashboard daemon.'),
}),
toolName: '',
toolName: ({ annotate }) => annotate ? 'browser_annotate' : '',
toolParams: () => ({}),
});

Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/tools/cli-daemon/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export async function startCliDaemonServer(

const server = net.createServer(socket => {
const connection = new SocketConnection(socket);
const abortController = new AbortController();
connection.onclose = () => abortController.abort();
connection.onmessage = async message => {
const { id, method, params } = message;
try {
Expand All @@ -91,7 +93,7 @@ export async function startCliDaemonServer(
} else if (method === 'run') {
const { toolName, toolParams } = parseCliCommand(params.args);
toolParams._meta = { cwd: params.cwd, raw: params.raw || params.json, json: !!params.json };
const response = await backend.callTool(toolName, toolParams);
const response = await backend.callTool(toolName, toolParams, abortController.signal);
await connection.send({ id, result: formatResult(response) });
} else {
throw new Error(`Unknown method: ${method}`);
Expand Down
25 changes: 12 additions & 13 deletions tests/mcp/dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,10 @@ async function drawAndSubmitAnnotation(dashboard: import('playwright-core').Page
}

function verifyAnnotateOutput(output: string, expectedText: string, outputDir: string) {
const lines = output.trim().split('\n');
expect(lines[0]).toMatch(new RegExp(`^\\{ x: \\d+, y: \\d+, width: \\d+, height: \\d+ \\}: ${expectedText}$`));
expect(lines[lines.length - 1]).toMatch(/^image: \.playwright-cli[\\/]annotations-.*\.png$/);
const pngRel = lines[lines.length - 1].replace(/^image: /, '');
const pngPath = path.resolve(outputDir, pngRel);
expect(output).toMatch(new RegExp(`\\{ x: \\d+, y: \\d+, width: \\d+, height: \\d+ \\}: ${expectedText}`));
const imageMatch = output.match(/- \[Annotation image\]\((\.playwright-cli[\\/]annotations-.*\.png)\)/);
expect(imageMatch).not.toBeNull();
const pngPath = path.resolve(outputDir, imageMatch![1]);
expect(fs.existsSync(pngPath)).toBe(true);
expect(fs.statSync(pngPath).size).toBeGreaterThan(0);
}
Expand All @@ -184,10 +183,10 @@ test('should capture annotations via show --annotate', async ({ connectToDashboa
});

test('should start dashboard and annotate when no dashboard is running', async ({ connectToDashboard, cli, server }) => {
await cli('open', server.EMPTY_PAGE);

const bindTitle = `--playwright-internal--${crypto.randomUUID()}`;
const annotatePromise = cli('show', '--annotate', { bindTitle });
await cli('open', server.EMPTY_PAGE, { bindTitle });

const annotatePromise = cli('show', '--annotate');
let done = false;
void annotatePromise.finally(() => { done = true; });

Expand All @@ -205,12 +204,12 @@ test('should start dashboard and annotate when no dashboard is running', async (
verifyAnnotateOutput(output, 'hi', test.info().outputDir);
});

test('should enter annotate mode on fresh dashboard.tsx mount with annotate', async ({ connectToDashboard, cli, server }) => {
await cli('-s=first', 'open', server.EMPTY_PAGE);
await cli('-s=second', 'open', server.EMPTY_PAGE);

test('should enter annotate mode on fresh dashboard.tsx mount with -s --annotate', async ({ connectToDashboard, cli, server }) => {
const bindTitle = `--playwright-internal--${crypto.randomUUID()}`;
const annotatePromise = cli('-s=second', 'show', '--annotate', { bindTitle });
await cli('-s=first', 'open', server.EMPTY_PAGE, { bindTitle });
await cli('-s=second', 'open', server.EMPTY_PAGE, { bindTitle });

const annotatePromise = cli('-s=second', 'show', '--annotate');
let done = false;
void annotatePromise.finally(() => { done = true; });

Expand Down
Loading