Files

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:migrate with .env.test set if needed).
  • Install Playwright browsers: npx playwright install.
  • Run tests: npm run test:e2e (or npx 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.describe blocks
  • Use test.beforeEach for common setup

Finding Elements

Prefer semantic selectors (in order of preference):

  1. getByRole() - Most robust, uses accessibility roles
  2. getByLabel() - For form fields
  3. getByText() - For unique text content
  4. getByTestId() - Last resort, requires adding data-testid

Test Files Structure

Current test files in this directory:

  • homepage.spec.js - Home page tests
  • navigation.spec.js - Navigation tests
  • auth.spec.js - Authentication tests
  • components.spec.js - Component interaction tests
  • forms.spec.js - Form tests
  • grammar.spec.js - Grammar feature tests
  • page-analyzer.spec.js - Page analysis utilities
  • advanced.spec.js - Advanced interaction examples
  • utils/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

  1. Navigation - page.goto() is more reliable than clicking links
  2. Selectors - getByRole() is more reliable than locator() with CSS selectors
  3. 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

  1. Understand page structure first: Use page-analyzer.spec.js to explore pages
  2. Review screenshots: Check test-results/ and screenshots/ to understand the UI
  3. Use semantic selectors: Prefer getByRole('link', { name: 'Kapitel 1' })
  4. Direct navigation: Use page.goto() for navigation rather than clicking links when appropriate
  5. Wait for stability: Use waitForLoadState('networkidle') after navigation
  6. Isolate tests: Each test should be independent and not rely on others
  7. 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:

  1. Form submission tests with various input scenarios
  2. Content interaction tests (exercise completion flows)
  3. User journey tests (end-to-end user scenarios)
  4. Visual regression tests
  5. Accessibility tests using Playwright's accessibility testing

Resources