DEV Community

Cover image for Setting state using cookies with Puppeteer and Playwright
Tim Nolet 👨🏻‍🚀 for Checkly

Posted on

14 3

Setting state using cookies with Puppeteer and Playwright

The HyperText Transfer Protocol (HTTP) is stateless, but cookies allow it to keep context consistent over the course of a session. In other words, by having our browser automatically exchange small amounts of data, we get to have websites recognise us and remember our preferences, the contents of our shopping baskets or the fact that we had just logged in to our account.

This article shows how we can use cookies and the Web Storage APIs to set state in our Puppeteer and Playwright scripts, opening up new scenarios and saving on execution time.

Reading and writing cookies to the browser

Reading or modifying cookies opens up useful possibilities. A practical example is skipping authentication when testing features available only after login. We could automate the login procedure, but there is no point in going through it for every test in our suite. Skipping it might free up precious time, speeding up delivery.

The following examples show how we can save existing cookies after logging in to GitHub and reuse them later to skip login. First, let us perform login with our credentials, read the cookies and save them to a file.

For Puppeteer:

const puppeteer = require('puppeteer')
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://github.com/login')
await page.type('#login_field', process.env.GITHUB_USER)
await page.type('#password', process.env.GITHUB_PWD)
await page.waitForSelector('.js-cookie-consent-reject')
await page.click('.js-cookie-consent-reject')
await page.$eval('[name="commit"]', (elem) => elem.click())
await page.waitForNavigation()
const cookies = await page.cookies()
const cookieJson = JSON.stringify(cookies)
fs.writeFileSync('cookies.json', cookieJson)
await browser.close()
})()

For Playwright:

const { chromium } = require('playwright')
const fs = require('fs');
(async () => {
const browser = await chromium.launch()
const context = await browser.newContext()
const page = await context.newPage()
await page.goto('https://github.com/login')
await page.type('#login_field', process.env.GITHUB_USER)
await page.type('#password', process.env.GITHUB_PWD)
await page.waitForSelector('.js-cookie-consent-reject')
await page.click('.js-cookie-consent-reject')
await page.$eval('[name="commit"]', (elem) => elem.click())
await page.waitForNavigation()
const cookies = await context.cookies()
const cookieJson = JSON.stringify(cookies)
fs.writeFileSync('cookies.json', cookieJson)
await browser.close()
})()

After a successful login, our saved cookies file will look something like this:

[
    {
        "name": "dotcom_user",
        "value": <YOUR_USERNAME>,
        "domain": ".github.com",
        "path": "/",
        "expires": 1633622615.629729,
        "size": 16,
        "httpOnly": true,
        "secure": true,
        "session": false,
        "sameSite": "Lax"
    },
    {
        "name": "user_session",
        "value": <YOUR_USER_SESSION_TOKEN>,
        "domain": "github.com",
        "path": "/",
        "expires": 1603296216.923899,
        "size": 60,
        "httpOnly": true,
        "secure": true,
        "session": false,
        "sameSite": "Lax"
    },

    ...

]
Enter fullscreen mode Exit fullscreen mode

We are now able to read the file later and load the cookies into our new browser session. Notice how we are logged in from the start, without having gone through the UI login procedure.

For Puppeteer:

const puppeteer = require('puppeteer')
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
const cookies = fs.readFileSync('cookies.json', 'utf8')
const deserializedCookies = JSON.parse(cookies)
await page.setCookie(...deserializedCookies)
await page.goto(`https://github.com/${process.env.GITHUB_USER}`)
await browser.close()
})()

For Playwright:

const { chromium } = require('playwright')
const fs = require('fs');
(async () => {
const browser = await chromium.launch()
const context = await browser.newContext()
const cookies = fs.readFileSync('cookies.json', 'utf8')
const deserializedCookies = JSON.parse(cookies)
await context.addCookies(deserializedCookies)
const page = await context.newPage()
await page.goto(`https://github.com/${process.env.GITHUB_USER}`)
await browser.close()
})()

TIP: Cookies come with an expiration date, so make sure the ones you are trying to reuse are still valid.

While brand new browser sessions with both Puppeteer and Playwright will not contain any cookies by default, there might be points when it is necessary to clear them.

In case you need to clear cookies, you can use page.deleteCookie(...cookies) with Puppeteer and browserContext.clearCookies() with Playwright.

TIP: Notice how Puppeteer handles cookies at page level while Playwright does so at context level.

localStorage and sessionStorage

Cookies are sent with every request, potentially deteriorating performance if used for storing large amounts of data. The localStorage and sessionStorage APIs can help us offload some of this data to the browser. Just like with cookies, Puppeteer and Playwright make accessing localStorage and sessionStorage straightforward.

Our test site, Danube, actually uses localStorage to keep track of a few things, such as the content of your cart. Let's see how we can access this state and then replicate it in a later session.

