PRACTICAL GUIDE / Playwright locator highlight custom style

Make Playwright locator highlights impossible to miss

Learn to style Playwright's locator overlay, capture useful evidence, clean it up safely, and avoid treating a visual debug aid as an assertion.

By The Testing AcademyUpdated August 4, 20269 min read
All field guides
In this guide5 sections
  1. What Playwright actually highlights
  2. Build a screenshot-worthy highlight
  3. Read the evidence before changing the locator
  4. Choose the fix that matches the failure
  5. Keep the debug aid out of product assertions

What you will learn

  • What Playwright actually highlights
  • Build a screenshot-worthy highlight
  • Read the evidence before changing the locator
  • Choose the fix that matches the failure

A checkout test clicks the wrong button, yet the locator looks reasonable in code. You add a highlight and still cannot tell which pale outline belongs to the target in a crowded screenshot. The useful fix is not a louder selector. It is a deliberate visual marker, paired with evidence that proves what the locator matched.

What Playwright actually highlights

locator.highlight() asks Playwright to draw an overlay around every element currently matched by the locator. It does not add a class to the application element, and it does not change the selector. The locator is still resolved against the live page when the call runs.

That distinction matters on reactive pages. A locator can identify one button before a re-render and a different button afterward if its text or surrounding structure changes. The overlay only tells you what matched at the instant it was drawn. It is a debugging observation, not a permanent identity tag.

Custom styling arrived in Playwright 1.60 through the style option. The option accepts either an inline CSS string or an object of CSS properties. Earlier versions support the basic highlight call but do not accept this option. If an editor reports that style is unknown, inspect the installed @playwright/test version before changing the code or silencing TypeScript.

The method returns a Disposable. Calling dispose() removes the associated overlay. Playwright also provides locator.hideHighlight(), but the returned handle is easier to reason about when several helpers can create highlights. The code that creates the marker owns its cleanup.

Two other details catch teams out:

  • All matches are highlighted. A bright box around three buttons is evidence of locator ambiguity, not a rendering defect.
  • Highlighting does not run an assertion. A detached target, wrong accessible name, or disabled control still needs a normal Playwright assertion that can fail the test.

Use the overlay to answer, "What did this locator point at?" Use expect to answer, "Was the product in the required state?"

Build a screenshot-worthy highlight

The following test is self-contained. Save it as locator-highlight.spec.ts in a Playwright Test project using version 1.60 or newer.

TypeScript
import { expect, test } from '@playwright/test';

test('captures the intended checkout control', async ({ page }, testInfo) => {
  await page.setContent(`
    <main>
      <h1>Basket</h1>
      <button type="button">Continue shopping</button>
      <button type="button">Checkout</button>
    </main>
  `);

  const checkout = page.getByRole('button', { name: 'Checkout', exact: true });
  await expect(checkout).toHaveCount(1);

  const marker = await checkout.highlight({
    style: [
      'outline: 4px solid #ffb000',
      'outline-offset: 4px',
      'background: rgba(255, 176, 0, 0.22)',
      'box-shadow: 0 0 0 8px rgba(255, 176, 0, 0.18)',
    ].join(';'),
  });

  try {
    const screenshotPath = testInfo.outputPath('checkout-target.png');
    await page.screenshot({ path: screenshotPath, fullPage: true });
    await testInfo.attach('checkout-target', {
      path: screenshotPath,
      contentType: 'image/png',
    });
  } finally {
    await marker.dispose();
  }

  await expect(checkout).toBeEnabled();
});

Three choices make this example dependable. First, toHaveCount(1) separates selector ambiguity from styling. Second, the screenshot is taken while the overlay exists. Third, cleanup sits in finally, so an attachment error cannot leave a marker active for later steps in the same page.

The amber fill is translucent on purpose. An opaque background can hide the button label, disabled appearance, validation message, or element covering the target. A thick outline gives reviewers a clear boundary while preserving the product state underneath it.

Run the focused test in a visible browser when tuning the style:

Shell
npx playwright test locator-highlight.spec.ts --headed --workers=1 --trace on

Headed mode lets you watch the overlay. The attached PNG records the exact marked frame. The trace provides the surrounding actions and DOM snapshots, but the PNG remains the clearest evidence of the custom color because it is captured while the marker is active.

Read the evidence before changing the locator

A missing or surprising box can come from four different failures. Treating them all as a selector problem wastes time.

Start with the count assertion. If it reports zero, the locator did not resolve. Check whether the page is in the expected frame, whether navigation completed, and whether the accessible name changed. The style is irrelevant because there was nothing to paint.

If the count is greater than one, open the screenshot and inspect every marked element. Repeated labels are common in responsive menus, sticky headers, and duplicated desktop/mobile markup. Prefer a locator rooted in the relevant region, such as a dialog or form, rather than adding .first() to make the failure disappear. .first() chooses by document order, which is often the unstable detail that caused the bug.

When the count is one but the wrong control is marked, inspect the locator's meaning. A text selector may match a container that includes the requested words, while a role locator targets the interactive control with that accessible name. Use the trace's DOM snapshot and accessibility details to see what the browser exposed. Do not infer the answer from pixels alone.

Finally, a correct element with no visible custom color usually points to a version or capture-timing issue. Confirm the package version, make sure the screenshot occurs before dispose(), and reproduce with one worker. Parallel tests should have isolated pages, but reducing concurrency removes unrelated animation and report noise while you verify the marker.

The strongest evidence set is small: the locator count, one marked screenshot, the assertion error, and the trace for the same attempt. Ten unlabelled screenshots make correlation harder, not easier.

