Documentation Index
Fetch the complete documentation index at: https://mintlify.com/microsoft/playwright/llms.txt
Use this file to discover all available pages before exploring further.
The Selectors API allows you to register custom selector engines that can be used throughout Playwright to locate elements.
Overview
Playwright supports multiple built-in selector engines (CSS, XPath, text, etc.). You can extend this with custom selector engines to match elements based on your own logic.
import { selectors } from '@playwright/test';
// Register a custom selector engine
await selectors.register('tag', {
query(root, selector) {
return root.querySelector(selector.toUpperCase());
},
queryAll(root, selector) {
return Array.from(root.querySelectorAll(selector.toUpperCase()));
},
});
// Use the custom selector
await page.locator('tag=button').click();
Methods
register(name, script, options)
Register a custom selector engine.
Name of the selector engine. Once registered, you can use it with the syntax name=selector.
script
string | Function | { path?: string, content?: string }
required
Script that evaluates to a selector engine instance. Can be:
- A function that returns a selector engine
- A string containing JavaScript code
- An object with
path to a JavaScript file or content with the code
Whether to run the selector engine script as a content script in the page context. Only applies to Chromium.
Returns: Promise<void>
Selector Engine Interface
Your selector engine must implement:
interface SelectorEngine {
// Find the first matching element
query(root: Element, selector: string): Element | null;
// Find all matching elements
queryAll(root: Element, selector: string): Element[];
}
Example: Tag Name Selector
await selectors.register('tag', () => {
return {
query(root, selector) {
return root.querySelector(selector.toUpperCase());
},
queryAll(root, selector) {
return Array.from(root.querySelectorAll(selector.toUpperCase()));
},
};
});
// Usage
await page.click('tag=button');
Example: Data Attribute Selector
await selectors.register('data', () => {
return {
query(root, selector) {
return root.querySelector(`[data-test="${selector}"]`);
},
queryAll(root, selector) {
return Array.from(root.querySelectorAll(`[data-test="${selector}"]`));
},
};
});
// Usage
await page.locator('data=submit-button').click();
Example: Loading from File
await selectors.register('custom', {
path: './my-selector-engine.js',
});
setTestIdAttribute(attributeName)
Set the attribute name to use for getByTestId() locators.
Attribute name to use for test ID selectors
Returns: void
By default, Playwright uses data-testid attribute. Change it to match your project’s convention:
import { selectors } from '@playwright/test';
// Use 'data-test' instead of 'data-testid'
selectors.setTestIdAttribute('data-test');
// Now this will look for data-test="my-button"
await page.getByTestId('my-button').click();
You can also configure this in playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
testIdAttribute: 'data-test',
},
});
Complete Example
import { test, selectors } from '@playwright/test';
// Register selector engine before tests run
test.beforeAll(async () => {
// Register a custom selector for aria-label
await selectors.register('label', () => {
return {
query(root, selector) {
return root.querySelector(`[aria-label="${selector}"]`);
},
queryAll(root, selector) {
return Array.from(
root.querySelectorAll(`[aria-label="${selector}"]`)
);
},
};
});
// Change test ID attribute
selectors.setTestIdAttribute('data-qa');
});
test('use custom selectors', async ({ page }) => {
await page.goto('https://example.com');
// Use custom 'label' selector
await page.click('label=Submit');
// Use modified test ID attribute
await page.getByTestId('login-button').click(); // Looks for data-qa
});
Best Practices
Selector Engine Naming
- Use descriptive names that indicate what the selector matches
- Avoid names that conflict with built-in engines (css, xpath, text, etc.)
- Use lowercase names for consistency
Error Handling
await selectors.register('safe', () => {
return {
query(root, selector) {
try {
return root.querySelector(selector);
} catch (e) {
console.error('Selector error:', e);
return null;
}
},
queryAll(root, selector) {
try {
return Array.from(root.querySelectorAll(selector));
} catch (e) {
console.error('Selector error:', e);
return [];
}
},
};
});
- Keep selector logic simple and fast
- Avoid expensive computations in
queryAll
- Cache results when appropriate
Limitations
- Custom selectors cannot be registered after tests have started
- Each selector engine name can only be registered once
- Content script mode only works in Chromium