We will first fill the cart by adding three items, then we will copy the contents of localStorage to a file.

For Puppeteer:

const puppeteer = require('puppeteer')
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://danube-webshop.herokuapp.com')
for (i = 1; i <= 3; i++) {
await page.waitForSelector(`.preview:nth-child(${i}) > .preview-author`);
await page.click(`.preview:nth-child(${i}) > .preview-author`);
await page.waitForSelector(".detail-wrapper > .call-to-action");
await page.click(".detail-wrapper > .call-to-action");
await page.waitForSelector("#logo");
await page.click("#logo");
}
const localStorage = await page.evaluate(() => JSON.stringify(window.localStorage));
fs.writeFileSync('localstorage.json', localStorage)
await browser.close()
})()

For Playwright:

const {chromium} = require('playwright')
const fs = require('fs');
(async () => {
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto('https://danube-webshop.herokuapp.com')
for (i = 1; i <= 3; i++) {
await page.click(`.preview:nth-child(${i}) > .preview-author`);
await page.click(".detail-wrapper > .call-to-action");
await page.click("#logo");
}
const localStorage = await page.evaluate(() => JSON.stringify(window.localStorage));
fs.writeFileSync('localstorage.json', localStorage)
await browser.close()
})()

In this case our file will look as follows:

{
  "cartContent": "[{\"id\":1,\"title\":\"Haben oder haben\",\"author\":\"Fric Eromm\",\"genre\":\"philosophy\",\"price\":\"9.95\",\"rating\":\"★★★★☆\",\"stock\":\"1\"},{\"id\":2,\"title\":\"Parry Hotter\",\"author\":\"J/K Rowlin'\",\"genre\":\"erotic\",\"price\":\"9.95\",\"rating\":\"★★★☆☆\",\"stock\":\"1\"},{\"id\":3,\"title\":\"Laughterhouse-Five\",\"author\":\"Truk Tugennov\",\"genre\":\"scifi\",\"price\":\"9.95\",\"rating\":\"★★★☆☆\",\"stock\":\"1\"}]"
}
Enter fullscreen mode Exit fullscreen mode

We can use the content of this file to set localStorage in a separate session. That way we will immediately start with the three items already in our shopping cart, potentially getting us closer to a specific scenario we want to test and thereby saving ourselves time.

For Puppeteer:

const puppeteer = require('puppeteer')
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto('https://danube-webshop.herokuapp.com')
const localStorage = fs.readFileSync('localstorage.json', 'utf8')
const deserializedStorage = JSON.parse(localStorage)
await page.evaluate(deserializedStorage => {
for (const key in deserializedStorage) {
localStorage.setItem(key, deserializedStorage[key]);
}
}, deserializedStorage);
await page.waitForSelector("#cart");
await page.click("#cart");
await browser.close()
})()

For Playwright:

const {chromium} = require('playwright')
const fs = require('fs');
(async () => {
const browser = await chromium.launch()
const page = await browser.newPage()
await page.goto('https://danube-webshop.herokuapp.com')
const localStorage = fs.readFileSync('localstorage.json', 'utf8')
const deserializedStorage = JSON.parse(localStorage)
await page.evaluate(deserializedStorage => {
for (const key in deserializedStorage) {
localStorage.setItem(key, deserializedStorage[key]);
}
}, deserializedStorage);
await page.waitForSelector("#cart")
await page.click("#cart")
await browser.close()
})()

You can interact with sessionStorage very much like we did with localStorage.

TIP: Do not underestimate the usefulness of having shorter execution time on scripts, especially when frequently running large batches/suites.

All the above examples can be run as follows:

On MacOS/Linux:

GITHUB_USER=username GITHUB_PWD=password node managing-cookies.js
Enter fullscreen mode Exit fullscreen mode

On Windows:

SET GITHUB_USER=username
SET GITHUB_PWD=password
node managing-cookies.js
Enter fullscreen mode Exit fullscreen mode

Takeaways

  1. We can use cookies and Web Storage APIs through Puppeteer and Playwright to set test state and speed up test suites.
  2. The Puppeteer and Playwright APIs for handling cookies are slightly different but achieve the same goals.

Further reading

  1. The official MDN docs for cookies.
  2. A practical guide to the Web Storage APIs, sessionStorage and localStorage.
  3. Official Puppeteer and Playwright docs around cookies.

This article was originally posted on theheadless.dev

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (3)

Collapse
 
kpendic profile image
Kresimir Pendic

Hey, thanks for script helped a lot.

One question.. do you have idea how to check expires on cookies and to compare it with current epoch timestamp and if it passed that script re-generates let's say cookies?

kindly, kres

Collapse
 
mehmetsahindev profile image
Mehmet Şahin

thanks, it helped a lot.

Collapse
 
ritikadas profile image
Ritika Das

Huge thanks for writing this article! Could finally understand how setting cookies work :)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs