[Feature]: support sessionStorage option for storageState persistence & restoration
🚀 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.
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
- Calls the api for a different auth per worker
- Saves the auth tokens per worker
- 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?
would love to see this supported out of the box.
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 :(
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.
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 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");
});
// 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);
},
any change here? not all sessionStorage are must, maybe we can pick some, or omit some.