# Playwright locator map

These are reference snippets for the later automation phase, not a test suite. Navigate to the correct page first. Prefer user-facing role and label locators; data-testid is a stable explicit hook. CSS/XPath are included for learning and can be more coupled to markup.

| Locator | Example | Where |
| --- | --- | --- |
| Role | `page.getByRole('button', { name: 'Create account', exact: true })` | Registration |
| Label | `page.getByLabel('Email address', { exact: true })` | Login / register |
| Placeholder | `page.getByPlaceholder('Search phones, laptops, audio…')` | Header |
| Text | `page.getByText('Order placed successfully!', { exact: true })` | Success page |
| Alt text | `page.getByAltText('Vertex Laptop One', { exact: true })` | Product image; scope when repeated |
| Title | `page.getByTitle('Click to enlarge')` | Detail image |
| Test ID | `page.getByTestId('cart-count')` | Header |
| CSS ID | `page.locator('#search')` | Header |
| CSS class | `page.locator('.product-card')` | Catalog |
| CSS attribute | `page.locator('[data-product-id="volt-001"]')` | Catalog |
| XPath | `page.locator('//input[@id="search"]')` | Header |
| XPath parent | `page.locator('//input[@id="email"]/parent::label')` | Login |
| XPath ancestor | `page.locator('//a[text()="Nova Smartphone One"]/ancestor::article')` | Catalog; exact visible product name |
| XPath following sibling | `page.locator('//td[text()="Nova Smartphone One"]/following-sibling::td[1]')` | Lab inventory table |
| XPath row to button | `page.locator('//tr[td="Vertex Laptop One"]//button')` | Lab inventory table |
| Filtering | `page.getByTestId('product-card').filter({ hasText: 'Nova Smartphone One' })` | Catalog |
| Chaining | `page.locator('[data-product-id="volt-001"]').getByRole('button', { name: 'Add to bag', exact: true })` | Catalog |
| Position | `page.getByTestId('product-card').nth(0)` | Catalog; zero-based, less robust than identity |
| Frame | `page.frameLocator('#support-frame').getByLabel('Message support')` | Locator lab |
| Shadow DOM | `page.locator('volt-offer').getByRole('button', { name: 'Reveal shadow offer' })` | Locator lab |

XPath does not pierce shadow roots. Use frameLocator to enter an iframe; ordinary page locators do not automatically enter a frame. Multiple matches require scoping, filtering, or deliberate positional selection. Do not solve strict-mode errors by blindly adding first().

## Stable data-testid hooks

`cart-count`, `product-card`, `product-price`, `product-quantity`, `specifications`, `cart-item`, `order-total`, `place-order`, `order-card`, `order-success`, `practice-name`, `modal-message`, `loaded-offer`, `draggable-product`, `drop-target`.

## Interaction examples for the later phase

```javascript
// Choose a category using a native select.
await page.locator('#filter-category').selectOption({ label: 'Laptops' });
// Select multiple values in the lab.
await page.locator('#multi-category').selectOption(['Phones', 'Gaming']);
// Wait on the resulting UI, rather than a hard-coded timeout.
await page.getByRole('button', { name: 'Load special offer' }).click();
await expect(page.getByTestId('loaded-offer')).toBeVisible();
// Work inside the support frame.
await page.frameLocator('#support-frame').getByLabel('Message support').fill('Where is my order?');
// Upload a local fixture (the demo does not send its bytes anywhere).
await page.getByLabel('Upload a sample receipt').setInputFiles('fixtures/receipt.txt');
// Set a date input in ISO format.
await page.getByLabel('Delivery date', { exact: true }).fill('2026-12-20');
// Drag the lab product into the drop zone.
await page.getByTestId('draggable-product').dragTo(page.getByTestId('drop-target'));
// Register the native-dialog handler BEFORE triggering the dialog.
page.once('dialog', dialog => dialog.accept());
await page.getByRole('button', { name: 'Native confirm', exact: true }).click();
// Start waiting BEFORE triggering a download.
const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Download sample receipt' }).click();
const receipt = await downloadPromise;
// Double-click is separate from an ordinary click.
await page.getByRole('button', { name: 'Double-click me' }).dblclick();
// Open-shadow-root lookup is supported by role locators.
await page.locator('volt-offer').getByRole('button', { name: 'Reveal shadow offer' }).click();
```

Reference: https://playwright.dev/docs/locators and https://playwright.dev/docs/other-locators
