donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
761 lines • 35.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserUtils = void 0;
const promises_1 = __importDefault(require("node:fs/promises"));
const child_process_1 = require("child_process");
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const playwright_1 = require("playwright");
const util_1 = require("util");
const envVars_1 = require("../envVars");
const ChromeNotInstalledException_1 = require("../exceptions/ChromeNotInstalledException");
const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException");
const Logger_1 = require("./Logger");
const sleep = (0, util_1.promisify)(setTimeout);
/**
* Utility class for managing browser instances and contexts across different browser types and configurations.
*
* Provides comprehensive browser automation capabilities including:
* - Device emulation with pre-configured browser/device combinations
* - Remote browser instance connection via Chrome DevTools Protocol (CDP)
* - BrowserBase cloud browser integration
* - Complete storage state management (cookies, localStorage, sessionStorage)
* - Proxy configuration and environment variable integration
* - Native Chrome installation support via CDP for "Desktop Chrome" device
*
* Supports multiple browser engines: Chromium, Chrome, Firefox, WebKit, and iOS Safari.
*
* @example
* ```typescript
* // Create a browser context with device emulation
* const config: BrowserConfig = {
* using: { type: 'device', deviceName: 'iPhone 13' }
* };
* const context = await BrowserUtils.create(config, './output');
*
* // Use native Chrome installation (special case)
* const chromeConfig: BrowserConfig = {
* using: { type: 'device', deviceName: 'Desktop Chrome' }
* };
* const chromeContext = await BrowserUtils.create(chromeConfig, './output');
*
* // Get supported devices
* const devices = BrowserUtils.getSupportedDevices();
*
* // Extract storage state
* const storageState = await BrowserUtils.getBrowserStorageState(context);
* ```
*
* @see {@link BrowserConfig} for configuration options
* @see {@link BrowserStorageState} for storage state structure
* @see {@link BrowserDevice} for device configuration format
*/
class BrowserUtils {
/**
* Loads browser device configurations from Playwright's built-in devices
* plus the local `assets/devices.json` overrides.
*
* The returned map keys the devices by their name (ex: 'Desktop Firefox').
*
* See `assets/devices.json` for details.
*
* @returns A Map containing device configurations keyed by device name
* @throws {Error} When the devices configuration file cannot be loaded
*/
static getSupportedDevices() {
return BrowserUtils.SUPPORTED_DEVICES;
}
/**
* Creates a browser context based on the provided configuration.
* Supports different browser types including device emulation, remote instances, and BrowserBase.
*
* Special case: When deviceName is "Desktop Chrome", launches the user's native Chrome
* installation and connects via CDP instead of using Playwright's bundled browser.
*
* @param browserConfig - Configuration object specifying browser type and settings.
* @param videoDir - If present, record video and store the artifacts in this directory.
* @param storageState - Optional browser storage state to restore (cookies, localStorage, sessionStorage).
* @returns A promise that resolves to a configured BrowserContext.
* @throws {InvalidParamValueException} When an invalid browser type is specified.
*/
static async create(browserConfig, videoDir, storageState, environ = envVars_1.env.pick('BROWSERBASE_API_KEY', 'PROXY_SERVER', 'PROXY_USERNAME', 'PROXY_PASSWORD')) {
const type = browserConfig.using.type;
let browserContext;
switch (type) {
case 'device': {
const deviceName = browserConfig.using.deviceName ?? BrowserUtils.DEFAULT_DEVICE_NAME;
// Special case for Desktop Chrome - use native Chrome installation via CDP
if (deviceName === BrowserUtils.DESKTOP_CHROME_DEVICE_NAME) {
browserContext = await BrowserUtils.forNativeChrome(browserConfig.using.headless ?? false, storageState, browserConfig.using.proxy, environ);
}
else {
browserContext = await BrowserUtils.forDevice(deviceName, browserConfig.using.headless ?? false, videoDir, storageState, browserConfig.using.proxy, environ);
}
break;
}
case 'remoteInstance': {
browserContext = await BrowserUtils.forRemoteBrowser(browserConfig.using.url, videoDir, storageState);
break;
}
case 'browserBase': {
const browserBaseResult = await BrowserUtils.forBrowserBase(browserConfig.using.sessionArgs, videoDir, storageState, environ);
browserContext = browserBaseResult.browserContext;
// Patch browser.close because the BrowserBase session maps to the browser lifecycle.
const originalBrowserClose = browserBaseResult.browser.close.bind(browserBaseResult.browser);
browserBaseResult.browser.close = async () => {
try {
await originalBrowserClose();
}
catch (_error) {
// Ignore, the browser may have already been closed and that is fine.
}
const body = {
projectId: browserBaseResult.browserBaseData.projectId,
status: 'REQUEST_RELEASE',
};
const password = environ.data.BROWSERBASE_API_KEY ?? '';
const options = {
method: 'POST',
headers: {
'X-BB-API-Key': password,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
};
try {
const resp = await fetch(`https://api.browserbase.com/v1/sessions/${browserBaseResult.browserBaseData.id}`, options);
if (!resp.ok) {
Logger_1.appLogger.warn(`Failed to release BrowserBase session '${browserBaseResult.browserBaseData.id}' due to error: ${resp.statusText}`);
}
}
catch (error) {
// Ignore, BrowserBase sessions expire automatically anyway.
Logger_1.appLogger.warn(`Failed to release BrowserBase session '${browserBaseResult.browserBaseData.id}'`, error);
}
};
break;
}
default: {
throw new InvalidParamValueException_1.InvalidParamValueException('type', type);
}
}
await BrowserUtils.attachSessionStorageToBrowserContext(browserContext, storageState);
return browserContext;
}
/**
* Gets the browser storage state including cookies, localStorage, and sessionStorage.
*
* @param browserContext - The browser context to extract storage state from
* @returns A promise that resolves to the complete browser storage state
*/
static async getBrowserStorageState(browserContext) {
let result;
try {
// First get the standard storage state (cookies and localStorage)
result = await browserContext.storageState({
indexedDB: true,
});
}
catch (error) {
Logger_1.appLogger.warn('Failed to get storage state with indexedDB, falling back to cookies only', error);
result = await browserContext.storageState({
indexedDB: false,
});
}
// Get all pages in the context
const pages = browserContext.pages();
// Process each page to collect sessionStorage data
for (const page of pages) {
const pageUrl = page.url().trim();
try {
if (pageUrl.length === 0 || pageUrl.startsWith('about:')) {
// Skip pages that might have navigation errors or are about:blank.
continue;
}
// Get the origin for the current page
let pageOrigin;
try {
pageOrigin = new URL(pageUrl).origin;
}
catch {
// Skip!
continue;
}
// Find if we already have an entry for this origin
let originEntry = result.origins.find((entry) => entry.origin === pageOrigin);
// If not, create a new entry
if (!originEntry) {
originEntry = {
origin: pageOrigin,
localStorage: [],
sessionStorage: [],
};
result.origins.push(originEntry);
}
else if (!('sessionStorage' in originEntry)) {
// If the entry exists but doesn't have sessionStorage yet, add the property
originEntry.sessionStorage = [];
}
// Extract sessionStorage from the page
const sessionStorageItems = await page.evaluate(() => {
const items = [];
for (let i = 0; i < sessionStorage.length; i++) {
const name = sessionStorage.key(i);
if (name) {
items.push({
name,
value: sessionStorage.getItem(name) || '',
});
}
}
return items;
});
// Add sessionStorage items to the origin entry
originEntry.sessionStorage = sessionStorageItems;
}
catch (error) {
Logger_1.appLogger.warn(`Failed to extract sessionStorage for page: ${pageUrl}`, error);
continue;
}
}
return result;
}
/**
* Launches the user's native Chrome installation and connects to it via CDP.
* This provides access to the user's real Chrome profile, extensions, and settings.
*
* @param headless - Whether to run Chrome in headless mode
* @param storageState - Optional browser storage state to restore
* @param proxy - Optional proxy configuration for the browser
* @throws {Error} When Chrome cannot be launched or CDP connection fails
*/
static async forNativeChrome(headless, storageState, proxy, environ) {
// Always use a dedicated user-data-dir to avoid handing off to an existing Chrome.
const userDataDir = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'donobu-chrome-'));
// Chrome will choose a free port and write it into DevToolsActivePort in userDataDir.
const chromeArgs = [
`--user-data-dir=${userDataDir}`,
'--remote-debugging-port=0',
'--disable-field-trial-config',
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-back-forward-cache',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-extensions-with-background-pages',
'--disable-component-update',
'--no-default-browser-check',
'--disable-default-apps',
'--disable-dev-shm-usage',
'--disable-features=AcceptCHFrame,AvoidUnnecessaryBeforeUnloadCheckSync,DestroyProfileOnBrowserClose,DialMediaRouteProvider,GlobalMediaControls,HttpsUpgrades,LensOverlay,MediaRouter,PaintHolding,ThirdPartyStoragePartitioning,Translate,AutoDeElevate,AutomationControlled',
'--enable-features=CDPScreenshotNewSurface',
'--allow-pre-commit-input',
'--disable-hang-monitor',
'--disable-ipc-flooding-protection',
'--disable-popup-blocking',
'--disable-prompt-on-repost',
'--disable-renderer-backgrounding',
'--force-color-profile=srgb',
'--metrics-recording-only',
'--no-first-run',
'--password-store=basic',
'--use-mock-keychain',
'--no-service-autorun',
'--export-tagged-pdf',
'--disable-search-engine-choice-screen',
'--unsafely-disable-devtools-self-xss-warnings',
'--edge-skip-compat-layer-relaunch',
'--enable-unsafe-swiftshader',
'about:blank',
];
if (headless) {
chromeArgs.push('--headless=new');
}
const expandedProxy = BrowserUtils.expandProxyConfiguration(proxy, environ);
if (expandedProxy) {
chromeArgs.push(`--proxy-server=${expandedProxy.server}`);
if (expandedProxy.bypass) {
chromeArgs.push(`--proxy-bypass-list=${expandedProxy.bypass}`);
}
}
Logger_1.appLogger.info(`Launching Chrome with args: ${chromeArgs.join(' ')}`);
const chromeProcess = await BrowserUtils.launchNativeChrome(chromeArgs);
// Helper: read DevToolsActivePort written by Chrome when --remote-debugging-port=0 is used
const readDevToolsPort = async (maxAttempts = 200, delayMs = 100) => {
const file = path_1.default.join(userDataDir, 'DevToolsActivePort');
for (let i = 0; i < maxAttempts; i++) {
try {
const contents = (await promises_1.default.readFile(file, 'utf8')).trim().split('\n');
// Format: first line is the port, second is the WebSocket path.
const port = Number(contents[0]);
if (!Number.isNaN(port)) {
return port;
}
}
catch {
/* file not ready yet */
}
await sleep(delayMs);
}
throw new Error('DevToolsActivePort not found; Chrome did not expose CDP');
};
let browser;
try {
const port = await readDevToolsPort();
const cdpUrl = `http://localhost:${port}`;
Logger_1.appLogger.info(`Connecting to Chrome CDP at: ${cdpUrl}`);
browser = await playwright_1.chromium.connectOverCDP(cdpUrl);
// IMPORTANT: Reuse the default (already-open) non-incognito context.
// Calling browser.newContext() would create an incognito context -> extra window.
const existingContexts = browser.contexts();
if (existingContexts.length === 0) {
throw new Error('No default Chrome context found after CDP connect');
}
const browserContext = existingContexts[0];
// Apply storage state (cookies) if provided.
if (storageState?.cookies?.length) {
try {
await browserContext.addCookies(storageState.cookies);
}
catch (e) {
Logger_1.appLogger.warn('Failed to add cookies to existing Chrome context', e);
}
}
// (sessionStorage/localStorage restoration happens via attachSessionStorageToBrowserContext later)
// Patch close: close Playwright connection, then Chrome, then remove temp profile.
const originalClose = browser.close.bind(browser);
browser.close = async () => {
try {
await originalClose();
}
catch (e) {
Logger_1.appLogger.warn('Error closing browser connection', e);
}
try {
if (!chromeProcess.killed) {
chromeProcess.kill('SIGTERM');
await sleep(1000);
if (!chromeProcess.killed) {
chromeProcess.kill('SIGKILL');
}
}
}
catch (e) {
Logger_1.appLogger.warn('Error terminating Chrome process', e);
}
try {
await promises_1.default.rm(userDataDir, { recursive: true, force: true });
}
catch (e) {
Logger_1.appLogger.warn('Error removing temp userDataDir', e);
}
};
return browserContext;
}
catch (error) {
try {
if (browser) {
await browser.close();
}
}
catch { }
try {
if (!chromeProcess.killed) {
chromeProcess.kill('SIGKILL');
}
}
catch { }
throw error;
}
}
/**
* Launches the native Chrome browser with the specified arguments.
* Attempts to find Chrome in common installation locations across different platforms.
*
* @param args - Command line arguments to pass to Chrome
* @returns The spawned Chrome process
* @throws {Error} When Chrome executable cannot be found or launched
*/
static async launchNativeChrome(args) {
const chromePaths = BrowserUtils.getChromePaths();
for (const chromePath of chromePaths) {
try {
Logger_1.appLogger.info(`Attempting to launch Chrome from: ${chromePath}`);
const process = (0, child_process_1.spawn)(chromePath, args, {
detached: false,
stdio: ['ignore', 'pipe', 'pipe'], // Capture stdout and stderr
});
// Set up error handling for the process
process.on('error', (error) => {
Logger_1.appLogger.error(`Chrome process error: ${error.message}`);
});
process.stdout?.on('data', (data) => {
Logger_1.appLogger.debug(`Chrome stdout: ${data.toString()}`);
});
process.stderr?.on('data', (data) => {
const message = data.toString();
// Don't log as error if it's just informational Chrome output
if (message.includes('DevTools listening on')) {
Logger_1.appLogger.info(`Chrome: ${message.trim()}`);
}
else {
Logger_1.appLogger.debug(`Chrome stderr: ${message}`);
}
});
// Wait a bit to see if the process starts successfully
await sleep(1000); // Increased wait time
if (!process.killed && process.pid) {
Logger_1.appLogger.info(`Launched Chrome from: ${chromePath} (PID: ${process.pid})`);
return process;
}
else {
Logger_1.appLogger.warn(`Chrome process died immediately for path: ${chromePath}`);
}
}
catch (error) {
Logger_1.appLogger.debug(`Failed to launch Chrome from ${chromePath}:`, error);
continue;
}
}
throw new ChromeNotInstalledException_1.ChromeNotInstalledException();
}
/**
* Returns an array of possible Chrome executable paths for the current platform.
*
* @returns Array of potential Chrome executable paths
*/
static getChromePaths() {
const platform = process.platform;
switch (platform) {
case 'win32':
return [
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
process.env.LOCALAPPDATA +
'\\Google\\Chrome\\Application\\chrome.exe',
].filter(Boolean);
case 'darwin':
return [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
'/usr/bin/google-chrome',
];
case 'linux':
return [
'/usr/bin/google-chrome',
'/usr/bin/google-chrome-stable',
'/usr/bin/chromium-browser',
'/usr/bin/chromium',
'/snap/bin/chromium',
];
default:
return ['/usr/bin/google-chrome', '/usr/bin/chromium'];
}
}
/**
* Connects to an existing Chromium browser using the Chrome DevTools Protocol (CDP) at the given URL.
*
* @param remoteBrowserInstanceUrl - The CDP endpoint URL of the remote browser instancevideoDir
* @param storageState - Optional browser storage state to restore
* @throws {InvalidParamValueException} When the remote browser URL is invalid or connection fails
* @private
*/
static async forRemoteBrowser(remoteBrowserInstanceUrl, videoDir, storageState) {
try {
const browser = await playwright_1.chromium.connectOverCDP(remoteBrowserInstanceUrl);
try {
const contextOptions = {
...(videoDir ? { recordVideo: { dir: videoDir } } : {}),
};
if (storageState) {
contextOptions.storageState = storageState;
}
return await browser.newContext(contextOptions);
}
catch (error) {
await browser.close();
throw error;
}
}
catch (_) {
throw new InvalidParamValueException_1.InvalidParamValueException('remoteBrowserInstanceUrl', remoteBrowserInstanceUrl);
}
}
/**
* Creates a browser and context for a specific device configuration.
* If {@link storageState} is present, must be an object conforming to what is returned by
* {@link BrowserContext.storageState()}.
*
* @param deviceName - Name of the device configuration to use
* @param headless - Whether to run the browser in headless mode
* @param videoDir - If present, record video and store the artifacts in this directory.
* @param storageState - Optional browser storage state to restore
* @param proxy - Optional proxy configuration for the browser
* @throws {InvalidParamValueException} When the device name is not found in supported devices
*/
static async forDevice(deviceName, headless, videoDir, storageState, proxy, environ) {
const { browserTypeName, browserContextOptions } = this.browserContextOptionsForDevice(deviceName, videoDir);
if (storageState) {
browserContextOptions.storageState = storageState;
}
const launchOptions = {
headless,
args: [
'--ignore-certificate-errors',
'--disable-blink-features=AutomationControlled',
],
proxy: this.expandProxyConfiguration(proxy, environ),
};
return await this.newBrowser(browserTypeName, launchOptions, browserContextOptions);
}
/**
* Builds browser context options for a specific device configuration.
*
* @param deviceName - Name of the device configuration to use.
* @param videoDir - If present, record video and store the artifacts in this directory.
* @returns An object containing the browser type name and context options.
* @throws {InvalidParamValueException} When the device name is not found in supported devices.
*/
static browserContextOptionsForDevice(deviceName, videoDir) {
const browserDevice = BrowserUtils.getSupportedDevices().get(deviceName);
if (!browserDevice) {
throw new InvalidParamValueException_1.InvalidParamValueException('deviceName', deviceName);
}
const browserContextOptions = {
userAgent: browserDevice.userAgent,
...(videoDir
? {
recordVideo: {
dir: videoDir,
size: {
width: browserDevice.viewport?.width ?? 1280,
height: browserDevice.viewport?.height ?? 720,
},
},
}
: {}),
viewport: {
width: browserDevice.viewport?.width ?? 1280,
height: browserDevice.viewport?.height ?? 720,
},
screen: {
width: browserDevice.screen?.width ?? browserDevice.viewport?.width ?? 1280,
height: browserDevice.screen?.height ?? browserDevice.viewport?.height ?? 720,
},
deviceScaleFactor: browserDevice.deviceScaleFactor ?? 1.0,
isMobile: browserDevice.isMobile ?? false,
hasTouch: browserDevice.hasTouch ?? false,
permissions: browserDevice.permissions,
};
return {
browserTypeName: browserDevice.defaultBrowserType.toLowerCase(),
browserContextOptions: browserContextOptions,
};
}
/**
* Returns the Playwright browser engine ('chromium', 'firefox', or 'webkit')
* required to run the given device name.
*
* 'chrome' and 'ios' (used internally by Playwright devices) are normalised
* to 'chromium' and 'webkit' respectively, so callers always receive one of
* the three canonical engine names.
*
* @throws {InvalidParamValueException} When deviceName is not in the supported device list.
*/
static getBrowserTypeForDeviceName(deviceName) {
const { browserTypeName } = BrowserUtils.browserContextOptionsForDevice(deviceName);
if (browserTypeName === 'chrome') {
return 'chromium';
}
if (browserTypeName === 'ios') {
return 'webkit';
}
return browserTypeName;
}
/**
* Expands proxy configuration by merging provided proxy settings with environment variables.
* Environment variables serve as fallbacks for missing proxy configuration values.
*
* @param proxy - Optional proxy configuration object
* @returns Expanded proxy configuration or undefined if no proxy is configured
*/
static expandProxyConfiguration(proxy, environ) {
const envProxyServer = environ.data.PROXY_SERVER;
const envProxyUsername = environ.data.PROXY_USERNAME;
const envProxyPassword = environ.data.PROXY_PASSWORD;
if (!proxy) {
return envProxyServer !== undefined
? {
server: envProxyServer,
username: envProxyUsername,
password: envProxyPassword,
}
: undefined;
}
return {
server: proxy.server,
bypass: proxy.bypass,
username: proxy.username ?? envProxyUsername,
password: proxy.password ?? envProxyPassword,
};
}
/**
* Creates a new browser instance of the specified type with given launch and context options.
* Handles cleanup by closing the browser if context creation fails.
*
* @param browserTypeName - The type of browser to launch
* @param launchOptions - Options for launching the browser
* @param browserContextOptions - Options for creating the browser context
* @throws {InvalidParamValueException} When an unsupported browser type is specified
*/
static async newBrowser(browserTypeName, launchOptions, browserContextOptions) {
let browser;
switch (browserTypeName) {
case 'firefox':
browser = await playwright_1.firefox.launch(launchOptions);
break;
case 'chromium':
browser = await playwright_1.chromium.launch(launchOptions);
break;
case 'chrome':
browser = await playwright_1.chromium.launch({
...launchOptions,
channel: 'chrome',
});
break;
case 'webkit':
case 'ios':
browser = await playwright_1.webkit.launch(launchOptions);
break;
default:
throw new InvalidParamValueException_1.InvalidParamValueException('browserType', browserTypeName);
}
try {
return await browser.newContext(browserContextOptions);
}
catch (error) {
await browser.close();
throw error;
}
}
/**
* Creates a BrowserBase session. Using this method requires the
* BROWSERBASE_API_KEY environment variable to be set.
*
* The returned browserBaseData object conforms to the response of the session
* creation API endpoint. See...
* https://docs.browserbase.com/reference/api/create-a-session#response-id
*
* @param sessionArgs - Arguments for creating the BrowserBase session.
* @param videoDir - If present, record video and store the artifacts in this directory.
* @param storageState - Optional browser storage state to restore.
* @returns A promise that resolves to an object containing the browser, context, and session data.
* @throws {InvalidParamValueException} When BrowserBase API key is missing or session creation fails.
*/
static async forBrowserBase(sessionArgs, videoDir, storageState, environ) {
const browserBaseData = await BrowserUtils.establishBrowserBaseSession(sessionArgs, environ);
const browser = await playwright_1.chromium.connectOverCDP(browserBaseData.connectUrl);
const contextOptions = {
...(videoDir ? { recordVideo: { dir: videoDir } } : {}),
};
if (storageState) {
contextOptions.storageState = storageState;
}
return {
browser: browser,
browserContext: await browser.newContext(contextOptions),
browserBaseData: browserBaseData,
};
}
/**
* Establishes a BrowserBase session. The returned structure matches the
* response structure from the BrowserBase session API. See...
* https://docs.browserbase.com/reference/api/create-a-session#response-id
*
* @param sessionArgs - Arguments for creating the BrowserBase session
* @returns A promise that resolves to the BrowserBase session data
* @throws {InvalidParamValueException} When the API key is missing or the API returns an error
*/
static async establishBrowserBaseSession(sessionArgs, environ) {
const password = environ.data.BROWSERBASE_API_KEY;
if (!password) {
throw new InvalidParamValueException_1.InvalidParamValueException(environ.keys.BROWSERBASE_API_KEY, null);
}
const options = {
method: 'POST',
headers: { 'X-BB-API-Key': password, 'Content-Type': 'application/json' },
body: JSON.stringify(sessionArgs),
};
const browserBaseData = await fetch('https://api.browserbase.com/v1/sessions', options).then((response) => response.json());
if (browserBaseData.error) {
throw new InvalidParamValueException_1.InvalidParamValueException(environ.keys.BROWSERBASE_API_KEY, '*** REDACTED ***', `${browserBaseData.error}: ${browserBaseData.message}`);
}
return browserBaseData;
}
/**
* Attaches sessionStorage data to a browser context by adding an initialization script.
* The script will restore sessionStorage items for each origin when pages are loaded.
*
* @param browserContext - The browser context to attach sessionStorage to
* @param storageState - Optional browser storage state containing sessionStorage data
*/
static async attachSessionStorageToBrowserContext(browserContext, storageState) {
// Add init script to restore sessionStorage if storage state is provided
if (storageState?.origins) {
// Transform the storage state to map origins to their sessionStorage
const sessionStorageByOrigin = {};
for (const origin of storageState.origins) {
if (origin.sessionStorage && origin.sessionStorage.length > 0) {
// Create a key-value map for this origin's sessionStorage
const sessionStorageMap = {};
for (const item of origin.sessionStorage) {
sessionStorageMap[item.name] = item.value;
}
sessionStorageByOrigin[origin.origin] = sessionStorageMap;
}
}
// Add the init script to restore sessionStorage based on the page's origin
await browserContext.addInitScript((storageData) => {
// Get current origin
const currentOrigin = window.location.origin;
// Check if we have sessionStorage data for this origin
if (storageData[currentOrigin]) {
// Restore the sessionStorage items
for (const [key, value] of Object.entries(storageData[currentOrigin])) {
window.sessionStorage.setItem(key, value);
}
console.log(`Restored ${Object.keys(storageData[currentOrigin]).length} sessionStorage items for ${currentOrigin}`);
}
}, sessionStorageByOrigin);
}
}
}
exports.BrowserUtils = BrowserUtils;
BrowserUtils.SUPPORTED_DEVICES = new Map([
...Object.entries(playwright_1.devices),
// We map 'Desktop Chrome' to these Chromium variants because we treat
// the normal 'Desktop Chrome' device name to detect if someone wants to
// use their normal Chrome installation, not the special Playwright version.
[
'Desktop Chromium',
{
...playwright_1.devices['Desktop Chrome'],
deviceScaleFactor: 2,
},
],
[
'Desktop Chromium with Media',
{
...playwright_1.devices['Desktop Chrome'],
permissions: ['geolocation', 'camera', 'microphone'],
deviceScaleFactor: 2,
},
],
]);
BrowserUtils.DEFAULT_DEVICE_NAME = 'Desktop Chromium';
BrowserUtils.DESKTOP_CHROME_DEVICE_NAME = 'Desktop Chrome';
//# sourceMappingURL=BrowserUtils.js.map