One-Time Authentication in Playwright Using Global Setup (Complete Beginner Guide)
- Anuradha Agarwal
- Mar 3
- 5 min read
Introduction
In the previous post, we introduced Playwright hooks. We used beforeAll along with storageState to solve an important real-world problem: avoiding repeated login before every test in a single spec file.
Earlier, each test had to perform login steps as part of its workflow. This made the scripts longer, slower, and harder to maintain. By moving the login logic into beforeAll, we were able to authenticate once, save the session state, and reuse it across tests in the same file. This was a major improvement because our tests could now focus on the actual workflows — such as editing account details or completing a purchase — instead of repeatedly performing setup steps.
As our Playwright automation framework grows, we naturally split our tests into multiple spec files.
For example, in our demo store project, we now have separate spec files for different workflows:
Purchase product workflow
Account edit workflow
Each of these tests requires one common step before execution: user authentication.
Initially, we implemented login inside each spec file using beforeAll. While this works, it introduces duplication and violates one of the core principles of building a scalable automation framework
Authentication should be handled once and reused across all tests.
Playwright provides a powerful built-in solution for this using Global Setup.
In this post, we will implement global authentication step by step and configure our framework so that login happens only once before all tests run.
Our Current Project Structure
Here is the actual structure of our Playwright project:
PLAYWRIGHT_LEARN
│
├── helpers
├── global-setup.ts
│
├── tests
├── globalsetup_purchase_workflow_demostore.spec.ts
├── globalsetup_account_edit_workflow_demostore.spec.ts
└── other spec files...
├── .env
├── playwright.config.ts
├── package.json
├── state.jsonThe key components are:
global-setup.ts → handles authentication
playwright.config.ts → configures Playwright to use global setup
.env → stores secure credentials
state.json → stores authenticated session
What is Global Setup?
Think of a global setup as preparing your environment before starting work.
Instead of logging in before every test, you log in once and reuse that session.
Playwright executes the global setup before any test begins.
The authentication session is saved to a file named storageState and then reused automatically.
Step 1: Store Credentials Securely Using .env
Create a file named .env in the project root.
Example:
DEMO_USER="anuradha.learn@gmail.com
"DEMO_PASS="Playwright#$"Important note: Since # is treated as a comment in .env, enclosing the password in quotes ensures it is read correctly.
Step 2: Create Global Setup File
Create the file:
helpers/global-setup.tsThis file performs a login once and saves the session.
Example implementation:
import { chromium, FullConfig, expect } from "@playwright/test";
import dotenv from "dotenv";
dotenv.config();
async function globalSetup(config: FullConfig)
{
const baseURL = "https://qa-cart.com";
const storageStatePath = "state.json";
const username = process.env.DEMO_USER!;
const password = process.env.DEMO_PASS!;
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(baseURL);
await page.locator("input[name='username']").fill(username);
await page.locator("input[name='password']").fill(password);
await page.getByRole("button", { name: /log in/i }).click();
await page.goto(`${baseURL}/my-account/`);
await expect(
page.getByRole("link", { name: /log out/i })
).toBeVisible();
await context.storageState({ path: storageStatePath });
await browser.close();}
export default globalSetup;This script logs in and saves the authentication in state.json.
Step 3: Configure Playwright to Use Global Setup
Open:
playwright.config.tsAdd globalSetup configuration:

