Sometimes an element has no accessible role, no visible text, no label, and no placeholder. A loading spinner. A container div. A dynamic widget. For these cases, Playwright gives you getByTestId. It finds elements by a special data-testid attribute that developers add specifically for testing.
<!-- Developer adds data-testid to elements that need it -->
<div data-testid="product-card">
<img data-testid="product-image" src="/laptop.jpg" alt="Laptop">
<span data-testid="product-price">$999</span>
<button>Add to Cart</button>
</div>import { test, expect } from '@playwright/test';
test('getByTestId examples', async ({ page }) => {
await page.goto('/shopping');
// Find by data-testid attribute
await expect(page.getByTestId('product-card')).toBeVisible();
await expect(page.getByTestId('product-price')).toHaveText('$999');
// Click an element with test ID
await page.getByTestId('checkout-button').click();
// Test ID on a container -- then find child with role
const card = page.getByTestId('product-card').first();
await card.getByRole('button', { name: 'Add to Cart' }).click();
});Some teams use data-test, data-cy, data-qa, or data-automation-id instead of data-testid. You can configure Playwright to use any attribute name.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
// Change the default test ID attribute
testIdAttribute: 'data-qa',
},
});
// Now getByTestId looks for data-qa instead of data-testid
// page.getByTestId('login-form') matches <form data-qa="login-form">getByTestId is a last resort, not a first choice. Some QA teams put data-testid on every single element. That defeats the purpose. Your tests become tied to test IDs instead of CSS classes -- same fragility, different attribute. Use user-facing locators first. Fall back to getByTestId only when nothing else works.
Coordinate with your development team on a naming convention for test IDs. Use kebab-case: product-card, checkout-button, user-menu. Prefix by feature: cart-total, cart-item-count, cart-empty-message. Consistency saves time.
Q: When would you use getByTestId instead of getByRole?
A: I use getByTestId when an element has no meaningful role, text, or label -- like a container div, a loading spinner, or a third-party chart component. It is also useful when multiple identical elements need distinction, like product cards in a grid. But I always try getByRole, getByText, or getByLabel first. getByTestId requires developer cooperation to add data-testid attributes, and it does not validate accessibility like getByRole does.
Key Point: getByTestId is a fallback for elements without accessible roles or visible text. Configure the attribute name in playwright.config.ts. Use it sparingly.