Hemmelig uses Playwright for end-to-end integration testing.
Playwright and browsers are installed as dev dependencies. If you need to install browser dependencies on your system:
sudo npx playwright install-depsThe e2e tests automatically use a separate test database (database/hemmelig-test.db) that is:
- Created fresh before each test run
- Migrated with the latest schema
- Seeded with a test user
- Deleted after tests complete
This ensures tests don't affect your development database.
Test User Credentials (created automatically):
- Email:
e2e-test@hemmelig.local - Username:
e2etestuser - Password:
E2ETestPassword123!
# Run all e2e tests
npm run test:e2e
# Run tests with interactive UI
npm run test:e2e:ui
# Run tests in debug mode
npm run test:e2e:debug
# Run a specific test file
npx playwright test tests/e2e/secret.spec.ts
# Run tests in headed mode (see the browser)
npx playwright test --headedTests are located in tests/e2e/:
| File | Description |
|---|---|
auth.spec.ts |
Authentication tests (setup, login, registration) |
home.spec.ts |
Homepage and secret form tests |
secret.spec.ts |
Secret creation, viewing, and deletion flows |
navigation.spec.ts |
Navigation and routing tests |
fixtures.ts |
Shared test fixtures (authenticatedPage) |
global-setup.ts |
Creates test database and user before tests |
global-teardown.ts |
Cleans up test database after tests |
The Playwright configuration is in playwright.config.ts:
- Test directory:
tests/e2e/ - Base URL:
http://localhost:5173 - Browser: Chromium (Desktop Chrome)
- Web server: Automatically starts Vite with test database
- Global setup: Creates fresh test database and test user
- Global teardown: Deletes test database
import { expect, test } from '@playwright/test';
test.describe('Feature Name', () => {
test('should do something', async ({ page }) => {
await page.goto('/');
// Interact with elements
await page.locator('.ProseMirror').fill('My secret');
await page.getByRole('button', { name: /create/i }).click();
// Assert results
await expect(page.getByText(/success/i)).toBeVisible();
});
});For tests that require authentication:
import { expect, test } from './fixtures';
test('should create a secret when logged in', async ({ authenticatedPage }) => {
await authenticatedPage.goto('/');
// authenticatedPage is already logged in with test user
await expect(authenticatedPage.locator('.ProseMirror')).toBeVisible();
});Interacting with the secret editor:
const editor = page.locator('.ProseMirror');
await editor.click();
await editor.fill('Secret content');Creating and viewing a secret:
// Create
await page.goto('/');
await page.locator('.ProseMirror').fill('My secret');
await page
.getByRole('button', { name: /create/i })
.first()
.click();
// Get the URL
const urlInput = page.locator('input[readonly]').first();
const secretUrl = await urlInput.inputValue();
// View
await page.goto(secretUrl);
await page.getByRole('button', { name: /unlock/i }).click();Tests run in CI with these settings (from playwright.config.ts):
forbidOnly: true- Fails if.onlyis left in testsretries: 2- Retries failed tests twiceworkers: 1- Single worker to prevent conflicts
- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Run E2E Tests
run: npm run test:e2eAfter running tests, view the HTML report:
npx playwright show-report- Run in debug mode:
npm run test:e2e:debug - Run with UI:
npm run test:e2e:ui - View traces: Failed tests generate traces in
test-results/ - Screenshots: Failed tests save screenshots automatically
- Use data-testid for stable selectors when possible
- Prefer user-facing selectors like
getByRole,getByText,getByPlaceholder - Add appropriate timeouts for async operations
- Keep tests independent - each test should work in isolation
- Use
.first()when multiple elements match to avoid strict mode violations