Playwright Locator Lens
Detects brittle Playwright locators as you write them, suggests resilient semantic alternatives with one-click auto-fixes, and scores your entire test suite on demand. Works with TypeScript, JavaScript, Java, C#, and Python projects. FeaturesMode 1 — Real-Time Analysis (automatic)
Mode 2 — Analyze Current File (on demand)Run via Command Palette to get a detailed line-by-line report for the file you are currently editing. Mode 3 — Full Workspace Audit (on demand)Scan every Playwright file in the project and get an overall project health report. Mode 4 — CLI (standalone, no VS Code required)Run directly from the terminal against any file, glob pattern, or directory. Perfect for CI/CD pipelines. Supports CLI UsageThe CLI works independently of VS Code — no editor needed.
|
| Flag | Effect |
|---|---|
--fix |
Rewrites files in-place, then shows the post-fix score |
--fix --dry-run |
Prints what would change, writes nothing |
Single file output
📄 Playwright Locator Lens — File Report
==========================================
File: tests/login.spec.ts
==========================================
Total locators: 8
✅ Resilient: 3
🟡 Improvable: 4 (auto-fix available in VS Code via 💡)
🔴 Brittle: 1 (manual refactor required)
🟡 Improvable locators:
Line 6: Prefer getByTestId('submit-btn') over locator('[data-testid="submit-btn"]') → .getByTestId('submit-btn')
Line 9: Prefer getByText('Sign in') over locator('text=Sign in') → .getByText('Sign in')
Line 12: Prefer getByPlaceholder('Enter email') over locator('[placeholder="Enter email"]') → .getByPlaceholder('Enter email')
Line 15: Prefer getByLabel('Close') over locator('[aria-label="Close"]') → .getByLabel('Close')
🔴 Brittle locators:
Line 18: Brittle XPath detected: "//div[@class='menu']". Use getByRole() ...
==========================================
Score: 41/100 — Poor
==========================================
Multiple files output
📊 Playwright Locator Lens — Workspace Report
==========================================
Pattern: tests/**/*.spec.ts
Scanned: *.ts, *.spec.ts, *.js, *.spec.js, *.mjs, *.spec.mjs, *.java, *.cs, *.py
(excluding node_modules, dist, out, *.d.ts)
==========================================
Total files: 24
Total locators: 312
✅ Resilient: 187 (59%)
🟡 Improvable: 98 (31%)
🔴 Brittle: 27 (8%)
Worst files (lowest score first):
tests/checkout.spec.ts Score: 23/100 — Critical 🔴 3 brittle 🟡 8 improvable
tests/login.spec.ts Score: 41/100 — Poor 🔴 1 brittle 🟡 4 improvable
tests/dashboard.spec.ts Score: 55/100 — Needs Improvement 🟡 5 improvable
==========================================
Overall Score: 67/100 — Good
==========================================
CLI exit codes
| Code | Meaning |
|---|---|
0 |
Clean — no brittle locators found, threshold met |
1 |
Brittle locators found OR score is below --threshold |
2 |
Usage error (unknown command, missing pattern, --dry-run without --fix) |
Use in CI/CD (GitHub Actions example)
- name: Check Playwright locator quality
run: npx playwright-locator-lens check . --threshold 70
# Or auto-fix before the quality gate
- name: Fix and enforce locator quality
run: npx playwright-locator-lens check . --fix --threshold 80
VS Code Commands
You can also run commands from the Command Palette (Ctrl+Shift+P).
Analyze Current File
Analyses only the file currently open in the editor and prints a line-by-line breakdown in the Output Channel.
Playwright Locator Lens: Analyze Current File
Audit Workspace
Scans all Playwright files in the workspace and prints a project-wide summary in the Output Channel.
Playwright Locator Lens: Audit Workspace
Detection Rules
| # | Severity | Pattern detected | Suggested fix | Auto-fix |
|---|---|---|---|---|
| 1 | 🟡 Warning | locator('[data-testid="x"]') |
getByTestId('x') |
✅ |
| 2 | 🟡 Warning | locator('text=Sign in') |
getByText('Sign in') |
✅ |
| 3 | 🟡 Warning | locator('[placeholder="x"]') |
getByPlaceholder('x') |
✅ |
| 4 | 🟡 Warning | locator('[aria-label="x"]') |
getByLabel('x') |
✅ |
| 5 | 🔴 Error | XPath selectors (// or xpath=) |
Manual refactor | ❌ |
| 6 | 🔴 Error | Brittle CSS chains (nth-child, deep nesting, class chains) | Manual refactor | ❌ |
| 7 | 🟡 Warning | Partial-match selectors (*=, ^=, $=) |
Manual refactor | ❌ |
Rules 1–4 work with all supported languages and quote styles (single, double, and backtick template literals).
Partial-match selectors (Rule 7)
Partial-match CSS attribute operators produce a warning but no auto-fix because getByTestId(), getByLabel(), and getByPlaceholder() only do exact matching — swapping them would silently change your test's behaviour.
| Locator | Problem |
|---|---|
locator('[data-testid*="btn"]') |
contains-match — cannot auto-fix |
locator('[data-testid^="submit"]') |
starts-with match — cannot auto-fix |
locator('[aria-label$="dialog"]') |
ends-with match — cannot auto-fix |
Recommended fix: use an exact data-testid value, or switch to getByRole() / getByText().
Backtick / template literal support
All rules detect locators written with any quote style:
// All three are detected and fixed identically
page.locator('[data-testid="submit"]') // single quote
page.locator("[data-testid='submit']") // double quote
page.locator(`[data-testid="submit"]`) // backtick
// Dynamic values in template literals are also handled
const id = 'submit-btn';
page.locator(`[data-testid="${id}"]`)
// 💡 Fix: .getByTestId(`${id}`)
Resilience Score
| Locator | Points |
|---|---|
getByRole(…) / GetByRole(…) / get_by_role(…) |
100 |
getByLabel(…) / GetByLabel(…) / get_by_label(…) |
90 |
getByPlaceholder(…) / GetByPlaceholder(…) / get_by_placeholder(…) |
85 |
getByText(…) / GetByText(…) / get_by_text(…) |
80 |
getByTestId(…) / GetByTestId(…) / get_by_test_id(…) |
75 |
locator('[data-testid]') / locator('[aria-label]') / locator('[placeholder]') / locator('text=') |
40 |
| XPath / complex CSS | 0 |
Score = average points across all locators in the file.
| Score | Rating |
|---|---|
| 90–100 | Excellent |
| 70–89 | Good |
| 50–69 | Needs Improvement |
| 30–49 | Poor |
| 0–29 | Critical |
Language & File Support
| Language | File types | Import detected | Method style |
|---|---|---|---|
| TypeScript | *.ts, *.spec.ts |
import { test } from '@playwright/test' |
getByTestId() |
| JavaScript | *.js, *.spec.js, *.mjs, *.spec.mjs |
import or require('@playwright/test') |
getByTestId() |
| Java | *.java |
import com.microsoft.playwright.* |
getByTestId() |
| C# | *.cs |
using Microsoft.Playwright |
GetByTestId() |
| Python | *.py |
from playwright.sync_api import / from playwright.async_api import |
get_by_test_id() |
| Path | Status |
|---|---|
*.d.ts |
❌ skipped |
node_modules/** |
❌ skipped |
dist/**, out/** |
❌ skipped |
Analysis only runs when a Playwright import is detected in the file — this prevents false positives on unrelated source files in the same project.
TypeScript / JavaScript example
// 🟡 Detected — auto-fix available (all quote styles supported)
page.locator('[data-testid="submit"]')
page.locator("[data-testid='submit']")
page.locator(`[data-testid="submit"]`)
// 💡 Fix: .getByTestId('submit')
page.locator('text=Sign in')
// 💡 Fix: .getByText('Sign in')
// 🟡 Detected — partial match, no auto-fix
page.locator('[data-testid*="btn"]')
// ⚠️ contains-match — cannot safely replace with getByTestId()
// 🔴 Detected — manual refactor required
page.locator("//div[@class='menu']")
page.locator('div > form > input')
// ✅ Good — no warning
page.getByRole('button', { name: 'Submit' })
page.getByTestId('submit')
Java example
// 🟡 Detected — auto-fix available
page.locator("[data-testid='submit']");
// 💡 Fix: .getByTestId("submit")
// 🔴 Detected — manual refactor required
page.locator("//div[@class='menu']");
page.locator("div > form > input");
// ✅ Good — no warning
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Submit"));
page.getByTestId("submit");
C# example
C# uses PascalCase method names. The auto-fix applies GetByTestId, GetByText, etc.:
// 🟡 Detected — auto-fix available
await page.Locator("[data-testid='submit']").ClickAsync();
// 💡 Fix: .GetByTestId("submit")
await page.Locator("[aria-label='Close']").ClickAsync();
// 💡 Fix: .GetByLabel("Close")
// 🔴 Detected — manual refactor required
await page.Locator("//div[@class='menu']").ClickAsync();
await page.Locator("div > form > input").ClickAsync();
// ✅ Good — no warning
await page.GetByRole(AriaRole.Button, new() { Name = "Submit" }).ClickAsync();
await page.GetByTestId("submit").ClickAsync();
Python example
Python uses snake_case method names. The auto-fix applies get_by_test_id, get_by_text, etc.:
# 🟡 Detected — auto-fix available
page.locator("[data-testid='submit']").click()
# 💡 Fix: .get_by_test_id("submit")
page.locator("[aria-label='Close']").click()
# 💡 Fix: .get_by_label("Close")
page.locator("[placeholder='Search']").fill("query")
# 💡 Fix: .get_by_placeholder("Search")
# 🔴 Detected — manual refactor required
page.locator("//div[@class='menu']").click()
page.locator("div > form > input").fill("value")
# ✅ Good — no warning
page.get_by_role("button", name="Submit").click()
page.get_by_test_id("submit").click()
Installation
From VS Code Marketplace:
- Open Extensions (
Ctrl+Shift+X) - Search for Playwright Locator Lens
- Click Install
From terminal:
code --install-extension usman-ghani123.playwright-locator-lens
Requirements
- VS Code
^1.85.0(for the extension) - Node.js
^18(for the CLI) - A project that uses
@playwright/testorplaywright - No additional runtime dependencies — fully self-contained
License
MIT