playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature]: support sessionStorage option for storageState persistence & restoration

Open mfulton26 opened this issue 1 year ago • 4 comments

🚀 Feature Request

BrowserContext.prototype.storageState() is very convenient. I appreciate that Session storage | Authentication | Playwright outlines how to also persist and restore sessionStorage but it would be even more convenient to have a built-in option.

Example

await page.context().storageState({ path: authFile, includeSessionStorage: true });

or, if richer control is desired:

await page.context().storageState({
  path: authFile,
  origins: {
    "http://localhost:3000": ["localStorage", "sessionStorage"],
    "https://example.com": ["sessionStorage"],
    "https://www.iana.org": [],
    "*": ["localStorage"]
  },
});

Motivation

Needing to write the extra code for interacting with the filesystem, stringifying and parsing JSON, adding the init script, etc. isn't too terrible but storing an access token in sessionStorage seems common enough to support out-of-the-box.

mfulton26 avatar May 31 '24 17:05 mfulton26

Hi, I'm looking for advice similar to this issue.

My application uses sessionStorage for auth tokens. I'm looking to have a setup which

  1. Calls the api for a different auth per worker
  2. Saves the auth tokens per worker
  3. In a worker scoped fixture use the tokens to authenticate the app

This would be a sort of combination between https://playwright.dev/docs/auth#session-storage and https://playwright.dev/docs/auth#advanced-scenarios

Are there any examples of this set-up while sessionStorage is not supported out of the app?

charlotteclough avatar Jun 12 '24 15:06 charlotteclough

would love to see this supported out of the box.

jebright avatar Jun 13 '24 18:06 jebright

Agree.

One of the reasons my org is looking to migrate from Cypress is due to the superior methods for paralell runs/sharding. However the fact that auth using sessionStorage makes this impossible for us :(

charlotteclough avatar Jun 14 '24 08:06 charlotteclough

Agree.

One of the reasons my org is looking to migrate from Cypress is due to the superior methods for paralell runs/sharding. However the fact that auth using sessionStorage makes this impossible for us :(

Playwright can persist and load sessionStorage into a new browser context. It doesn't currently do so as part of its built-in convenience methods but you can do it manually through other Playwright and fs APIs. See https://playwright.dev/docs/auth#session-storage.

mfulton26 avatar Jun 14 '24 16:06 mfulton26

Update: Workaround (Inspired by SO) Instead of setup add it to the test or test.beforeEach.

    await page.evaluate((storage) => {
      for (const [key, value] of Object.entries<string>(storage)) {
        window.sessionStorage.setItem(key, value);
      }
    }, sessionStorage);

addInitScript seems undebugable. Following this approach no session storage is set. Also neither debug breakpoints nor console logs get shown anywhere. For me personally this example lacks explanation. Get and set have simply been put together which might be fine for people having extended knowledge about Playwrights internals but for me I'm not able to get this running. How can I debug the init script to see which keys and values are set? This is my authentication setup script and it gets executed but has no effect:

setup("authenticate", async ({ context }) => {
  const authFile = path.join(__dirname, "../playwright/.auth/session.json");
  const sessionStorage = JSON.parse(readFileSync(authFile, "utf-8"));
  for (const [key, value] of Object.entries<string>(sessionStorage))
    console.log(key, value);
  await context.addInitScript((storage) => {
    for (const [key, value] of Object.entries<string>(storage))
      window.sessionStorage.setItem(key, value);
  }, sessionStorage);
});

Makoehle avatar Oct 28 '24 11:10 Makoehle

@Makoehle I'm doing something similar in my test suite. It would still be nice to have this built-in though:

persistence during setup project:

setup("auth", async ({ page }) => {
    // persist state from sessionStorage (functionality not yet built into playwright, see https://github.com/microsoft/playwright/issues/31108)
    const sessionStorageState = await page.evaluate((): string => JSON.stringify(sessionStorage));
    await writeFile(sessionStorageStatePath, sessionStorageState, "utf-8");
});

test fixture:

    // support restoring state for sessionStorage (playwright currently only supports localStorage and cookies, see https://github.com/microsoft/playwright/issues/31108)
    context: async ({ context, baseURL, sessionStorageState: sessionStorageStatePath }, use) => {
        if (sessionStorageStatePath === undefined) {
            // option not used
        } else if (baseURL === undefined) {
            console.warn("baseURL is not set; skipping session storage restoration init script");
        } else {
            const { hostname } = new URL(baseURL);
            const sessionStorageState = await readFile(sessionStorageStatePath, "utf-8");
            const sessionStorageEntries = Object.entries(JSON.parse(sessionStorageState) as Record<string, string>);
            await context.addInitScript(
                function restoreSessionStorage(input) {
                    if (window.location.hostname !== input.hostname) {
                        console.log(
                            `Unexpected window.location. Expecting ${input.hostname}, got ${window.location.hostname}`,
                        );
                        return;
                    }
                    for (const [key, value] of input.sessionStorageEntries) {
                        console.log(`Copying session storage key ${key}.`);
                        window.sessionStorage.setItem(key, value);
                    }
                },
                { hostname, sessionStorageEntries },
            );
        }

        await use(context);
    },

mfulton26 avatar Oct 28 '24 14:10 mfulton26

any change here? not all sessionStorage are must, maybe we can pick some, or omit some.

geminiyellow avatar Jan 09 '25 06:01 geminiyellow