test-utils icon indicating copy to clipboard operation
test-utils copied to clipboard

Setting cookies in context does not appear to be working

Open crossatko opened this issue 2 years ago โ€ข 16 comments

I am trying to set and test cookies. I have a simple test that loads a page where the only content is the value of a cookie. In the browser, it works even with server-side rendering, but in the test, I am not able to get the cookie value to appear.

I have tried using the Playwright context, hoping that the Nuxt test utils would recognize it somehow. I have also tried using the storageState.cookies to set the cookies, but the test always fails.

Can anyone point me in the right direction?

Example using context:

it('renders cookie value', async () => {
  await createBrowser()
  const browser = await getBrowser()
  const context = await browser.newContext()
  await context.addCookies([
    {
      name: 'test',
      value: 'test cookie',
      path: '/',
      domain: 'localhost'
    }
  ])

  const page = await createPage('/')

  expect(await page.innerHTML('body')).toContain('test cookie')
  await context.close()
  await browser.close()
})

Example using storageState.cookies

it('renders cookie value', async () => {
  const page = await createPage('/', {
    storageState: {
      cookies: [
        {
          name: 'test',
          value: 'test cookie',
          path: '/',
          domain: 'localhost',
          expires: -1,
          httpOnly: false,
          secure: false,
          sameSite: 'None'
        }
      ],
      origins: []
    }
  })

  expect(await page.innerHTML('body')).toContain('test cookie')
})

crossatko avatar Mar 01 '23 15:03 crossatko

You can pass a options object to createPage which includes a way to configure cookies (via storageState.cookies).

danielroe avatar Mar 01 '23 15:03 danielroe

That's what I tried, in the second example above, test still fails. Is there any documentation for this? Thanks

crossatko avatar Mar 01 '23 15:03 crossatko

Ah, serves me right for not reading to end. ๐Ÿ˜†

I would guess the issue is that you haven't specified the port in the domain. You can get a full URL from url('/') and extract the domain from that.

danielroe avatar Mar 01 '23 15:03 danielroe

Will try that, thanks!

crossatko avatar Mar 01 '23 15:03 crossatko

Unfortunately still no success. this should set the domain as localhost:PORT but test still fails. No idea why

it('renders test cookie value', async () => {
  const page = await createPage('/', {
    storageState: {
      cookies: [
        {
          name: 'test',
          value: 'test-cookie',
          path: '/',
          domain: new URL(url('/')).host,
          expires: -1,
          httpOnly: false,
          secure: false,
          sameSite: 'None'
        }
      ],
      origins: []
    }
  })

  expect(await page.innerHTML('body')).toContain('test-cookie')
})

This is the frontend, but again, everything works in the browser when I set the cookie manually and refresh page.

<script setup lang="ts">
import { useCookie } from '#imports'
const test = useCookie<string>('test')
</script>

<template>
  <div>
    {{ test }}
  </div>
</template>

I tried to put together a reproduction example on StackBlitz, but they may be blocking cookies or something because I could not make it work.

If you have any ideas, please let me know. I'm starting to get kind of desperate ๐Ÿ˜….

crossatko avatar Mar 01 '23 16:03 crossatko

Reopened - I'll try to have a look later.

danielroe avatar Mar 01 '23 18:03 danielroe

Wait, just to be clear - you are using Nuxt 3, right? (This is an old repo we should probably close.)

danielroe avatar Mar 01 '23 18:03 danielroe

yes, I'm using Nuxt3.

crossatko avatar Mar 01 '23 19:03 crossatko

Running into the same issue unfortunately. This makes it impossible to test with cookies. Would greatly appreciate if this get fixed! ๐Ÿ™

rinux55 avatar May 11 '23 15:05 rinux55

Yep same issue here, setting a cookie using

    const page = await createPage("/", {
      // @ts-ignore
      storageState: {
        cookies: [
          // @ts-ignore
          {
            name: "accessToken",
            value: "test",
            domain: "localhost",
            path: "/",
          },
        ],
      },
    })

But when useCookie("accessToken") is used in my middleware, it shows it as undefined.

nathanjcollins avatar Nov 28 '23 11:11 nathanjcollins

Yep same issue here, setting a cookie using

    const page = await createPage("/", {
      // @ts-ignore
      storageState: {
        cookies: [
          // @ts-ignore
          {
            name: "accessToken",
            value: "test",
            domain: "localhost",
            path: "/",
          },
        ],
      },
    })

But when useCookie("accessToken") is used in my middleware, it shows it as undefined.

It is working for me. You are doing it wrong. The setup in test-utils use 127.0.0.1 as domain. My suggestion is to use addCookies with name and value only. Then debug page.context().cookies() and copy paste the values. One wrong value makes it appear as it is not working. But it is.

Ballpin avatar Mar 08 '24 14:03 Ballpin

Yep same issue here, setting a cookie using

    const page = await createPage("/", {
      // @ts-ignore
      storageState: {
        cookies: [
          // @ts-ignore
          {
            name: "accessToken",
            value: "test",
            domain: "localhost",
            path: "/",
          },
        ],
      },
    })

But when useCookie("accessToken") is used in my middleware, it shows it as undefined.

It is working for me. You are doing it wrong. The setup in test-utils use 127.0.0.1 as domain. My suggestion is to use addCookies with name and value only. Then debug page.context().cookies() and copy paste the values. One wrong value makes it appear as it is not working. But it is.