This tells Playwright to execute the global setup before running tests.
Step 4: Update Spec Files (No Login Required)
Now your spec files no longer need login code.
Example spec file:
import { test, expect } from "@playwright/test";test("Verify purchase workflow", async ({ page }) => {
await page.goto("/demoshop");
await expect(
page.getByRole("heading", { name: "DemoShop" }))
.toBeVisible();
});Playwright automatically loads an authenticated session from state.json.
At this point, our test.spec files are free from test data(handled in json) , config data(handled in playwright config file and sensitive dat - in env file). We do not have any beforeall(login logic) as well, and no context creation too.
Step 5: Execution Flow
When you run:
npx playwright testPlaywright performs the following steps:
Runs global setup
Launches the browser and logs in
Saves authentication state in state.json
Runs all spec files
Each test starts already authenticated
Authentication runs only once.
Debugging Tip: Run Global Setup in Visible Mode
While debugging, use:
const browser = await chromium.launch({ headless: false });This allows you to observe the login visually.
Once confirmed, switch back to headless mode.
Benefits of Global Setup
Using a global setup provides major advantages.
Authentication runs only once, improving performance.
Spec files remain clean and focused on test logic.
Framework becomes easier to maintain and scale.
Sensitive credentials remain secure using environment variables.
Test execution becomes faster and more reliable.
Upgrading to a better Option
What we achieved, but what we may miss as we deal with report traces results, which may need cleanup as we run the suite every time
Global Setup the Playwright-Recommended Way: Project Dependencies
Instead of running a separate global setup script “outside” the test runner, we create a dedicated setup project that runs before everything else.
This approach integrates beautifully with Playwright Test Runner.
Why Project Dependencies are recommended
When you use the globalSetup config option, Playwright runs it like a “standalone script.”
That means:
It won’t show in the HTML report
It won’t generate traces
fixtures are not available
You manually launch browsers
With project dependencies, setup is treated like a real Playwright test project, so:
It shows in the report
traces and artefacts are available
fixtures like { page } and { browser } work
config options (headless, retries, etc.) apply automatically
Step-by-step: Use Project Dependencies for One-Time Authentication
We will create:
A setup project that logs in and writes state.json
All other projects/tests will depend on the setup project
Every test will automatically start authenticated using storageState
Step 1: Create a setup test file
Create this file in your tests/ folder:
tests/global.setup.ts
This is a special “setup test” that performs login and saves the auth state.
import { test, expect } from "@playwright/test";
import dotenv from "dotenv";
dotenv.config();
test("global setup: login and save storageState", async ({ page }) => {
const baseURL = "https://qa-cart.com";
const storageStatePath = "state.json";
const username = process.env.DEMO_USER!;
const password = process.env.DEMO_PASS!;
await page.goto(baseURL);
await expect(page.getByRole("heading", { name: /login/i })).toBeVisible();
await page.locator("input[name='username']").fill(username);
await page.locator("input[name='password']").fill(password);
await page.getByRole("button", { name: /log in/i }).click();
// Verify login on a stable page
await page.goto(`${baseURL}`);
await expect(page.getByRole("link", { name: /log out|logout/i }).first()).toBeVisible();
// Save login session
await page.context().storageState({ path: storageStatePath });
});Step 2: Update playwright.config.ts with setup project + dependencies
Now we define multiple “projects”:
setup (runs first)
chromium/firefox / webkit etc. (depends on setup)

Step 3: Keep your spec files clean (no login code!)
No beforeAll login. No auth duplication—no manual context creation.
In this journey, we moved from duplicating login logic inside every spec file to implementing a professional, scalable authentication architecture using Playwright’s recommended Project Dependencies model.
Let’s recap what we achieved.
Where We Started
Initially, authentication was written inside each spec file using beforeAll.
It worked, but:
Login logic was duplicated
Maintenance became harder
Setup logic was tightly coupled with test logic
Scaling across multiple spec files felt messy
What We Built
We implemented:
A dedicated global.setup.ts file
A setup project configured inside playwright.config.ts
Project dependencies to ensure setup runs before workflow tests
Authentication session stored using storageState
Clean spec files without login logic
Now, authentication runs once before all dependent projects.
Spec files remain focused only on business workflows.
Our Current Flow
When we run:
npx playwright testExecution happens in this order:
Setup project runs
User logs in
Authentication state is saved
Workflow spec files execute
All tests start already authenticated
Authentication is cleanly separated from test logic.
Final Project Structure
project-root│
├── tests/
│
├── global.setup.ts│
├── purchase_workflow.spec.ts│
├── account_edit.spec.ts│
├── state.json
├── playwright.config.ts
├── .envThis is a scalable, maintainable Playwright test framework structure.
What’s Next
Now that authentication is architected properly, our next steps will focus on:
Adding structured reporting enhancements
Improving logging
Implementing teardown strategies
Cleaning up test data after execution - global tear down
Adding trace and artifact management




Comments