Playwright Global Setup and Teardown A Practical Guide
Master Playwright’s global setup and teardown to optimize your test suite. Learn state sharing, database seeding, and best practices.
Test automation frameworks run faster today than ever before. Teams now expect complete end-to-end test suites to finish their execution in just a few minutes.
But repeatedly logging into your web application or spinning up fresh databases for every single test slows down your pipeline drastically.
This guide explains how a solid playwright global setup fixes this delay, while a proper teardown phase ensures your environment stays completely clean.
What is playwright global setup
A playwright global setup is a specific configuration phase that runs exactly once before your entire test suite starts executing.
Playwright uses worker processes to run tests in parallel. Each worker operates in complete isolation. If you do not prepare your environment globally, every single worker will have to repeat the exact same preparation steps.
This repetition creates massive bottlenecks in your test automation pipeline. It also increases the load on your backend servers unnecessarily.
By utilizing a playwright global setup, you prepare the environment beforehand. You can log in via an API, save the session cookies, and share them across all isolated workers automatically.
Definition: A global setup in Playwright is an asynchronous script that executes one time at the very beginning of the test run, before any parallel worker processes launch.
This approach radically speeds up execution times. It also reduces server costs since you no longer hit the authentication endpoints thousands of times per run.
When you migrate your framework, perhaps migrating from Cypress to Playwright, adapting to this global initialization phase is crucial. Cypress handles state differently, but Playwright gives you full architectural control.

Global setup vs worker setup
Many developers confuse global setup with worker-level setup. They are very different concepts.
Worker-level setups run via the beforeAll hook inside your test files. If you configure five workers to run tests concurrently, the beforeAll hook executes five times.
A playwright global setup runs before Playwright even boots up those five workers. It runs strictly one time.
This makes the global approach the perfect place for heavy, one-time infrastructure tasks.
How to configure playwright global setup
Configuring your playwright global setup requires minimal code but proper structural planning.
Playwright provides two primary ways to initialize globally. You can use the legacy globalSetup property in your configuration file, or you can use project dependencies.
Project dependencies represent the modern and recommended approach. They offer better flexibility and allow you to view the setup steps directly in the HTML report.
Let us explore the modern project dependencies method. It aligns with modern Playwright best practices.
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
In this configuration, we define a dedicated project named "setup". The actual testing project "chromium" strictly depends on it.
Playwright reads this dependency and guarantees that the setup project runs and completes successfully before the chromium project starts.
Next, you need to create the actual setup script file.
import { test as setup, expect } from '@playwright/test';
setup('authenticate user', async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('input[name="username"]', 'admin');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page.locator('text=Welcome Admin')).toBeVisible();
await page.context().storageState({ path: 'playwright/.auth/user.json' });
});
This file behaves exactly like a standard test. You get access to the page fixture, assertions, and reporting capabilities.
Tip: Always assert that your login was successful before saving the storage state. If you save the state before the server sets the cookies, all subsequent tests will fail.
Handling authentication state efficiently
Authentication is usually the most time consuming step in any test scenario.
A web page requires rendering, the browser needs to execute JavaScript, and the backend must validate the credentials.
When you use a playwright global setup, you completely eliminate this repetitive wait time.
Once the initial script logs in, it captures the storageState. This state object contains all cookies, local storage items, and session storage variables.
The test configuration then reads this JSON file and injects it into every new browser context automatically.
Your tests will start directly on the logged in dashboard instead of the login screen. This single change is often the biggest factor in reducing Playwright CI runtime.
Using api contexts for faster setups
While using the browser page to log in works well, it is still relatively slow.
For maximum performance, you should use the API request context to handle your authentication during the playwright global setup.
import { test as setup, request } from '@playwright/test';
import fs from 'fs';
setup('api authentication', async () => {
const apiContext = await request.newContext();
const response = await apiContext.post('https://api.example.com/login', {
data: {
username: 'admin',
password: 'password123'
}
});
const authState = await apiContext.storageState();
fs.writeFileSync('playwright/.auth/user.json', JSON.stringify(authState));
});
This API approach takes milliseconds compared to the seconds required for a full UI render.
When you heavily optimize Playwright workers, shifting your global authentication from the UI layer to the API layer provides massive scalability.
Note: Some applications use complex security tokens that are difficult to replicate via simple API calls. In those rare cases, sticking to the UI login method is perfectly acceptable.
Database seeding for stable environments
Testing requires predictable data. If your data changes unexpectedly, your assertions will fail randomly.
These random failures create unreliable pipelines. Implementing a strong database seeding process within your playwright global setup is the best way to prevent flaky tests.
When you seed a database globally, you insert exactly the data your tests expect to find.
You can create admin users, product catalogs, and specific account statuses.
import { test as setup } from '@playwright/test';
import { PrismaClient } from '@prisma/client';
setup('seed database', async () => {
const prisma = new PrismaClient();
// Clear existing data safely
await prisma.user.deleteMany({});
// Insert fresh test data
await prisma.user.create({
data: {
email: '[email protected]',
role: 'ADMIN',
status: 'ACTIVE'
}
});
await prisma.$disconnect();
});
This script connects directly to your database instance and injects the required records before any tests execute.
If you struggle with tests failing intermittently due to shared state or bad data, investing in flaky test analysis will show that improper setup is often the root cause.
Testing against a known state eliminates this entire category of bugs.
Your comprehensive playwright teardown guide
While setup prepares the stage, teardown cleans it up.
A reliable playwright teardown guide is essential for keeping your test environments healthy over time.
If you constantly insert database records but never delete them, your test database will eventually run out of storage or suffer from severe performance degradation.
Unlike the modern setup projects, teardown is generally still handled using the traditional globalTeardown property in the config file.
import { FullConfig } from '@playwright/test';
import { PrismaClient } from '@prisma/client';
async function globalTeardown(config: FullConfig) {
console.log('Starting global teardown phase...');
const prisma = new PrismaClient();
// Clean up all data created during the test run
await prisma.testRecords.deleteMany({
where: {
createdAt: {
gte: new Date(Date.now() - 3600000) // Delete records from last hour
}
}
});
await prisma.$disconnect();
console.log('Teardown complete.');
}
export default globalTeardown;
You must link this script inside your configuration file.
import { defineConfig } from '@playwright/test';
export default defineConfig({
globalTeardown: require.resolve('./global-teardown'),
// ... other settings
});
The teardown function receives the full configuration object, allowing you to read environment variables and output directories.
Beyond database cleanup, this playwright teardown guide recommends using this phase for reporting. You can trigger automated Slack messages with pass/fail ratios or upload coverage reports to an external bucket.
Integrating AI tools into this final phase allows you to leverage the Playwright AI ecosystem for automated failure analysis.
Managing multiple test environments
Testing against a single local environment is rarely enough for modern engineering teams.
You likely run your tests against local host, a staging server, and occasionally a production environment.
Your playwright global setup must dynamically adapt to these different targets. Hardcoding URLs or database credentials will break your pipeline instantly when the environment changes.
Using environment variables is the standard industry practice to handle this complexity.
The dotenv package is incredibly popular for this exact reason. You can load specific .env files directly inside your global setup script based on a system variable.
import { test as setup } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
setup('load environment variables', async () => {
const environment = process.env.TEST_ENV || 'local';
dotenv.config({
path: path.resolve(__dirname, `../../.env.${environment}`),
});
console.log(`Running tests against: ${process.env.BASE_URL}`);
});
By separating your configuration into .env.local, .env.staging, and .env.production, your setup becomes highly modular.
When you compare testing frameworks, especially Playwright vs Cypress, Playwright's native Node.js environment makes reading system files and dynamic variables much easier to orchestrate.

