From d0e5d179d28ce8a4c881509816043369e17dfd3b Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 16 Mar 2026 19:07:27 +0000 Subject: [PATCH] chore(cli): allow both selectors and locators --- .../playwright-core/src/tools/backend/tab.ts | 11 +++++++---- .../src/tools/cli-client/skill/SKILL.md | 10 +++++----- tests/mcp/cli-core.spec.ts | 18 +++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/playwright-core/src/tools/backend/tab.ts b/packages/playwright-core/src/tools/backend/tab.ts index 1570af91752ac..4fb129b9ec979 100644 --- a/packages/playwright-core/src/tools/backend/tab.ts +++ b/packages/playwright-core/src/tools/backend/tab.ts @@ -18,6 +18,7 @@ import url from 'url'; import { EventEmitter } from 'events'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; +import { locatorOrSelectorAsSelector } from '../../utils/isomorphic/locatorParser'; import { ManualPromise } from '../../utils/isomorphic/manualPromise'; import { debug } from '../../utilsBundle'; @@ -438,10 +439,12 @@ export class Tab extends EventEmitter { await this._initializedPromise; return Promise.all(params.map(async param => { if (param.selector) { - const locator = this.page.locator(param.selector); - if (!await locator.isVisible()) - throw new Error(`Selector ${param.selector} does not match any elements.`); - return { locator, resolved: asLocator('javascript', param.selector) }; + const selector = locatorOrSelectorAsSelector('javascript', param.selector, this.context.config.testIdAttribute || 'data-testid'); + const handle = await this.page.$(selector); + if (!handle) + throw new Error(`"${param.selector}" does not match any elements.`); + handle.dispose().catch(() => {}); + return { locator: this.page.locator(selector), resolved: asLocator('javascript', selector) }; } else { try { let locator = this.page.locator(`aria-ref=${param.ref}`); diff --git a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md index 2d417d46789f4..33bc1bc3612f7 100644 --- a/packages/playwright-core/src/tools/cli-client/skill/SKILL.md +++ b/packages/playwright-core/src/tools/cli-client/skill/SKILL.md @@ -222,17 +222,17 @@ playwright-cli snapshot playwright-cli click e15 ``` -You can also use css or role selectors, for example when explicitly asked for it. +You can also use css selectors or Playwright locators. ```bash # css selector playwright-cli click "#main > button.submit" -# role selector -playwright-cli click "role=button[name=Submit]" +# role locator +playwright-cli click "getByRole('button', { name: 'Submit' })" -# chaining css and role selectors -playwright-cli click "#footer >> role=button[name=Submit]" +# test id +playwright-cli click "getByTestId('submit-button')" ``` ## Browser Sessions diff --git a/tests/mcp/cli-core.spec.ts b/tests/mcp/cli-core.spec.ts index 785ba1a189b45..9a86a2bb73874 100644 --- a/tests/mcp/cli-core.spec.ts +++ b/tests/mcp/cli-core.spec.ts @@ -226,31 +226,31 @@ await page.locator('button').click(); \`\`\``); }); -test('click button with role selector', async ({ cli, server }) => { +test('click button with role locator', async ({ cli, server }) => { server.setContent('/', ``, 'text/html'); const { snapshot } = await cli('open', server.PREFIX); expect(snapshot).toContain(`- button "Submit" [ref=e2]`); - const { output, snapshot: clickSnapshot } = await cli('click', 'role=button'); + const { output, snapshot: clickSnapshot } = await cli('click', 'getByRole("button", { name: "Submit" })'); expect(clickSnapshot).toBeTruthy(); expect(output).toContain(`### Ran Playwright code \`\`\`js -await page.locator('role=button').click(); +await page.getByRole('button', { name: 'Submit' }).click(); \`\`\``); }); -test('click button with mixed css + role selector', async ({ cli, server }) => { - server.setContent('/', `
`, 'text/html'); +test('click button with test id locator', async ({ cli, server }) => { + server.setContent('/', `
`, 'text/html'); const { snapshot } = await cli('open', server.PREFIX); expect(snapshot).toContain(`- button "Submit" [ref=e3]`); - const { output, snapshot: clickSnapshot } = await cli('click', '#main >> role=button'); + const { output, snapshot: clickSnapshot } = await cli('click', 'getByTestId("main").getByRole("button")'); expect(clickSnapshot).toBeTruthy(); expect(output).toContain(`### Ran Playwright code \`\`\`js -await page.locator('#main').locator('role=button').click(); +await page.getByTestId('main').getByRole('button').click(); \`\`\``); }); @@ -261,7 +261,7 @@ test('click button with wrong css selector', async ({ cli, server }) => { expect(snapshot).toContain(`- button "Submit" [ref=e2]`); const { output } = await cli('click', '#target'); - expect(output).toContain(`Error: Selector #target does not match any elements.`); + expect(output).toContain(`"#target" does not match any elements.`); }); test('partial snapshot', async ({ cli, server }) => { @@ -277,7 +277,7 @@ test('partial snapshot', async ({ cli, server }) => { expect(strictError).toContain(`strict mode violation`); const { output: noMatchError } = await cli('snapshot', '#target'); - expect(noMatchError).toContain(`Selector "#target" does not match any element`); + expect(noMatchError).toContain(`"#target" does not match any element`); }); test('snapshot depth', async ({ cli, server }) => {