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
46 changes: 46 additions & 0 deletions packages/playwright-core/src/tools/backend/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,54 @@ const videoChapter = defineTool({
},
});

const actionPosition = z.enum(['top-left', 'top', 'top-right', 'bottom-left', 'bottom', 'bottom-right']);

const videoShowActions = defineTool({
capability: 'devtools',

schema: {
name: 'browser_video_show_actions',
title: 'Show action overlays',
description: 'Annotate subsequent actions performed on the page with a callout that names the action and highlights the target element. Useful while video recording or screencasting.',
inputSchema: z.object({
duration: z.number().optional().describe('How long each action annotation stays on screen, in milliseconds. Defaults to 500.'),
position: actionPosition.optional().describe('Where to place the action title relative to the page. Defaults to top-right.'),
}),
type: 'readOnly',
},

handle: async (context, params, response) => {
const tab = context.currentTabOrDie();
await tab.page.screencast.showActions({
duration: params.duration,
position: params.position,
});
response.addTextResult('Action annotations enabled.');
},
});

const videoHideActions = defineTool({
capability: 'devtools',

schema: {
name: 'browser_video_hide_actions',
title: 'Hide action overlays',
description: 'Stop annotating actions performed on the page.',
inputSchema: z.object({}),
type: 'readOnly',
},

handle: async (context, params, response) => {
const tab = context.currentTabOrDie();
await tab.page.screencast.hideActions();
response.addTextResult('Action annotations disabled.');
},
});

export default [
videoStart,
videoStop,
videoChapter,
videoShowActions,
videoHideActions,
];
4 changes: 4 additions & 0 deletions packages/playwright-core/src/tools/cli-client/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ playwright-cli video-start video.webm
playwright-cli video-chapter "Chapter Title" --description="Details" --duration=2000
playwright-cli video-stop

# annotate each subsequent action (click, type, ...) with a callout naming the action and highlighting the target
playwright-cli video-show-actions --duration=600 --position=top-right
playwright-cli video-hide-actions

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

Expand Down
25 changes: 25 additions & 0 deletions packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,29 @@ const videoChapter = declareCommand({
toolParams: ({ title, description, duration }) => ({ title, description, duration }),
});

const actionPositionArg = z.enum(['top-left', 'top', 'top-right', 'bottom-left', 'bottom', 'bottom-right']);

const videoShowActions = declareCommand({
name: 'video-show-actions',
description: 'Annotate subsequent CLI/MCP actions on the page with a callout that names the action and highlights the target element',
category: 'devtools',
args: z.object({}),
options: z.object({
duration: numberArg.optional().describe('How long each action annotation stays on screen, in milliseconds. Defaults to 500.'),
position: actionPositionArg.optional().describe('Where to place the action title: top-left, top, top-right, bottom-left, bottom, bottom-right. Defaults to top-right.'),
}),
toolName: 'browser_video_show_actions',
toolParams: ({ duration, position }) => ({ duration, position }),
});

const videoHideActions = declareCommand({
name: 'video-hide-actions',
description: 'Stop annotating actions performed on the page',
category: 'devtools',
toolName: 'browser_video_hide_actions',
toolParams: () => ({}),
});

const dashboardShow = declareCommand({
name: 'show',
description: 'Show Playwright Dashboard',
Expand Down Expand Up @@ -1201,6 +1224,8 @@ const commandsArray: AnyCommandSchema[] = [
videoStart,
videoStop,
videoChapter,
videoShowActions,
videoHideActions,
dashboardShow,
pauseAt,
resume,
Expand Down
15 changes: 15 additions & 0 deletions tests/mcp/cli-devtools.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,21 @@ test('video-chapter', async ({ cli, server }) => {
await cli('video-stop');
});

test('video-show-actions and video-hide-actions', async ({ cli, server }) => {
await cli('open', server.HELLO_WORLD);
const { output: showOutput } = await cli('video-show-actions', '--duration=200', '--position=bottom-right');
expect(showOutput).toContain('Action annotations enabled.');
const { output: hideOutput } = await cli('video-hide-actions');
expect(hideOutput).toContain('Action annotations disabled.');
});

test('video-show-actions rejects invalid position', async ({ cli, server }) => {
await cli('open', server.HELLO_WORLD);
const { error, exitCode } = await cli('video-show-actions', '--position=middle');
expect(exitCode).not.toBe(0);
expect(error).toContain('position');
});

test('generate-locator', async ({ cli, server }) => {
server.setContent('/', `<button>Submit</button>`, 'text/html');
await cli('open', server.PREFIX);
Expand Down
43 changes: 43 additions & 0 deletions tests/mcp/video.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,49 @@ test('reports missing ffmpeg, not missing browser, when recordVideo is enabled',
});
});

test.describe('action overlays', () => {
test.use({ mcpArgs: ['--caps=devtools'] });

test('browser_video_show_actions and browser_video_hide_actions', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toHaveResponse({
code: expect.stringContaining(`page.goto('http://localhost`),
});

expect(await client.callTool({
name: 'browser_video_show_actions',
arguments: { duration: 200, position: 'bottom-right' },
})).toHaveResponse({
result: 'Action annotations enabled.',
});

expect(await client.callTool({
name: 'browser_video_hide_actions',
arguments: {},
})).toHaveResponse({
result: 'Action annotations disabled.',
});
});

test('browser_video_show_actions rejects invalid position', async ({ client, server }) => {
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.HELLO_WORLD },
})).toHaveResponse({
code: expect.stringContaining(`page.goto('http://localhost`),
});

expect(await client.callTool({
name: 'browser_video_show_actions',
arguments: { position: 'middle' },
})).toHaveResponse({
isError: true,
});
});
});

async function produceFrames(client: Client) {
expect(await client.callTool({
name: 'browser_evaluate',
Expand Down
Loading