Thanks for the suggestions, but this still isn't working for me. Not including a domain/path I get an error: image

If I set domain to 127.0.0.1, still the same issue. Cookie is not set :(

nathanjcollins avatar Mar 09 '24 21:03 nathanjcollins

Unfortunately still no success. this should set the domain as localhost:PORT but test still fails. No idea why

it('renders test cookie value', async () => {
  const page = await createPage('/', {
    storageState: {
      cookies: [
        {
          name: 'test',
          value: 'test-cookie',
          path: '/',
          domain: new URL(url('/')).host,
          expires: -1,
          httpOnly: false,
          secure: false,
          sameSite: 'None'
        }
      ],
      origins: []
    }
  })

  expect(await page.innerHTML('body')).toContain('test-cookie')
})

This is the frontend, but again, everything works in the browser when I set the cookie manually and refresh page.

<script setup lang="ts">
import { useCookie } from '#imports'
const test = useCookie<string>('test')
</script>

<template>
  <div>
    {{ test }}
  </div>
</template>

I tried to put together a reproduction example on StackBlitz, but they may be blocking cookies or something because I could not make it work.

If you have any ideas, please let me know. I'm starting to get kind of desperate ๐Ÿ˜….

I think for domain you should pass the hostname instead of host like this: domain: new URL(url('/')).hostname as host also includes the port but for the cookie it should only be the domain excluding the port.

henritoivar avatar Mar 18 '24 12:03 henritoivar

Investigation:

(TL;DR: at the example at the end works)

In the original bug report:

const context = await browser.newContext()
await context.addCookies(...)
const page = await createPage('/')

is used, which won't work, because this will result in two isolated browser contexts:

  1. The one you create in line 1 + 2 where you set the actual cookie
  2. The one which await createPage creates internally (it uses browser.newPage() under the hood which is a helper which creates a browser context and a page on it).

If you do:

const page = await createPage('/')
await page.context().addCookies(...)

it's too late, because createPage() internally already has navigated. So you could do another await page.reload() afterwards, which is not ideal and only works in some scenarios (cookie overrides?).

I saw above that storageState works sometimes, this is correct when passing it to createPage(). storageState is not really a developer experience optimised API (it was intended to be generated via page.context().storageState()), hence either declaring a lot of defaults or // @ts-ignore is needed. The following example works tho if someone looks for workarounds:

 const page = await createPage('/', {
      storageState: {
        cookies: [
          {
            name: 'counter',
            value: '1337',
            domain: new URL(url('/')).hostname,
            path: '/',
            expires: -1,
            httpOnly: false,
            secure: false,
            sameSite: 'Lax',
          }
        ],
        origins: []
      }
    })
    expect(await page.innerHTML('body')).toContain('Counter: 1337')

This will only create 1 browser context and set the cookie when creating the page before the navigation.

mxschmitt avatar Mar 18 '24 15:03 mxschmitt

Really good explanation @mxschmitt.

I will just add an example here.

import {setup} from '@nuxt/test-utils/e2e'
import {fileURLToPath} from 'node:url'
import type {Page} from "playwright-core";
import {afterEach, assert, beforeEach, describe, expect, it} from 'vitest'
import {createPage, url} from "@nuxt/test-utils";
import {fail} from "node:assert";

describe('Cookies Test Suite', async () => {
  await setup({
    rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url))
  })

  describe('With cookies in storage', () => {
    let page: Page | undefined = undefined;

    beforeEach(async () => {
      page = await createPage('/', {
        storageState: {
          cookies: [
            {
              path: "/",
              domain: "127.0.0.1",
              expires: -1,
              name: "my_cookie",
              secure: false,
              value: "12345",
              sameSite: "Lax",
              httpOnly: false
            }
          ],
          origins: []
        },
      })
    })

    afterEach(async () => {
      if (page) {
        await page.context().clearCookies();
        await page.close();
      }
    })

    it('should have cookie when page is created', async () => {
      if (page === undefined) assert.fail("Page never became an instance of Page")
      const cookies = await (page as Page).context().cookies();

      expect(cookies[0].name).toEqual("my_cookie");
      expect(cookies[0].value).toEqual("12345");
    });
  })


  describe("Without Cookies in storage", () => {
    let page: Page | undefined = undefined;
    beforeEach(async () => {
      page = await createPage('/');
    })

    afterEach(async () => {
      if (page) {
        await page.context().clearCookies();
        await page.close();
      }
    })

    it('should add cookie later on', async () => {
      if (!page) return fail("Page is not an instance of Page");

      await page.context().addCookies([
        {
          name: "my_cookie2",
          value: "12345",
          url: url("/")
        }
      ]);

      const cookies = await page.context().cookies();
      expect(cookies[0].name).toEqual("my_cookie2");
      expect(cookies[0].value).toEqual("12345");
    });
  })
})

image

I wanted to add it in the stackblitz but im not sure how to do it.

Ballpin avatar Mar 18 '24 17:03 Ballpin

My issue was that I was not set up correctly with test-utils - I had only added the npm package and not added it as a module, or set vitest's environment to Nuxt - just in case someone makes the same mistake as me. The docs for getting set up with testing are actually there now which helped a lot, when I had set this up originally they were not there.

nathanjcollins avatar Mar 20 '24 14:03 nathanjcollins