How to generate Playwright tests from a user story (with AI)

If you've ever stared at a Jira ticket like "User can reset password via email link, link expires after 1 hour" and thought "great, now I write 12 test cases and 600 lines of Playwright code" — this post is for you.

The shortcut: an AI test generator that reads the requirement, decides what to cover, drafts the steps, and emits a working Playwright suite. Below is the exact flow.

The requirement we'll use

User should be able to reset their password via the email link, and the link should expire after 1 hour.

That's it. One sentence. We're going to turn it into a test plan, then into runnable scripts.

Step 1 — Frame the requirement so the AI can reason about it

Most "AI test generation" tools fail because they treat the requirement as a black box. The first improvement: explicitly call out the acceptance criteria, edge cases, and out-of-scope items. Even a one-line requirement deserves three columns of structure.

For the password reset story above:

The clearer the input, the better the AI's output. This is true for humans too.

Step 2 — Generate the test plan

This is where AI earns its keep. Given the framed requirement, the model produces a plan — case ID, title, preconditions, steps, expected results — typically 8 to 15 cases for a story of this size.

Sample output (truncated):

ID Title Priority
TC-01 Reset link is sent to a registered email High
TC-02 Link expires after exactly 1 hour High
TC-03 Invalid token returns a clear error High
TC-04 Already-used link cannot be reused High
TC-05 Unregistered email shows no enumeration hint Medium
... ... ...

Notice TC-05 — no enumeration hint. The AI inferred this from the security framing even though we didn't ask. That's the difference between AI generation and template generation.

Step 3 — Generate Playwright code from the plan

Each test case gets converted into a Playwright script. The pattern:

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

test('TC-02: Link expires after exactly 1 hour', async ({ page, request }) => {
  // Setup — request a reset link, then time-travel the DB
  await request.post('/api/auth/forgot-password', {
    data: { email: 'test+expire@example.com' },
  });
  await timeTravel('+61 minutes');

  // Exercise — click the expired link
  await page.goto(getResetLinkFromMailbox('test+expire@example.com'));

  // Assert — the page communicates expiry clearly
  await expect(page.getByText(/link.*expired/i)).toBeVisible();
  await expect(page.getByRole('link', { name: /request a new/i })).toBeVisible();
});

The hard part isn't writing this — it's getting the locators right. Generated locators based on guesses about your DOM are useless. SpacePilot QA solves this by crawling the live app before generating, so every selector is grounded in your real markup.

Step 4 — Run, observe, refine

Generation is never one-shot. The first run will surface 2-3 cases that need adjustment — maybe TC-05 needs different copy, or your reset page renders inside a modal you didn't tell the AI about. The right loop is:

  1. Run the suite
  2. Look at the failures
  3. Fix the test (or fix the requirement, often more revealing)
  4. Re-run

After 2-3 iterations you have a passing suite. Total time start-to-finish: 30-60 minutes for a story that would normally take a half-day of test writing.

What this gets you

Three concrete wins:

What it doesn't get you: exploratory testing, performance testing, or "I have a hunch this will break under concurrent load" intuition. Those still need humans.

The deeper point

AI test generation isn't about replacing QA engineers. It's about moving them up the stack — from typing locators to designing test strategy, from writing assertions to deciding what's worth asserting in the first place.

If you're skeptical, run one ticket through it. Pick a real story you'd otherwise write yourself. Compare the output to what you'd produce manually. The gap closes fast.