playwright
playwright copied to clipboard
[Feature] Set storage state when using persistent context
I want to set Local storage for a browser but open it in non-incognito mode..
playwright = Playwright.create();
chromium = playwright.chromium();
browser = chromium.launch(new BrowserType.LaunchOptions().setHeadless(false));
browserContext = browser.newContext(new Browser.NewContextOptions().setStorageStatePath(Paths.get("./Properties/logss/abc.txt")));
page = browserContext.newPage();
Here i am able to set localstorage but only in incognito mode.. But i want to open browser in Non-Incognito mode so i am launching it with persistent context..
browserContext=chromium.launchPersistentContext(Paths.get("C:\\Users\\mohib.s\\AppData\\Local\\Google\\Chrome\\User Data"), new BrowserType.LaunchPersistentContextOptions().setChannel("chrome").setHeadless(false));
How can i set the session storage for browser opened with persistent context.. Can you please help me with this
@Mosorathia If you are launching the persistent context, you can set up the user data dir to your liking by using the browser with that user data dir first. localStorage
and many other things will be persisted in the user data dir for you.
@dgozman I want to set the local storage on launch of the browser at run time.. I don't want to set it prior to the execution..
Is there a way like Browser.NewContextOptions().setStorageStatePath()
for setting local storage at launch time when opened in non-incognito mode using persistentContext..
There is not yet, marking it as a feature request by that.
I also want to vote for this. I have following scenario:
- Extension which works in different apps
- Some of this apps (sites) to login require 2MFA authorization. To do this without persistent context I use globalSetup -> login using MFA -> save storage -> reuse in other tests. But using persistent context it's impossible to do.
Would be really helpful to have an ability to use/set storage state in persistent context.
Another vote, I desperately need a way to deal with both MFA and extensions.
why hasnt been this added yet wtf? its been a year, we need to be able to set AND clear localstorage/cookies for persistent context!
Recently I've observed that even on launching a browser context using .LaunchPersistentContextAsync
the opened browser window is using profile information from anywhere on my machine instead of restricting itself to the userdatadir that is passed. For example if I launch a browser context by passing the path to an empty directory in LaunchPersistentContextAync function I'd expect it to stop at the login page and prompt for an email address, however it continues to detect my work profile in my machine and completes the login with that. Is there a way to prevent this detection of profiles that are not directly in userDataDir?
NEED THIS FIXED, CMON GUYS
bump
A workaround to set cookies for the persistent context:
// Get cookies from some other context, for example the one you were authenticating in.
const cookies = await someOtherContext.cookies();
// Alternatively, retrieve cookies from a saved storage state.
const cookies = require('path/to/storageState.json').cookies;
....
// Now launch persistent context and add cookies.
const context = await chromium.launchPersistentContext('path/to/user/data/dir');
await context.addCookies(cookies);
// Persistent context has cookies and is ready to use.
const page = context.pages()[0];
await page.goto('https://playwright.dev');
A workaround to set cookies for the persistent context:
// Get cookies from some other context, for example the one you were authenticating in. const cookies = await someOtherContext.cookies(); // Alternatively, retrieve cookies from a saved storage state. const cookies = require('path/to/storageState.json').cookies; .... // Now launch persistent context and add cookies. const context = await chromium.launchPersistentContext('path/to/user/data/dir'); await context.addCookies(cookies); // Persistent context has cookies and is ready to use. const page = context.pages()[0]; await page.goto('https://playwright.dev');
but bro loading cookies is only half of what loadStorage does, loadStorage or whatever is the function name, loads local storage as well under "Origins". How to do this programatically?
bump cmon guys
come on guys!!
Are you making any improvement or work on this, please?
Any news?
Another vote. Need it for extensions + cookie authentication.
Extensions require persistent context. While automatic authentication for all tests in project "LoggedIn" cannot find the saved storageState.
// test/tests/e2e/fixtures.ts
import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
context: async ({ }, use) => {
const pathToExtension = path.join(__dirname, '../../../src');
const context = await chromium.launchPersistentContext('', {
headless: false,
args: [
`--headless=new`,
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
// for manifest v3:
let [background] = context.serviceWorkers();
if (!background)
background = await context.waitForEvent('serviceworker');
const extensionId = background.url().split('/')[2];
await use(extensionId);
},
});
export const expect = test.expect;
// test/playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
import dotenv from 'dotenv'
import path from 'path';
dotenv.config({ path: path.resolve(__dirname, 'environment', '.env.production') });
export default defineConfig({
// Folder for all tests files
testDir: 'tests/e2e',
// Folder for test artifacts such as screenshots, videos, traces, etc.
outputDir: 'test_results',
// // path to the global setup files.
// globalSetup: require.resolve('./global-setup'),
// // path to the global teardown files.
// globalTeardown: require.resolve('./global-teardown'),
// Each test timeout [msec]
timeout: 5000,
// Fail the build on CI if you accidentally left test.only in the source code.
forbidOnly: !!process.env.CI,
// Opt out of parallel tests on CI.
workers: process.env.CI ? 1 : undefined,
use: {
// Maximum time each action such as `click()` can take. Defaults to 0 (no limit).
actionTimeout: 0,
// Name of the browser that runs tests. For example `chromium`, `firefox`, `webkit`.
browserName: 'chromium',
// Toggles bypassing Content-Security-Policy.
bypassCSP: false,
// Channel to use, for example "chrome", "chrome-beta", "msedge", "msedge-beta".
channel: 'chrome',
// Run browser in headless mode.
headless: false,
},
projects: [
{
name: 'LoginSetup',
testMatch: 'login.setup.spec.ts',
testDir: 'tests/e2e/login_setup',
retries: 0
},
{
name: 'LoggedIn',
testDir: 'tests/e2e/logged_in',
dependencies: ['LoginSetup'],
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
}
},
{
name: 'LoggedOut',
testDir: 'tests/e2e/logged_out'
}
]
},
);
// test/tests/e2e/login_setup/login.setup.spec.ts
import { type Page, type BrowserContext } from '@playwright/test';
import { test, expect } from '../fixtures';
const authFile = 'playwright/.auth/user.json';
const popupUrl = (extensionId: string) => `chrome-extension://${extensionId}/popup/popup.html`
const server_base_url = process.env.SERVER_URL
// first call to server may take much longer before it warms up,
// so we set a longer timeout for this specific test
test.setTimeout(10000)
test('popup login', async ({ page, context, extensionId }) => {
const loginPage = await openLoginPage(page, context, extensionId)
expect(await loginPage.url()).toBe(`${server_base_url}/login`)
const dashboardPage = await login(loginPage)
expect(await dashboardPage.url()).toBe(`${server_base_url}/`)
await page.context().storageState({ path: authFile });
});
async function login(loginPage: Page): Promise<Page> {
const username = process.env.LOGIN_USERNAME
const password = process.env.LOGIN_PASS
await loginPage.locator('input[type=email]').fill(username)
await loginPage.locator('input[type=password]').fill(password)
const loginButtonOnLoginPage = await loginPage.getByRole('button').filter({hasText: 'Login'})
await loginButtonOnLoginPage.click()
await loginPage.waitForURL(server_base_url);
return loginPage
}
async function openLoginPage(page: Page, context: BrowserContext, extensionId: string): Promise<Page> {
await page.goto(popupUrl(extensionId))
const loginButton = page.getByRole('button').filter({hasText: 'Login'})
// Start waiting for new page before clicking. Note no await.
const pagePromise = context.waitForEvent('page');
await loginButton.click()
const loginPage = await pagePromise;
await loginPage.waitForLoadState();
return loginPage
}
// test/tests/e2e/logged_in/popup.spec.ts
import { test, expect } from '../fixtures';
const popupUrl = (extensionId: string) => `chrome-extension://${extensionId}/popup/popup.html`
test('popup page', async ({ page, extensionId, context }) => {
await page.goto(popupUrl(extensionId))
const state = await context.storageState()
// Fails here because there is no cookies in the context storage state:
expect(state.cookies.length).toBeGreaterThan(0) // <---- Fails !!!
Another up-vote for this feature request. On my recommendation, my company invested a lot of time and effort on Playwright-java, now we're struck on this issue for a while.
Another vote for extensions + cookie authentication. @airbender-1 is there any workaround to inject a storageState to a running context?
Another vote, we need this as well
It is a very important feature. I need it to create an authorization setup for extension tests. Upvote!
Another upvote. This is a particularly helpful feature for testing extensions
@tg44 referencing your comment
My workaround was injecting cookies from file (json)
import { type BrowserContext } from '@playwright/test';
import path from 'path';
import fs from 'fs';
import { storageStateRelativePath } from './test_constants'
import { test as base } from './fixtures-incognito';
// see: https://github.com/microsoft/playwright/issues/26693
process.env.PW_CHROMIUM_ATTACH_TO_OTHER = "1";
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
context: async ({ context }, use) => {
// Patch [BUG](https://github.com/microsoft/playwright/issues/14949)
// manually inject saved storageState if the state file is found
// 1. cookies
const cookies = loadCookies()
if (cookies) await context.addCookies(cookies);
// 2. localStorage - TODO
const state = await context.storageState()
expect(state.cookies.length).toBeGreaterThan(0)
await use(context);
await context.close();
},
});
type Cookies = Parameters<BrowserContext['addCookies']>[0]
function loadCookies(): Cookies | null {
const authFile = path.resolve(__dirname, '..', '..', storageStateRelativePath)
if (!fs.existsSync(authFile)) return null
const cookies: Cookies = require(authFile).cookies
return cookies
}
export const expect = test.expect;
authFile is just a json with default hard-coded value for cookie of specific user.
{
"cookies": [
{
"name": "mycookie-name",
"value": "ab123...",
"domain": ".app.mydomain",
"path": "/",
"expires": 1708554480.933069,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
}
],
"origins": []
}
Here is the fixture-incognito that the above "test" fixture depends on:
import { test as base, chromium, type BrowserContext } from '@playwright/test';
import path from 'path';
export const test = base.extend<{
context: BrowserContext;
extensionId: string;
}>({
context: async ({ }, use) => {
const pathToExtension = path.join(__dirname, '../../../src');
const context = await chromium.launchPersistentContext('', {
headless: false,
args: [
`--headless=new`,
`--disable-extensions-except=${pathToExtension}`,
`--load-extension=${pathToExtension}`,
],
// slowMo: 2000
});
await use(context);
await context.close();
},
extensionId: async ({ context }, use) => {
// for manifest v3:
let [background] = context.serviceWorkers();
if (!background)
background = await context.waitForEvent('serviceworker');
const extensionId = background.url().split('/')[2];
await use(extensionId);
},
});
export const expect = test.expect;
Rather than mess around with loading and injecting the cookies (which was insufficient for us since we also needed to set up local storage and browser extension storage), what we did was to reuse the profile from an initial project that logs the test user in. We duplicate it for every new test context and it comes loaded with the needed auth cookies and local storage for our tests to do their thing. See:
https://github.com/pixiebrix/pixiebrix-extension/blob/main/end-to-end-tests/fixtures/authSetup.ts#L78-L86 https://github.com/pixiebrix/pixiebrix-extension/blob/main/end-to-end-tests/fixtures/extensionBase.ts#L77-L89
Another up-vote for this feature request