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
Original file line number Diff line number Diff line change
Expand Up @@ -427,16 +427,25 @@ class AttachedPage {

async screenshot(): Promise<{ data: string; viewportWidth: number; viewportHeight: number, ariaSnapshot: string }> {
const buffer = await this._page.screenshot({ type: 'png' });
const vp = this._page.viewportSize();
const vp = await this._viewportSize();
const ariaSnapshot = await this._page.ariaSnapshot({ boxes: true, mode: 'ai' });
return {
data: buffer.toString('base64'),
viewportWidth: vp?.width ?? 0,
viewportHeight: vp?.height ?? 0,
viewportWidth: vp.width,
viewportHeight: vp.height,
ariaSnapshot,
};
}

private async _viewportSize(): Promise<{ width: number; height: number }> {
// Pages whose context was created with `viewport: null` (e.g. headed `playwright-cli open --headed`)
// have no fixed viewport, so `viewportSize()` returns null. Fall back to the live window size.
const vp = this._page.viewportSize();
if (vp)
return vp;
return await this._page.evaluate(() => ({ width: window.innerWidth, height: window.innerHeight }));
}

private async _startScreencast(page: api.Page) {
await page.screencast.start({
onFrame: ({ data }: { data: Buffer }) => {
Expand Down
37 changes: 37 additions & 0 deletions tests/mcp/dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,43 @@ test('should annotate via direct browser_annotate MCP call', async ({ connectToD
expect(text).toMatch(/- \[Annotation image\]\(.*\.png\)/);
});

test('should annotate when context has no fixed viewport', async ({ connectToDashboard, boundBrowser, startClient, cliEnv, server }) => {
// Simulates headed `playwright-cli open --headed`, which launches with viewport: null
// so that the browser window controls the page size. https://github.com/microsoft/playwright/issues/40565
const context = await boundBrowser.newContext({ viewport: null });
const page = await context.newPage();
expect(page.viewportSize()).toBe(null);
await page.goto(server.EMPTY_PAGE);

const bindTitle = `--playwright-internal--${crypto.randomUUID()}`;
const { client } = await startClient({
args: ['--endpoint=default', '--caps=devtools'],
env: {
...cliEnv,
PWTEST_DASHBOARD_APP_BIND_TITLE: bindTitle,
},
});

const annotatePromise = client.callTool({ name: 'browser_annotate' });
let done = false;
void annotatePromise.then(() => { done = true; });

const browser = await connectToDashboard(bindTitle);
try {
const dashboard = browser.contexts()[0].pages()[0];
await expect(dashboard.getByRole('main', { name: 'Dashboard: annotate' })).toBeVisible();
await drawAndSubmitAnnotation(dashboard, 'no-viewport');
} finally {
await browser.close().catch(() => {});
}

const result = await annotatePromise;
expect(done).toBe(true);
const text = (result.content as any).map(c => c.text ?? '').join('\n');
expect(text).toMatch(/\{ x: \d+, y: \d+, width: \d+, height: \d+ \}: no-viewport/);
expect(text).toMatch(/- \[Annotation image\]\(.*\.png\)/);
});

test('should cancel browser_annotate when the MCP request is aborted', async ({ connectToDashboard, boundBrowser, startClient, cliEnv, server }) => {
const page = await boundBrowser.newPage();
await page.goto(server.EMPTY_PAGE);
Expand Down
Loading