Compare the viewport as well as the element. A fixed header can cover a correctly matched control, and a responsive breakpoint can render a second navigation tree. The box answers where the target is, while the screenshot answers whether a user could make sense of it. If the marker sits outside the captured viewport or under an overlay, inspect scrolling and obstruction before rewriting the locator.

Keep the test attempt in the attachment name when retries are enabled. A screenshot from the passing retry cannot explain what the first attempt matched. Playwright associates attachments with individual results, but exported files and defect trackers often lose that surrounding hierarchy. A name such as checkout-target-retry-1.png survives that move better than screenshot.png.

Choose the fix that matches the failure

An old Playwright version has a straightforward fix: upgrade to 1.60 or newer if the repository can absorb it. The cost is broader than one type definition. Browser binaries and CI caches must move with the package, and release notes may contain behavior changes that deserve a focused regression run. If the upgrade cannot happen now, keep the default highlight instead of injecting test-only classes into the application DOM.

An ambiguous locator needs a semantic fix. Scope the search to a stable component, dialog, row, or landmark, then assert that the result is unique. The trade-off is additional locator code and, sometimes, a request for better accessible names or test IDs from the product team. That cost buys a contract the reader can understand.

A hard-to-see overlay needs a visual fix. Choose a high-contrast outline that remains visible on both light and dark surfaces. Keep opacity low enough to preserve the UI beneath it. The trade-off is that a team-wide color scheme requires documentation. If red means failure in one helper and merely "selected" in another, screenshots become misleading.

An evidence gap needs a reporting fix. Attach one named screenshot to the failing attempt and include the expected target in the attachment name. Storage grows with every attachment, so capture these images only around disputed locators or behind a diagnostic mode. A permanent screenshot after every action is expensive and rarely reviewed.

If a team uses colors consistently, write down their meaning. One workable convention is amber for the element a test intends to use and red for an element that unexpectedly also matched. Create each highlight separately, keep both disposable handles, and remove both after one comparison screenshot. The extra setup is worthwhile only when reviewers regularly compare candidate locators. Without a legend, two colored boxes merely add decoration.

Dynamic pages introduce another cost. The overlay marks the geometry resolved when highlight() runs, but an animation, virtualized list, or layout shift can move the underlying control before the screenshot. Keep the interval between highlighting and capture short. If the application is still moving, wait on the product's stable state first. A delay added only to make the box line up hides the same synchronization defect that can break the real action.

Custom CSS should remain simple. An outline, translucent background, and box shadow survive most page designs. Complex filters or opaque fills can change what reviewers perceive, while animation in the marker makes still screenshots arbitrary. The purpose is precise identification, not a visual effect showcase.

Keep the debug aid out of product assertions

The overlay is deliberately outside the user's experience. It does not prove that a control is visible, enabled, reachable by keyboard, or safe to click. It also says nothing about whether the next page loaded correctly. Those claims belong in web-first assertions such as toBeVisible(), toBeEnabled(), or an assertion on the resulting business state.

Avoid committed highlights in ordinary regression paths. They add protocol work, alter screenshots, and can obscure the very visual defect a test is meant to catch. A visual comparison with an active marker will fail for the marker rather than the product.

Do not use a highlight to settle accessibility questions. A box can surround a <div> that has no useful role or name. Inspect the accessible representation and test the behavior a keyboard or assistive-technology user relies on.

Skip custom styling when the default overlay already answers the question during local debugging. A shared helper, attachment convention, and color vocabulary all require maintenance. Add that machinery only when screenshots pass between people, such as CI triage, code review, or a defect report where the target would otherwise be unclear.

Most importantly, never make a passing test depend on whether the overlay looked correct to a human. The screenshot is supporting evidence. The assertion is the decision.

// FIELD DISPATCH

Get the QA Field Notes

Weekly QA battles, AI testing guides, and interview drills. Free on Substack.

// LIVE COURSE / THE TESTING ACADEMY

Playwright Automation Mastery

Go beyond Selenium. Master Playwright with JS/TS in 90 days.

From the instructor behind this guide.

Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 25, 2026 / Reviewed August 4, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Official playwright.dev reference

    playwright.dev

    Primary documentation selected and verified for the claims in this guide.

  2. 02
    Official playwright.dev reference

    playwright.dev

    Primary documentation selected and verified for the claims in this guide.

  3. 03
    Official playwright.dev reference

    playwright.dev

    Primary documentation selected and verified for the claims in this guide.

  4. 04
    Official playwright.dev reference

    playwright.dev

    Primary documentation selected and verified for the claims in this guide.

FAQ / QUICK ANSWERS

Questions testers ask

How do I change the color of a Playwright locator highlight?

Pass a CSS string or property object through the `style` option of `locator.highlight()`. That option requires Playwright 1.60 or newer, so check the installed package before copying the example.

Why does TypeScript reject the style option on locator.highlight()?

The installed Playwright types are probably older than 1.60, when custom highlight styling was added. Upgrade the package and browser binaries together, or use the default highlight until the project can move safely.

Does a highlighted locator prove that the selector is unique?

No. A highlight shows what the locator resolves to at that moment, and one locator may match several elements. Assert the expected count or use a web-first product assertion separately.

How should I remove a custom highlight after taking a screenshot?

Keep the `Disposable` returned by `highlight()` and call `dispose()` in a `finally` block. `locator.hideHighlight()` can also remove a highlight, but the returned handle makes ownership and cleanup explicit.

Will the custom highlight appear in a Playwright screenshot?

A screenshot taken while the overlay is active captures the visual marker. Take it before disposal, then attach it to the test result if reviewers need the image in the HTML report.