Playwright Authentication Tutorial: Complete Guide to Login, Storage State, Tokens, Fixtures, and CI/CD

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:

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

    ↓

Run test

    ↓

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.

ConceptPurpose
AuthenticationVerify identity
AuthorizationVerify permissions
SessionMaintains authenticated state
CookieStores session-related data
TokenRepresents authentication/authorization information
JWTA 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

Install Playwright browsers

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

   ↓

GitHub Actions

   ↓

Playwright

   ↓

Authentication

   ↓

E-commerce tests

   ↓

HTML report

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

ErrorCauseSolution
Redirected to loginAuthentication expiredRegenerate storage state
401Invalid/missing credentialsCheck credentials/token
403Insufficient permissionsVerify user role
Missing cookieIncorrect authentication flowInspect context cookies
Invalid storage stateCorrupted/expired stateRecreate state
Login works locally but not CIEnvironment differencesCheck CI secrets
Token expiredShort token lifetimeAuthenticate during setup
Tests interfereShared user stateIsolate accounts/data
.auth committedIncorrect Git configurationAdd it to .gitignore
Password visible in logsDebug loggingMask 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:

  1. URL
  2. Response status
  3. Cookies
  4. Local storage
  5. Token validity
  6. Environment variables
  7. Screenshot
  8. Trace
  9. HTML report

Playwright Authentication Learning Roadmap for Beginners

Follow this progression:

Level 1: Playwright basics

Learn:

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

Test data

Multiple roles

Parallel execution

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.

Leave a Comment

Your email address will not be published. Required fields are marked *