Storage state persistence broken in Stagehand v3 - userDataDir doesn't work, storageState() method missing
Before submitting an issue, please:
Environment Information
Please provide the following information to help us reproduce and resolve your issue:
Stagehand:
- Language/SDK: TypeScript
- Stagehand version: 3.0.1
AI Provider:
- Provider: Google
- Model: gemini-2.5-flash-preview-04-17
Issue Description
Storage state persistence is broken in Stagehand v3. The userDataDir and preserveUserDataDir options in localBrowserLaunchOptions do not persist browser data (cookies, localStorage) as expected. Additionally, the storageState() method is no longer available on the context/page objects, preventing manual extraction of authentication state.
Expected Behavior:
- When
userDataDir is specified, Chrome should create and use that directory to store user data
- When
preserveUserDataDir: true is set, the directory should persist after stagehand.close()
- The directory should contain Chrome's standard profile structure (e.g.,
Default subdirectory)
- When a new Stagehand instance is created with the same
userDataDir, it should automatically load the persisted authentication state
Actual Behavior:
- The
userDataDir directory is never created - it doesn't exist after init(), during the session, or after close()
- Even when manually creating the directory before
init(), Chrome does not write any data to it
- The
storageState() method is not available on stagehand.context or page.context(), preventing manual extraction
- Authentication state cannot be persisted between sessions
Steps to Reproduce
- Create a Stagehand instance with
userDataDir and preserveUserDataDir: true
- Perform login to establish authentication (cookies are set successfully)
- Close the Stagehand instance
- Check if the
userDataDir directory exists and contains data
Minimal Reproduction Code
import { Stagehand } from '@browserbasehq/stagehand';
import { join } from 'path';
import { existsSync, readdirSync } from 'fs';
const adminUserDataDir = join(process.cwd(), 'chrome-user-data', 'admin');
const stagehand = new Stagehand({
env: 'LOCAL',
verbose: 1,
localBrowserLaunchOptions: {
headless: false,
userDataDir: adminUserDataDir,
preserveUserDataDir: true
}
});
await stagehand.init();
const page = stagehand.context.pages()[0];
// Perform login (cookies are set successfully)
await page.goto('https://example.com/login');
// ... login steps ...
// Login succeeds, cookies exist (verified via document.cookie)
console.log(`Directory exists after init: ${existsSync(adminUserDataDir)}`); // false
await stagehand.close();
console.log(`Directory exists after close: ${existsSync(adminUserDataDir)}`); // false
// Even if directory is manually created before init(), it remains empty
if (existsSync(adminUserDataDir)) {
const files = readdirSync(adminUserDataDir);
console.log(`Directory contents: ${files.length} items`); // 0 - empty
}
Error Messages / Log trace
No errors are thrown, but the directory is never created:
Directory exists after init: false
Directory exists after close: false
When attempting to use storageState() method:
// Attempt 1: stagehand.context.storageState()
const storageState = await stagehand.context.storageState();
// Error: stagehand.context.storageState is not a function
// Attempt 2: page.context().storageState()
const page = stagehand.context.pages()[0];
const storageState = await page.context().storageState();
// Error: page.context is not a function
Screenshots / Videos
N/A - Issue is about missing functionality rather than visual bugs.
Root Cause Analysis
Issue 1: userDataDir Not Passed to Chrome
The userDataDir option in localBrowserLaunchOptions appears to not be passed correctly to the underlying Chrome browser instance. This could be due to:
- Stagehand v3's new architecture using Chrome DevTools Protocol instead of Playwright
- The option being filtered out or ignored during browser launch
- A bug in how Stagehand v3 handles
localBrowserLaunchOptions
Issue 2: storageState() Method Removed
In Stagehand v2, storageState() was available via Playwright's BrowserContext. In Stagehand v3:
- Stagehand removed its internal Playwright dependency
- The
context object is no longer a Playwright BrowserContext
- The
storageState() method was not re-implemented in Stagehand v3's new architecture
Issue 3: API Changes Not Documented
The migration from v2 to v3 removed critical functionality without:
- Clear documentation of breaking changes
- Migration guide for storage state persistence
- Alternative methods for persisting authentication
Previous Approach (Playwright Browser Context)
Before Stagehand v3, we could use Playwright's native browser context methods:
Method 1: Using storageState() Method
import { chromium } from '@playwright/test';
const browser = await chromium.launch();
const context = await browser.newContext();
// Perform login
const page = await context.newPage();
await page.goto('https://example.com/login');
// ... login steps ...
// Save storage state (cookies + localStorage)
await context.storageState({ path: 'storage-state/admin.json' });
// Later, load storage state
const context2 = await browser.newContext({
storageState: 'storage-state/admin.json'
});
Method 2: Using launchPersistentContext with userDataDir
import { chromium } from '@playwright/test';
// Launch browser with persistent user data directory
const context = await chromium.launchPersistentContext('./chrome-user-data/admin', {
headless: false
});
// Login and close - data automatically persisted
await context.close();
// Later, launch with same userDataDir - authentication persists
const context2 = await chromium.launchPersistentContext('./chrome-user-data/admin', {
headless: false
});
Method 3: Using Stagehand v2 (with Playwright under the hood)
import { Stagehand } from '@browserbasehq/stagehand';
const stagehand = new Stagehand({ env: 'LOCAL' });
await stagehand.init();
await loginAsAdmin(stagehand);
// Access Playwright context and save storage state
const playwrightContext = stagehand.page.context();
await playwrightContext.storageState({ path: 'storage-state/admin.json' });
Key Differences from Stagehand v3:
- ✅
context.storageState() method was available
- ✅
page.context() returned Playwright BrowserContext
- ✅
userDataDir actually worked and persisted data
- ✅ No manual cookie/localStorage extraction needed
Current Workaround
We've implemented a manual workaround that extracts cookies and localStorage manually:
// Extract cookies and localStorage
const cookies = await page.evaluate(() => {
return document.cookie.split(';').map(cookie => {
const [name, ...valueParts] = cookie.trim().split('=');
return { name, value: valueParts.join('=') };
});
});
const localStorage = await page.evaluate(() => {
const items = [];
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i);
if (key) {
items.push({ name: key, value: window.localStorage.getItem(key) || '' });
}
}
return items;
});
// Save to JSON file in Playwright storage state format
// Load and apply when creating new browser instances
Limitations of workaround:
- Requires manual extraction code
- May miss HttpOnly cookies (not accessible via
document.cookie)
- localStorage must be applied after navigating to the correct origin
- More error-prone than native browser persistence
Impact
- High: Authentication state cannot be persisted between test runs
- High: Tests must re-authenticate on every run, increasing execution time
- Medium: Workarounds require manual cookie/localStorage extraction and application
- Medium: No official way to share authentication state across multiple test files
Proposed Solutions
Option 1: Fix userDataDir Support
Ensure userDataDir and preserveUserDataDir are correctly passed to Chrome and that Chrome actually uses the specified directory.
Option 2: Re-implement storageState() Method
Add a storageState() method to Stagehand v3's context object that extracts and returns cookies/localStorage in Playwright format:
const storageState = await stagehand.context.storageState();
await stagehand.context.storageState({ path: 'storage-state/admin.json' });
Option 3: Provide Official Storage State API
Create a new Stagehand v3 API for persisting and loading authentication state:
// Save storage state
await stagehand.saveStorageState('path/to/storage-state.json');
// Load storage state
const stagehand = new Stagehand({
env: 'LOCAL',
storageState: 'path/to/storage-state.json'
});
Related Issues
Are there any related issues or PRs?
- Related to: Stagehand v3 migration (removed Playwright dependency)
- Related to:
page.context() is no longer available
- Related to:
stagehand.context is not a Playwright BrowserContext
Additional Context
- This issue affects all users migrating from Stagehand v2 to v3
- The workaround is functional but not ideal for production use
- We recommend prioritizing a fix as this is a critical feature for test automation
- Documentation on storage state persistence in v3 is missing
Storage state persistence broken in Stagehand v3 - userDataDir doesn't work, storageState() method missing
Before submitting an issue, please:
Environment Information
Please provide the following information to help us reproduce and resolve your issue:
Stagehand:
AI Provider:
Issue Description
Storage state persistence is broken in Stagehand v3. The
userDataDirandpreserveUserDataDiroptions inlocalBrowserLaunchOptionsdo not persist browser data (cookies, localStorage) as expected. Additionally, thestorageState()method is no longer available on the context/page objects, preventing manual extraction of authentication state.Expected Behavior:
userDataDiris specified, Chrome should create and use that directory to store user datapreserveUserDataDir: trueis set, the directory should persist afterstagehand.close()Defaultsubdirectory)userDataDir, it should automatically load the persisted authentication stateActual Behavior:
userDataDirdirectory is never created - it doesn't exist afterinit(), during the session, or afterclose()init(), Chrome does not write any data to itstorageState()method is not available onstagehand.contextorpage.context(), preventing manual extractionSteps to Reproduce
userDataDirandpreserveUserDataDir: trueuserDataDirdirectory exists and contains dataMinimal Reproduction Code
Error Messages / Log trace
No errors are thrown, but the directory is never created:
When attempting to use
storageState()method:Screenshots / Videos
N/A - Issue is about missing functionality rather than visual bugs.
Root Cause Analysis
Issue 1: userDataDir Not Passed to Chrome
The
userDataDiroption inlocalBrowserLaunchOptionsappears to not be passed correctly to the underlying Chrome browser instance. This could be due to:localBrowserLaunchOptionsIssue 2: storageState() Method Removed
In Stagehand v2,
storageState()was available via Playwright'sBrowserContext. In Stagehand v3:contextobject is no longer a PlaywrightBrowserContextstorageState()method was not re-implemented in Stagehand v3's new architectureIssue 3: API Changes Not Documented
The migration from v2 to v3 removed critical functionality without:
Previous Approach (Playwright Browser Context)
Before Stagehand v3, we could use Playwright's native browser context methods:
Method 1: Using storageState() Method
Method 2: Using launchPersistentContext with userDataDir
Method 3: Using Stagehand v2 (with Playwright under the hood)
Key Differences from Stagehand v3:
context.storageState()method was availablepage.context()returned Playwright BrowserContextuserDataDiractually worked and persisted dataCurrent Workaround
We've implemented a manual workaround that extracts cookies and localStorage manually:
Limitations of workaround:
document.cookie)Impact
Proposed Solutions
Option 1: Fix userDataDir Support
Ensure
userDataDirandpreserveUserDataDirare correctly passed to Chrome and that Chrome actually uses the specified directory.Option 2: Re-implement storageState() Method
Add a
storageState()method to Stagehand v3's context object that extracts and returns cookies/localStorage in Playwright format:Option 3: Provide Official Storage State API
Create a new Stagehand v3 API for persisting and loading authentication state:
Related Issues
Are there any related issues or PRs?
page.context()is no longer availablestagehand.contextis not a Playwright BrowserContextAdditional Context