Playwright’s global setup is how you ensure a consistent, clean environment before any of your tests even start.

Imagine you’re running a suite of tests for an e-commerce site. Before you can even think about testing a user adding an item to their cart, someone needs to be logged in, or at least have a known user state. Or maybe you need to seed a database with specific test data. That’s what global setup is for. It’s your pre-flight checklist.

Let’s see it in action. Suppose we want to ensure a user is logged in for all our tests. We’ll create a global.setup.ts file in our tests directory.

// tests/global.setup.ts
import { test as setup, expect } from '@playwright/test';

const authFile = '.auth/user.json';

setup('authenticate', async ({ page }) => {
  // Go to the login page
  await page.goto('https://example.com/login');
  // Enter the username. Using a placeholder here, but in reality,
  // you'd use environment variables or a secure vault.
  await page.locator('[name="username"]').fill('testuser');
  // Enter the password. Same security considerations as username.
  await page.locator('[name="password"]').fill('password123');
  // Click the login button
  await page.locator('button[type="submit"]').click();
  // Wait until the page navigates to the dashboard or a post-login element
  await expect(page).toHaveURL('https://example.com/dashboard');

  // Save the authenticated state to a file
  await page.context().storageState({ path: authFile });
});

Now, in our playwright.config.ts, we need to tell Playwright to run this setup file and use the saved state.

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // ... other configurations ...

  // Define the global setup file
  globalSetup: require.resolve('./tests/global.setup.ts'),

  use: {
    // ... other configurations ...

    // Use the storage state saved by the global setup
    storageState: '.auth/user.json',
  },

  // ... other configurations ...
});

With this setup, every time Playwright runs, it will first execute global.setup.ts. If the authFile doesn’t exist or is stale (Playwright is smart enough to detect this), it will run the authenticate function. This function logs in, and crucially, saves the browser context’s storageState (cookies, local storage, session storage) to .auth/user.json. Subsequent tests will then automatically load this storageState, meaning they start already logged in.

This is incredibly powerful for several reasons. It ensures every test run begins with a clean, predictable state, eliminating flaky tests that depend on previous, potentially failed, test runs. It also dramatically speeds up your test execution because you’re not logging in for every single test file or, worse, every single test case. You log in once, globally.

The storageState is the key here. It’s a snapshot of everything the browser knows about your session: cookies, local storage, session storage, and even IndexedDB. By saving this, you’re essentially capturing the "logged-in" state of the browser context. When you tell Playwright to use this storageState in your playwright.config.ts, it pre-populates the browser context for all your tests with that saved state.

Playwright’s globalSetup hook is a powerful tool for establishing a consistent pre-test environment. It executes once before any tests run, making it ideal for tasks like user authentication, database seeding, or setting up API mocks that need to be present for the entire test suite. The storageState mechanism is particularly elegant for handling authentication, allowing you to log in once and have that state persisted across all your tests without re-authenticating repeatedly.

After this, you’ll likely want to think about how to handle different user roles or states for specific test scenarios.

Want structured learning?

Take the full Playwright course →