How to Review a Playwright Test Written by Claude Code
Claude Code generates a Playwright test. The code looks clean, the test passes, and the pull request is ready.
Before approving it, ask one question:
If the feature were broken, could this test still pass?
That question catches problems a syntax review will miss. A generated test can click the correct buttons, display a success message, and finish without verifying the behavior its name promises.
To review a Playwright test written by Claude Code, check its intended behavior, the source of its expected results, its assertions, mocked dependencies, data isolation, and execution evidence. For important functionality, confirm that a relevant defect makes it fail.
The goal is to establish what the passing result actually proves.
Start With the Test’s Claim
Read the test name before reading its implementation.
Suppose it says:
“Customer can update their billing address.”
That could mean several things:
- The address form accepts input.
- Clicking Save displays a confirmation.
- The server stores the updated address.
- The address remains correct in a fresh session.
- Future invoices use the new address.
These are different claims. A test covering the first two does not establish the others.
Rewrite the intended behavior as one precise sentence:
An authenticated customer can save a valid billing address, and the application retrieves that address when the billing page is reopened.
Now review whether the test supports that claim.
This is especially relevant when requirements are incomplete. In DeviQA’s AI-Assisted Development: 2026 Software Testing Report, 56% of 4,000 surveyed QA professionals reported spending additional testing time clarifying expected behavior.
Generating a test before resolving that ambiguity can produce a polished implementation of the wrong expectation.
Check Where the Expected Result Came From
Ask Claude to identify the source of each important expectation:
- An acceptance criterion.
- An approved business rule.
- An API contract.
- A verified example.
- An assumption inferred from the current implementation.
The last category deserves particular attention.
If Claude reads a function with an incorrect calculation and generates a test expecting the same result, the test may preserve the defect.
A test generated from existing behavior can document that behavior without proving it is correct.
Review the Assertions Before the Clicks
A test’s assertions tell you more about its value than the number of actions it performs.
Consider this fragment:
await page.getByLabel(‘Billing address’).fill(’10 Oak Street’);
await page.getByRole(‘button’, { name: ‘Save’, exact: true }).click();
await expect(page.getByText(‘Saved successfully’, { exact: true })).toBeVisible();
This checks that a message appears after the interaction. It does not establish that the address was stored.
The application could display the message optimistically and then fail to save.
Look for an Observable Change
A stronger review asks whether the test:
- Starts from a known, different address.
- Performs the update through the intended interface.
- Waits for a meaningful completion signal.
- Retrieves the saved state again.
- Checks the exact value.
The initial state matters. If the account already contains “10 Oak Street,” a broken Save button could go unnoticed.
This is a common source of false confidence: the test verifies the desired final state without proving that the action caused it.
Match the Assertion to the Claim
Useful checks depend on what the test is meant to establish:
- Persistence: retrieve the value again after saving.
- Permissions: verify that the restricted action is rejected, not merely hidden.
- Calculation: compare against an independently established amount.
- Deletion: confirm that the specific record is no longer retrievable through the relevant interface.
- Transaction creation: verify the correct transaction and its identifying details.
A page reload is useful, but it does not always prove server-side persistence. The application might restore data from browser storage. If server persistence is the claim, use an appropriate server read or a fresh authenticated browser context that does not carry the previous page’s local state.
Review the evidence at the level where the requirement applies.
Check Whether Mocks Remove the Behavior Under Test
Mocking is useful for controlling dependencies and reproducing unusual conditions. It also changes what a test can prove.
Suppose a billing-address test intercepts the update request and returns a successful response. It then intercepts the next read request and returns the new address.
The test can pass even if the real backend cannot save addresses.
That may be a valid frontend test. It should be described accordingly.
Playwright’s API mocking documentation explicitly demonstrates that a fulfilled mock can replace the real API call.
Review Every Intercepted Request
For each route handler, ask:
- Which system behavior is being replaced?
- Is that behavior part of the test’s stated claim?
- Does the mock validate the request, or return success regardless of its contents?
- Could the route pattern intercept unrelated requests?
- Where is the real integration verified?
A mock that returns success for any payload could hide a frontend sending the wrong field name, account ID, or value.
A useful review comment would be:
This verifies how the interface handles a successful mocked response. Please rename the test to reflect that scope, validate the outgoing payload, and identify where actual persistence is covered.
The mock is not automatically a problem. The mismatch between the test’s name and its evidence is.
Inspect Locators for Ambiguity
Generated locators can be syntactically correct while targeting the wrong element.
This deserves scrutiny:
page.getByRole(‘button’, { name: ‘Save’ }).first()
Why the first button? Is it the billing form’s Save button, or simply the first matching element in the current layout?
Playwright’s locator documentation explains strictness and the use of locators that identify elements by role, label, text, or explicit test contracts.
A better approach is often to scope the locator to the intended container:
const billingForm = page.getByRole(‘form’, { name: ‘Billing details’ });
await billingForm.getByRole(‘button’, { name: ‘Save’, exact: true }).click();
This example assumes that the form has the accessible name “Billing details.” The actual locator must match the application.
Treat These Patterns as Review Questions
- first() or nth(): Does position represent a real requirement, or hide ambiguity?
- Long CSS chains: Will a harmless layout change break the test?
- Broad text matching: Could another message satisfy the assertion?
- Generated class names: Are they stable application contracts?
- Test IDs: Do they identify the intended business element clearly?
A test ID is often appropriate. A role-based locator is not automatically sufficient. The important question is whether the locator reliably identifies the intended element.
Review Waiting Behavior Separately From Business Logic
Playwright’s automatic waiting helps with browser interactions. It does not know when your business operation has finished.
According to the actionability documentation, a click normally checks conditions such as visibility, stability, and whether the element receives events.
Those checks do not establish that a payment settled, a background job completed, or a database write became visible.
Replace Fixed Delays With Meaningful Conditions
This is a warning sign:
await page.waitForTimeout(3000);
The delay assumes that three seconds is enough. It also waits three seconds when the application finishes immediately.
Look for a condition connected to the behavior:
- A loading indicator disappears.
- A specific response arrives.
- A record reaches the required state.
- A locator displays the expected value.
If the test waits for a network response, the response listener should be registered before the triggering action. Otherwise, a fast response may arrive before the test starts listening.
Use Retrying Assertions Correctly
Compare these expressions:
expect(await message.isVisible()).toBe(true);
await expect(message).toBeVisible();
The first checks a boolean obtained at that moment. The second uses a locator assertion that retries until the condition succeeds or the assertion times out.
Playwright’s assertions guide documents this retrying behavior.
Get Michael Brown’s stories in your inbox
Join Medium for free to get updates from this writer.
Also check that asynchronous assertions are awaited. A missing await can leave verification unfinished when the test proceeds or ends.
Investigate Forced Clicks
A generated repair may introduce:
await saveButton.click({ force: true });
Forced clicks bypass certain actionability checks. An overlay blocking the button could be a real user-facing defect, so forcing the click may avoid the condition the test should expose.
Require a specific explanation for the bypass.
Verify Both Browser Isolation and Server-State Isolation
Playwright provides isolated browser contexts for tests, as described in its test-isolation documentation.
That does not reset the application database.
Two tests can have separate cookies and still update the same customer record.
For an address-update test, inspect:
- How the account is created.
- Whether its starting address is known.
- Whether another test can modify it concurrently.
- Whether retries encounter state left by a failed attempt.
- How cleanup works if an assertion fails.
Shared Authentication Does Not Guarantee Independent Tests
Reusing authentication state can be appropriate. However, tests that modify shared server-side state need a suitable account or data-allocation strategy.
Playwright’s authentication guidance distinguishes scenarios where a shared account is suitable from those requiring separate accounts.
Watch for:
- Hardcoded accounts used across multiple workers.
- Cleanup that runs only at the end of a successful test.
- A test that depends on an earlier test creating a record.
- Date-dependent data with no controlled reference time.
- Random input that makes failures difficult to reproduce.
Unique identifiers can prevent collisions. Important business values should remain deliberate and understandable.
Read the Entire Diff, Including “Helpful” Repairs
The test file is only part of the change.
Claude Code may also modify fixtures, shared helpers, configuration, snapshots, or application code. Review those changes together.
Look for:
- Assertions removed or weakened.
- New test.skip() or test.fixme() calls.
- Retry counts increased.
- Timeouts extended without an explanation.
- Errors caught and ignored.
- Real integrations replaced with mocks.
- Snapshots updated without checking the visual difference.
- Application behavior changed to satisfy the generated test.
These changes are not always wrong. They can, however, alter the meaning of a passing result.
A useful review question is:
Did this change fix the problem, or make the test stop observing it?
If the task was to add a regression test, an unrelated application change needs its own explanation.
Inspect Execution Evidence, Including a Passing Run
A passing result is more useful when you can see what happened.
Run the test with tracing enabled and inspect the relevant actions, page states, and requests. Playwright’s Trace Viewer provides this execution evidence.
For the billing-address example, check:
- The expected account was active.
- The initial address differed from the new address.
- The intended Save button was clicked.
- The request contained the correct value.
- The test observed the saved result rather than stale content.
- No mock replaced the persistence behavior being claimed.
A passing trace can reveal an incorrect path just as a failing trace can reveal a defect.
Repeat With Retries Disabled
For a focused stability check:
npx playwright test tests/billing-address.spec.ts — repeat-each=10 — retries=0
The Playwright command-line reference documents these options. Adapt the file path and project selection to your repository.
Ten successful runs do not prove reliability. They provide a small check for timing or state problems.
Also run the test alongside relevant tests using the suite’s normal worker configuration. A test can pass alone and fail when shared server state is modified concurrently.
Playwright classifies a test that fails initially and passes on retry as flaky. A successful retry is evidence to investigate, rather than proof that the original failure was harmless.
Prove That the Test Detects a Relevant Regression
The strongest review question remains:
What deliberate defect would make this test fail?
For important functionality, introduce a narrow defect on a temporary local branch:
- Stop saving the updated address.
- Return the wrong account’s value.
- Remove the permission check.
- Change the calculation boundary.
- Skip the final state transition.
Run the test and inspect why it fails.
A build error does not demonstrate that the test detects the intended regression. The application should still run, and the test should fail because the protected behavior is wrong.
Then revert the deliberate defect and rerun the test.
This is particularly useful when code and tests were generated together. Both may otherwise share the same misunderstanding.
Distinguish a Test Check From a Failure-Handling Test
Changing an assertion to an obviously incorrect value checks whether that assertion executes.
Simulating a failed backend response checks how the interface handles that response.
Breaking persistence in the local application checks whether the test detects a persistence regression.
Each exercise is useful, but they answer different questions. Choose the one that matches the test’s claim.
Ask Claude Code for Evidence, Not Reassurance
A vague request such as “review this test” can produce a similarly vague response.
Give Claude a structured review task. Its best-practices documentation recommends providing missing context and concrete verification checks.
A practical prompt is:
Review this Playwright test without editing files.
State the exact behavior it verifies and identify the source of each expected result. List the systems replaced by mocks and any shared server-side state.
Find realistic defects that could exist while the test still passes. Check locators, asynchronous assertions, waiting behavior, cleanup, and changes to shared configuration.
Distinguish confirmed problems from assumptions. Cite the relevant file and line for each finding.
Propose one controlled defect that should make the test fail. Report which commands you actually ran, their results, and anything you could not verify.
Claude’s review can accelerate investigation. It does not provide independent confirmation of a business rule unless that rule comes from a separate, reliable source.
A Practical Approval Checklist
Before merging a Playwright test written by Claude Code, confirm:
- The claim is precise: the name matches what the test actually verifies.
- The expectation is justified: important values come from requirements or another reliable reference.
- The state changes: the test cannot pass simply because the desired result already existed.
- The assertions matter: they check the business outcome at the appropriate level.
- Mocks are explicit: replaced systems are understood and the coverage claim is accurate.
- Locators are unambiguous: element selection does not rely on accidental page order.
- Waiting is purposeful: assertions and completion signals replace arbitrary delays.
- Data is controlled: tests do not silently depend on shared mutable state.
- The diff preserves coverage: fixes have not weakened assertions or hidden failures.
- Execution supports the claim: reviewed results show the intended path.
- Important regressions are detectable: a relevant controlled defect causes a meaningful failure.
Frequently Asked Questions
Are Playwright Tests Written by Claude Code Reliable?
They can be, but authorship does not establish reliability. Review the test’s expectations, assertions, dependencies, data, and execution behavior. A generated test should meet the same standards as a manually written one.
Is a Passing Test Enough to Approve It?
No. A test can pass because it checks too little, mocks the behavior it claims to verify, or starts with the desired state already present. Review what the result proves and which realistic defects it could miss.
Should Every Generated Test Be Reviewed Manually?
Every test change should follow the team’s review policy. Automated checks can identify syntax issues and some problematic patterns. Human review remains important for business meaning, coverage boundaries, and risk. Review depth should reflect the impact of failure.
What Is the Most Useful Question to Ask During Review?
Ask: “Which realistic defect could still exist while this test passes?”
That question turns a review from checking whether the code looks reasonable into examining whether the test provides useful protection.









