Imported from DanielTavares33/automationexercise (
AGENTS.md). Install upstream withnpx skills add DanielTavares33/automationexercise. Copyright stays with the author.
AGENTS.md
Guide for coding agents working in this Playwright BDD test automation repository.
Commands
Build & Lint
# Install dependencies
npm ci
# Install Playwright browsers (required before first run)
npx playwright install chromium
# TypeScript compilation check
npx tsc --noEmit
Running Tests
CRITICAL: Always run bddgen before playwright test. Never run playwright test alone.
Using the CI/parallel test script (recommended for CI/CD and parallel execution):
# Run all tests in parallel, headless mode, with 2 retries
./scripts/run-tests.sh
# Or use npm script
npm run test:ci
# Run specific tag filter
./scripts/run-tests.sh smoke # @smoke scenarios
./scripts/run-tests.sh regression # @regression scenarios
./scripts/run-tests.sh login # @login scenarios
# Custom worker count
WORKERS=8 ./scripts/run-tests.sh
# Custom workers with tag filter
WORKERS=8 ./scripts/run-tests.sh smoke
Using npm scripts (interactive development):
# Run all tests (auto-runs bddgen first)
npm test
# Run by tag group
npm run test:smoke # @smoke scenarios — fast CI gate
npm run test:regression # full suite
npm run test:login # login feature tests
npm run test:register # register feature tests
npm run test:products # products feature tests
npm run test:checkout # checkout feature tests
# Run by custom tag
npm run test:tag TAG=positive # all @positive scenarios
npm run test:tag TAG=negative # all @negative scenarios
# Run single test by scenario name
bddgen && playwright test --grep "Login User with correct email"
# Run single feature file
bddgen && playwright test --grep @login
# Custom workers (default: 4)
WORKERS=8 npm test
# Headless mode
HEADLESS=true npm test
# Combined
WORKERS=4 HEADLESS=true npm run test:smoke
Reports
Playwright HTML Report:
# Open HTML report after test run
npx playwright show-report
Allure Report:
# Generate and open Allure report (after test execution)
npm run allure:generate
npm run allure:open
# Or generate + serve in one command (quick view)
npm run allure:serve
# Run tests + auto-open Allure report
npm run test:allure
Allure Features:
- 📊 Test execution trends and analytics
- 📈 Historical data (with GitHub Pages setup)
- 🔍 Flaky test detection
- 📋 BDD scenario step-by-step breakdown
- 📷 Automatic screenshot/video attachments on failure
- 🏷️ Categorization by features, severity, and tags
Using Allure Annotations in Step Definitions:
import { allure } from '../support/allure-helpers';
Given('I am on the home page', async ({ homePage }) => {
allure.feature('Homepage');
allure.story('User Navigation');
allure.severity('critical');
await homePage.visitHomePage();
});
Available Annotations:
allure.feature(name)— Group by feature areaallure.story(name)— Group by user storyallure.severity(level)— Set priority (blocker/critical/normal/minor/trivial)allure.tag(tag)— Add custom tagsallure.description(text)— Add test descriptionallure.issue(id, url)— Link to GitHub issueallure.owner(name)— Set test ownerallure.attach(name, content, type)— Attach custom data
GitHub Actions CI/CD
This repository includes three GitHub Actions workflows for automated testing:
1. Manual Test Execution
Trigger: Manual (workflow_dispatch)
File: .github/workflows/manual-tests.yml
Run tests on-demand from GitHub UI with customizable parameters:
- Test Suite: Choose from all, smoke, regression, or specific feature tags
- Custom Tag: Override with any custom tag
- Workers: Configure parallel execution (1-8 workers)
- Browser: Select chromium, firefox, or webkit
Usage:
- Go to GitHub Actions tab
- Select "Manual Test Execution" workflow
- Click "Run workflow"
- Configure parameters and run
Features:
- ✅ Manual trigger with flexible options
- ✅ Artifact uploads (HTML report, test results)
- ✅ Test summary in workflow output
- ✅ 60-minute timeout
2. PR Test Validation
Trigger: Pull request (on open/sync/reopen)
File: .github/workflows/pr-tests.yml
Automatically runs smoke tests on pull requests to validate changes:
Features:
- ✅ Runs @smoke tests (fast feedback, ~5-10 min)
- ✅ Blocks merge if tests fail
- ✅ Posts test results as PR comment
- ✅ Artifact uploads for debugging
- ✅ 30-minute timeout
Branches: Triggers on PRs to main, master, or develop
3. Scheduled Regression Tests
Trigger: Scheduled (daily at 2:00 AM UTC) or manual
File: .github/workflows/scheduled-tests.yml
Runs full regression suite nightly:
Features:
- ✅ Full @regression test suite
- ✅ Creates GitHub issue on failure (auto-labeled)
- ✅ Artifact uploads (30-day retention)
- ✅ Test summary in workflow output
- ✅ 90-minute timeout
Schedule: Daily at 2:00 AM UTC (configurable via cron)
CI Configuration
The Playwright config automatically detects CI environment and applies:
- Retries: 2 retries for failed tests (0 locally)
- Reporters: HTML + GitHub + List (HTML only locally)
- Headless: Forced true in CI
- Workers: Configurable via WORKERS env var
Viewing Test Results
From GitHub Actions:
- Navigate to Actions tab
- Select workflow run
- Download artifacts (HTML report, test results)
- View test summary in workflow output
HTML Report:
- Retention: 30 days (regression), 14 days (PR)
- Includes screenshots, traces, and detailed test results
- Download artifact and open
index.htmllocally
Architecture
Framework: playwright-bdd + @playwright/test in TypeScript (NOT Cucumber)
Target site: https://automationexercise.com
Config: playwright.config.ts (single source of truth)
Directory Structure
features/ # Gherkin .feature files organized by domain
locators/ # One class per page with readonly Locator fields
pages/ # Page Object Models extending BasePage
step_definitions/ # Step implementations using createBdd(test)
support/
fixtures.ts # Custom Playwright fixtures (integration layer)
.features-gen/ # Auto-generated by bddgen (gitignored, never edit)
Code Style Guidelines
Imports
Order: Playwright → third-party libraries → local modules
// 1. Playwright imports
import { Page, Locator, expect } from '@playwright/test';
// 2. Third-party libraries
import { faker } from '@faker-js/faker';
// 3. Local modules (relative paths)
import { BasePage } from './base.page';
import { HomeLocators } from '../locators/home.locators';
Locator Classes
Rules:
- One class per page in
locators/<page>.locators.ts - CSS selectors ONLY (no
{ hasText }options, no role-based selectors) - All fields are
readonly Locator - Initialize all locators in constructor
- Group related locators with comments
import { Page, Locator } from 'playwright';
export class HomeLocators {
// Navigation
readonly signupLoginButton: Locator;
readonly productsButton: Locator;
// Footer subscription
readonly subscriptionEmailInput: Locator;
readonly subscriptionButton: Locator;
constructor(page: Page) {
this.signupLoginButton = page.locator('[data-qa="signup-login"]');
this.productsButton = page.locator('.shop-menu a[href="/products"]');
this.subscriptionEmailInput = page.locator('input#susbscribe_email');
this.subscriptionButton = page.locator('button#subscribe');
}
}
Preferred selector strategies:
[data-qa="..."]attributes (best)- ID selectors:
#element-id - CSS selectors with specific attributes:
form[action="/login"] p[style="color: red;"] - Class + attribute combinations:
.shop-menu a[href="/products"]
Page Object Models
Rules:
- One class per page in
pages/<page>.page.ts - Extend
BasePage - Constructor receives
Page, instantiates locator class - Method order: constructor → public methods → private methods
- All async methods return
Promise<void>(explicit typing) - Use
this.locatorsto access elements - Use
this.pagefor page-level operations
import { Page, expect } from '@playwright/test';
import { BasePage } from './base.page';
import { HomeLocators } from '../locators/home.locators';
export class HomePage extends BasePage {
readonly locators: HomeLocators;
constructor(page: Page) {
super(page);
this.locators = new HomeLocators(page);
}
// Public methods
async clickProducts(): Promise<void> {
await this.locators.productsButton.click();
}
async verifySubscriptionHeading(): Promise<void> {
await expect(this.locators.subscriptionHeading).toBeVisible();
}
// Private methods
private async handlePopup(): Promise<void> {
// implementation
}
}
Step Definitions
Rules:
- Use
createBdd(test)fromplaywright-bdd - Import
testfromsupport/fixtures.ts(NOT from@playwright/test) - Steps receive destructured fixtures:
async ({ homePage, loginPage }) => { } - NO
thiscontext, NO World class - Use switch statements for parameterized steps
- Always include
defaultcase withthrow new Error()
import { createBdd } from 'playwright-bdd';
import { test } from '../support/fixtures';
const { Given, When, Then } = createBdd(test);
Given('I visit the home page', async ({ homePage }) => {
await homePage.visitHomePage();
});
When('I click on the {string} button', async ({ homePage, loginPage }, buttonText: string) => {
switch (buttonText) {
case 'Products':
await homePage.clickProducts();
break;
case 'Login':
await loginPage.clickLoginButton();
break;
default:
throw new Error(`Unknown button: ${buttonText}`);
}
});
Fixtures
Location: support/fixtures.ts
Pattern: Extend playwright-bdd's test with custom fixtures
import { test as base } from 'playwright-bdd';
import { HomePage } from '../pages/home.page';
export const test = base.extend<{
homePage: HomePage;
}>({
homePage: async ({ page }, use) => {
await use(new HomePage(page));
},
});
When adding a new page:
- Create
locators/<page>.locators.ts - Create
pages/<page>.page.ts - Add fixture to
support/fixtures.ts - Create
step_definitions/<page>.steps.ts
Naming Conventions
- Variables/methods: camelCase (
clickLoginButton,verifyAccountCreated) - Classes: PascalCase (
HomePage,LoginLocators) - Files: lowercase with underscores for multi-word (
contact_us.page.ts) - Constants: UPPER_SNAKE_CASE (
TEST_PASSWORD,AD_DOMAINS) - Private fields: prefix with underscore (
_registeredEmail)
TypeScript & Types
- Strict mode enabled (
tsconfig.json) - Explicit return types for all async methods:
Promise<void> - Use type imports when importing only types
- Avoid
any— use proper types or generics
Error Handling
- Use
throw new Error()with descriptive messages - Always include
defaultcase in switch statements - Include context in error messages:
default:
throw new Error(`Unknown button: ${buttonText}`);
Key Patterns
GDPR Consent Popup
HomePage.visitHomePage() automatically handles Google consent popup. Always use this method for fresh navigation:
async visitHomePage(): Promise<void> {
await this.page.goto('https://automationexercise.com/');
await this.googleConsentPopup();
}
private async googleConsentPopup() {
const consentButton = this.page.getByRole('button', { name: 'Consent' });
if (await consentButton.isVisible()) {
await consentButton.click();
}
}
Shared Step Dispatch
Generic steps like When I click on the {string} button live in common.steps.ts and use switch statements to delegate to appropriate page objects. Add new cases rather than creating duplicate steps.
Test Data Generation
Use @faker-js/faker for random test data:
import { faker } from '@faker-js/faker';
await this.locators.nameInput.fill(faker.person.fullName());
await this.locators.emailInput.fill(faker.internet.email());
Ad Blocking
support/fixtures.ts extends the base page fixture to block ad domains, improving test stability.
Gherkin Guidelines
Tag dimensions:
- Execution tier:
@smoke(fast, critical path) or@regression(full suite) - Outcome:
@positive(happy path) or@negative(error cases) - Domain:
@login,@register,@products,@checkout, etc.
@smoke @positive @login
Scenario: Login User with correct email and password
Given I visit the home page
When I click on the "Signup / Login" button
Then I should see the login form
Workflow Rules
- Never run tests automatically after code edits — only when explicitly requested
- Always run
bddgenbeforeplaywright test(or use npm scripts) - Never edit
.features-gen/— it's auto-generated - Use CSS selectors only in locator classes
- Extend fixtures in
support/fixtures.tsfor new page objects - Import test from fixtures, not from
@playwright/testin step definitions - Never commit or push changes — only the user can commit and push. After making changes, inform the user what was done and let them decide whether to commit and push.