JOURNAL

The QC Tester’s Prompt Engineering Playbook

5 proven prompt patterns for generating high-quality Playwright tests with AI. Learn to control AI output, review generated code, avoid common mistakes, and build reusable prompt templates. Part 7 of the QC team's guide to automation with AI.

qc automation ai prompts

Read with AI

Choose content to copy and paste into your AI assistant. Nothing is sent automatically. CMS content is converted to Markdown; original Markdown is used when available.

The difference between mediocre AI-generated tests and production-ready ones isn’t the AI model — it’s the prompt. I’ve seen the same AI tool produce brittle, unmaintainable tests from vague prompts and excellent, well-structured tests from specific ones. The prompt is the new skill.

This post is your playbook. Five patterns that consistently produce good test code, a review checklist for AI output, the most common mistakes, and reusable templates you can copy and customize.

Pattern 1: Explore First, Generate Second

The most important pattern. Never ask AI to write tests without first understanding the page.

Bad Prompt ❌

Write Playwright tests for the blog page.

Result: The AI guesses at selectors, invents features that don’t exist, and uses brittle CSS class selectors.

Good Prompt ✅

First, use Playwright MCP to navigate to http://localhost:4321/blog.
Explore the page:
- What interactive elements are there?
- What does the search do?
- What are the tag filters?
- How many posts are visible?
- What happens when you click a post?

DO NOT write any code yet. Report what you find.

Then, after the AI reports back:

Based on what you found, write Playwright tests using:
- Page Object Model in tests/pages/BlogPage.ts
- Tests in tests/e2e/blog.spec.ts
- Import { test, expect } from '../fixtures/base.fixture'
- Use getByRole() and getByText() locators only

Why this works: The AI has real data about the page. It uses accurate selectors from the accessibility tree instead of guessing.

Pattern 2: Specify the Architecture You Want

AI tools default to the simplest approach — inline tests with hardcoded selectors. You need to tell them the pattern you want.

Bad Prompt ❌

Write a test for the login page.

Result:

test('login', async ({ page }) => {
  await page.goto('/login');
  await page.locator('#email').fill('[email protected]');
  await page.locator('#password').fill('pass');
  await page.locator('.submit-btn').click();
  await page.waitForTimeout(2000);
  expect(page.url()).toContain('dashboard');
});

Problems: CSS selectors, hardcoded wait, no Page Object, no proper assertions.

Good Prompt ✅

Write Playwright tests for the login page following these conventions:

ARCHITECTURE:
- Create a LoginPage class in tests/pages/LoginPage.ts
- Tests in tests/e2e/auth.spec.ts
- Import { test, expect } from '../fixtures/base.fixture'

LOCATORS (in priority order):
1. getByRole() — for buttons, links, headings
2. getByLabel() — for form inputs
3. getByPlaceholder() — for search fields
4. getByText() — for static text
5. getByTestId() — last resort

ASSERTIONS:
- Use expect(page).toHaveURL() for navigation checks
- Use expect(locator).toBeVisible() for visibility
- Use expect(locator).toContainText() for content
- NEVER use page.waitForTimeout()
- NEVER use expect with raw string comparison

TEST CASES:
1. Successful login with valid credentials → redirects to /dashboard
2. Invalid password → shows error message
3. Empty email → shows validation error
4. Empty password → shows validation error
5. Login button is disabled while request is in progress

Each test must be independent — no test should depend on another.

Why this works: You’ve defined the architecture, locator strategy, assertion patterns, and specific test cases. The AI has clear constraints to follow.

Pattern 3: Provide Edge Case Context

AI generates happy-path tests naturally. The edge cases — the ones that catch real bugs — come from your domain knowledge.

Prompt Template

Write tests for the [FEATURE] feature.

HAPPY PATH:
- [describe the normal flow]

EDGE CASES (from my experience as QC):
- What happens when [edge case 1]?
- What happens when [edge case 2]?
- What happens when [edge case 3]?

ERROR STATES:
- Use page.route() to mock API failures
- Test: API returns 500 → error message visible
- Test: API returns empty data → empty state visible
- Test: API times out (10s delay) → loading state then timeout message

