6.3 KiB
End-to-End Testing with Playwright
Last Updated: November 30, 2025
This directory contains E2E tests using Playwright to test the application in real browsers.
Prerequisites
- Node.js: v20+ (included in dev container)
- Environment: Run commands inside the dev container at
/host/gradesa - Database: Tests run against the test database (configured via
.env.test)
Initial Setup
Playwright is already configured in this project. If setting up from scratch:
npm install --save-dev @playwright/test
npx playwright install
Quick E2E checklist
- Ensure the app is running (dev server at
http://localhost:3000). - Ensure the test database is migrated/available (
npm run db:migratewith.env.testset if needed). - Install Playwright browsers:
npx playwright install. - Run tests:
npm run test:e2e(ornpx playwright test <file>).
Commands
Run these commands from /host/gradesa inside the container:
npm run test:e2e # Run all tests
npm run test:e2e:ui # Run with UI mode
npm run test:e2e:debug # Run in debug mode
npx playwright test [file] # Run specific test file
CI/CD Integration
Playwright tests run automatically in the GitHub Actions pipeline:
- Triggered on pull requests to
master - Tests run in headless mode
- Screenshots and videos captured on failure
- Test results available in GitHub Actions artifacts
Writing New Tests
File Naming
Follow the existing pattern: feature-name.spec.js
Test Structure
import { test, expect } from "@playwright/test";
test.describe("Feature Name", () => {
test.beforeEach(async ({ page }) => {
// Setup: navigate to page, login if needed
await page.goto("/your-route");
});
test("should do something specific", async ({ page }) => {
// Arrange: Set up test data
// Act: Perform user actions
// Assert: Verify results
await expect(page.locator("selector")).toBeVisible();
});
});
Naming Conventions
- Use descriptive test names:
should display error message when form is invalid - Group related tests in
test.describeblocks - Use
test.beforeEachfor common setup
Finding Elements
Prefer semantic selectors (in order of preference):
getByRole()- Most robust, uses accessibility rolesgetByLabel()- For form fieldsgetByText()- For unique text contentgetByTestId()- Last resort, requires addingdata-testid
Test Files Structure
Current test files in this directory:
homepage.spec.js- Home page testsnavigation.spec.js- Navigation testsauth.spec.js- Authentication testscomponents.spec.js- Component interaction testsforms.spec.js- Form testsgrammar.spec.js- Grammar feature testspage-analyzer.spec.js- Page analysis utilitiesadvanced.spec.js- Advanced interaction examplesutils/helpers.js- Helper functions
Developer Notes
The following sections contain observations and findings from E2E test development. These are helpful for understanding the application structure and writing effective tests.
Key Findings
- Navigation -
page.goto()is more reliable than clicking links - Selectors -
getByRole()is more reliable thanlocator()with CSS selectors - Structure - Main navigation has links to grammar, resources, and auth pages
Page Structure Notes
- Home page: Links to resources, grammar, login, and register
- Resources: Chapter navigation and content
- No hamburger menu or mobile-specific UI elements
Screenshots
Playwright automatically captures screenshots on test failures. These are saved in:
test-results/directory (gitignored)- Available as artifacts in CI/CD runs
To manually capture screenshots during development:
await page.screenshot({ path: "debug-screenshot.png" });
Best Practices
- Understand page structure first: Use
page-analyzer.spec.jsto explore pages - Review screenshots: Check
test-results/andscreenshots/to understand the UI - Use semantic selectors: Prefer
getByRole('link', { name: 'Kapitel 1' }) - Direct navigation: Use
page.goto()for navigation rather than clicking links when appropriate - Wait for stability: Use
waitForLoadState('networkidle')after navigation - Isolate tests: Each test should be independent and not rely on others
- Clean up: Reset state after tests (e.g., delete created data)
Common Issues & Debugging
Element Not Found
- Solution: Use more robust selectors (role-based)
- Debug: Use
await page.pause()to inspect the page - Debug: Print available elements:
console.log(await page.content())
Strict Mode Violations
- Error: "locator resolved to multiple elements"
- Solution: Make selector more specific or use
.first(),.last(),.nth()
Navigation Timeouts
- Solution: Use direct navigation with
page.goto() - Solution: Increase timeout:
await page.goto(url, { timeout: 60000 })
Timing Issues
- Solution: Use Playwright's auto-waiting (preferred)
- Solution: Explicit waits:
await page.waitForSelector('selector') - Avoid:
page.waitForTimeout()- this is an anti-pattern
Debugging Tips
// Pause test execution
await page.pause();
// Take screenshot at specific point
await page.screenshot({ path: "debug.png" });
// Print page HTML
console.log(await page.content());
// Check what's visible
const visible = await page.locator("selector").isVisible();
console.log("Element visible:", visible);
Further Development
Planned improvements:
- Form submission tests with various input scenarios
- Content interaction tests (exercise completion flows)
- User journey tests (end-to-end user scenarios)
- Visual regression tests
- Accessibility tests using Playwright's accessibility testing
Resources
- Playwright Documentation - Getting started guide
- Playwright API Reference - Complete API docs
- Playwright Best Practices - Official best practices
- Playwright Test Generator - Generate tests by recording actions
- Debugging Tests - Debugging guide