get_test_ids
Discover all test identifiers on the page, grouped by attribute type. Enables reliable element selection for test automation workflows.
Instructions
Discover all test identifiers on the page (data-testid, data-test, data-cy, etc.). Returns a compact text list grouped by attribute type. Essential for test-driven workflows and understanding what elements can be reliably selected. Use the returned test IDs with selector shortcuts like 'testid:submit-button'.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| attributes | No | Comma-separated list of test ID attributes to search for (default: 'data-testid,data-test,data-cy') | |
| showAll | No | If true, display all test IDs without truncation. If false (default), shows first 8 test IDs per attribute with a summary for longer lists. |
Implementation Reference
- The GetTestIdsTool class (extends BrowserToolBase) with the execute() method (lines 71-224) that discovers test identifiers on the page by querying for data-testid, data-test, data-cy attributes, deduplicating, and returning a formatted text output.
export class GetTestIdsTool extends BrowserToolBase { static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { return { name: "get_test_ids", description: "Discover all test identifiers on the page (data-testid, data-test, data-cy, etc.). Returns a compact text list grouped by attribute type. Essential for test-driven workflows and understanding what elements can be reliably selected. Use the returned test IDs with selector shortcuts like 'testid:submit-button'.", annotations: ANNOTATIONS.readOnly, priority: 6, outputs: [ "'Found N test IDs' header or 'Found 0 test IDs' with tips", "For each attribute group: attribute name with count and a compact comma-separated list (or truncated with '... and X more')", "Optional duplicate warnings: attribute:value appears N times", "Suggestion block with best practices and usage tip for selector shortcuts", ], examples: [ "get_test_ids({})", "get_test_ids({ showAll: true })", "get_test_ids({ attributes: 'data-testid,data-cy' })", ], exampleOutputs: [ { call: "get_test_ids({})", output: `Found 5 test IDs:\n\ndata-testid (3):\n submit, email-input, password-input\n\ndata-cy (2):\n navbar, footer\n\nš” Tip: Use these test IDs with selector shortcuts:\n testid:submit ā [data-testid=\"submit\"]` }, { call: "get_test_ids({ showAll: false })", output: `Found 14 test IDs:\n\ndata-testid (12):\n submit, email-input, password-input, remember-me, login-form, link-register, link-forgot, header-title,\n ... and 4 more\n š” Use showAll: true to see all 12 test IDs\n\ndata-cy (2):\n navbar, footer` } ], inputSchema: { type: "object", properties: { attributes: { type: "string", description: "Comma-separated list of test ID attributes to search for (default: 'data-testid,data-test,data-cy')" }, showAll: { type: "boolean", description: "If true, display all test IDs without truncation. If false (default), shows first 8 test IDs per attribute with a summary for longer lists." } }, required: [], }, }; } /** * Execute the test ID discovery tool */ async execute(args: any, context: ToolContext): Promise<ToolResponse> { return this.safeExecute(context, async (page) => { const attributes = args.attributes ? args.attributes.split(',').map((a: string) => a.trim()) : ['data-testid', 'data-test', 'data-cy']; const showAll = args.showAll === true; try { // Discover all test IDs on the page const discoveryData = await page.evaluate((attrs: string[]) => { const byAttribute: { [key: string]: string[] } = {}; const duplicates: { [key: string]: { [value: string]: number } } = {}; let totalCount = 0; attrs.forEach((attr) => { const elements = document.querySelectorAll(`[${attr}]`); const values: string[] = []; const counts: { [value: string]: number } = {}; elements.forEach((el) => { const value = el.getAttribute(attr); if (value) { values.push(value); totalCount++; // Track duplicates counts[value] = (counts[value] || 0) + 1; } }); if (values.length > 0) { byAttribute[attr] = values; // Store duplicates (values that appear more than once) const attrDuplicates: { [value: string]: number } = {}; Object.entries(counts).forEach(([value, count]) => { if (count > 1) { attrDuplicates[value] = count; } }); if (Object.keys(attrDuplicates).length > 0) { duplicates[attr] = attrDuplicates; } } }); return { totalCount, byAttribute, duplicates, }; }, attributes); // Format compact text output const lines: string[] = []; if (discoveryData.totalCount === 0) { lines.push('Found 0 test IDs'); lines.push(''); lines.push('ā No test ID attributes found on this page.'); lines.push(''); lines.push('Searched for:'); attributes.forEach((attr) => { lines.push(` ⢠${attr}`); }); lines.push(''); lines.push('Suggestions:'); lines.push(' - Use inspect_dom to see page structure'); lines.push(' - Consider adding test IDs to interactive elements'); lines.push(' - Example: <button data-testid="submit-button">Submit</button>'); } else { lines.push(`Found ${discoveryData.totalCount} test ID${discoveryData.totalCount > 1 ? 's' : ''}:`); lines.push(''); // Group by attribute type Object.entries(discoveryData.byAttribute).forEach(([attr, values]) => { lines.push(`${attr} (${values.length}):`); // Format values in a compact way if (showAll || values.length <= 10) { // Show all if requested or if 10 or fewer lines.push(` ${values.join(', ')}`); } else { // Show first 8, then indicate more const shown = values.slice(0, 8); const remaining = values.length - 8; lines.push(` ${shown.join(', ')},`); lines.push(` ... and ${remaining} more`); lines.push(` š” Use showAll: true to see all ${values.length} test IDs`); } lines.push(''); }); // Add duplicate warnings const hasDuplicates = Object.keys(discoveryData.duplicates).length > 0; if (hasDuplicates) { lines.push('ā Warning: Duplicate test IDs found (test IDs should be unique):'); lines.push(''); Object.entries(discoveryData.duplicates).forEach(([attr, dups]) => { Object.entries(dups).forEach(([value, count]) => { lines.push(` ${attr}: "${value}" appears ${count} times`); }); }); lines.push(''); lines.push('ā Impact of Duplicate Test IDs:'); lines.push(' - Flaky tests (selectors match multiple elements)'); lines.push(' - Ambiguous interactions (which element to click?)'); lines.push(' - Test automation will fail or behave unpredictably'); lines.push(''); lines.push('š§ How to Fix:'); lines.push(' 1. Use query_selector_all to locate all duplicates'); // Add example for the first duplicate const firstDupAttr = Object.keys(discoveryData.duplicates)[0]; const firstDupValue = Object.keys(discoveryData.duplicates[firstDupAttr])[0]; const firstDupCount = discoveryData.duplicates[firstDupAttr][firstDupValue]; if (firstDupAttr === 'data-testid') { lines.push(` query_selector_all({ selector: "testid:${firstDupValue}" })`); } else { lines.push(` query_selector_all({ selector: "[${firstDupAttr}='${firstDupValue}']" })`); } lines.push(' 2. Identify which elements should keep the test ID'); lines.push(' 3. Rename duplicates to be unique and descriptive'); lines.push(` Example: "${firstDupValue}" ā "${firstDupValue}-primary", "${firstDupValue}-mobile"`); lines.push(' 4. If one is hidden/unused, consider removing it entirely'); lines.push(''); lines.push('š” Best Practice: Test IDs must be unique across the entire page'); lines.push(''); } // Add usage tip lines.push('š” Tip: Use these test IDs with selector shortcuts:'); const firstAttr = Object.keys(discoveryData.byAttribute)[0]; const firstValue = discoveryData.byAttribute[firstAttr][0]; if (firstAttr === 'data-testid') { lines.push(` testid:${firstValue} ā [data-testid="${firstValue}"]`); } else { lines.push(` ${firstAttr}:${firstValue} ā [${firstAttr}="${firstValue}"]`); } } return createSuccessResponse(lines.join('\n')); } catch (error) { return createErrorResponse(`Failed to discover test IDs: ${(error as Error).message}`); } }); } } - Tool metadata including name='get_test_ids', description, inputSchema with 'attributes' (string) and 'showAll' (boolean) parameters, and example outputs.
static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { return { name: "get_test_ids", description: "Discover all test identifiers on the page (data-testid, data-test, data-cy, etc.). Returns a compact text list grouped by attribute type. Essential for test-driven workflows and understanding what elements can be reliably selected. Use the returned test IDs with selector shortcuts like 'testid:submit-button'.", annotations: ANNOTATIONS.readOnly, priority: 6, outputs: [ "'Found N test IDs' header or 'Found 0 test IDs' with tips", "For each attribute group: attribute name with count and a compact comma-separated list (or truncated with '... and X more')", "Optional duplicate warnings: attribute:value appears N times", "Suggestion block with best practices and usage tip for selector shortcuts", ], examples: [ "get_test_ids({})", "get_test_ids({ showAll: true })", "get_test_ids({ attributes: 'data-testid,data-cy' })", ], exampleOutputs: [ { call: "get_test_ids({})", output: `Found 5 test IDs:\n\ndata-testid (3):\n submit, email-input, password-input\n\ndata-cy (2):\n navbar, footer\n\nš” Tip: Use these test IDs with selector shortcuts:\n testid:submit ā [data-testid=\"submit\"]` }, { call: "get_test_ids({ showAll: false })", output: `Found 14 test IDs:\n\ndata-testid (12):\n submit, email-input, password-input, remember-me, login-form, link-register, link-forgot, header-title,\n ... and 4 more\n š” Use showAll: true to see all 12 test IDs\n\ndata-cy (2):\n navbar, footer` } ], inputSchema: { type: "object", properties: { attributes: { type: "string", description: "Comma-separated list of test ID attributes to search for (default: 'data-testid,data-test,data-cy')" }, showAll: { type: "boolean", description: "If true, display all test IDs without truncation. If false (default), shows first 8 test IDs per attribute with a summary for longer lists." } }, required: [], }, }; } - src/tools/browser/register.ts:29-80 (registration)Import of GetTestIdsTool and its inclusion in BROWSER_TOOL_CLASSES array (line 80) which is registered via registry.ts (line 65).
import { GetTestIdsTool } from './inspection/get_test_ids.js'; import { QuerySelectorTool } from './inspection/query_selector.js'; import { FindByTextTool } from './inspection/find_by_text.js'; import { CheckVisibilityTool } from './inspection/check_visibility.js'; import { CompareElementAlignmentTool } from './inspection/compare_element_alignment.js'; import { InspectAncestorsTool } from './inspection/inspect_ancestors.js'; import { ElementExistsTool } from './inspection/element_exists.js'; import { MeasureElementTool } from './inspection/measure_element.js'; import { GetComputedStylesTool } from './inspection/get_computed_styles.js'; // Evaluation import { EvaluateTool } from './evaluation/evaluate.js'; // Console import { GetConsoleLogsTool, ClearConsoleLogsTool } from './console/get_console_logs.js'; // Network import { ListNetworkRequestsTool } from './network/list_network_requests.js'; import { GetRequestDetailsTool } from './network/get_request_details.js'; // Waiting import { WaitForElementTool } from './waiting/wait_for_element.js'; import { WaitForNetworkIdleTool } from './waiting/wait_for_network_idle.js'; export const BROWSER_TOOL_CLASSES: ToolClass[] = [ // Navigation (5) NavigateTool, GoHistoryTool, ScrollToElementTool, ScrollByTool, // Lifecycle (2) CloseTool, SetColorSchemeTool, // Interaction (7) ClickTool, FillTool, SelectTool, HoverTool, UploadFileTool, DragTool, PressKeyTool, // Content (3) ScreenshotTool, GetTextTool, GetHtmlTool, // Inspection (10) InspectDomTool, GetTestIdsTool, - TestIdDiscoveryResult interface defining the shape of discovery data: totalCount, byAttribute, and duplicates.
interface TestIdDiscoveryResult { totalCount: number; byAttribute: { [attribute: string]: string[]; }; duplicates: { [attribute: string]: { [value: string]: number; }; }; }