BOUNDARY VALUES:
- Test with empty string input
- Test with maximum length input (500 characters)
- Test with special characters: <script>alert('xss')</script>
- Test with unicode characters: 日本語テスト

Real Example: Product Search

Write tests for the product search feature.

HAPPY PATH:
- User types "laptop" in search, sees laptop results

EDGE CASES (from my QC experience):
- User pastes a very long string (500+ characters) into search
- User types special characters: @#$%^&*()
- User searches while previous search is still loading
- User clears search after filtering by tag
- User presses Enter with empty search field
- User searches with leading/trailing spaces: "  laptop  "

ERROR STATES (use page.route to mock):
- Search API returns 500 → "Search is temporarily unavailable" message
- Search API returns empty array → "No products found" with suggestion to try different keywords
- Search API takes >5 seconds → loading spinner appears

MOBILE CONSIDERATIONS:
- Search on mobile viewport (375x667)
- Virtual keyboard doesn't cover results

Why this works: You’re combining your domain knowledge (the edge cases you know about from manual testing) with AI’s ability to quickly write the code for each scenario.

Pattern 4: Iterate with Failure Context

When AI-generated tests fail, don’t rewrite them manually. Feed the error back to the AI with context.

The Iteration Loop

Step 1: Run the tests

npx playwright test --project=e2e

Step 2: Copy the failure

The test "user can filter by tag" failed:

Error: Timed out 5000ms waiting for expect(locator).toBeVisible()

  Locator: getByRole('button', { name: 'ai' })
  Expected: visible
  Received: <element not found>

  Call log:
    - waiting for getByRole('button', { name: 'ai' })

Test ran on: http://localhost:4321/blog

Step 3: Ask for a fix

This Playwright test failed. The error says it can't find 
a button with name 'ai'. 

I checked the page manually — the tag filters are actually 
rendered as <span> elements with class "tag-pill", not buttons.
The text is lowercase "ai".

Fix the locator and update the Page Object accordingly.

Step 4: Verify and repeat

npx playwright test --project=e2e

Pro Tip: Copy as Prompt from Playwright

Playwright’s HTML report has a “Copy as Prompt” button on every failed test. This copies the failure context in a format optimized for AI tools:

npx playwright show-report

Click the failing test → click “Copy as Prompt” → paste into Claude or Copilot Chat. The AI gets the full error context and fixes the test accurately.

Pattern 5: Generate Data-Driven Tests

This pattern is perfect for scenarios with many input variations — the kind that take hours to test manually.

Prompt Template

Generate a data-driven Playwright test for the [FEATURE].

Test the same flow with these inputs:
| Input | Expected Result |
|-------|----------------|
| [input 1] | [result 1] |
| [input 2] | [result 2] |
| [input 3] | [result 3] |
| ...       | ...         |

Use a scenarios array and iterate with for...of.
Each row should be a separate test with a descriptive name.
Follow the Page Object Model pattern.

Real Example

Generate a data-driven test for the registration form.

Test these scenarios:
| Email | Password | Confirm | Expected |
|-------|----------|---------|----------|
| [email protected] | Strong123! | Strong123! | Success redirect to /welcome |
| | password | password | "Email is required" error |
| not-an-email | password | password | "Invalid email format" error |
| [email protected] | short | short | "Password must be at least 8 characters" |
| [email protected] | password | different | "Passwords do not match" |
| [email protected] | Strong123! | Strong123! | "Email already registered" |
| [email protected] | NoSpecial1 | NoSpecial1 | "Password must contain a special character" |

Structure the scenarios array with clear types.
Use LoginPage page object for the form interactions.

Controlling AI Output Quality

The Review Checklist

Before committing any AI-generated test, check these:

## AI Test Review Checklist

