lecture 7 of 170 completed

Locator strategy and making the app testable

At scale, locators are a policy rather than a skill. And the biggest wins come from changing the application, not the tests.

Four hundred brittle selectors, no test IDs, and a redesign next quarter. This is one of the most common situations a senior engineer inherits, and the fix is only half technical.

The technical half: a locator hierarchy, written down.

Playwright's own guidance is user-facing locators first, and at scale that needs to become a policy rather than a preference:

1. Role and accessible name. getByRole('button', { name: 'Save contact' }). Best default. Stable, readable, and it fails when a screen reader user would also struggle, which means your suite checks accessibility for free.

2. Label, placeholder, text. For form fields and content, matching how users identify things.

3. Test ID. getByTestId('save-contact'). Most stable of all, and it requires cooperation from developers.

4. CSS. Only for structural containers with meaningful class names.

5. XPath. Effectively never. If you need it, something upstream is wrong.

The policy is not the list. The policy is: which of these is required, who enforces it, and what happens in review when someone uses a nth-child selector. A hierarchy nobody enforces is a preference.

Making it enforceable. Configure the test ID attribute so it matches whatever your application already uses:

use: { testIdAttribute: 'data-qa' },

Then add a lint rule that fails the build on raw CSS positional selectors in spec files. Six weeks is roughly how long an unenforced convention survives; I have watched it decay twice.

Now the half that matters more: changing the application.

The work with the biggest payoff for a senior automation engineer is often not writing tests. It is getting the application changed so tests can be written well. That is a negotiation, and it goes better with three specific arguments.

Argument one: frame it as a shared cost, not a QA request. "Every missing test ID costs roughly two hours of QA time and produces a test that breaks on the next redesign." Time and rework are the units developers and managers both respond to.

Argument two: tie it to accessibility. getByRole works when the application has proper roles and accessible names. If your tests cannot find a button by its name, assistive technology cannot either. That reframes test IDs from a QA nicety into an accessibility improvement with a second benefit, and it usually has a stakeholder already.

Argument three: make it cheap to comply. Do not file a ticket saying "add test IDs everywhere". Add them yourself to the components you need, in a pull request, following the naming convention you propose. Reviewing a working change is far easier than acting on a request.

Roll it out along the seam. Never big-bang. Pick the highest-value flow, add test IDs there, migrate those tests, and show the before and after: how many tests broke in the last redesign versus how many would now. That number is the argument for the next area.

Locators as an API. In a large suite, locators belong to page objects, and page objects expose intent:

class ContactsPage {
  constructor(private page: Page) {}

  row(name: string) {
    return this.page.getByRole('row').filter({ hasText: name });
  }

  deleteButtonFor(name: string) {
    return this.row(name).getByRole('button', { name: 'Delete' });
  }
}

The composition here is the Playwright-specific advantage: row() returns a locator, and deleteButtonFor() scopes within it. Because locators are lazy descriptions rather than captured elements, this composes safely even when the table re-renders.

Strict mode is a feature, not an obstacle. Playwright fails when a locator matches multiple elements. Teams sometimes reach for .first() to silence it. That converts a real ambiguity warning into a test that acts on an arbitrary element. Read the failure and narrow the locator instead.

Remember this: write the hierarchy down and enforce it, then spend your effort making the application testable, because that is where the payoff is.

Reference: Locators.

now prove it

Reading is the setup. Solve these problems in the editor. We break the app on purpose to check your test would catch it.