···104104 timeout: 1_000,
105105})
106106```
107107+108108+## `persistentContext` <Version>4.1.0</Version> {#persistentcontext}
109109+110110+- **Type:** `boolean | string`
111111+- **Default:** `false`
112112+113113+When enabled, Vitest uses Playwright's [persistent context](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context) instead of a regular browser context. This allows browser state (cookies, localStorage, DevTools settings, etc.) to persist between test runs.
114114+115115+::: warning
116116+This option is ignored when running tests in parallel (e.g. when headless with [`fileParallelism`](/config/fileparallelism) enalbed) since persistent context cannot be shared across parallel sessions.
117117+:::
118118+119119+- When set to `true`, the user data is stored in `./node_modules/.cache/vitest-playwright-user-data`
120120+- When set to a string, the value is used as the path to the user data directory
121121+122122+```ts [vitest.config.js]
123123+import { playwright } from '@vitest/browser-playwright'
124124+import { defineConfig } from 'vitest/config'
125125+126126+export default defineConfig({
127127+ test: {
128128+ browser: {
129129+ provider: playwright({
130130+ persistentContext: true,
131131+ // or specify a custom directory:
132132+ // persistentContext: './my-browser-data',
133133+ }),
134134+ instances: [{ browser: 'chromium' }],
135135+ },
136136+ },
137137+})
138138+```
+70-22
packages/browser-playwright/src/playwright.ts
···7676 * @default 0 (no timeout)
7777 */
7878 actionTimeout?: number
7979+8080+ /**
8181+ * Use a persistent context instead of a regular browser context.
8282+ * This allows browser state (cookies, localStorage, DevTools settings, etc.) to persist between test runs.
8383+ * When set to `true`, the user data is stored in `./node_modules/.cache/vitest-playwright-user-data`.
8484+ * When set to a string, the value is used as the path to the user data directory.
8585+ *
8686+ * Note: This option is ignored when running tests in parallel (e.g. headless with fileParallelism enabled)
8787+ * because persistent context cannot be shared across parallel sessions.
8888+ * @default false
8989+ * @see {@link https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context}
9090+ */
9191+ persistentContext?: boolean | string
7992}
80938194export function playwright(options: PlaywrightProviderOptions = {}): BrowserProviderOption<PlaywrightProviderOptions> {
···94107 public supportsParallelism = true
9510896109 public browser: Browser | null = null
110110+ public persistentContext: BrowserContext | null = null
9711198112 public contexts: Map<string, BrowserContext> = new Map()
99113 public pages: Map<string, Page> = new Map()
···137151 })
138152 }
139153140140- private async openBrowser() {
154154+ private async openBrowser(openBrowserOptions: { parallel: boolean }) {
141155 await this._throwIfClosing()
142156143157 if (this.browserPromise) {
···202216 }
203217204218 debug?.('[%s] initializing the browser with launch options: %O', this.browserName, launchOptions)
205205- this.browser = await playwright[this.browserName].launch(launchOptions)
219219+ let persistentContextOption = this.options.persistentContext
220220+ if (persistentContextOption && openBrowserOptions.parallel) {
221221+ persistentContextOption = false
222222+ this.project.vitest.logger.warn(
223223+ c.yellow(`The persistentContext option is ignored because tests are running in parallel.`),
224224+ )
225225+ }
226226+ if (persistentContextOption) {
227227+ const userDataDir
228228+ = typeof this.options.persistentContext === 'string'
229229+ ? this.options.persistentContext
230230+ : './node_modules/.cache/vitest-playwright-user-data'
231231+ // TODO: how to avoid default "about" page?
232232+ this.persistentContext = await playwright[this.browserName].launchPersistentContext(
233233+ userDataDir,
234234+ {
235235+ ...launchOptions,
236236+ ...this.getContextOptions(),
237237+ },
238238+ )
239239+ this.browser = this.persistentContext.browser()!
240240+ }
241241+ else {
242242+ this.browser = await playwright[this.browserName].launch(launchOptions)
243243+ }
206244 this.browserPromise = null
207245 return this.browser
208246 })()
···346384 }
347385 }
348386349349- private async createContext(sessionId: string) {
387387+ private async createContext(sessionId: string, openBrowserOptions: { parallel: boolean }) {
350388 await this._throwIfClosing()
351389352390 if (this.contexts.has(sessionId)) {
···354392 return this.contexts.get(sessionId)!
355393 }
356394357357- const browser = await this.openBrowser()
395395+ const browser = await this.openBrowser(openBrowserOptions)
358396 await this._throwIfClosing(browser)
359397 const actionTimeout = this.options.actionTimeout
398398+ const options = this.getContextOptions()
399399+ // TODO: investigate the consequences for Vitest 5
400400+ // else {
401401+ // if UI is disabled, keep the iframe scale to 1
402402+ // options.viewport ??= this.project.config.browser.viewport
403403+ // }
404404+ const context = this.persistentContext ?? await browser.newContext(options)
405405+ await this._throwIfClosing(context)
406406+ if (actionTimeout != null) {
407407+ context.setDefaultTimeout(actionTimeout)
408408+ }
409409+ debug?.('[%s][%s] the context is ready', sessionId, this.browserName)
410410+ this.contexts.set(sessionId, context)
411411+ return context
412412+ }
413413+414414+ private getContextOptions(): BrowserContextOptions {
360415 const contextOptions = this.options.contextOptions ?? {}
361416 const options = {
362417 ...contextOptions,
···365420 if (this.project.config.browser.ui) {
366421 options.viewport = null
367422 }
368368- // TODO: investigate the consequences for Vitest 5
369369- // else {
370370- // if UI is disabled, keep the iframe scale to 1
371371- // options.viewport ??= this.project.config.browser.viewport
372372- // }
373373- const context = await browser.newContext(options)
374374- await this._throwIfClosing(context)
375375- if (actionTimeout != null) {
376376- context.setDefaultTimeout(actionTimeout)
377377- }
378378- debug?.('[%s][%s] the context is ready', sessionId, this.browserName)
379379- this.contexts.set(sessionId, context)
380380- return context
423423+ return options
381424 }
382425383426 public getPage(sessionId: string): Page {
···421464 }
422465 }
423466424424- private async openBrowserPage(sessionId: string) {
467467+ private async openBrowserPage(sessionId: string, options: { parallel: boolean }) {
425468 await this._throwIfClosing()
426469427470 if (this.pages.has(sessionId)) {
···431474 this.pages.delete(sessionId)
432475 }
433476434434- const context = await this.createContext(sessionId)
477477+ const context = await this.createContext(sessionId, options)
435478 const page = await context.newPage()
436479 debug?.('[%s][%s] the page is ready', sessionId, this.browserName)
437480 await this._throwIfClosing(page)
···453496 return page
454497 }
455498456456- async openPage(sessionId: string, url: string): Promise<void> {
499499+ async openPage(sessionId: string, url: string, options: { parallel: boolean }): Promise<void> {
457500 debug?.('[%s][%s] creating the browser page for %s', sessionId, this.browserName, url)
458458- const browserPage = await this.openBrowserPage(sessionId)
501501+ const browserPage = await this.openBrowserPage(sessionId, options)
459502 debug?.('[%s][%s] browser page is created, opening %s', sessionId, this.browserName, url)
460503 await browserPage.goto(url, { timeout: 0 })
461504 await this._throwIfClosing(browserPage)
···504547 this.browser = null
505548 await Promise.all([...this.pages.values()].map(p => p.close()))
506549 this.pages.clear()
507507- await Promise.all([...this.contexts.values()].map(c => c.close()))
550550+ if (this.persistentContext) {
551551+ await this.persistentContext.close()
552552+ }
553553+ else {
554554+ await Promise.all([...this.contexts.values()].map(c => c.close()))
555555+ }
508556 this.contexts.clear()
509557 await browser?.close()
510558 debug?.('[%s] provider is closed', this.browserName)