Part 05 · Operations & Advanced Card 37
Global Setup & Teardown
Card 37: Global Setup & Teardown
What This Pattern Solves
Card 19 describes auth storage state. This card shows how to run that login once before the whole suite using the modern setup project pattern instead of test.beforeAll. A setup project logs in each role a single time, writes a storageState file per role, and specs consume those files with test.use(\{ storageState \}). The config wires it via dependencies: ['setup'], so the setup project always runs first.
How It Works
setupproject (src/auth.setup.ts) matches*.setup.tsand logs in each role once, savingplaywright/.auth/<role>.jsonand writing a.setup-donemarker.dependencies: ['setup']on the functional projects guarantees the setup project completes before any spec runs.test.use(\{ storageState: 'playwright/.auth/admin.json' \})loads a role’s saved auth per describe block; omitting it leaves the test unauthenticated (and protected routes redirect to/login)..gitignoretheplaywright/.auth/directory.
Code Example
// playwright.config.ts
export default defineConfig({
projects: [
// Auth bootstrap: logs in once per role, writes storageState files.
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
// Functional suite depends on setup so the auth files exist first.
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
dependencies: ['setup'],
},
],
});
// src/auth.setup.ts
import * as fs from 'fs';
import { test as setup, expect } from '@playwright/test';
import { loginAs } from './e2e-patterns/login/flow';
const AUTH_DIR = 'playwright/.auth';
setup('authenticate as admin', async ({ page }) => {
fs.mkdirSync(AUTH_DIR, { recursive: true });
await loginAs(page, 'admin', 'adminpass');
await expect(page.getByTestId('dashboard-message')).toContainText('admin');
await page.context().storageState({ path: `${AUTH_DIR}/admin.json` });
fs.writeFileSync(`${AUTH_DIR}/.setup-done`, 'setup-completed');
});
// a spec consuming a role
test.describe('admin role', () => {
test.use({ storageState: `${AUTH_DIR}/admin.json` });
test('admin reaches the protected page with no UI login', async ({ page }) => {
await page.goto('/protected');
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
});
Alternative: globalSetup / globalTeardown
The older config-level lifecycle still works and is documented here for completeness. The setup-project approach above is preferred because it shows in the report, gets traces/retries, and parallelises per role.
// playwright.config.ts
export default defineConfig({
globalSetup: './global-setup.ts',
globalTeardown: './global-teardown.ts',
});
// global-setup.ts — runs once before all tests, manages its own browser
export default async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const ctx = await browser.newContext();
const page = await ctx.newPage();
await loginAs(page, 'admin', 'adminpass');
await ctx.storageState({ path: 'playwright/.auth/admin.json' });
await ctx.close();
await browser.close();
}
Run This Example
pnpm test src/37-global-setup-teardown
Key Concepts
setupproject +dependencies: ['setup']: Preferred way to run login once before the suite; appears in the report and supports traces/retries.test.use(\{ storageState \}): Selects a saved role per describe block; omit it for an unauthenticated test.- Marker file:
playwright/.auth/.setup-doneproves the setup project ran first. globalSetup/globalTeardown: Config-level alternative; runs once and manages its own browser. ReceivesFullConfig.
Common Mistakes
- Forgetting
.gitignorefor auth files. - Using
test.beforeAllto log in per file instead of a shared setup project. - Forgetting
dependencies: ['setup'], so storageState files don’t exist yet.
Related Patterns
- Previous: Card 36 (File Uploads & Downloads)
- Complementary: Card 19 (Auth Storage State), Card 33 (Worker-Scoped Fixtures), Card 20 (API Seeding)
Live Demo
👇 This component is what the Playwright test interacts with:
Loading…
Run This Example
pnpm test src/37-global-setup-teardown