Introduction
Authentication is one of the most important parts of modern web automation.
Almost every real-world application has protected pages. Users must log in before accessing dashboards, products, orders, profiles, payments, or administration features.
For QA Automation Engineers and SDETs, simply automating a login form is not enough. You should also understand sessions, cookies, tokens, JWT authentication, storage state, API authentication, role-based access, fixtures, and CI/CD secrets.
This playwright authentication tutorial explains these concepts step by step using Playwright with TypeScript.
You will learn how to:
- Automate a login page
- Reuse authentication between tests
- Save authentication state
- Work with cookies
- Work with bearer and JWT tokens
- Authenticate through APIs
- Create authentication fixtures
- Implement Page Object Model
- Test admin and normal-user roles
- Run authentication tests in parallel
- Debug authentication failures
- Run authenticated tests in CI/CD
- Protect credentials and authentication files
The official Playwright documentation recommends saving authenticated browser state and reusing it across tests when appropriate, which can avoid repeatedly performing the UI login flow.
What Is Playwright Authentication?
Playwright authentication means establishing an authenticated browser or API session before executing protected application tests.
A typical flow looks like this:
Test starts
↓
Authenticate user
↓
Receive session/cookies/tokens
↓
Store authentication state
↓
Open protected application
↓
↓
Logout / clean up
For example:
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(process.env.TEST_USERNAME!);
await page.getByLabel(‘Password’).fill(process.env.TEST_PASSWORD!);
await page.getByRole(‘button’, { name: ‘Login’ }).click();
await expect(page).toHaveURL(/dashboard/);
This is the simplest form of Playwright login authentication.
However, large test suites should normally avoid repeating the complete login UI flow for every test.
That is where authentication state becomes important.
Authentication vs Authorization
These two terms are often confused.
Authentication
Authentication answers:
Who are you?
Examples:
- Username and password
- OAuth login
- JWT
- Session cookie
- API token
Authorization
Authorization answers:
What are you allowed to do?
For example:
User
├── View products
├── Add products to cart
└── Place orders
Admin
├── View products
├── Manage products
├── Manage users
└── View reports
A good automation framework should test both.
| Concept | Purpose |
| Authentication | Verify identity |
| Authorization | Verify permissions |
| Session | Maintains authenticated state |
| Cookie | Stores session-related data |
| Token | Represents authentication/authorization information |
| JWT | A structured token commonly used by APIs |
Playwright Authentication Setup
For this playwright authentication tutorial for beginners, assume the project uses TypeScript.
Create a project:
npm init playwright@latest
Choose:
TypeScript
tests
A practical project structure is:
playwright-authentication/
│
├── tests/
│ ├── login.spec.ts
│ ├── dashboard.spec.ts
│ └── admin.spec.ts
│
├── pages/
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ └── AdminPage.ts
│
├── fixtures/
│ └── auth.fixture.ts
│
├── playwright/
│ └── .auth/
│
├── playwright.config.ts
├── package.json
└── .env
Install dotenv if your project needs local environment-variable loading:
npm install dotenv
Example .env:
TEST_USERNAME=test-user@example.com
TEST_PASSWORD=TestPassword123
BASE_URL=https://example.test
Never use real production credentials in automation code.
Basic Playwright Login Authentication Example
Create:
tests/login.spec.ts
Example:
import { test, expect } from ‘@playwright/test’;
test(‘user can login successfully’, async ({ page }) => {
await page.goto(`${process.env.BASE_URL}/login`);
await page.getByLabel(‘Email’).fill(
process.env.TEST_USERNAME!
);
await page.getByLabel(‘Password’).fill(
process.env.TEST_PASSWORD!
);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
await expect(page).toHaveURL(/dashboard/);
await expect(
page.getByRole(‘heading’, {
name: /dashboard/i
})
).toBeVisible();
});
How this works
Step 1
Open the login page:
await page.goto(`${process.env.BASE_URL}/login`);
Step 2
Read credentials from environment variables:
process.env.TEST_USERNAME
Step 3
Fill the login form:
await page.getByLabel(‘Email’).fill(…);
Step 4
Submit:
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
Step 5
Verify authentication:
await expect(page).toHaveURL(/dashboard/);
This approach is easy to understand, but it can become inefficient when hundreds of tests need authentication.
Playwright Storage State
One of the most important concepts in this playwright authentication tutorial guide is storageState.
Playwright can save browser authentication information such as cookies and local storage and reuse it in later tests.
The basic idea is:
Login once
↓
Save authentication state
↓
Reuse state
↓
Run many authenticated tests
Creating authentication state
Create:
tests/auth.setup.ts
Example:
import { test as setup, expect } from ‘@playwright/test’;
const authFile = ‘playwright/.auth/user.json’;
setup(‘authenticate’, async ({ page }) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(
process.env.TEST_USERNAME!
);
await page.getByLabel(‘Password’).fill(
process.env.TEST_PASSWORD!
);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
await expect(page).toHaveURL(/dashboard/);
await page.context().storageState({
path: authFile
});
});
The resulting state file can contain authentication-related browser state.
Then configure it:
import { defineConfig } from ‘@playwright/test’;
export default defineConfig({
use: {
baseURL: process.env.BASE_URL,
storageState: ‘playwright/.auth/user.json’
}
});
Now authenticated tests can start directly from the protected application.
Playwright Storage State Example
Consider this test:
import { test, expect } from ‘@playwright/test’;
test(‘authenticated user can access dashboard’, async ({
page
}) => {
await page.goto(‘/dashboard’);
await expect(
page.getByRole(‘heading’, {
name: /dashboard/i
})
).toBeVisible();
});
There is no login code in the test.
The authentication state is loaded before the test starts.
This is one of the biggest advantages of Playwright’s authentication model.
Cookies and Session-Based Authentication
Traditional web applications often use session cookies.
After login:
POST /login
↓
Server validates credentials
↓
Server creates session
↓
Browser receives cookie
↓
Browser sends cookie on future requests
Playwright allows you to inspect cookies:
const cookies = await page.context().cookies();
console.log(cookies);
You can also add a cookie when your test environment requires it:
await page.context().addCookies([
{
name: ‘session_id’,
value: process.env.SESSION_ID!,
domain: ‘example.test’,
path: ‘/’
}
]);
Then navigate to the protected page:
await page.goto(‘/dashboard’);
Security warning
Do not hard-code session cookies into your repository.
Avoid:
value: ‘abc123-real-session-token’
Use environment variables or secure CI/CD secrets instead.
Token and JWT Authentication
Modern single-page applications and APIs frequently use bearer tokens.
A request may look like:
Authorization: Bearer <token>
A JWT generally contains three parts:
Header.Payload.Signature
The browser or API client uses the token to authenticate requests.
For test automation, don’t place a real token directly into source code.
Instead:
const token = process.env.API_TOKEN!;
await page.setExtraHTTPHeaders({
Authorization: `Bearer ${token}`
});
Use this only when the application architecture and test environment support it.
Playwright API Authentication
UI login is not always the fastest authentication approach.
Playwright’s APIRequestContext can be used to perform API calls directly.
Example:
import { test, expect } from ‘@playwright/test’;
test(‘API authentication’, async ({ request }) => {
const response = await request.post(‘/api/login’, {
data: {
username: process.env.TEST_USERNAME,
password: process.env.TEST_PASSWORD
}
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body.token).toBeTruthy();
});
This approach is useful when authentication is available through an API.
API authentication workflow
API login
↓
Receive token
↓
Use token
↓
Call protected API
↓
Validate response
For example:
const loginResponse = await request.post(‘/api/login’, {
data: {
username: process.env.TEST_USERNAME,
password: process.env.TEST_PASSWORD
}
});
const { token } = await loginResponse.json();
const response = await request.get(‘/api/orders’, {
headers: {
Authorization: `Bearer ${token}`
}
});
expect(response.ok()).toBeTruthy();
This is the foundation of a practical Playwright API authentication tutorial.
Authentication with Page Object Model
Page Object Model makes authentication reusable.
Create:
pages/LoginPage.ts
import { Page } from ‘@playwright/test’;
export class LoginPage {
constructor(private page: Page) {}
emailInput = this.page.getByLabel(‘Email’);
passwordInput = this.page.getByLabel(‘Password’);
loginButton = this.page.getByRole(‘button’, {
name: ‘Login’
});
async login(
username: string,
password: string
) {
await this.emailInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
Test:
import { test, expect } from ‘@playwright/test’;
import { LoginPage } from ‘../pages/LoginPage’;
test(‘login using POM’, async ({ page }) => {
const loginPage = new LoginPage(page);
await page.goto(‘/login’);
await loginPage.login(
process.env.TEST_USERNAME!,
process.env.TEST_PASSWORD!
);
await expect(page).toHaveURL(/dashboard/);
});
This keeps authentication logic separate from test logic.
Playwright Authentication Fixtures
Fixtures are useful when authentication is required by many tests.
Example:
import {
test as base,
expect
} from ‘@playwright/test’;
type AuthFixtures = {
authenticatedPage: void;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
await page.goto(‘/login’);
await page.getByLabel(‘Email’).fill(
process.env.TEST_USERNAME!
);
await page.getByLabel(‘Password’).fill(
process.env.TEST_PASSWORD!
);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
await expect(page).toHaveURL(/dashboard/);
await use();
}
});
export { expect };
Test:
import { test, expect } from ‘../fixtures/auth.fixture’;
test(‘authenticated dashboard’, async ({
page,
authenticatedPage
}) => {
await page.goto(‘/dashboard’);
await expect(
page.getByRole(‘heading’, {
name: /dashboard/i
})
).toBeVisible();
});
In larger frameworks, a more efficient approach is often to create storage state once and have fixtures consume it.
Multi-User and Role-Based Authentication
Enterprise applications often have different users.
For example:
user.json
admin.json
manager.json
Configure separate projects:
projects: [
{
name: ‘user’,
use: {
storageState: ‘playwright/.auth/user.json’
}
},
{
name: ‘admin’,
use: {
storageState: ‘playwright/.auth/admin.json’
}
}
]
Then:
test(‘admin can access admin page’, async ({ page }) => {
await page.goto(‘/admin’);
await expect(
page.getByRole(‘heading’, {
name: /admin/i
})
).toBeVisible();
});
A normal user test might verify:
test(‘normal user cannot access admin page’, async ({ page }) => {
await page.goto(‘/admin’);
await expect(
page.getByText(/access denied|forbidden/i)
).toBeVisible();
});
This tests both authentication and authorization.
Playwright Authentication with Parallel Execution
Parallel testing introduces an important authentication consideration.
If every worker shares the same account and modifies its state, tests can interfere with one another.
For example:
Worker 1 → adds product to cart
Worker 2 → removes product
Worker 3 → changes profile
The result can become unpredictable.
Better approaches include:
- Separate accounts
- Isolated test data
- Independent storage states
- Worker-scoped authentication
- API-based test-data preparation
For read-only tests, sharing an authentication state may be acceptable.
For state-changing workflows, isolation is safer.
Playwright Authentication in CI/CD
Never store credentials directly in GitHub Actions YAML.
Bad:
env:
TEST_PASSWORD: “MyRealPassword123”
Instead, configure repository or organization secrets.
Example:
– name: Run Playwright tests
run: npx playwright test
env:
BASE_URL: ${{ secrets.BASE_URL }}
TEST_USERNAME: ${{ secrets.TEST_USERNAME }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
A complete simplified workflow:
name: Playwright Authentication Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
– uses: actions/checkout@v6
– uses: actions/setup-node@v6
with:
node-version: lts/*
– run: npm ci
– run: npx playwright install –with-deps
– run: npx playwright test
env:
BASE_URL: ${{ secrets.BASE_URL }}
TEST_USERNAME: ${{ secrets.TEST_USERNAME }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
– name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: playwright-report
path: playwright-report/
Playwright’s CI guidance covers installing browsers and publishing reports from CI runs.
Debugging Playwright Authentication Failures
Authentication failures can be difficult to diagnose.
Common symptoms include:
401 Unauthorized
403 Forbidden
Redirected back to login
Missing cookie
Expired token
Invalid storage state
Run headed
npx playwright test –headed
Debug mode
npx playwright test –debug
Capture a screenshot
await page.screenshot({
path: ‘debug-login.png’,
fullPage: true
});
Use tracing
In configuration:
use: {
trace: ‘retain-on-failure’
}
Then inspect the trace using Playwright Trace Viewer.
Tracing is especially useful because it can show:
- Actions
- Network activity
- Screenshots
- DOM snapshots
- Timing
- Errors
HTML reporting
Run:
npx playwright test
npx playwright show-report
A good authentication debugging workflow is:
Failure
↓
Check URL
↓
Check response status
↓
Inspect cookies/storage
↓
Inspect screenshot
↓
Inspect trace
↓
Check credentials
↓
Check environment
Real-World E-Commerce Playwright Authentication Project
A strong portfolio project can combine authentication with an e-commerce workflow.
Recommended structure
ecommerce-playwright/
│
├── pages/
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ ├── ProductsPage.ts
│ ├── CartPage.ts
│ ├── CheckoutPage.ts
│ └── AdminPage.ts
│
├── fixtures/
│ └── auth.fixture.ts
│
├── tests/
│ ├── login.spec.ts
│ ├── products.spec.ts
│ ├── cart.spec.ts
│ ├── checkout.spec.ts
│ └── admin.spec.ts
│
├── playwright/
│ └── .auth/
│
├── playwright.config.ts
└── package.json
Workflow
1. Login
Validate:
- Valid credentials
- Invalid password
- Invalid username
- Empty fields
- Locked account
2. Logout
Verify:
Authenticated
↓
Logout
↓
Login page
↓
Protected URL inaccessible
3. Session persistence
Login once.
Save storage state.
Use it for:
- Dashboard
- Products
- Cart
- Checkout
4. Product search
Authenticated user searches:
Laptop
Verify the results.
5. Cart
Add a product.
Verify:
- Product name
- Quantity
- Price
- Total
6. Checkout
Validate:
- Shipping address
- Payment page
- Order summary
- Confirmation
7. Admin authentication
Create a separate admin storage state.
Verify:
Admin → Admin dashboard → Product management
And:
Normal user → Admin URL → Access denied
8. API authentication
Use API login for setup.
For example:
const response = await request.post(‘/api/auth/login’, {
data: {
username: process.env.TEST_USERNAME,
password: process.env.TEST_PASSWORD
}
});
expect(response.ok()).toBeTruthy();
9. Reporting
Configure:
reporter: [
[‘html’],
[‘list’]
]
10. Failure debugging
Use:
use: {
screenshot: ‘only-on-failure’,
trace: ‘retain-on-failure’,
video: ‘retain-on-failure’
}
11. CI/CD
Run the entire framework through:
GitHub
↓
↓
Playwright
↓
Authentication
↓
E-commerce tests
↓
This is a strong QA/SDET portfolio project because it demonstrates much more than basic login automation.
Authentication Security Best Practices
Authentication automation deals with sensitive information.
Treat credentials and authentication state as secrets.
Never commit .env
Add:
.env
to .gitignore.
Never commit authentication state
Add:
playwright/.auth/
to .gitignore.
Example:
.env
playwright/.auth/
playwright-report/
test-results/
Playwright’s authentication documentation specifically warns that stored authentication state can contain sensitive cookies and headers that could impersonate a user.
Use environment variables
const username = process.env.TEST_USERNAME;
const password = process.env.TEST_PASSWORD;
Use CI/CD secrets
Use:
GitHub Secrets
Jenkins Credentials
Azure DevOps Variable Groups
instead of source-code credentials.
Use dedicated test accounts
Don’t automate against a real employee account.
Create:
qa-user@example.com
qa-admin@example.com
with controlled permissions.
Don’t print tokens
Avoid:
console.log(token);
because CI logs can expose secrets.
Don’t commit JWTs
Never store:
const token = ‘eyJhbGciOiJIUzI1Ni…’;
in source control.
Common Playwright Authentication Errors and Solutions
| Error | Cause | Solution |
| Redirected to login | Authentication expired | Regenerate storage state |
| 401 | Invalid/missing credentials | Check credentials/token |
| 403 | Insufficient permissions | Verify user role |
| Missing cookie | Incorrect authentication flow | Inspect context cookies |
| Invalid storage state | Corrupted/expired state | Recreate state |
| Login works locally but not CI | Environment differences | Check CI secrets |
| Token expired | Short token lifetime | Authenticate during setup |
| Tests interfere | Shared user state | Isolate accounts/data |
| .auth committed | Incorrect Git configuration | Add it to .gitignore |
| Password visible in logs | Debug logging | Mask secrets and remove logs |
Playwright Authentication Interview Questions
1. What is Playwright authentication?
It is the process of establishing an authenticated browser or API session so Playwright can test protected application functionality.
2. What is storageState in Playwright?
storageState stores authentication-related browser state such as cookies and local storage so tests can reuse an authenticated session.
3. Why use storage state?
It prevents every test from repeating the UI login flow.
This makes test suites faster and easier to maintain.
4. Where should authentication files be stored?
A common location is:
playwright/.auth/
These files should not be committed to Git when they contain usable credentials or session state.
5. How do you handle authentication in CI?
Use CI/CD secret storage:
TEST_USERNAME
TEST_PASSWORD
API_TOKEN
and inject them as environment variables.
6. What is the difference between authentication and authorization?
Authentication verifies identity.
Authorization verifies permissions.
7. How do you test JWT authentication?
Obtain the token through a secure test API flow and send it using an Authorization header where appropriate:
headers: {
Authorization: `Bearer ${token}`
}
8. How do you authenticate through an API?
Use Playwright’s request fixture or APIRequestContext to call the authentication endpoint and then use the resulting token/session for protected requests.
9. How do you test multiple roles?
Create separate authentication states:
user.json
admin.json
manager.json
and assign them to different Playwright projects or fixtures.
10. How do you debug an authentication failure?
Check:
- URL
- Response status
- Cookies
- Local storage
- Token validity
- Environment variables
- Screenshot
- Trace
- HTML report
Playwright Authentication Learning Roadmap for Beginners
Follow this progression:
Level 1: Playwright basics
Learn:
- Installation
- Locators
- Assertions
- Pages
- Browser contexts
Level 2: Playwright TypeScript
Learn:
- TypeScript
- Classes
- Interfaces
- Async/await
- Modules
Level 3: Login automation
Practice:
- Valid login
- Invalid login
- Logout
- Session timeout
Level 4: Authentication state
Learn:
storageState
Cookies
Local storage
Sessions
Level 5: API authentication
Learn:
API login
Bearer tokens
JWT
APIRequestContext
Level 6: Framework architecture
Learn:
POM
Fixtures
Multiple roles
Level 7: CI/CD
Learn:
GitHub Actions
Docker
Secrets
Reports
Traces
Level 8: Build a portfolio framework
Build the e-commerce authentication project.
Your framework can demonstrate:
Playwright + TypeScript + Authentication State + API Testing + Fixtures + POM + CI/CD + Reporting
This combination is particularly valuable for QA Automation Engineers, SDETs, Senior SDETs, QA Leads, and Automation Framework Developers.
FAQs: Playwright Authentication Tutorial
What is Playwright authentication?
Playwright authentication establishes a logged-in browser or API session so automated tests can access protected application features.
How do I get started with Playwright authentication?
Start with a login test, then learn storageState so authentication can be reused across multiple tests.
How do I save authentication state in Playwright?
Use:
await page.context().storageState({
path: ‘playwright/.auth/user.json’
});
Can Playwright authenticate through an API?
Yes. Playwright provides API request capabilities that can be used to authenticate through backend endpoints.
Can Playwright handle JWT authentication?
Yes. You can use JWT/bearer tokens with API requests or configure the browser context appropriately for the application’s authentication architecture.
Can Playwright handle cookies?
Yes. You can inspect, add, and manage browser cookies through the browser context.
Should authentication state be committed to Git?
No, not when it contains usable cookies, headers, tokens, or other credentials. Keep playwright/.auth/ out of source control.
How can I test admin and normal users?
Create separate authentication states and run tests against the appropriate state.
Why should I use fixtures for authentication?
Fixtures centralize authentication setup and make tests cleaner, reusable, and easier to maintain.