Best practices for a stable pipeline
Mastering your playwright global setup requires adhering to a few critical best practices.
First, always make your setup and teardown scripts completely idempotent. Idempotency means that running the script multiple times produces the exact same result without causing errors.
If your setup script creates a user, it should first check if that user already exists, or cleanly delete the old one. Do not let your script crash because of a "User already exists" database constraint.
Second, wrap your teardown logic in generous try/catch blocks.
If a teardown script throws an unhandled error, Playwright will exit with a failure code, even if all your tests passed perfectly.
async function globalTeardown() {
try {
await clearTestDatabases();
await sendSlackNotification();
} catch (error) {
console.error('Teardown failed, but tests completed:', error);
// Do not throw the error to prevent failing the CI build
}
}
Third, never overcomplicate the initialization phase. If you need complex data structures for just one specific test file, handle that data inside that file's beforeAll block.
Keep the global scripts strictly for resources that every single test requires.
When your suite grows massive, you will eventually implement Playwright sharding to split tests across multiple CI machines. Your setup logic must run flawlessly on every shard independently.
Proper Playwright test reporting also benefits from a clean setup phase, as logs remain uncluttered and focused on actual test steps.
Conclusion
Implementing a well structured playwright global setup transforms an unstable, slow test suite into a robust engineering asset.
By saving authentication state, seeding databases predictably, and managing environments dynamically, you remove the biggest roadblocks in modern test automation.
Pairing this with a reliable playwright teardown guide ensures your infrastructure remains healthy, avoiding data bloat and random pipeline failures over time.
If you are exploring more advanced automation concepts, discovering how to integrate Playwright tests with Antigravity can further enhance your testing capabilities. You can also leverage modern AI test generation tools to build your initial test structures automatically.
To continuously improve your abilities and dive deeper into API mocking and visual regressions, check out the dedicated playwright skill repository for advanced tutorials.
FAQs

Ayush Mania
Forward Development Engineer