### Locators
- [ ] Uses getByRole() or getByLabel() — NOT CSS selectors
- [ ] No hardcoded IDs unless using data-testid
- [ ] Locators are specific enough (won't match multiple elements)

### Assertions
- [ ] Uses expect() with auto-retrying matchers (toBeVisible, toHaveURL, etc.)
- [ ] NO page.waitForTimeout() calls
- [ ] Assertions are meaningful (not just "element exists")
- [ ] Negative cases tested (error states, empty states)

### Structure
- [ ] Test is independent — doesn't depend on other tests
- [ ] Uses beforeEach for navigation, not beforeAll
- [ ] No shared mutable state between tests
- [ ] Descriptive test names that explain the scenario

### Page Objects
- [ ] All selectors live in Page Object, not in test file
- [ ] No assertions inside Page Objects
- [ ] Methods describe user actions, not implementation details

### Maintenance
- [ ] Would I understand this test in 6 months?
- [ ] If the UI changes, how many files need updating? (Should be 1)
- [ ] Is there unnecessary duplication?

Common AI Mistakes to Fix

AI Mistake How to Spot Fix
CSS selectors .btn-primary, #email-input Replace with getByRole(), getByLabel()
Hardcoded waits waitForTimeout(3000) Replace with expect().toBeVisible()
Non-specific locators page.locator('button') Add { name: 'Submit' }
Tests depend on order Test 2 assumes Test 1’s state Add proper setup in beforeEach
Missing error handling Only happy path tested Add error state tests with page.route()
Duplicate selectors Same selector in multiple tests Create a Page Object
String assertions expect(text).toBe('exact match') Use toContainText() for flexibility

Reusable Prompt Templates

Template: Page Object + Tests

Create a Playwright Page Object and tests for the [PAGE_NAME] page
at [URL].

Page Object (tests/pages/[PageName]Page.ts):
- Locators for all interactive elements
- Methods for common user actions
- No assertions

Tests (tests/e2e/[page-name].spec.ts):
- Import test from '../fixtures/base.fixture'
- Use test.describe('[Page Name]') grouping
- beforeEach navigates to the page
- Test these scenarios:
  1. [Happy path scenario]
  2. [Validation scenario]
  3. [Error state scenario]
  4. [Edge case scenario]

Use getByRole/getByLabel/getByText locators only.

Template: BDD Feature File + Steps

Write a Cucumber BDD feature file and step definitions for [FEATURE].

Feature file (tests/features/[feature].feature):
- Write in Given/When/Then format
- Include a Background section for common setup
- Include Scenario Outline with Examples table for data variations
- Use @smoke tag for critical scenarios

Step definitions (tests/steps/[feature].steps.ts):
- Import CustomWorld from '../support/world'
- Use existing Page Objects from tests/pages/
- Use Playwright assertions (expect from @playwright/test)

Template: API Test Suite

Write Playwright API tests for the [ENDPOINT] endpoint.

File: tests/api/[endpoint].spec.ts

Test these HTTP scenarios:
- GET [path] → 200 with expected response shape
- GET [path] with filters → filtered results
- POST [path] with valid data → 201 with created resource
- POST [path] with invalid data → 400 with error message
- POST [path] without auth → 401
- DELETE [path]/:id → 204
- DELETE [path]/:nonexistent → 404

Use Playwright's request fixture.
Assert response status, content-type, and body structure.

How to Build Your Prompt Library

Create a folder in your test project for prompt templates:

tests/
├── prompts/                ← Your team's prompt library
│   ├── page-object.md      ← Template for generating Page Objects
│   ├── e2e-test.md         ← Template for E2E tests
│   ├── api-test.md         ← Template for API tests
│   ├── bdd-feature.md      ← Template for BDD feature files
│   └── debug-fix.md        ← Template for debugging failing tests

When anyone on the team needs to generate tests with AI, they grab the template, fill in the specifics, and get consistent, high-quality output every time.

Series Navigation

In Part 8, we’ll tackle the collaboration challenge — how Dev and QC teams share test automation work effectively, including repo structure, code reviews, task splitting, and the Definition of Done.

Discussion

Comments are reviewed before publication. Your email is kept private.

Add a comment

Name and email are required. Do not include confidential information.

Privacy & data

Loading anti-spam verification…

← Back to allĐọc tiếng Việt