# Playwright Reviewer

> Review existing Playwright test files for best practices, anti-patterns, selector quality, flakiness risks, and maintainability issues. Use when: auditing test code quality, improving selector strategy, fixing flaky tests, enforcing team standards.

- Skill: `bavithiranhardy14/playwright-reviewer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add bavithiranhardy14/playwright-reviewer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/bavithiranhardy14/playwright-reviewer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: bavithiranhardy14 (https://skillmd.com/u/bavithiranhardy14)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/bavithiranhardy14/playwright-reviewer

---


# Playwright Test Reviewer

## When to Use

- Auditing existing Playwright test files before PR merge
- Identifying flaky test patterns early
- Enforcing team coding standards
- Improving selector resilience
- Reducing CI/CD failure noise

## What This Skill Reviews

### 1. Selector Quality Checker

Copilot will flag these **bad selectors** and suggest fixes:

| Anti-Pattern | Risk | Better Alternative |
|---|---|---|
| `page.click("div > div > span")` | Breaks on DOM change | `page.click("[data-testid='submit']")` |
| `page.click(".btn-primary")` | CSS class can change | `page.getByRole('button', { name: 'Submit' })` |
| `page.click("#id_123abc")` | Auto-generated IDs | `page.getByLabel('Email')` |
| `page.locator("xpath=//div[3]")` | Position-dependent | `page.getByText('Dashboard')` |

**Preferred Selector Priority (Playwright best practice):**
```python
# 1. Role-based — most resilient
page.get_by_role("button", name="Login")

# 2. Label-based — accessible
page.get_by_label("Email address")

# 3. Placeholder
page.get_by_placeholder("Enter username")

# 4. Test ID — stable custom attribute
page.get_by_test_id("submit-btn")   # data-testid="submit-btn"

# 5. Text-based
page.get_by_text("Welcome back")

# AVOID — fragile
page.locator("div.container > form > button:nth-child(2)")
```

---

### 2. Flakiness Pattern Detector

Copilot will flag these **flaky patterns**:

```python
# ❌ FLAKY — hard-coded sleep
import time
time.sleep(3)
page.click("#submit")

# ✅ FIXED — auto-wait
page.click("#submit")  # Playwright auto-waits for actionability

# ❌ FLAKY — no wait before assertion
assert page.is_visible("#dashboard")

# ✅ FIXED — wait for element
page.wait_for_selector("#dashboard")
assert page.is_visible("#dashboard")

# ❌ FLAKY — race condition on navigation
page.click("#login-btn")
assert "dashboard" in page.url  # URL may not have changed yet

# ✅ FIXED — wait for URL
page.click("#login-btn")
page.wait_for_url("**/dashboard")
assert "dashboard" in page.url
```

---

### 3. Test Isolation Checker

```python
# ❌ BAD — shared state between tests
class TestLogin:
    page = None  # class-level — shared across tests!
    
    def test_one(self):
        self.page.goto(...)
    
    def test_two(self):
        self.page.click(...)   # depends on test_one state

# ✅ GOOD — fixture-based isolation
class TestLogin:
    
    @pytest.fixture(autouse=True)
    def setup(self, page):       # fresh page per test
        self.page = page.page
        self.page.goto("https://app.example.com/login")
```

---

### 4. Assertion Quality Checker

```python
# ❌ WEAK assertion
assert page.is_visible("#msg")

# ✅ STRONG — Playwright's expect() with auto-retry
from playwright.sync_api import expect

expect(page.locator("#msg")).to_be_visible()
expect(page.locator("#msg")).to_have_text("Login successful")
expect(page).to_have_url("**/dashboard")
expect(page.locator("#count")).to_have_text("5")

# ✅ Negative assertions
expect(page.locator(".error")).not_to_be_visible()
```

---

### 5. Page Object Compliance Checker

Copilot checks if raw locators are leaking into test files:

```python
# ❌ BAD — locators directly in test
def test_login(page):
    page.fill("#username", "admin")
    page.fill("#password", "pass")
    page.click("#loginBtn")

# ✅ GOOD — locators inside page object only
def test_login(page):
    login = LoginPage(page)
    login.login("admin", "pass")
```

---

### 6. Screenshot on Failure Hook Checker

```python
# ❌ Missing — no failure evidence
@pytest.fixture(scope="function")
def page(browser_context):
    page = browser_context.new_page()
    yield page
    page.close()

# ✅ CORRECT — auto-screenshot on failure
@pytest.fixture(scope="function")
def page(browser_context, request):
    page = browser_context.new_page()
    yield page
    if request.node.rep_call.failed:
        page.screenshot(path=f"reports/screenshots/FAIL_{request.node.name}.png")
    page.close()
```

---

### 7. Full Review Checklist

When you ask Copilot to review a Playwright test file, it will check:

- [ ] No `time.sleep()` calls
- [ ] Selectors use role/label/test-id over CSS/XPath
- [ ] `expect()` used instead of raw `assert is_visible()`
- [ ] Each test uses a fresh page/context (no shared state)
- [ ] Page objects used — no raw locators in test files
- [ ] Screenshot on failure hook is present
- [ ] Trace recording enabled in CI mode
- [ ] No hard-coded credentials in test files
- [ ] Network mocking used where API calls are not under test
- [ ] Tests are independent — no execution order dependency

## Usage

Ask Copilot:
> *"Review this Playwright test file for best practices and flag any anti-patterns"*

> *"Check my selectors for flakiness risks"*

> *"Does this test follow Page Object Model correctly?"*

