Authentication
When to Use
Use
storageStateto log in once per CI run per role and replay credentials across tests. Use programmatic API login (not UI login) when the API endpoint exists.
Decision
| Approach | When |
|---|---|
Programmatic API token (POST /oauth/token, /user/login?_format=json) |
Always preferred when the API exists — faster, never breaks because of UI redesigns, isolates auth from feature tests |
| UI login | Only when verifying the actual login form, or when no API exists |
| Mixed | One UI login per worker into storageState, then every test reads the storage state |
Pattern
// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import path from 'path';
const userFile = path.join(__dirname, '../playwright/.auth/user.json');
setup('authenticate as user', async ({ page }) => {
await page.goto('/user/login');
await page.getByLabel('Username').fill(process.env.TEST_USER!);
await page.getByLabel('Password').fill(process.env.TEST_PASS!);
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByRole('navigation')).toContainText('Log out');
await page.context().storageState({ path: userFile });
});
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
dependencies: ['setup'],
},
],
});
Multi-role
// playwright.config.ts
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'admin',
testMatch: /admin\/.*\.spec\.ts/,
use: { storageState: 'playwright/.auth/admin.json' },
dependencies: ['setup'],
},
{
name: 'editor',
testMatch: /editor\/.*\.spec\.ts/,
use: { storageState: 'playwright/.auth/editor.json' },
dependencies: ['setup'],
},
{ name: 'anonymous', testMatch: /anon\/.*\.spec\.ts/ },
],
HTTP basic auth (staging environments)
use: {
httpCredentials: {
username: process.env.STAGING_USER!,
password: process.env.STAGING_PASS!,
},
}
Common Mistakes
- Wrong: Committing
playwright/.auth/*.json— leaks session cookies. Right: Add.auth/to.gitignore - Wrong: UI login per test — slow. Right: Use
storageStateonce per CI run - Wrong: Hard-coded credentials in tests. Right: Use env vars or a secrets manager
See Also
- Fixtures — wrapping auth reload detection in a fixture
- Drupal & DDEV Patterns — Drupal session cookie specifics (
SESS…/SSESS…) - Reference: Playwright Authentication