This directory contains comprehensive end-to-end tests for Ben Balter's website using Playwright.
helpers.ts: Common test utilities and helper functionshomepage.spec.ts: Tests for the homepageblog-posts.spec.ts: Tests for blog post pagesresume.spec.ts: Tests for the resume pagepages.spec.ts: Tests for static pages (About, Contact, Press)accessibility-astro.spec.ts: Accessibility tests (WCAG compliance)performance-astro.spec.ts: Performance and optimization testsseo-astro.spec.ts: General SEO and metadata tests
# Install dependencies
npm install
# Install Playwright browsers
npx playwright install# Run all tests (builds the site and starts the local Worker, `npm run preview:worker`, automatically)
npm run test:e2e
# Run tests in headed mode (see browser)
npm run test:e2e:headed
# Run tests in UI mode (interactive)
npm run test:e2e:ui
# Debug tests
npm run test:e2e:debug
# View test report
npm run test:e2e:report# Run a specific test file
npx playwright test homepage.spec.ts
# Run tests matching a pattern
npx playwright test --grep "accessibility"
# Run tests in a specific browser
npx playwright test --project=chromiumTests run against the production Worker (worker/index.js) served locally by wrangler dev --local on 127.0.0.1:8792, not astro preview. That way they cover what production serves: Markdown content negotiation, POST /api/event, public/_headers, the built _redirects, trailing-slash redirects, and the 404 page. Local mode needs no Cloudflare login.
Playwright reuses a Worker that's already running on that port, so you can keep one up between runs:
SKIP_PDF=1 npm run build # skip /resume.pdf to build faster (its link check then fails)
npm run preview:worker # serves dist-astro/ on http://127.0.0.1:8792
npx playwright test # in another terminalTo test a different server or port, set BASE_URL. Playwright then starts no server of its own:
BASE_URL=http://127.0.0.1:4330 npx playwright test- Page loads successfully
- Navigation and footer present
- Responsive design
- Semantic HTML structure
- Meta tags and SEO
- Post listing accessible
- Individual posts render correctly
- Metadata present
- Images have alt text
- Links are valid
- Page loads and displays content
- Work experience section present
- Contact information available
- Print-friendly layout
- About, Contact, and Press pages load
- Content is meaningful
- Navigation works
- Proper titles
- Basic WCAG compliance
- Heading hierarchy
- Lang attribute
- Skip links/main landmarks
- Form labels
- Button accessible names
- Link accessible text
- Keyboard navigation
- Load time within acceptable limits
- Reasonable number of requests
- JavaScript optimization
- Image optimization
- Asset caching
- Font loading
General SEO Tests (seo-astro.spec.ts):
- Meta descriptions (with length validation)
- Open Graph tags (og:title, og:description, og:type, og:url)
- Twitter Card tags
- Canonical URLs
- Proper title tags (with length validation)
- Robots meta tags (indexability)
- Structured data (JSON-LD validation)
- Sitemap accessibility
- robots.txt accessibility
- RSS feed accessibility
- Blog post specific meta tags
The Playwright configuration is in playwright.config.ts at the root of the repository.
Key settings:
- Base URL:
http://127.0.0.1:8792, the local Worker (can be overridden withBASE_URLenv var) - Browser: Chromium (Desktop Chrome)
- Workers: 50% of CPUs in CI for parallel execution, unlimited locally
- Timeouts: 15s navigation, 5s actions (optimized for fast static site)
- Retries: 1 retry on CI, 0 locally
- Reporters: HTML and list reporters, GitHub Actions reporter on CI
The tests have been optimized for speed:
- Parallel Execution: Workers in CI run tests concurrently
- Fast Page Loads:
waitForPageReady()usesloadstate instead ofnetworkidlefor most tests - Reduced Timeouts: Navigation and action timeouts reduced from 30s/10s to 15s/5s
- Shared Page State:
beforeEachhooks eliminate redundant page navigations - Minimal Waits: Only wait for
networkidlewhen necessary (e.g., performance tests)
-
waitForPageReady(): Fast load helper (recommended for most tests)- Waits for
domcontentloadedandloadstates - ~1–3 seconds faster than
waitForFullLoad - Use when you don't need to wait for all network activity
- Waits for
-
waitForFullLoad(): Complete load helper (use sparingly)- Waits for
domcontentloadedandnetworkidlestates - Waits until there are no more than 2 network connections for at least 500ms
- Use for performance tests or when testing lazy-loaded content
- Waits for
Tests run automatically on every push via the GitHub Actions workflow at .github/workflows/playwright.yml.
The workflow:
- Checks out the code
- Sets up Ruby and Node.js
- Builds the Astro site
- Starts an Astro preview server
- Runs Playwright tests in parallel
- Uploads test reports as artifacts
When adding new tests, follow these guidelines:
- Use helpers: Import and use helper functions from
helpers.tsfor common checks - Use
waitForPageReady(): Prefer this overwaitForFullLoad()for faster tests - Group related tests: Use
beforeEachto navigate once for multiple tests - Be specific: Test specific functionality, not everything at once
- Be independent: Tests should not depend on each other
- Use descriptive names: Test names should clearly describe what they test
- Handle edge cases: Consider missing content, error states, etc.
- Follow patterns: Look at existing tests for patterns to follow
import { test, expect } from '@playwright/test';
import { checkCommonElements, waitForPageReady } from './helpers';
test.describe('New Feature', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/some-page');
await waitForPageReady(page); // Use waitForPageReady for speed
});
test('should do something specific', async ({ page }) => {
// Your test assertions - page is already loaded from beforeEach
await expect(page.locator('.some-element')).toBeVisible();
});
test('should do another thing', async ({ page }) => {
// No need to navigate again - page state is shared
await expect(page.locator('.another-element')).toBeVisible();
});
});When tests fail, Playwright captures:
- Screenshots (on failure)
- Videos (on failure)
- Traces (on retry)
These are saved to:
test-results/- Test outputplaywright-report/- HTML report
# Run in debug mode with Playwright Inspector
npm run test:e2e:debug
# Run specific test in debug mode
npx playwright test homepage.spec.ts --debug# Run in UI mode for interactive debugging
npm run test:e2e:ui-
Keep tests fast:
- Use
waitForPageReady()instead ofwaitForFullLoad()when possible - Group tests with
beforeEachto avoid redundant navigations - Use efficient selectors
- Avoid unnecessary waits
- Use
-
Keep tests reliable:
- Use proper wait conditions, not fixed timeouts
- Wait for elements to be visible/hidden before interacting
- Handle async operations properly
-
Keep tests maintainable:
- Use helpers for common operations
- Avoid duplication
- Extract complex selectors into variables
-
Keep tests readable:
- Clear test names
- Good structure with describe blocks
- Comments when needed
- One assertion focus per test
-
Run tests frequently:
- Run locally before committing
- Check CI results
- Fix flaky tests immediately