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.

On this page
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
- Part 1: From Manual Tester to Automation Engineer — The Mindset Shift
- Part 2: How to Plan Automation for Any Project — A Practical Framework
- Part 3: Your First Playwright Test — A Step-by-Step Guide for Manual Testers
- Part 4: Page Objects, Fixtures, and Real-World Playwright Patterns
- Part 5: BDD with Cucumber and Playwright — Writing Tests in Plain English
- Part 6: Using AI to Write Tests — Claude, GitHub Copilot, and Antigravity
- Part 7: The QC Tester’s Prompt Engineering Playbook (you are here)
- Part 8: Sharing the Work — How Dev and QC Teams Collaborate on Test Automation
- Part 9: Measuring and Improving Quality — Metrics That Actually Matter
- Part 10: The Complete Best Practices Checklist for Automation, AI, and Quality
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.