May 10, 2026

Practicing Playwright: Logging in by Storing and Using an Authentication Cookie in Your Automated Tests

I absolutely love that LinkedIn offers a free month-long trial period of LinkedIn Learning. Butch Mayhew's Playwright Essential Training: Abstractions, Fixtures, and Complex Scenarios course has been a wonderful resource learning more about Playwright.

With this blog post, I will be walking through Butch's code on how to set up an automated test to log into an app without going through the user interface. All it needs is the login cookie. 
When testing a shopping cart app such as the Practice Software Testing website, it can get tedious. You need to open a browser, go to the login page, enter a username, enter a password, hit the login button, and verify you have logged in correctly every time you want to test something in the shopping cart. 

If you are testing something unrelated to logging in, why have your tests go through the UI to authenticate? Why not have your automated test run that login test once, temporarily save the login cookie once it is produced, then reuse the login cookie, importing it into other tests?

What is Playwright?


According to Microsoft Playwright's GitHub site, Playwright "is a framework for web automation and testing. It drives Chromium, Firefox, and WebKit with a single API — in your tests, in your scripts, and as a tool for AI agents".

Playwright comes in many features. From https://github.com/microsoft/playwright 
Best forInstall
Playwright TestEnd-to-end testingnpm init playwright@latest
Playwright CLICoding agents (Claude Code, Copilot)npm i -g @playwright/cli@latest
Playwright MCPAI agents and LLM-driven automationnpx @playwright/mcp@latest
Playwright LibraryBrowser automation scriptsnpm i playwright
VS Code ExtensionTest authoring and debugging in VS CodeInstall from Marketplace


... Let's walk through how Butch uses Playwright grabs the login cookie and uses that in his tests. 

Getting an Authorization Cookie Through the Website

Before we do anything with our test automation, let's examine an authorization cookie. 
  • Open up Chrome and go to https://practicesoftwaretesting.com/auth/login
  • Right click on the page and select "Inspect" to open Chrome Developer Tools.
  • Select the "Network" tab.
  • Enter a username and password of customer@practicesoftwaretesting.com / welcome01
  • Select the "Login" button, then view the Network tab. 
  • On the network tab, click on the "account" entry. 
  • Select the "Cookies" tab in Chrome Developer Tools. 
Under Request Cookies, you will see something like this:

