donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
256 lines • 11.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserFramework = void 0;
const playwright_extra_1 = require("playwright-extra");
const puppeteer_extra_plugin_stealth_1 = __importDefault(require("puppeteer-extra-plugin-stealth"));
const JsonUtils_1 = require("../utils/JsonUtils");
const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException");
const path_1 = __importDefault(require("path"));
const envVars_1 = require("../envVars");
const Logger_1 = require("../utils/Logger");
class BrowserFramework {
constructor(browserConfig, browser, browserContext, browserBaseData) {
this.browserConfig = browserConfig;
this.browser = browser;
this.browserContext = browserContext;
this.browserBaseData = browserBaseData;
}
/**
* Closes the browser and browser context. If the browser is a BrowserBase
* instance, it will also release the session.
*/
async close() {
try {
await this.browserContext.close();
}
catch (_error) {
// Ignore, the browser context may have already been closed and that is fine.
}
try {
await this.browser.close();
}
catch (_error) {
// Ignore, the browser may have already been closed and that is fine.
}
if (this.browserConfig.using.type === 'browserBase' &&
this.browserBaseData) {
const password = process.env[envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY] ?? '';
const options = {
method: 'POST',
headers: {
'X-BB-API-Key': password,
'Content-Type': 'application/json',
},
body: `{"projectId":"${this.browserBaseData.projectId}","status":"REQUEST_RELEASE"}`,
};
try {
await fetch(`https://api.browserbase.com/v1/sessions/${this.browserBaseData.id}`, options);
}
catch (error) {
// Ignore, BrowserBase sessions expire automatically anyway.
Logger_1.appLogger.warn('Failed to release BrowserBase session', error);
}
}
}
/**
* Loads all the pre-canned browser device configurations from the local
* filesystem.
*
* The returned map keys the devices by their name (ex: 'Desktop Firefox').
*
* See `assets/devices.json` for details.
*/
static getSupportedDevices() {
const rawDevices = JsonUtils_1.JsonUtils.readResourceAsJson(path_1.default.join('devices.json'));
if (!rawDevices) {
throw new Error('Failed to load devices configuration');
}
return new Map(Object.entries(rawDevices));
}
static async create(browserConfig, outputDir, storageState) {
const type = browserConfig.using.type;
switch (type) {
case 'device': {
const { browser, browserContext } = await BrowserFramework.forDevice(browserConfig.using.deviceName ??
BrowserFramework.DEFAULT_DEVICE_NAME, browserConfig.using.headless ?? false, outputDir, storageState);
return new BrowserFramework(browserConfig, browser, browserContext);
}
case 'remoteInstance': {
const { browser, browserContext } = await BrowserFramework.forRemoteBrowser(browserConfig.using.url, outputDir, storageState);
return new BrowserFramework(browserConfig, browser, browserContext);
}
case 'browserBase': {
const { browser, browserContext, browserBaseData } = await BrowserFramework.forBrowserBase(browserConfig.using.sessionArgs, outputDir, storageState);
return new BrowserFramework(browserConfig, browser, browserContext, browserBaseData);
}
default: {
throw new InvalidParamValueException_1.InvalidParamValueException('type', type);
}
}
}
/**
* Connects to an existing Chromium browser using the Chrome DevTools Protocol (CDP) at the given
* URL. If the URL contains "${env.VARIABLE_NAME}", then it will be expanded to the value of the
* specified environment variable.
*/
static async forRemoteBrowser(remoteBrowserInstanceUrl, outputDir, storageState) {
const expandedUrl = this.expandEnvVariables(remoteBrowserInstanceUrl);
try {
const browser = await playwright_extra_1.chromium.connectOverCDP(expandedUrl);
const contextOptions = {
recordVideo: { dir: outputDir },
};
if (storageState) {
contextOptions.storageState = storageState;
}
return {
browser: browser,
browserContext: await browser.newContext(contextOptions),
};
}
catch (_) {
throw new InvalidParamValueException_1.InvalidParamValueException('remoteBrowserInstanceUrl', remoteBrowserInstanceUrl);
}
}
/**
* If {@link storageState} is present, must be an object conforming to what is returned by
* {@link BrowserContext.storageState()}.
*/
static async forDevice(deviceName, headless, outputDir, storageState) {
const device = BrowserFramework.getSupportedDevices().get(deviceName);
if (!device) {
throw new InvalidParamValueException_1.InvalidParamValueException('deviceName', deviceName);
}
const contextOptions = {
userAgent: device.userAgent,
recordVideo: {
dir: outputDir,
size: {
width: device.viewport?.width ?? 1280,
height: device.viewport?.height ?? 720,
},
},
viewport: {
width: device.viewport?.width ?? 1280,
height: device.viewport?.height ?? 720,
},
screen: {
width: device.screen?.width ?? device.viewport?.width ?? 1280,
height: device.screen?.height ?? device.viewport?.height ?? 720,
},
deviceScaleFactor: device.deviceScaleFactor ?? 1.0,
isMobile: device.isMobile ?? false,
hasTouch: device.hasTouch ?? false,
};
if (storageState) {
contextOptions.storageState = storageState;
}
const launchOptions = {
headless,
args: [
'--ignore-certificate-errors',
'--disable-blink-features=AutomationControlled',
],
};
let browser;
switch (device.defaultBrowserType.toLowerCase()) {
case 'firefox':
browser = await playwright_extra_1.firefox.launch(launchOptions);
break;
case 'chromium':
browser = await playwright_extra_1.chromium.launch(launchOptions);
break;
case 'chrome':
browser = await playwright_extra_1.chromium.launch({
...launchOptions,
channel: 'chrome',
});
break;
case 'webkit':
case 'ios':
browser = await playwright_extra_1.webkit.launch(launchOptions);
break;
default:
throw new InvalidParamValueException_1.InvalidParamValueException('browserType', device.defaultBrowserType);
}
try {
const browserContext = await browser.newContext(contextOptions);
return { browser, browserContext };
}
catch (error) {
await browser.close();
throw error;
}
}
/**
* Creates a BrowserBase session. If the project ID
* contains "${env.VARIABLE_NAME}", then it will be expanded to the value of
* the specified environment variable. 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
*/
static async forBrowserBase(sessionArgs, outputDir, storageState) {
const browserBaseData = await BrowserFramework.establishBrowserBaseSession(sessionArgs);
const browser = await playwright_extra_1.chromium.connectOverCDP(browserBaseData.connectUrl);
const contextOptions = {
recordVideo: { dir: outputDir },
};
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 structore from the BrowserBase session API. See...
* https://docs.browserbase.com/reference/api/create-a-session#response-id
*/
static async establishBrowserBaseSession(sessionArgs) {
const expandedSessionArgs = {
...sessionArgs,
projectId: this.expandEnvVariables(sessionArgs.projectId),
};
const password = process.env[envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY];
if (!password) {
throw new InvalidParamValueException_1.InvalidParamValueException(envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY, null);
}
const options = {
method: 'POST',
headers: { 'X-BB-API-Key': password, 'Content-Type': 'application/json' },
body: JSON.stringify(expandedSessionArgs),
};
const browserBaseData = await fetch('https://api.browserbase.com/v1/sessions', options).then((response) => response.json());
if (browserBaseData.error) {
throw new InvalidParamValueException_1.InvalidParamValueException(envVars_1.ENV_VAR_NAMES.BROWSERBASE_API_KEY, '(redacted)', `${browserBaseData.error}: ${browserBaseData.message}`);
}
return browserBaseData;
}
static expandEnvVariables(str) {
const pattern = /\${env\.(\w+)}/g;
return str.replace(pattern, (_, envVarName) => {
const envVarValue = process.env[envVarName];
if (!envVarValue) {
throw new InvalidParamValueException_1.InvalidParamValueException(envVarName, null);
}
return envVarValue;
});
}
}
exports.BrowserFramework = BrowserFramework;
BrowserFramework.DEFAULT_DEVICE_NAME = 'Desktop Chromium';
(() => {
playwright_extra_1.firefox.use((0, puppeteer_extra_plugin_stealth_1.default)());
playwright_extra_1.chromium.use((0, puppeteer_extra_plugin_stealth_1.default)());
playwright_extra_1.webkit.use((0, puppeteer_extra_plugin_stealth_1.default)());
})();
//# sourceMappingURL=BrowserFramework.js.map