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
5 changes: 3 additions & 2 deletions packages/playwright-core/src/tools/backend/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const find = defineTabTool({
inputSchema: z.object({
text: z.string().optional().describe('Plain text to search for in the page snapshot (case-insensitive substring match). Provide either text or regex, not both.'),
regex: z.string().optional().refine(v => !v || isValidRegex(v), { message: 'Invalid regular expression' }).describe('Regular expression to search for in the page snapshot. Matching is case-sensitive by default; wrap the pattern in slashes to add flags, e.g. "/error/i" for case-insensitive. Provide either text or regex, not both.'),
filename: z.string().optional().describe('Save results to a file instead of returning them in the response. Relative file names are resolved against the workspace root.'),
}),
type: 'readOnly',
},
Expand Down Expand Up @@ -69,7 +70,7 @@ const find = defineTabTool({
}

if (!matchedLines.length) {
response.addTextResult(`No matches found for ${query}.`);
await response.addResult('Find results', `No matches found for ${query}.`, { prefix: 'find', ext: 'md', suggestedFilename: params.filename });
return;
}

Expand Down Expand Up @@ -106,7 +107,7 @@ const find = defineTabTool({
return out.join('\n');
});
const matchWord = matchedLines.length === 1 ? 'match' : 'matches';
response.addTextResult(`Found ${matchedLines.length} ${matchWord} for ${query}:\n\n${snippets.join('\n\n----\n\n')}`);
await response.addResult('Find results', `Found ${matchedLines.length} ${matchWord} for ${query}:\n\n${snippets.join('\n\n----\n\n')}`, { prefix: 'find', ext: 'md', suggestedFilename: params.filename });
},
});

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 @@ -398,9 +398,10 @@ const find = declareCommand({
}),
options: z.object({
regex: z.string().optional().describe('Regular expression to search for in the page snapshot. Provide either a text argument or --regex, not both.'),
filename: z.string().optional().describe('Save results to a file instead of returning them in the response.'),
}),
toolName: 'browser_find',
toolParams: ({ text, regex }) => ({ text, regex }),
toolParams: ({ text, regex, filename }) => ({ text, regex, filename }),
});

const generateLocator = declareCommand({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ playwright-cli find "Sign in"
playwright-cli find --regex "Sign (in|up)"
# wrap the regexp in slashes to add flags, e.g. /i for case-insensitive
playwright-cli find --regex "/sign (in|up)/i"
# save results to a file when a query produces too many matches
playwright-cli find "Add" --filename=results.md
playwright-cli eval "document.title"
playwright-cli eval "el => el.textContent" e5
# get element id, class, or any attribute not visible in the snapshot
Expand Down
15 changes: 15 additions & 0 deletions tests/mcp/cli-find.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import fs from 'fs';

import { test, expect } from './cli-fixtures';

const listPage = `
Expand Down Expand Up @@ -58,3 +60,16 @@ test('find reports no matches', async ({ cli, server }) => {
const { output } = await cli('find', 'Pineapples');
expect(output).toContain('No matches found for "Pineapples".');
});

test('find --filename', async ({ cli, server }, testInfo) => {
server.setContent('/', listPage, 'text/html');
await cli('open', server.PREFIX);

const { output } = await cli('find', 'Bananas', '--filename=find.md');
expect(output).toContain('[Find results](./find.md)');
expect(output).not.toContain('Apples');

const content = fs.readFileSync(testInfo.outputPath('find.md'), 'utf-8');
expect(content).toContain('Found 1 match for "Bananas":');
expect(content).toContain('Apples');
});
18 changes: 18 additions & 0 deletions tests/mcp/find.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import fs from 'fs';

import { test, expect } from './fixtures';

const listPage = `
Expand Down Expand Up @@ -229,3 +231,19 @@ test('browser_find rejects invalid regex', async ({ client, server }) => {
isError: true,
});
});

test('browser_find saves results to file', async ({ client, server }, testInfo) => {
server.setContent('/', listPage, 'text/html');
await client.callTool({ name: 'browser_navigate', arguments: { url: server.PREFIX } });

const response = await client.callTool({
name: 'browser_find',
arguments: { text: 'Bananas', filename: 'find.md' },
});
expect(response).toHaveTextResponse(expect.stringContaining('find.md'));
expect(response).toHaveTextResponse(expect.not.stringContaining('Apples'));

const content = await fs.promises.readFile(testInfo.outputPath('find.md'), 'utf8');
expect(content).toContain('Found 1 match for "Bananas":');
expect(content).toContain('Apples');
});
Loading