"cookies": [
    {
      "name": "cf_clearance",
      "value": "2Rv345ZtjSaGUFRwq6j2QO4Qp3EBG0T.5URv.oenfjg-1778187254-1.2.1.1-90Z63mBartyo.dUm4bwavAggVG9pgrzcYOO0zPS18qBEujmzIq3tnWwjx2b5jQypbZywJMVEIncDFSwJBaAD0ephO3cctJrLkS449MVHNO7ZSuqlAPhzVYGw8vVH4doqYc2R_5WXTY47OHVLRAgnblq7U9glSiaJUvoOKmc6I.Mo6FPl3MrhGmfMxSTVyTkcCBJn8t_C9b1margtoukM7yMuQdNPpkNItvrrZdIQT.TpGQx2sNX7cS_KS8TY2Ghyc0_QLKFZFqC_5ZgZHMad5xgzgvJEm2NZmO3RBLSPKH5bpDFbuynt7g4oIwYfIvWd4pTpTB447hpP5qMPnLOp.Q",
      "domain": ".practicesoftwaretesting.com",
      "path": "/",
      "expires": 1809723253.921407,
      "httpOnly": true,
      "secure": true,
      "sameSite": "None",
      "partitionKey": "https://practicesoftwaretesting.com",

Cross-Indexing this with the Chrome DevTools Docs on Cookies, this means:  

Name: "The cookie's name", in this case "cf_clearance", is a security token issued by Cloudflare after passing a challenge. [ See Cloudflare docs ]

Value. Claude AI guessed that the Value of this cookie was this... but was not sure since this value is proprietary. 
  • Random token: 2Rv345ZtjSaGUFRwq6j2QO4Qp3EBG0T.5URv.oenfjg
  • Unix timestamp: 1778187254 (Around May 4, 2026)
  • Version Level: 1.2.1.1, the Clearance level encoding
  • Signature payload: 90Z63mBartyo... HMAC-binding (Hash-Based Message Authentication Codes) to IP, user agent, and session. 
Domain: "The hosts that are allowed to receive the cookie", in this case, practicesoftwaretesting.com.

Path: "The URL that must exist in the requested URL in order to send the Cookie header", in this case the root folder. 

Expires / Max-Age. "The cookie's expiration date or maximum age. For session cookies this value is always Session". According to UnixTimeStamp.com "1809723253.921407" would expire on May 10, 2026 on 3:36 am UTC and 49 seconds.  
  • What is a Unix Time Stamp? "The unix time stamp is a way to track time as a running total of seconds. This count starts at the Unix Epoch on January 1st, 1970 at UTC. Therefore, the unix time stamp is merely the number of seconds between a particular date and the Unix Epoch. It should also be pointed out (thanks to the comments from visitors to this site) that this point in time technically does not change no matter where you are located on the globe. This is very useful to computer systems for tracking and sorting dated information in dynamic and distributed applications both online and client side" - UnixTimeStamp.com
HttpOnly: "If true, this field indicates that the cookie should only be used over HTTP, and JavaScript modification is not allowed"... and it is set to true. 

Secure: "If true, this field indicates that the cookie can only be sent to the server over a secure, HTTPS connection"... and it is set to true. 

SameSite: "Contains Strict or Lax if the cookie is using the experimental SameSite attribute".

Partition Key: "For cookies with independent partition state, the partition key is the site of the top-level URL the browser was visiting at the start of the request to the endpoint that set the cookie.
Priority. Contains Low, Medium (default), or High if using deprecated cookie Priority attribute"... and ours is "https://practicesoftwaretesting.com"

By logging into the site, it generates this cookie on the back end, allowing you to keep accessing the site. 

If you generate this cooking programmatically before running the tests, and temporally save it, you can then use it in all preceding tests in that test run. 

Step One: Setup the Setup

The Playwright Configuration file has in its projects array two projects: One to set up the, er, setup, and one to set up the Chromium run. 

First, Butch set up in his Playwright Configuration file, a project dependency, in a project that he called "setup". 


playwright.config.ts:
projects: [
    {
      name: "setup",
      testMatch: /.*\.setup\.ts/,
    },
    {
      name: "chromium",
      dependencies: ["setup"],
      use: {
        ...devices["Desktop Chrome"],
        permissions: ["clipboard-read", "geolocation"],
      },
    },


According to Playwright.Dev, "Dependencies are a list of projects that need to run before the tests in another project run. They can be useful for configuring the global setup actions so that one project depends on this running first.

"When working with tests that have a dependency, the dependency will always run first and once all tests from this project have passed, then the other projects will run in parallel.

"Running order: Tests in the 'setup' project run. Once all tests from this project have passed, then the tests from the dependent projects will start running.

"Tests in the 'chromium' [...] projects run together. By default, these projects will run in parallel, subject to the maximum workers limit".

  • name: "setup": A project with the name of  "setup". 
  • testMatch: /.*setup\.ts,: This pattern matcher searches on level up for anything with "setup" in the middle of the filename, such as "auth.setup.ts" in the "tests/" directory.
  • name: "chromium": A project that directs the test automation when it runs to use the Desktop Chrome browser devices.
  • dependencies: ["setup"]: Before the Chrome browser has kicked off, we tell it we need to run the project called "setup" first. 
  • use: ...devices["Desktop Chrome"]: Playwright has an enormous list of devices in its device registry, such as various models of the Blackberry, Galaxy, iPad, iPhone, Microsoft Lumia, Nexus, Nokia, Pixel, Moto, and Desktop Devices (Chrome, Edge, Firefox, Safari) with many different user agents, viewports, and screen width and heights. 
  • use: permissions: Playwright's browser context, according to the official Playwright Docs, has differing sets of permissions in the permission array for each browser emulation, such as emulating the accelerometer, ambient light sensor, background sync, camera, clipboard read and write, geolocation, gyroscope, local fonts, local network access, magnetometer, microphone, midi, notifications, payment-handler, storage-access, and screen-wake-lock. 
  • [clipboard-read]: We need to give the Chromium browser Playwright spins up permission to read the authentication cookie that is generated the first time we log in. Note, according to notes in Playwright's source code in the permissions library, there is no "clipboard-read" in WebKit's API.

Step Two: Get and Save the Authorization Cookie

Next, in auth.setup.ts, Butch has the automated test go to the site, log in, and save the cookie to a file.

tests/ auth.setup.ts: 

import { test as setup, expect } from "@playwright/test";
import { LoginPage } from "../lib/pages/login/login.page";

setup("Create customer 01 auth", async ({ page, context }) => {
  const email = "customer@practicesoftwaretesting.com";
  const password = "welcome01";
  const customer01AuthFile = ".auth/customer01.json";

  const loginPage = new LoginPage(page);

  await loginPage.goto();

  await loginPage.login(email, password);

  await expect(page.getByTestId("nav-menu")).toContainText("Jane Doe");
  await context.storageState({ path: customer01AuthFile });
});
https://github.com/LinkedInLearning/playwright-essential-training-abstractions-fixtures-and-complex-scenarios-4278224/blob/98d2080e018acb194938157cb7c2fc8993ca21a5/tests/auth.setup.ts

What is this code doing?
  • Import the test runner playwright/test into our TypeScript file, setting up an alias for the test runner, calling it "setup" to make the test more readable, and to mark this as performing initialization tasks. To learn more about how aliases work in TypeScript see FreeCodeCamp's explanation, and read about the test method in the Playwright.dev docs.  
  • Import the expect assertion library into the TypeScript file, so we then can use the expect method, detecting if an element is attached, checkbox is checked, element is disabled, editable, enabled, focused, not visible, visible, contains text, has certain CSS classes, DOM attributes, ids, CSS or JavaScript properties, ARIA roles, screenshots, matches text, or if pages have screenshots, titles, or URLs, failing near immediately if we don't see that condition. [ See Playwright.Dev docs about test assertions ]. We can also match if it meets any instance of a class, matches anything, of an array contains certain elements, if a number is close to equal, a string contains a substring, a string matches a certain substring. All of these expects can be negated chaining a not to it. 
  • setup("Create customer 01 auth" is being used by Butch Mayhew instead of saying "test("Create customer 01 auth") to make it more readable. 
  • async uses the JavaScript keyword async. FreeCodeCamp says that, "Async programming is a programming paradigm that allows you to write code that runs asynchronously. In contrast to synchronous programming, which executes code sequentially, async programming allows code to run in the background while the rest of the program continues to execute. This is particularly useful for tasks that may take a long time to complete, such as fetching data from a remote API".
  • page, context are passing these built-in fixtures into the test. The Page instance is created for each test, used in isolation from one another. [ See Playwright.dev docs on test fixtures. ] "Test fixtures are used to establish the environment for each test, giving the test everything it needs and nothing else. Test fixtures are isolated between tests. With fixtures, you can group tests based on their meaning, instead of their common setup". The "page" fixtures uses type Page, and "context" fixtures uses type BrowserContext in order to configure context. Fixtures can encapsulate setup and teardown, are reusable between test files.
  • const email, password, customer01AuthFile is declaring constant variables to store what the username, password, and where the auth cookie should be stored, to make the code more readable. 
  • const loginPage is instantiating a new login page using the login page object Butch has defined, not covered in this blog entry. The login page Butch set up contains methods where you can goto https://practicesoftwaretesting.com/auth/login, enter the username and password in the login method using the constants we defined, expecting that we see a greeting to the user. 
The real magic is the last bit of code: context.storageState({ path: customer01AuthFile });
  • Playwright has storage and authentication methods where you can manage cookies, localStorage and sessionStorage [ See official docs ]. Session Storage is session scoped, where data is cleared out when the browser tab eventually closes. 
  • According to the Playwright Docs / Authentication, when you "[c]reate a new setup project in the config and declare it as a dependency for all your testing project [...]. This project will always run and authenticate before all the tests. All testing projects should use the authenticated state as storageState".

The Cookie is Temporarily Saved!


A new folder and file is created in the root folder when this command is run: .auth/customer01.json:

{
  "cookies": [
    {
      "name": "cf_clearance",
      "value": "2Rv345ZtjSaGUFRwq6j2QO4Qp3EBG0T.5URv.oenfjg-1778187254-1.2.1.1-90Z63mBartyo.dUm4bwavAggVG9pgrzcYOO0zPS18qBEujmzIq3tnWwjx2b5jQypbZywJMVEIncDFSwJBaAD0ephO3cctJrLkS449MVHNO7ZSuqlAPhzVYGw8vVH4doqYc2R_5WXTY47OHVLRAgnblq7U9glSiaJUvoOKmc6I.Mo6FPl3MrhGmfMxSTVyTkcCBJn8t_C9b1margtoukM7yMuQdNPpkNItvrrZdIQT.TpGQx2sNX7cS_KS8TY2Ghyc0_QLKFZFqC_5ZgZHMad5xgzgvJEm2NZmO3RBLSPKH5bpDFbuynt7g4oIwYfIvWd4pTpTB447hpP5qMPnLOp.Q",
      "domain": ".practicesoftwaretesting.com",
      "path": "/",
      "expires": 1809723253.921407,
      "httpOnly": true,
      "secure": true,
      "sameSite": "None",
      "partitionKey": "https://practicesoftwaretesting.com",
      "_crHasCrossSiteAncestor": false
    }
  ],
  "origins": [
    {
      "origin": "https://practicesoftwaretesting.com",
      "localStorage": [
        {
          "name": "auth-token",
          "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL2FwaS5wcmFjdGljZXNvZnR3YXJldGVzdGluZy5jb20vdXNlcnMvbG9naW4iLCJpYXQiOjE3NzgxODcyNTUsImV4cCI6MTc3ODE4NzU1NSwibmJmIjoxNzc4MTg3MjU1LCJqdGkiOiJoeFJ1MmkwNWpDamVrNUdXIiwic3ViIjoiMDFLUjIwRFFYQUpTVEJEWkg2S0Q1R1QwRVQiLCJwcnYiOiIyM2JkNWM4OTQ5ZjYwMGFkYjM5ZTcwMWM0MDA4NzJkYjdhNTk3NmY3Iiwicm9sZSI6InVzZXIifQ.Iqv8TGSweXLUfk7UExbIem_kOoIde9apIrzFZ9cCVpo"
        }
      ]
    }
  ]
}

Step Three: Make Sure The Cookie Is Only Temporarily Saved. Do Not Upload the Cookie To GitHub!!!

It is very important that you do NOT save the .auth cookie, or any other credentials, in GitHub. 

If you examine the .gitignore file Butch Mayhew has set up, you can see that along with test_results, node_modules, .tmp files, you will not be uploading anything in the .auth/ folder. 

Step Four: Implement the Auth Cookie for Login

Now that the Setup function generated a new cookie, we then can then use this credential setting up the test to use storageState. 

tests / homepage / home.spec.ts:
  
test.describe("Home page customer 01 auth", () => {
  test.use({ storageState: ".auth/customer01.json" });
  test.beforeEach(async ({ page }) => {
    await page.goto("/");
  });  

How do we know this is successful? We verify that the sign in navigation is not visible, and instead we are greeted by the username, in this case, "Jane Doe". 


tests / homepage / home.spec.ts:
  
  test("check customer 01 is signed in", async ({ page }) => {
    await expect(page.getByTestId("nav-sign-in")).not.toBeVisible();
    await expect(page.getByTestId("nav-menu")).toContainText("Jane Doe");
  }); 

If we don't see the sign in page, and instead we see the nav menu for "Jane Doe", then everything has passed. 

So, that's how we use authentication cookies in tests! 

Happy Testing!

-T.J. Maher
Software Engineer in Test

BlueSky | YouTubeLinkedIn | Articles

No comments:

Post a Comment