Update npm packages (73 packages including @jqhtml 2.3.36)

Update npm registry domain from privatenpm.hanson.xyz to npm.internal.hanson.xyz

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-02-20 11:31:28 +00:00
parent d01a6179aa
commit b5eb27a827
1690 changed files with 47348 additions and 16848 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,59 +3,58 @@
"browsers": [
{
"name": "chromium",
"revision": "1200",
"revision": "1208",
"installByDefault": true,
"browserVersion": "143.0.7499.4"
"browserVersion": "145.0.7632.6",
"title": "Chrome for Testing"
},
{
"name": "chromium-headless-shell",
"revision": "1200",
"revision": "1208",
"installByDefault": true,
"browserVersion": "143.0.7499.4"
"browserVersion": "145.0.7632.6",
"title": "Chrome Headless Shell"
},
{
"name": "chromium-tip-of-tree",
"revision": "1380",
"revision": "1401",
"installByDefault": false,
"browserVersion": "143.0.7488.0"
"browserVersion": "146.0.7644.0",
"title": "Chrome Canary for Testing"
},
{
"name": "chromium-tip-of-tree-headless-shell",
"revision": "1380",
"revision": "1401",
"installByDefault": false,
"browserVersion": "143.0.7488.0"
"browserVersion": "146.0.7644.0",
"title": "Chrome Canary Headless Shell"
},
{
"name": "firefox",
"revision": "1497",
"revision": "1509",
"installByDefault": true,
"browserVersion": "144.0.2"
"browserVersion": "146.0.1",
"title": "Firefox"
},
{
"name": "firefox-beta",
"revision": "1493",
"revision": "1504",
"installByDefault": false,
"browserVersion": "145.0b10"
"browserVersion": "146.0b8",
"title": "Firefox Beta"
},
{
"name": "webkit",
"revision": "2227",
"revision": "2248",
"installByDefault": true,
"revisionOverrides": {
"debian11-x64": "2105",
"debian11-arm64": "2105",
"mac10.14": "1446",
"mac10.15": "1616",
"mac11": "1816",
"mac11-arm64": "1816",
"mac12": "2009",
"mac12-arm64": "2009",
"mac13": "2140",
"mac13-arm64": "2140",
"ubuntu20.04-x64": "2092",
"ubuntu20.04-arm64": "2092"
},
"browserVersion": "26.0"
"browserVersion": "26.0",
"title": "WebKit"
},
{
"name": "ffmpeg",

View File

@@ -124,7 +124,7 @@ Playwright version: ${version}`);
printInstalledBrowsers(groupedByPlaywrightMinorVersion.get(version));
}
}
import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--list", "prints list of browsers from all playwright installations").option("--force", "force reinstall of stable browser channels").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) {
import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--list", "prints list of browsers from all playwright installations").option("--force", "force reinstall of already installed browsers").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) {
if ((0, import_utils.isLikelyNpxGlobal)()) {
console.error((0, import_ascii.wrapInASCIIBox)([
`WARNING: It looks like you are running 'npx playwright install' without first`,
@@ -156,8 +156,7 @@ import_utilsBundle.program.command("install [browser...]").description("ensure b
throw new Error(`Only one of --dry-run and --list can be specified`);
if (options.dryRun) {
for (const executable of executables) {
const version = executable.browserVersion ? `version ` + executable.browserVersion : "";
console.log(`browser: ${executable.name}${version ? " " + version : ""}`);
console.log(import_server.registry.calculateDownloadTitle(executable));
console.log(` Install location: ${executable.directory ?? "<system>"}`);
if (executable.downloadURLs?.length) {
const [url, ...fallbacks] = executable.downloadURLs;
@@ -171,8 +170,7 @@ import_utilsBundle.program.command("install [browser...]").description("ensure b
const browsers2 = await import_server.registry.listInstalledBrowsers();
printGroupedByPlaywrightVersion(browsers2);
} else {
const force = args.length === 0 ? false : !!options.force;
await import_server.registry.install(executables, { force });
await import_server.registry.install(executables, { force: options.force });
await import_server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || "javascript").catch((e) => {
e.name = "Playwright Host validation warning";
console.error(e);
@@ -423,6 +421,7 @@ async function openPage(context, url) {
}
async function open(options, url) {
const { context } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH });
await context._exposeConsoleApi();
await openPage(context, url);
}
async function codegen(options, url) {

View File

@@ -46,6 +46,7 @@ __export(api_exports, {
Locator: () => import_locator.Locator,
Mouse: () => import_input.Mouse,
Page: () => import_page.Page,
PageAgent: () => import_pageAgent.PageAgent,
Playwright: () => import_playwright.Playwright,
Request: () => import_network.Request,
Response: () => import_network.Response,
@@ -81,6 +82,7 @@ var import_jsHandle = require("./jsHandle");
var import_network = require("./network");
var import_fetch = require("./fetch");
var import_page = require("./page");
var import_pageAgent = require("./pageAgent");
var import_selectors = require("./selectors");
var import_tracing = require("./tracing");
var import_video = require("./video");
@@ -118,6 +120,7 @@ var import_webError = require("./webError");
Locator,
Mouse,
Page,
PageAgent,
Playwright,
Request,
Response,

View File

@@ -62,11 +62,9 @@ class Browser extends import_channelOwner.ChannelOwner {
context._onClose();
await this._channel.disconnectFromReusedContext({ reason });
}
async _innerNewContext(options = {}, forReuse) {
options = this._browserType._playwright.selectors._withSelectorOptions({
...this._browserType._playwright._defaultContextOptions,
...options
});
async _innerNewContext(userOptions = {}, forReuse) {
const options = this._browserType._playwright.selectors._withSelectorOptions(userOptions);
await this._instrumentation.runBeforeCreateBrowserContext(options);
const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = import_browserContext.BrowserContext.from(response.context);

View File

@@ -76,6 +76,7 @@ class BrowserContext extends import_channelOwner.ChannelOwner {
this.tracing = import_tracing.Tracing.from(initializer.tracing);
this.request = import_fetch.APIRequestContext.from(initializer.requestContext);
this.request._timeoutSettings = this._timeoutSettings;
this.request._checkUrlAllowed = (url) => this._checkUrlAllowed(url);
this.clock = new import_clock.Clock(this);
this._channel.on("bindingCall", ({ binding }) => this._onBinding(import_page.BindingCall.from(binding)));
this._channel.on("close", () => this._onClose());
@@ -474,6 +475,40 @@ class BrowserContext extends import_channelOwner.ChannelOwner {
this._onRecorderEventSink = void 0;
await this._channel.disableRecorder();
}
async _exposeConsoleApi() {
await this._channel.exposeConsoleApi();
}
_setAllowedProtocols(protocols) {
this._allowedProtocols = protocols;
}
_checkUrlAllowed(url) {
if (!this._allowedProtocols)
return;
let parsedURL;
try {
parsedURL = new URL(url);
} catch (e) {
throw new Error(`Access to ${url} is blocked. Invalid URL: ${e.message}`);
}
if (!this._allowedProtocols.includes(parsedURL.protocol))
throw new Error(`Access to "${parsedURL.protocol}" URL is blocked. Allowed protocols: ${this._allowedProtocols.join(", ")}. Attempted URL: ${url}`);
}
_setAllowedDirectories(rootDirectories) {
this._allowedDirectories = rootDirectories;
}
_checkFileAccess(filePath) {
if (!this._allowedDirectories)
return;
const path = this._platform.path().resolve(filePath);
const isInsideDir = (container, child) => {
const path2 = this._platform.path();
const rel = path2.relative(container, child);
return !!rel && !rel.startsWith("..") && !path2.isAbsolute(rel);
};
if (this._allowedDirectories.some((root) => isInsideDir(root, path)))
return;
throw new Error(`File access denied: ${filePath} is outside allowed roots. Allowed roots: ${this._allowedDirectories.length ? this._allowedDirectories.join(", ") : "none"}`);
}
}
async function prepareStorageState(platform, storageState) {
if (typeof storageState !== "string")

View File

@@ -73,13 +73,13 @@ class BrowserType extends import_channelOwner.ChannelOwner {
return await this._serverLauncher.launchServer(options);
}
async launchPersistentContext(userDataDir, options = {}) {
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
(0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
options = this._playwright.selectors._withSelectorOptions({
...this._playwright._defaultLaunchOptions,
...this._playwright._defaultContextOptions,
...options
});
await this._instrumentation.runBeforeCreateBrowserContext(options);
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
const contextParams = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const persistentParams = {
...contextParams,
@@ -169,7 +169,8 @@ class BrowserType extends import_channelOwner.ChannelOwner {
endpointURL,
headers,
slowMo: params.slowMo,
timeout: new import_timeoutSettings.TimeoutSettings(this._platform).timeout(params)
timeout: new import_timeoutSettings.TimeoutSettings(this._platform).timeout(params),
isLocal: params.isLocal
});
const browser = import_browser.Browser.from(result.browser);
browser._connectToBrowserType(this, {}, params.logger);

View File

@@ -48,6 +48,7 @@ var import_worker = require("./worker");
var import_writableStream = require("./writableStream");
var import_validator = require("../protocol/validator");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
var import_pageAgent = require("./pageAgent");
class Root extends import_channelOwner.ChannelOwner {
constructor(connection) {
super(connection, "Root", "", {});
@@ -261,6 +262,9 @@ class Connection extends import_eventEmitter.EventEmitter {
case "Page":
result = new import_page.Page(parent, type, guid, initializer);
break;
case "PageAgent":
result = new import_pageAgent.PageAgent(parent, type, guid, initializer);
break;
case "Playwright":
result = new import_playwright.Playwright(parent, type, guid, initializer);
break;

View File

@@ -230,6 +230,9 @@ async function convertInputFiles(platform, files, context) {
if (!items.every((item) => typeof item === "string"))
throw new Error("File paths cannot be mixed with buffers");
const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(platform, items);
localPaths?.forEach((path) => context._checkFileAccess(path));
if (localDirectory)
context._checkFileAccess(localDirectory);
if (context._connection.isRemote()) {
const files2 = localDirectory ? (await platform.fs().promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter((f) => f.isFile()).map((f) => platform.path().join(f.path, f.name)) : localPaths;
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({

View File

@@ -78,6 +78,9 @@ const Events = {
WebSocket: "websocket",
Worker: "worker"
},
PageAgent: {
Turn: "turn"
},
WebSocket: {
Close: "close",
Error: "socketerror",

View File

@@ -39,10 +39,8 @@ class APIRequest {
this._playwright = playwright;
}
async newContext(options = {}) {
options = {
...this._playwright._defaultContextOptions,
...options
};
options = { ...options };
await this._playwright._instrumentation.runBeforeCreateRequestContext(options);
const storageState = typeof options.storageState === "string" ? JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, "utf8")) : options.storageState;
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
...options,
@@ -135,6 +133,7 @@ class APIRequestContext extends import_channelOwner.ChannelOwner {
(0, import_assert.assert)(options.maxRedirects === void 0 || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
(0, import_assert.assert)(options.maxRetries === void 0 || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
const url = options.url !== void 0 ? options.url : options.request.url();
this._checkUrlAllowed?.(url);
const method = options.method || options.request?.method();
let encodedParams = void 0;
if (typeof options.params === "string")

View File

@@ -101,6 +101,7 @@ class Frame extends import_channelOwner.ChannelOwner {
}
async goto(url, options = {}) {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
this.page().context()._checkUrlAllowed(url);
return network.Response.fromNullable((await this._channel.goto({ url, ...options, waitUntil, timeout: this._navigationTimeout(options) })).response);
}
_setupNavigationWaiter(options) {

View File

@@ -48,6 +48,7 @@ var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_manualPromise = require("../utils/isomorphic/manualPromise");
var import_rtti = require("../utils/isomorphic/rtti");
var import_consoleMessage = require("./consoleMessage");
var import_pageAgent = require("./pageAgent");
class Page extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
@@ -62,6 +63,7 @@ class Page extends import_channelOwner.ChannelOwner {
this._closeWasCalled = false;
this._harRouters = [];
this._locatorHandlers = /* @__PURE__ */ new Map();
this._instrumentation.onPage(this);
this._browserContext = parent;
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, this._browserContext._timeoutSettings);
this.keyboard = new import_input.Keyboard(this);
@@ -502,7 +504,8 @@ class Page extends import_channelOwner.ChannelOwner {
}
async close(options = {}) {
this._closeReason = options.reason;
this._closeWasCalled = true;
if (!options.runBeforeUnload)
this._closeWasCalled = true;
try {
if (this._ownedContext)
await this._ownedContext.close();
@@ -673,6 +676,30 @@ class Page extends import_channelOwner.ChannelOwner {
}
return result.pdf;
}
// @ts-expect-error agents are hidden
async agent(options = {}) {
const params = {
api: options.provider?.api,
apiEndpoint: options.provider?.apiEndpoint,
apiKey: options.provider?.apiKey,
apiTimeout: options.provider?.apiTimeout,
apiCacheFile: options.provider?._apiCacheFile,
doNotRenderActive: options._doNotRenderActive,
model: options.provider?.model,
cacheFile: options.cache?.cacheFile,
cacheOutFile: options.cache?.cacheOutFile,
maxTokens: options.limits?.maxTokens,
maxActions: options.limits?.maxActions,
maxActionRetries: options.limits?.maxActionRetries,
// @ts-expect-error runAgents is hidden
secrets: options.secrets ? Object.entries(options.secrets).map(([name, value]) => ({ name, value })) : void 0,
systemPrompt: options.systemPrompt
};
const { agent } = await this._channel.agent(params);
const pageAgent = import_pageAgent.PageAgent.from(agent);
pageAgent._expectTimeout = options?.expect?.timeout;
return pageAgent;
}
async _snapshotForAI(options = {}) {
return await this._channel.snapshotForAI({ timeout: this._timeoutSettings.timeout(options), track: options.track });
}

64
node_modules/playwright-core/lib/client/pageAgent.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var pageAgent_exports = {};
__export(pageAgent_exports, {
PageAgent: () => PageAgent
});
module.exports = __toCommonJS(pageAgent_exports);
var import_channelOwner = require("./channelOwner");
var import_events = require("./events");
var import_page = require("./page");
class PageAgent extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._page = import_page.Page.from(initializer.page);
this._channel.on("turn", (params) => this.emit(import_events.Events.PageAgent.Turn, params));
}
async expect(expectation, options = {}) {
const timeout = options.timeout ?? this._expectTimeout ?? 5e3;
await this._channel.expect({ expectation, ...options, timeout });
}
async perform(task, options = {}) {
const timeout = this._page._timeoutSettings.timeout(options);
const { usage } = await this._channel.perform({ task, ...options, timeout });
return { usage };
}
async extract(query, schema, options = {}) {
const timeout = this._page._timeoutSettings.timeout(options);
const { result, usage } = await this._channel.extract({ query, schema: this._page._platform.zodToJsonSchema(schema), ...options, timeout });
return { result, usage };
}
async usage() {
const { usage } = await this._channel.usage({});
return usage;
}
async dispose() {
await this._channel.dispose();
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PageAgent
});

View File

@@ -66,6 +66,9 @@ const emptyPlatform = {
streamWritable: (channel) => {
throw new Error("Streams are not available");
},
zodToJsonSchema: (schema) => {
throw new Error("Zod is not available");
},
zones: { empty: noopZone, current: () => noopZone }
};
// Annotate the CommonJS export names for ESM import in node:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

84
node_modules/playwright-core/lib/mcpBundle.js generated vendored Executable file
View File

@@ -0,0 +1,84 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var mcpBundle_exports = {};
__export(mcpBundle_exports, {
CallToolRequestSchema: () => CallToolRequestSchema,
Client: () => Client,
ListRootsRequestSchema: () => ListRootsRequestSchema,
ListToolsRequestSchema: () => ListToolsRequestSchema,
Loop: () => Loop,
PingRequestSchema: () => PingRequestSchema,
ProgressNotificationSchema: () => ProgressNotificationSchema,
SSEClientTransport: () => SSEClientTransport,
SSEServerTransport: () => SSEServerTransport,
Server: () => Server,
StdioClientTransport: () => StdioClientTransport,
StdioServerTransport: () => StdioServerTransport,
StreamableHTTPClientTransport: () => StreamableHTTPClientTransport,
StreamableHTTPServerTransport: () => StreamableHTTPServerTransport,
z: () => z,
zodToJsonSchema: () => zodToJsonSchema
});
module.exports = __toCommonJS(mcpBundle_exports);
var bundle = __toESM(require("./mcpBundleImpl"));
const zodToJsonSchema = bundle.zodToJsonSchema;
const Client = bundle.Client;
const Server = bundle.Server;
const SSEClientTransport = bundle.SSEClientTransport;
const SSEServerTransport = bundle.SSEServerTransport;
const StdioClientTransport = bundle.StdioClientTransport;
const StdioServerTransport = bundle.StdioServerTransport;
const StreamableHTTPServerTransport = bundle.StreamableHTTPServerTransport;
const StreamableHTTPClientTransport = bundle.StreamableHTTPClientTransport;
const CallToolRequestSchema = bundle.CallToolRequestSchema;
const ListRootsRequestSchema = bundle.ListRootsRequestSchema;
const ProgressNotificationSchema = bundle.ProgressNotificationSchema;
const ListToolsRequestSchema = bundle.ListToolsRequestSchema;
const PingRequestSchema = bundle.PingRequestSchema;
const Loop = bundle.Loop;
const z = bundle.z;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CallToolRequestSchema,
Client,
ListRootsRequestSchema,
ListToolsRequestSchema,
Loop,
PingRequestSchema,
ProgressNotificationSchema,
SSEClientTransport,
SSEServerTransport,
Server,
StdioClientTransport,
StdioServerTransport,
StreamableHTTPClientTransport,
StreamableHTTPServerTransport,
z,
zodToJsonSchema
});

147
node_modules/playwright-core/lib/mcpBundleImpl/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
var serializers_exports = {};
__export(serializers_exports, {
parseSerializedValue: () => parseSerializedValue,
serializePlainValue: () => serializePlainValue,
serializeValue: () => serializeValue
});
module.exports = __toCommonJS(serializers_exports);
@@ -90,6 +91,9 @@ function innerParseSerializedValue(value, handles, refs, accessChain) {
function serializeValue(value, handleSerializer) {
return innerSerializeValue(value, handleSerializer, { lastId: 0, visited: /* @__PURE__ */ new Map() }, []);
}
function serializePlainValue(arg) {
return serializeValue(arg, (value) => ({ fallThrough: value }));
}
function innerSerializeValue(value, handleSerializer, visitorInfo, accessChain) {
const handle = handleSerializer(value);
if ("fallThrough" in handle)
@@ -188,5 +192,6 @@ const constructorToTypedArrayKind = new Map(Object.entries(typedArrayKindToConst
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
parseSerializedValue,
serializePlainValue,
serializeValue
});

View File

@@ -512,7 +512,6 @@ import_validatorPrimitives.scheme.BrowserTypeLaunchParams = (0, import_validator
timeout: import_validatorPrimitives.tFloat,
env: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
headless: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tBoolean),
devtools: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tBoolean),
proxy: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({
server: import_validatorPrimitives.tString,
bypass: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
@@ -542,7 +541,6 @@ import_validatorPrimitives.scheme.BrowserTypeLaunchPersistentContextParams = (0,
timeout: import_validatorPrimitives.tFloat,
env: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
headless: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tBoolean),
devtools: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tBoolean),
proxy: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({
server: import_validatorPrimitives.tString,
bypass: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
@@ -621,7 +619,8 @@ import_validatorPrimitives.scheme.BrowserTypeConnectOverCDPParams = (0, import_v
endpointURL: import_validatorPrimitives.tString,
headers: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
slowMo: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tFloat),
timeout: import_validatorPrimitives.tFloat
timeout: import_validatorPrimitives.tFloat,
isLocal: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tBoolean)
});
import_validatorPrimitives.scheme.BrowserTypeConnectOverCDPResult = (0, import_validatorPrimitives.tObject)({
browser: (0, import_validatorPrimitives.tChannel)(["Browser"]),
@@ -821,6 +820,7 @@ import_validatorPrimitives.scheme.WorkerWaitForEventInfoParams = (0, import_vali
import_validatorPrimitives.scheme.WebSocketWaitForEventInfoParams = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoParams");
import_validatorPrimitives.scheme.ElectronApplicationWaitForEventInfoParams = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoParams");
import_validatorPrimitives.scheme.AndroidDeviceWaitForEventInfoParams = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoParams");
import_validatorPrimitives.scheme.PageAgentWaitForEventInfoParams = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoParams");
import_validatorPrimitives.scheme.EventTargetWaitForEventInfoResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.BrowserContextWaitForEventInfoResult = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoResult");
import_validatorPrimitives.scheme.PageWaitForEventInfoResult = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoResult");
@@ -828,6 +828,7 @@ import_validatorPrimitives.scheme.WorkerWaitForEventInfoResult = (0, import_vali
import_validatorPrimitives.scheme.WebSocketWaitForEventInfoResult = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoResult");
import_validatorPrimitives.scheme.ElectronApplicationWaitForEventInfoResult = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoResult");
import_validatorPrimitives.scheme.AndroidDeviceWaitForEventInfoResult = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoResult");
import_validatorPrimitives.scheme.PageAgentWaitForEventInfoResult = (0, import_validatorPrimitives.tType)("EventTargetWaitForEventInfoResult");
import_validatorPrimitives.scheme.BrowserContextInitializer = (0, import_validatorPrimitives.tObject)({
isChromium: import_validatorPrimitives.tBoolean,
requestContext: (0, import_validatorPrimitives.tChannel)(["APIRequestContext"]),
@@ -1075,6 +1076,8 @@ import_validatorPrimitives.scheme.BrowserContextEnableRecorderParams = (0, impor
import_validatorPrimitives.scheme.BrowserContextEnableRecorderResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.BrowserContextDisableRecorderParams = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.BrowserContextDisableRecorderResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.BrowserContextExposeConsoleApiParams = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.BrowserContextExposeConsoleApiResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.BrowserContextNewCDPSessionParams = (0, import_validatorPrimitives.tObject)({
page: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tChannel)(["Page"])),
frame: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tChannel)(["Frame"]))
@@ -1490,6 +1493,25 @@ import_validatorPrimitives.scheme.PageUpdateSubscriptionParams = (0, import_vali
enabled: import_validatorPrimitives.tBoolean
});
import_validatorPrimitives.scheme.PageUpdateSubscriptionResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.PageAgentParams = (0, import_validatorPrimitives.tObject)({
api: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
apiKey: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
apiEndpoint: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
apiTimeout: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
apiCacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
cacheOutFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
doNotRenderActive: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tBoolean),
maxActions: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxActionRetries: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
model: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
systemPrompt: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString)
});
import_validatorPrimitives.scheme.PageAgentResult = (0, import_validatorPrimitives.tObject)({
agent: (0, import_validatorPrimitives.tChannel)(["PageAgent"])
});
import_validatorPrimitives.scheme.FrameInitializer = (0, import_validatorPrimitives.tObject)({
url: import_validatorPrimitives.tString,
name: import_validatorPrimitives.tString,
@@ -2881,6 +2903,63 @@ import_validatorPrimitives.scheme.JsonPipeSendParams = (0, import_validatorPrimi
import_validatorPrimitives.scheme.JsonPipeSendResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.JsonPipeCloseParams = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.JsonPipeCloseResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.PageAgentInitializer = (0, import_validatorPrimitives.tObject)({
page: (0, import_validatorPrimitives.tChannel)(["Page"])
});
import_validatorPrimitives.scheme.PageAgentTurnEvent = (0, import_validatorPrimitives.tObject)({
role: import_validatorPrimitives.tString,
message: import_validatorPrimitives.tString,
usage: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({
inputTokens: import_validatorPrimitives.tInt,
outputTokens: import_validatorPrimitives.tInt
}))
});
import_validatorPrimitives.scheme.PageAgentPerformParams = (0, import_validatorPrimitives.tObject)({
task: import_validatorPrimitives.tString,
maxActions: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxActionRetries: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
cacheKey: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
timeout: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
});
import_validatorPrimitives.scheme.PageAgentPerformResult = (0, import_validatorPrimitives.tObject)({
usage: (0, import_validatorPrimitives.tType)("AgentUsage")
});
import_validatorPrimitives.scheme.PageAgentExpectParams = (0, import_validatorPrimitives.tObject)({
expectation: import_validatorPrimitives.tString,
maxActions: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxActionRetries: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
cacheKey: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
timeout: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
});
import_validatorPrimitives.scheme.PageAgentExpectResult = (0, import_validatorPrimitives.tObject)({
usage: (0, import_validatorPrimitives.tType)("AgentUsage")
});
import_validatorPrimitives.scheme.PageAgentExtractParams = (0, import_validatorPrimitives.tObject)({
query: import_validatorPrimitives.tString,
schema: import_validatorPrimitives.tAny,
maxActions: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxActionRetries: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
cacheKey: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
timeout: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
});
import_validatorPrimitives.scheme.PageAgentExtractResult = (0, import_validatorPrimitives.tObject)({
result: import_validatorPrimitives.tAny,
usage: (0, import_validatorPrimitives.tType)("AgentUsage")
});
import_validatorPrimitives.scheme.PageAgentDisposeParams = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.PageAgentDisposeResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.PageAgentUsageParams = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
import_validatorPrimitives.scheme.PageAgentUsageResult = (0, import_validatorPrimitives.tObject)({
usage: (0, import_validatorPrimitives.tType)("AgentUsage")
});
import_validatorPrimitives.scheme.AgentUsage = (0, import_validatorPrimitives.tObject)({
turns: import_validatorPrimitives.tInt,
inputTokens: import_validatorPrimitives.tInt,
outputTokens: import_validatorPrimitives.tInt
});
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ValidationError,

View File

@@ -321,8 +321,7 @@ const defaultLaunchOptions = {
handleSIGINT: false,
handleSIGTERM: false,
handleSIGHUP: false,
headless: true,
devtools: false
headless: true
};
const optionsThatAllowBrowserReuse = [
"headless",

View File

@@ -0,0 +1,335 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var actionRunner_exports = {};
__export(actionRunner_exports, {
runAction: () => runAction,
traceParamsForAction: () => traceParamsForAction
});
module.exports = __toCommonJS(actionRunner_exports);
var import_expectUtils = require("../utils/expectUtils");
var import_urlMatch = require("../../utils/isomorphic/urlMatch");
var import_stringUtils = require("../../utils/isomorphic/stringUtils");
var import_time = require("../../utils/isomorphic/time");
var import_crypto = require("../utils/crypto");
var import_ariaSnapshot = require("../../utils/isomorphic/ariaSnapshot");
var import_locatorGenerators = require("../../utils/isomorphic/locatorGenerators");
var import_utilsBundle = require("../../utilsBundle");
var import_errors = require("../errors");
async function runAction(progress, mode, page, action, secrets) {
const parentMetadata = progress.metadata;
const frame = page.mainFrame();
const callMetadata = callMetadataForAction(progress, frame, action, mode);
callMetadata.log = parentMetadata.log;
progress.metadata = callMetadata;
await frame.instrumentation.onBeforeCall(frame, callMetadata, parentMetadata.id);
let error;
const result = await innerRunAction(progress, mode, page, action, secrets).catch((e) => error = e);
callMetadata.endTime = (0, import_time.monotonicTime)();
callMetadata.error = error ? (0, import_errors.serializeError)(error) : void 0;
callMetadata.result = error ? void 0 : result;
await frame.instrumentation.onAfterCall(frame, callMetadata);
if (error)
throw error;
return result;
}
async function innerRunAction(progress, mode, page, action, secrets) {
const frame = page.mainFrame();
const commonOptions = { strict: true, noAutoWaiting: mode === "generate" };
switch (action.method) {
case "navigate":
await frame.goto(progress, action.url);
break;
case "click":
await frame.click(progress, action.selector, {
button: action.button,
clickCount: action.clickCount,
modifiers: action.modifiers,
...commonOptions
});
break;
case "drag":
await frame.dragAndDrop(progress, action.sourceSelector, action.targetSelector, { ...commonOptions });
break;
case "hover":
await frame.hover(progress, action.selector, {
modifiers: action.modifiers,
...commonOptions
});
break;
case "selectOption":
await frame.selectOption(progress, action.selector, [], action.labels.map((a) => ({ label: a })), { ...commonOptions });
break;
case "pressKey":
await page.keyboard.press(progress, action.key);
break;
case "pressSequentially": {
const secret = secrets?.find((s) => s.name === action.text)?.value ?? action.text;
await frame.type(progress, action.selector, secret, { ...commonOptions });
if (action.submit)
await page.keyboard.press(progress, "Enter");
break;
}
case "fill": {
const secret = secrets?.find((s) => s.name === action.text)?.value ?? action.text;
await frame.fill(progress, action.selector, secret, { ...commonOptions });
if (action.submit)
await page.keyboard.press(progress, "Enter");
break;
}
case "setChecked":
if (action.checked)
await frame.check(progress, action.selector, { ...commonOptions });
else
await frame.uncheck(progress, action.selector, { ...commonOptions });
break;
case "expectVisible": {
await runExpect(frame, progress, mode, action.selector, { expression: "to.be.visible", isNot: !!action.isNot }, "visible", "toBeVisible", "");
break;
}
case "expectValue": {
if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") {
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]);
await runExpect(frame, progress, mode, action.selector, { expression: "to.have.value", expectedText, isNot: !!action.isNot }, action.value, "toHaveValue", "expected");
} else if (action.type === "checkbox" || action.type === "radio") {
const expectedValue = { checked: action.value === "true" };
await runExpect(frame, progress, mode, action.selector, { selector: action.selector, expression: "to.be.checked", expectedValue, isNot: !!action.isNot }, action.value ? "checked" : "unchecked", "toBeChecked", "");
} else {
throw new Error(`Unsupported element type: ${action.type}`);
}
break;
}
case "expectAria": {
const expectedValue = (0, import_ariaSnapshot.parseAriaSnapshotUnsafe)(import_utilsBundle.yaml, action.template);
await runExpect(frame, progress, mode, "body", { expression: "to.match.aria", expectedValue, isNot: !!action.isNot }, "\n" + action.template, "toMatchAriaSnapshot", "expected");
break;
}
case "expectURL": {
if (!action.regex && !action.value)
throw new Error("Either url or regex must be provided");
if (action.regex && action.value)
throw new Error("Only one of url or regex can be provided");
const expected = action.regex ? (0, import_stringUtils.parseRegex)(action.regex) : (0, import_urlMatch.constructURLBasedOnBaseURL)(page.browserContext._options.baseURL, action.value);
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([expected]);
await runExpect(frame, progress, mode, void 0, { expression: "to.have.url", expectedText, isNot: !!action.isNot }, expected, "toHaveURL", "expected");
break;
}
case "expectTitle": {
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value], { normalizeWhiteSpace: true });
await runExpect(frame, progress, mode, void 0, { expression: "to.have.title", expectedText, isNot: !!action.isNot }, action.value, "toHaveTitle", "expected");
break;
}
}
}
async function runExpect(frame, progress, mode, selector, options, expected, matcherName, expectation) {
const result = await frame.expect(progress, selector, {
...options,
// When generating, we want the expect to pass or fail immediately and give feedback to the model.
noAutoWaiting: mode === "generate",
timeoutForLogs: mode === "generate" ? void 0 : progress.timeout
});
if (!result.matches === !options.isNot) {
const received = matcherName === "toMatchAriaSnapshot" ? "\n" + result.received.raw : result.received;
const expectedSuffix = typeof expected === "string" ? "" : " pattern";
const expectedDisplay = typeof expected === "string" ? expected : expected.toString();
throw new Error((0, import_expectUtils.formatMatcherMessage)(import_expectUtils.simpleMatcherUtils, {
isNot: options.isNot,
matcherName,
expectation,
locator: selector ? (0, import_locatorGenerators.asLocatorDescription)("javascript", selector) : void 0,
timedOut: result.timedOut,
timeout: mode === "generate" ? void 0 : progress.timeout,
printedExpected: options.isNot ? `Expected${expectedSuffix}: not ${expectedDisplay}` : `Expected${expectedSuffix}: ${expectedDisplay}`,
printedReceived: result.errorMessage ? "" : `Received: ${received}`,
errorMessage: result.errorMessage
// Note: we are not passing call log, because it will be automatically appended on the client side,
// as a part of the agent.{perform,expect} call.
}));
}
}
function traceParamsForAction(progress, action, mode) {
const timeout = progress.timeout;
switch (action.method) {
case "navigate": {
const params = {
url: action.url,
timeout
};
return { type: "Frame", method: "goto", params };
}
case "click": {
const params = {
selector: action.selector,
strict: true,
modifiers: action.modifiers,
button: action.button,
clickCount: action.clickCount,
timeout
};
return { type: "Frame", method: "click", params };
}
case "drag": {
const params = {
source: action.sourceSelector,
target: action.targetSelector,
timeout
};
return { type: "Frame", method: "dragAndDrop", params };
}
case "hover": {
const params = {
selector: action.selector,
modifiers: action.modifiers,
timeout
};
return { type: "Frame", method: "hover", params };
}
case "pressKey": {
const params = {
key: action.key
};
return { type: "Page", method: "keyboardPress", params };
}
case "pressSequentially": {
const params = {
selector: action.selector,
text: action.text,
timeout
};
return { type: "Frame", method: "type", params };
}
case "fill": {
const params = {
selector: action.selector,
strict: true,
value: action.text,
timeout
};
return { type: "Frame", method: "fill", params };
}
case "setChecked": {
if (action.checked) {
const params = {
selector: action.selector,
strict: true,
timeout
};
return { type: "Frame", method: "check", params };
} else {
const params = {
selector: action.selector,
strict: true,
timeout
};
return { type: "Frame", method: "uncheck", params };
}
}
case "selectOption": {
const params = {
selector: action.selector,
strict: true,
options: action.labels.map((label) => ({ label })),
timeout
};
return { type: "Frame", method: "selectOption", params };
}
case "expectValue": {
if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") {
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]);
const params = {
selector: action.selector,
expression: "to.have.value",
expectedText,
isNot: !!action.isNot,
timeout
};
return { type: "Frame", method: "expect", title: "Expect Value", params };
} else if (action.type === "checkbox" || action.type === "radio") {
const params = {
selector: action.selector,
expression: "to.be.checked",
isNot: !!action.isNot,
timeout
};
return { type: "Frame", method: "expect", title: "Expect Checked", params };
} else {
throw new Error(`Unsupported element type: ${action.type}`);
}
}
case "expectVisible": {
const params = {
selector: action.selector,
expression: "to.be.visible",
isNot: !!action.isNot,
timeout
};
return { type: "Frame", method: "expect", title: "Expect Visible", params };
}
case "expectAria": {
const params = {
selector: "body",
expression: "to.match.snapshot",
expectedText: [],
isNot: !!action.isNot,
timeout
};
return { type: "Frame", method: "expect", title: "Expect Aria Snapshot", params };
}
case "expectURL": {
const expected = action.regex ? (0, import_stringUtils.parseRegex)(action.regex) : action.value;
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([expected]);
const params = {
selector: void 0,
expression: "to.have.url",
expectedText,
isNot: !!action.isNot,
timeout
};
return { type: "Frame", method: "expect", title: "Expect URL", params };
}
case "expectTitle": {
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value], { normalizeWhiteSpace: true });
const params = {
selector: void 0,
expression: "to.have.title",
expectedText,
isNot: !!action.isNot,
timeout
};
return { type: "Frame", method: "expect", title: "Expect Title", params };
}
}
}
function callMetadataForAction(progress, frame, action, mode) {
const callMetadata = {
id: `call@${(0, import_crypto.createGuid)()}`,
objectId: frame.guid,
pageId: frame._page.guid,
frameId: frame.guid,
startTime: (0, import_time.monotonicTime)(),
endTime: 0,
log: [],
...traceParamsForAction(progress, action, mode)
};
return callMetadata;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
runAction,
traceParamsForAction
});

View File

@@ -0,0 +1,128 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var actions_exports = {};
__export(actions_exports, {
cachedActionsSchema: () => cachedActionsSchema
});
module.exports = __toCommonJS(actions_exports);
var import_mcpBundle = require("../../mcpBundle");
const modifiersSchema = import_mcpBundle.z.array(
import_mcpBundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])
);
const navigateActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("navigate"),
url: import_mcpBundle.z.string()
});
const clickActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("click"),
selector: import_mcpBundle.z.string(),
button: import_mcpBundle.z.enum(["left", "right", "middle"]).optional(),
clickCount: import_mcpBundle.z.number().optional(),
modifiers: modifiersSchema.optional()
});
const dragActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("drag"),
sourceSelector: import_mcpBundle.z.string(),
targetSelector: import_mcpBundle.z.string()
});
const hoverActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("hover"),
selector: import_mcpBundle.z.string(),
modifiers: modifiersSchema.optional()
});
const selectOptionActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("selectOption"),
selector: import_mcpBundle.z.string(),
labels: import_mcpBundle.z.array(import_mcpBundle.z.string())
});
const pressActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("pressKey"),
key: import_mcpBundle.z.string()
});
const pressSequentiallyActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("pressSequentially"),
selector: import_mcpBundle.z.string(),
text: import_mcpBundle.z.string(),
submit: import_mcpBundle.z.boolean().optional()
});
const fillActionSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("fill"),
selector: import_mcpBundle.z.string(),
text: import_mcpBundle.z.string(),
submit: import_mcpBundle.z.boolean().optional()
});
const setCheckedSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("setChecked"),
selector: import_mcpBundle.z.string(),
checked: import_mcpBundle.z.boolean()
});
const expectVisibleSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("expectVisible"),
selector: import_mcpBundle.z.string(),
isNot: import_mcpBundle.z.boolean().optional()
});
const expectValueSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("expectValue"),
selector: import_mcpBundle.z.string(),
type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]),
value: import_mcpBundle.z.string(),
isNot: import_mcpBundle.z.boolean().optional()
});
const expectAriaSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("expectAria"),
template: import_mcpBundle.z.string(),
isNot: import_mcpBundle.z.boolean().optional()
});
const expectURLSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("expectURL"),
value: import_mcpBundle.z.string().optional(),
regex: import_mcpBundle.z.string().optional(),
isNot: import_mcpBundle.z.boolean().optional()
});
const expectTitleSchema = import_mcpBundle.z.object({
method: import_mcpBundle.z.literal("expectTitle"),
value: import_mcpBundle.z.string(),
isNot: import_mcpBundle.z.boolean().optional()
});
const actionSchema = import_mcpBundle.z.discriminatedUnion("method", [
navigateActionSchema,
clickActionSchema,
dragActionSchema,
hoverActionSchema,
selectOptionActionSchema,
pressActionSchema,
pressSequentiallyActionSchema,
fillActionSchema,
setCheckedSchema,
expectVisibleSchema,
expectValueSchema,
expectAriaSchema,
expectURLSchema,
expectTitleSchema
]);
const actionWithCodeSchema = actionSchema.and(import_mcpBundle.z.object({
code: import_mcpBundle.z.string()
}));
const cachedActionsSchema = import_mcpBundle.z.record(import_mcpBundle.z.string(), import_mcpBundle.z.object({
actions: import_mcpBundle.z.array(actionWithCodeSchema)
}));
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
cachedActionsSchema
});

View File

@@ -0,0 +1,111 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var codegen_exports = {};
__export(codegen_exports, {
generateCode: () => generateCode
});
module.exports = __toCommonJS(codegen_exports);
var import_locatorGenerators = require("../../utils/isomorphic/locatorGenerators");
var import_stringUtils = require("../../utils/isomorphic/stringUtils");
async function generateCode(sdkLanguage, action) {
switch (action.method) {
case "navigate": {
return `await page.goto(${(0, import_stringUtils.escapeWithQuotes)(action.url)});`;
}
case "click": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
return `await page.${locator}.click(${(0, import_stringUtils.formatObjectOrVoid)({
button: action.button,
clickCount: action.clickCount,
modifiers: action.modifiers
})});`;
}
case "drag": {
const sourceLocator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.sourceSelector);
const targetLocator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.targetSelector);
return `await page.${sourceLocator}.dragAndDrop(${targetLocator});`;
}
case "hover": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
return `await page.${locator}.hover(${(0, import_stringUtils.formatObjectOrVoid)({
modifiers: action.modifiers
})});`;
}
case "pressKey": {
return `await page.keyboard.press(${(0, import_stringUtils.escapeWithQuotes)(action.key, "'")});`;
}
case "selectOption": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
return `await page.${locator}.selectOption(${action.labels.length === 1 ? (0, import_stringUtils.escapeWithQuotes)(action.labels[0]) : "[" + action.labels.map((label) => (0, import_stringUtils.escapeWithQuotes)(label)).join(", ") + "]"});`;
}
case "pressSequentially": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
const code = [`await page.${locator}.pressSequentially(${(0, import_stringUtils.escapeWithQuotes)(action.text)});`];
if (action.submit)
code.push(`await page.keyboard.press('Enter');`);
return code.join("\n");
}
case "fill": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
const code = [`await page.${locator}.fill(${(0, import_stringUtils.escapeWithQuotes)(action.text)});`];
if (action.submit)
code.push(`await page.keyboard.press('Enter');`);
return code.join("\n");
}
case "setChecked": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
if (action.checked)
return `await page.${locator}.check();`;
else
return `await page.${locator}.uncheck();`;
}
case "expectVisible": {
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
const notInfix = action.isNot ? "not." : "";
return `await expect(page.${locator}).${notInfix}toBeVisible();`;
}
case "expectValue": {
const notInfix = action.isNot ? "not." : "";
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
if (action.type === "checkbox" || action.type === "radio")
return `await expect(page.${locator}).${notInfix}toBeChecked({ checked: ${action.value === "true"} });`;
return `await expect(page.${locator}).${notInfix}toHaveValue(${(0, import_stringUtils.escapeWithQuotes)(action.value)});`;
}
case "expectAria": {
const notInfix = action.isNot ? "not." : "";
return `await expect(page.locator('body')).${notInfix}toMatchAria(\`
${(0, import_stringUtils.escapeTemplateString)(action.template)}
\`);`;
}
case "expectURL": {
const arg = action.regex ? (0, import_stringUtils.parseRegex)(action.regex).toString() : (0, import_stringUtils.escapeWithQuotes)(action.value);
const notInfix = action.isNot ? "not." : "";
return `await expect(page).${notInfix}toHaveURL(${arg});`;
}
case "expectTitle": {
const notInfix = action.isNot ? "not." : "";
return `await expect(page).${notInfix}toHaveTitle(${(0, import_stringUtils.escapeWithQuotes)(action.value)});`;
}
}
throw new Error("Unknown action " + action.method);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
generateCode
});

View File

@@ -0,0 +1,150 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var context_exports = {};
__export(context_exports, {
Context: () => Context
});
module.exports = __toCommonJS(context_exports);
var import_browserContext = require("../browserContext");
var import_actionRunner = require("./actionRunner");
var import_codegen = require("./codegen");
var import_stringUtils = require("../../utils/isomorphic/stringUtils");
class Context {
constructor(page, agentParams, events) {
this._actions = [];
this._history = [];
this.page = page;
this.agentParams = agentParams;
this.sdkLanguage = page.browserContext._browser.sdkLanguage();
this.events = events;
this._budget = { tokens: agentParams.maxTokens };
}
async runActionAndWait(progress, action) {
return await this.runActionsAndWait(progress, [action]);
}
async runActionsAndWait(progress, action, options) {
const error = await this.waitForCompletion(progress, async () => {
for (const a of action) {
await (0, import_actionRunner.runAction)(progress, "generate", this.page, a, this.agentParams?.secrets ?? []);
const code = await (0, import_codegen.generateCode)(this.sdkLanguage, a);
this._actions.push({ ...a, code });
}
return void 0;
}, options).catch((error2) => error2);
return await this.snapshotResult(progress, error);
}
async runActionNoWait(progress, action) {
return await this.runActionsAndWait(progress, [action], { noWait: true });
}
actions() {
return this._actions.slice();
}
history() {
return this._history;
}
pushHistory(item) {
this._history.push(item);
this._actions = [];
}
consumeTokens(tokens) {
if (this._budget.tokens === void 0)
return;
this._budget.tokens = Math.max(0, this._budget.tokens - tokens);
}
maxTokensRemaining() {
return this._budget.tokens;
}
async waitForCompletion(progress, callback, options) {
if (options?.noWait)
return await callback();
const requests = [];
const requestListener = (request) => requests.push(request);
const disposeListeners = () => {
this.page.browserContext.off(import_browserContext.BrowserContext.Events.Request, requestListener);
};
this.page.browserContext.on(import_browserContext.BrowserContext.Events.Request, requestListener);
let result;
try {
result = await callback();
await progress.wait(500);
} finally {
disposeListeners();
}
const requestedNavigation = requests.some((request) => request.isNavigationRequest());
if (requestedNavigation) {
await this.page.mainFrame().waitForLoadState(progress, "load");
return result;
}
const promises = [];
for (const request of requests) {
if (["document", "stylesheet", "script", "xhr", "fetch"].includes(request.resourceType()))
promises.push(request.response().then((r) => r?.finished()));
else
promises.push(request.response());
}
await progress.race([...promises, progress.wait(5e3)]);
if (!promises.length)
await progress.wait(500);
return result;
}
async takeSnapshot(progress) {
const { full } = await this.page.snapshotForAI(progress, { doNotRenderActive: this.agentParams.doNotRenderActive });
return full;
}
async snapshotResult(progress, error) {
const snapshot = this._redactText(await this.takeSnapshot(progress));
const text = [];
if (error)
text.push(`# Error
${(0, import_stringUtils.stripAnsiEscapes)(error.message)}`);
else
text.push(`# Success`);
text.push(`# Page snapshot
${snapshot}`);
return {
isError: !!error,
content: [{ type: "text", text: text.join("\n\n") }]
};
}
async refSelectors(progress, params) {
return Promise.all(params.map(async (param) => {
try {
const { resolvedSelector } = await this.page.mainFrame().resolveSelector(progress, `aria-ref=${param.ref}`);
return resolvedSelector;
} catch (e) {
throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);
}
}));
}
_redactText(text) {
const secrets = this.agentParams?.secrets;
if (!secrets)
return text;
const redactText = (text2) => {
for (const { name, value } of secrets)
text2 = text2.replaceAll(value, `<secret>${name}</secret>`);
return text2;
};
return redactText(text);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Context
});

View File

@@ -0,0 +1,156 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var expectTools_exports = {};
__export(expectTools_exports, {
default: () => expectTools_default
});
module.exports = __toCommonJS(expectTools_exports);
var import_mcpBundle = require("../../mcpBundle");
var import_locatorUtils = require("../../utils/isomorphic/locatorUtils");
var import_yaml = require("../../utils/isomorphic/yaml");
var import_tool = require("./tool");
const expectVisible = (0, import_tool.defineTool)({
schema: {
name: "browser_expect_visible",
title: "Expect element visible",
description: "Expect element is visible on the page",
inputSchema: import_mcpBundle.z.object({
role: import_mcpBundle.z.string().describe('ROLE of the element. Can be found in the snapshot like this: `- {ROLE} "Accessible Name":`'),
accessibleName: import_mcpBundle.z.string().describe('ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: `- role "{ACCESSIBLE_NAME}"`'),
isNot: import_mcpBundle.z.boolean().optional().describe("Expect the opposite")
})
},
handle: async (progress, context, params) => {
return await context.runActionAndWait(progress, {
method: "expectVisible",
selector: (0, import_locatorUtils.getByRoleSelector)(params.role, { name: params.accessibleName }),
isNot: params.isNot
});
}
});
const expectVisibleText = (0, import_tool.defineTool)({
schema: {
name: "browser_expect_visible_text",
title: "Expect text visible",
description: `Expect text is visible on the page. Prefer ${expectVisible.schema.name} if possible.`,
inputSchema: import_mcpBundle.z.object({
text: import_mcpBundle.z.string().describe('TEXT to expect. Can be found in the snapshot like this: `- role "Accessible Name": {TEXT}` or like this: `- text: {TEXT}`'),
isNot: import_mcpBundle.z.boolean().optional().describe("Expect the opposite")
})
},
handle: async (progress, context, params) => {
return await context.runActionAndWait(progress, {
method: "expectVisible",
selector: (0, import_locatorUtils.getByTextSelector)(params.text),
isNot: params.isNot
});
}
});
const expectValue = (0, import_tool.defineTool)({
schema: {
name: "browser_expect_value",
title: "Expect value",
description: "Expect element value",
inputSchema: import_mcpBundle.z.object({
type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the element"),
element: import_mcpBundle.z.string().describe("Human-readable element description"),
ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot"),
value: import_mcpBundle.z.string().describe('Value to expect. For checkbox, use "true" or "false".'),
isNot: import_mcpBundle.z.boolean().optional().describe("Expect the opposite")
})
},
handle: async (progress, context, params) => {
const [selector] = await context.refSelectors(progress, [{ ref: params.ref, element: params.element }]);
return await context.runActionAndWait(progress, {
method: "expectValue",
selector,
type: params.type,
value: params.value,
isNot: params.isNot
});
}
});
const expectList = (0, import_tool.defineTool)({
schema: {
name: "browser_expect_list_visible",
title: "Expect list visible",
description: "Expect list is visible on the page, ensures items are present in the element in the exact order",
inputSchema: import_mcpBundle.z.object({
listRole: import_mcpBundle.z.string().describe("Aria role of the list element as in the snapshot"),
listAccessibleName: import_mcpBundle.z.string().optional().describe("Accessible name of the list element as in the snapshot"),
itemRole: import_mcpBundle.z.string().describe("Aria role of the list items as in the snapshot, should all be the same"),
items: import_mcpBundle.z.array(import_mcpBundle.z.string().describe("Text to look for in the list item, can be either from accessible name of self / nested text content")),
isNot: import_mcpBundle.z.boolean().optional().describe("Expect the opposite")
})
},
handle: async (progress, context, params) => {
const template = `- ${params.listRole}:
${params.items.map((item) => ` - ${params.itemRole}: ${(0, import_yaml.yamlEscapeValueIfNeeded)(item)}`).join("\n")}`;
return await context.runActionAndWait(progress, {
method: "expectAria",
template
});
}
});
const expectURL = (0, import_tool.defineTool)({
schema: {
name: "browser_expect_url",
title: "Expect URL",
description: "Expect the page URL to match the expected value. Either provide a url string or a regex pattern.",
inputSchema: import_mcpBundle.z.object({
url: import_mcpBundle.z.string().optional().describe("Expected URL string. Relative URLs are resolved against the baseURL."),
regex: import_mcpBundle.z.string().optional().describe("Regular expression pattern to match the URL against, e.g. /foo.*/i."),
isNot: import_mcpBundle.z.boolean().optional().describe("Expect the opposite")
})
},
handle: async (progress, context, params) => {
return await context.runActionAndWait(progress, {
method: "expectURL",
value: params.url,
regex: params.regex,
isNot: params.isNot
});
}
});
const expectTitle = (0, import_tool.defineTool)({
schema: {
name: "browser_expect_title",
title: "Expect title",
description: "Expect the page title to match the expected value.",
inputSchema: import_mcpBundle.z.object({
title: import_mcpBundle.z.string().describe("Expected page title."),
isNot: import_mcpBundle.z.boolean().optional().describe("Expect the opposite")
})
},
handle: async (progress, context, params) => {
return await context.runActionAndWait(progress, {
method: "expectTitle",
value: params.title,
isNot: params.isNot
});
}
});
var expectTools_default = [
expectVisible,
expectVisibleText,
expectValue,
expectList,
expectURL,
expectTitle
];

View File

@@ -0,0 +1,204 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var pageAgent_exports = {};
__export(pageAgent_exports, {
pageAgentExpect: () => pageAgentExpect,
pageAgentExtract: () => pageAgentExtract,
pageAgentPerform: () => pageAgentPerform
});
module.exports = __toCommonJS(pageAgent_exports);
var import_fs = __toESM(require("fs"));
var import_path = __toESM(require("path"));
var import_tool = require("./tool");
var import_utilsBundle = require("../../utilsBundle");
var import_mcpBundle = require("../../mcpBundle");
var import_actionRunner = require("./actionRunner");
var import_performTools = __toESM(require("./performTools"));
var import_expectTools = __toESM(require("./expectTools"));
var actions = __toESM(require("./actions"));
async function pageAgentPerform(progress, context, userTask, callParams) {
const cacheKey = (callParams.cacheKey ?? userTask).trim();
if (await cachedPerform(progress, context, cacheKey))
return;
const task = `
### Instructions
- Perform the following task on the page.
- Your reply should be a tool call that performs action the page".
### Task
${userTask}
`;
progress.disableTimeout();
await runLoop(progress, context, import_performTools.default, task, void 0, callParams);
await updateCache(context, cacheKey);
}
async function pageAgentExpect(progress, context, expectation, callParams) {
const cacheKey = (callParams.cacheKey ?? expectation).trim();
if (await cachedPerform(progress, context, cacheKey))
return;
const task = `
### Instructions
- Call one of the "browser_expect_*" tools to verify / assert the condition.
- You can call exactly one tool and it can't be report_results, must be one of the assertion tools.
### Expectation
${expectation}
`;
progress.disableTimeout();
await runLoop(progress, context, import_expectTools.default, task, void 0, callParams);
await updateCache(context, cacheKey);
}
async function pageAgentExtract(progress, context, query, schema, callParams) {
const task = `
### Instructions
Extract the following information from the page. Do not perform any actions, just extract the information.
### Query
${query}`;
const { result } = await runLoop(progress, context, [], task, schema, callParams);
return result;
}
async function runLoop(progress, context, toolDefinitions, userTask, resultSchema, params) {
if (!context.agentParams.api || !context.agentParams.model)
throw new Error(`This action requires the API and API key to be set on the page agent. Did you mean to --run-agents=missing?`);
if (!context.agentParams.apiKey)
throw new Error(`This action requires API key to be set on the page agent.`);
if (context.agentParams.apiEndpoint && !URL.canParse(context.agentParams.apiEndpoint))
throw new Error(`Agent API endpoint "${context.agentParams.apiEndpoint}" is not a valid URL.`);
const snapshot = await context.takeSnapshot(progress);
const { tools, callTool, reportedResult, refusedToPerformReason } = (0, import_tool.toolsForLoop)(progress, context, toolDefinitions, { resultSchema, refuseToPerform: "allow" });
const secrets = Object.fromEntries((context.agentParams.secrets || [])?.map((s) => [s.name, s.value]));
const apiCacheTextBefore = context.agentParams.apiCacheFile ? await import_fs.default.promises.readFile(context.agentParams.apiCacheFile, "utf-8").catch(() => "{}") : "{}";
const apiCacheBefore = JSON.parse(apiCacheTextBefore || "{}");
const loop = new import_mcpBundle.Loop({
api: context.agentParams.api,
apiEndpoint: context.agentParams.apiEndpoint,
apiKey: context.agentParams.apiKey,
apiTimeout: context.agentParams.apiTimeout ?? 0,
model: context.agentParams.model,
maxTokens: params.maxTokens ?? context.maxTokensRemaining(),
maxToolCalls: params.maxActions ?? context.agentParams.maxActions ?? 10,
maxToolCallRetries: params.maxActionRetries ?? context.agentParams.maxActionRetries ?? 3,
summarize: true,
debug: import_utilsBundle.debug,
callTool,
tools,
secrets,
cache: apiCacheBefore,
...context.events
});
const task = [];
if (context.agentParams.systemPrompt) {
task.push("### System");
task.push(context.agentParams.systemPrompt);
task.push("");
}
task.push("### Task");
task.push(userTask);
if (context.history().length) {
task.push("### Context history");
task.push(context.history().map((h) => `- ${h.type}: ${h.description}`).join("\n"));
task.push("");
}
task.push("### Page snapshot");
task.push(snapshot);
task.push("");
const { error, usage } = await loop.run(task.join("\n"), { signal: progress.signal });
context.consumeTokens(usage.input + usage.output);
if (context.agentParams.apiCacheFile) {
const apiCacheAfter = { ...apiCacheBefore, ...loop.cache() };
const sortedCache = Object.fromEntries(Object.entries(apiCacheAfter).sort(([a], [b]) => a.localeCompare(b)));
const apiCacheTextAfter = JSON.stringify(sortedCache, void 0, 2);
if (apiCacheTextAfter !== apiCacheTextBefore) {
await import_fs.default.promises.mkdir(import_path.default.dirname(context.agentParams.apiCacheFile), { recursive: true });
await import_fs.default.promises.writeFile(context.agentParams.apiCacheFile, apiCacheTextAfter);
}
}
if (refusedToPerformReason())
throw new Error(`Agent refused to perform action: ${refusedToPerformReason()}`);
if (error)
throw new Error(`Agentic loop failed: ${error}`);
return { result: reportedResult ? reportedResult() : void 0 };
}
async function cachedPerform(progress, context, cacheKey) {
if (!context.agentParams?.cacheFile)
return;
const cache = await cachedActions(context.agentParams?.cacheFile);
const entry = cache.actions[cacheKey];
if (!entry)
return;
for (const action of entry.actions)
await (0, import_actionRunner.runAction)(progress, "run", context.page, action, context.agentParams.secrets ?? []);
return entry.actions;
}
async function updateCache(context, cacheKey) {
const cacheFile = context.agentParams?.cacheFile;
const cacheOutFile = context.agentParams?.cacheOutFile;
const cacheFileKey = cacheFile ?? cacheOutFile;
const cache = cacheFileKey ? await cachedActions(cacheFileKey) : { actions: {}, newActions: {} };
const newEntry = { actions: context.actions() };
cache.actions[cacheKey] = newEntry;
cache.newActions[cacheKey] = newEntry;
if (cacheOutFile) {
const entries = Object.entries(cache.newActions);
entries.sort((e1, e2) => e1[0].localeCompare(e2[0]));
await import_fs.default.promises.writeFile(cacheOutFile, JSON.stringify(Object.fromEntries(entries), void 0, 2));
} else if (cacheFile) {
const entries = Object.entries(cache.actions);
entries.sort((e1, e2) => e1[0].localeCompare(e2[0]));
await import_fs.default.promises.writeFile(cacheFile, JSON.stringify(Object.fromEntries(entries), void 0, 2));
}
}
const allCaches = /* @__PURE__ */ new Map();
async function cachedActions(cacheFile) {
let cache = allCaches.get(cacheFile);
if (!cache) {
const content = await import_fs.default.promises.readFile(cacheFile, "utf-8").catch(() => "");
let json;
try {
json = JSON.parse(content.trim() || "{}");
} catch (error) {
throw new Error(`Failed to parse cache file ${cacheFile}:
${error.message}`);
}
const parsed = actions.cachedActionsSchema.safeParse(json);
if (parsed.error)
throw new Error(`Failed to parse cache file ${cacheFile}:
${import_mcpBundle.z.prettifyError(parsed.error)}`);
cache = { actions: parsed.data, newActions: {} };
allCaches.set(cacheFile, cache);
}
return cache;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pageAgentExpect,
pageAgentExtract,
pageAgentPerform
});

View File

@@ -0,0 +1,262 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var performTools_exports = {};
__export(performTools_exports, {
default: () => performTools_default
});
module.exports = __toCommonJS(performTools_exports);
var import_mcpBundle = require("../../mcpBundle");
var import_tool = require("./tool");
const navigateSchema = import_mcpBundle.z.object({
url: import_mcpBundle.z.string().describe("URL to navigate to")
});
const navigate = (0, import_tool.defineTool)({
schema: {
name: "browser_navigate",
title: "Navigate to URL",
description: "Navigate to a URL",
inputSchema: navigateSchema
},
handle: async (progress, context, params) => {
return await context.runActionNoWait(progress, {
method: "navigate",
url: params.url
});
}
});
const snapshot = (0, import_tool.defineTool)({
schema: {
name: "browser_snapshot",
title: "Page snapshot",
description: "Capture accessibility snapshot of the current page, this is better than screenshot",
inputSchema: import_mcpBundle.z.object({})
},
handle: async (progress, context, params) => {
return await context.snapshotResult(progress);
}
});
const elementSchema = import_mcpBundle.z.object({
element: import_mcpBundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
});
const clickSchema = elementSchema.extend({
doubleClick: import_mcpBundle.z.boolean().optional().describe("Whether to perform a double click instead of a single click"),
button: import_mcpBundle.z.enum(["left", "right", "middle"]).optional().describe("Button to click, defaults to left"),
modifiers: import_mcpBundle.z.array(import_mcpBundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
});
const click = (0, import_tool.defineTool)({
schema: {
name: "browser_click",
title: "Click",
description: "Perform click on a web page",
inputSchema: clickSchema
},
handle: async (progress, context, params) => {
const [selector] = await context.refSelectors(progress, [params]);
return await context.runActionAndWait(progress, {
method: "click",
selector,
button: params.button,
modifiers: params.modifiers,
clickCount: params.doubleClick ? 2 : void 0
});
}
});
const drag = (0, import_tool.defineTool)({
schema: {
name: "browser_drag",
title: "Drag mouse",
description: "Perform drag and drop between two elements",
inputSchema: import_mcpBundle.z.object({
startElement: import_mcpBundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
startRef: import_mcpBundle.z.string().describe("Exact source element reference from the page snapshot"),
endElement: import_mcpBundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
endRef: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
})
},
handle: async (progress, context, params) => {
const [sourceSelector, targetSelector] = await context.refSelectors(progress, [
{ ref: params.startRef, element: params.startElement },
{ ref: params.endRef, element: params.endElement }
]);
return await context.runActionAndWait(progress, {
method: "drag",
sourceSelector,
targetSelector
});
}
});
const hoverSchema = elementSchema.extend({
modifiers: import_mcpBundle.z.array(import_mcpBundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
});
const hover = (0, import_tool.defineTool)({
schema: {
name: "browser_hover",
title: "Hover mouse",
description: "Hover over element on page",
inputSchema: hoverSchema
},
handle: async (progress, context, params) => {
const [selector] = await context.refSelectors(progress, [params]);
return await context.runActionAndWait(progress, {
method: "hover",
selector,
modifiers: params.modifiers
});
}
});
const selectOptionSchema = elementSchema.extend({
values: import_mcpBundle.z.array(import_mcpBundle.z.string()).describe("Array of values to select in the dropdown. This can be a single value or multiple values.")
});
const selectOption = (0, import_tool.defineTool)({
schema: {
name: "browser_select_option",
title: "Select option",
description: "Select an option in a dropdown",
inputSchema: selectOptionSchema
},
handle: async (progress, context, params) => {
const [selector] = await context.refSelectors(progress, [params]);
return await context.runActionAndWait(progress, {
method: "selectOption",
selector,
labels: params.values
});
}
});
const pressKey = (0, import_tool.defineTool)({
schema: {
name: "browser_press_key",
title: "Press a key",
description: "Press a key on the keyboard",
inputSchema: import_mcpBundle.z.object({
key: import_mcpBundle.z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`"),
modifiers: import_mcpBundle.z.array(import_mcpBundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
})
},
handle: async (progress, context, params) => {
return await context.runActionAndWait(progress, {
method: "pressKey",
key: params.modifiers ? [...params.modifiers, params.key].join("+") : params.key
});
}
});
const typeSchema = elementSchema.extend({
text: import_mcpBundle.z.string().describe("Text to type into the element"),
submit: import_mcpBundle.z.boolean().optional().describe("Whether to submit entered text (press Enter after)"),
slowly: import_mcpBundle.z.boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
});
const type = (0, import_tool.defineTool)({
schema: {
name: "browser_type",
title: "Type text",
description: "Type text into editable element",
inputSchema: typeSchema
},
handle: async (progress, context, params) => {
const [selector] = await context.refSelectors(progress, [params]);
if (params.slowly) {
return await context.runActionAndWait(progress, {
method: "pressSequentially",
selector,
text: params.text,
submit: params.submit
});
} else {
return await context.runActionAndWait(progress, {
method: "fill",
selector,
text: params.text,
submit: params.submit
});
}
}
});
const fillForm = (0, import_tool.defineTool)({
schema: {
name: "browser_fill_form",
title: "Fill form",
description: "Fill multiple form fields. Always use this tool when you can fill more than one field at a time.",
inputSchema: import_mcpBundle.z.object({
fields: import_mcpBundle.z.array(import_mcpBundle.z.object({
name: import_mcpBundle.z.string().describe("Human-readable field name"),
type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the field"),
ref: import_mcpBundle.z.string().describe("Exact target field reference from the page snapshot"),
value: import_mcpBundle.z.string().describe("Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.")
})).describe("Fields to fill in")
})
},
handle: async (progress, context, params) => {
const actions = [];
for (const field of params.fields) {
const [selector] = await context.refSelectors(progress, [{ ref: field.ref, element: field.name }]);
if (field.type === "textbox" || field.type === "slider") {
actions.push({
method: "fill",
selector,
text: field.value
});
} else if (field.type === "checkbox" || field.type === "radio") {
actions.push({
method: "setChecked",
selector,
checked: field.value === "true"
});
} else if (field.type === "combobox") {
actions.push({
method: "selectOption",
selector,
labels: [field.value]
});
}
}
return await context.runActionsAndWait(progress, actions);
}
});
const setCheckedSchema = elementSchema.extend({
checked: import_mcpBundle.z.boolean().describe("Whether to check the checkbox")
});
const setChecked = (0, import_tool.defineTool)({
schema: {
name: "browser_set_checked",
title: "Set checked",
description: "Set the checked state of a checkbox",
inputSchema: setCheckedSchema
},
handle: async (progress, context, params) => {
const [selector] = await context.refSelectors(progress, [params]);
return await context.runActionAndWait(progress, {
method: "setChecked",
selector,
checked: params.checked
});
}
});
var performTools_default = [
navigate,
snapshot,
click,
drag,
hover,
selectOption,
pressKey,
type,
fillForm,
setChecked
];

109
node_modules/playwright-core/lib/server/agent/tool.js generated vendored Normal file
View File

@@ -0,0 +1,109 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var tool_exports = {};
__export(tool_exports, {
defineTool: () => defineTool,
toolsForLoop: () => toolsForLoop
});
module.exports = __toCommonJS(tool_exports);
var import_mcpBundle = require("../../mcpBundle");
var import_stringUtils = require("../../utils/isomorphic/stringUtils");
function defineTool(tool) {
return tool;
}
function toolsForLoop(progress, context, toolDefinitions, options = {}) {
const tools = toolDefinitions.map((tool) => {
const result = {
name: tool.schema.name,
description: tool.schema.description,
inputSchema: import_mcpBundle.z.toJSONSchema(tool.schema.inputSchema)
};
return result;
});
if (options.resultSchema) {
tools.push({
name: "report_result",
description: "Report the result of the task.",
inputSchema: options.resultSchema
});
}
if (options.refuseToPerform === "allow") {
tools.push({
name: "refuse_to_perform",
description: "Refuse to perform action.",
inputSchema: {
type: "object",
properties: {
reason: {
type: "string",
description: `Call this when you believe that you can't perform the action because something is wrong with the page. The reason will be reported to the user.`
}
},
required: ["reason"]
}
});
}
let reportedResult;
let refusedToPerformReason;
const callTool = async (params) => {
if (params.name === "report_result") {
reportedResult = params.arguments;
return {
content: [{ type: "text", text: "Done" }],
isError: false
};
}
if (params.name === "refuse_to_perform") {
refusedToPerformReason = params.arguments.reason;
return {
content: [{ type: "text", text: "Done" }],
isError: false
};
}
const tool = toolDefinitions.find((t) => t.schema.name === params.name);
if (!tool) {
return {
content: [{
type: "text",
text: `Tool ${params.name} not found. Available tools: ${toolDefinitions.map((t) => t.schema.name)}`
}],
isError: true
};
}
try {
return await tool.handle(progress, context, params.arguments);
} catch (error) {
return {
content: [{ type: "text", text: (0, import_stringUtils.stripAnsiEscapes)(error.message) }],
isError: true
};
}
};
return {
tools,
callTool,
reportedResult: options.resultSchema ? () => reportedResult : void 0,
refusedToPerformReason: () => refusedToPerformReason
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
defineTool,
toolsForLoop
});

View File

@@ -103,7 +103,7 @@ class Artifact extends import_instrumentation.SdkObject {
if (!this._unaccessibleErrorMessage)
await import_fs.default.promises.unlink(this._localPath).catch((e) => {
});
await this.reportFinished(new import_errors.TargetClosedError());
await this.reportFinished(new import_errors.TargetClosedError(this.closeReason()));
}
async reportFinished(error) {
if (this._finished)

View File

@@ -130,10 +130,13 @@ class BidiBrowser extends import_browser.Browser {
const page2 = this._findPageForFrame(parentFrameId);
if (page2) {
page2._session.addFrameBrowsingContext(event.context);
page2._page.frameManager.frameAttached(event.context, parentFrameId);
const frame = page2._page.frameManager.frame(event.context);
if (frame)
frame._url = event.url;
const frame = page2._page.frameManager.frameAttached(event.context, parentFrameId);
frame._url = event.url;
page2._getFrameNode(frame).then((node) => {
const attributes = node?.value?.attributes;
frame._name = attributes?.name ?? attributes?.id ?? "";
});
return;
}
return;
}
@@ -142,6 +145,7 @@ class BidiBrowser extends import_browser.Browser {
context = this._defaultContext;
if (!context)
return;
context.doGrantGlobalPermissionsForURL(event.url);
const session = this._connection.createMainFrameBrowsingContextSession(event.context);
const opener = event.originalOpener && this._findPageForFrame(event.originalOpener);
const page = new import_bidiPage.BidiPage(context, session, opener || null);
@@ -217,6 +221,8 @@ class BidiBrowserContext extends import_browserContext.BrowserContext {
}
if (this._options.extraHTTPHeaders)
promises.push(this.doUpdateExtraHTTPHeaders());
if (this._options.permissions)
promises.push(this.doGrantPermissions("*", this._options.permissions));
await Promise.all(promises);
}
possiblyUninitializedPages() {
@@ -275,17 +281,34 @@ class BidiBrowserContext extends import_browserContext.BrowserContext {
);
}
async doGrantPermissions(origin, permissions) {
if (origin === "null")
return;
const currentPermissions = this._originToPermissions.get(origin) || [];
const toGrant = permissions.filter((permission) => !currentPermissions.includes(permission));
this._originToPermissions.set(origin, [...currentPermissions, ...toGrant]);
await Promise.all(toGrant.map((permission) => this._setPermission(origin, permission, bidi.Permissions.PermissionState.Granted)));
if (origin === "*") {
await Promise.all(this._bidiPages().flatMap(
(page) => page._page.frames().map(
(frame) => this.doGrantPermissions(new URL(frame._url).origin, permissions)
)
));
} else {
await Promise.all(toGrant.map((permission) => this._setPermission(origin, permission, bidi.Permissions.PermissionState.Granted)));
}
}
async doGrantGlobalPermissionsForURL(url) {
const permissions = this._originToPermissions.get("*");
if (!permissions)
return;
await this.doGrantPermissions(new URL(url).origin, permissions);
}
async doClearPermissions() {
const currentPermissions = [...this._originToPermissions.entries()];
this._originToPermissions = /* @__PURE__ */ new Map();
await Promise.all(currentPermissions.map(([origin, permissions]) => permissions.map(
(p) => this._setPermission(origin, p, bidi.Permissions.PermissionState.Prompt)
)));
await Promise.all(currentPermissions.flatMap(([origin, permissions]) => {
if (origin !== "*")
return permissions.map((p) => this._setPermission(origin, p, bidi.Permissions.PermissionState.Prompt));
}));
}
async _setPermission(origin, permission, state) {
await this._browser._browserSession.send("permissions.setPermission", {
@@ -349,21 +372,42 @@ class BidiBrowserContext extends import_browserContext.BrowserContext {
await Promise.all(ids.map((script) => this._browser._browserSession.send("script.removePreloadScript", { script })));
}
async doUpdateRequestInterception() {
if (this.requestInterceptors.length > 0 && !this._interceptId) {
const { intercept } = await this._browser._browserSession.send("network.addIntercept", {
phases: [bidi.Network.InterceptPhase.BeforeRequestSent],
urlPatterns: [{ type: "pattern" }]
});
this._interceptId = intercept;
}
if (this.requestInterceptors.length === 0 && this._interceptId) {
const intercept = this._interceptId;
this._interceptId = void 0;
await this._browser._browserSession.send("network.removeIntercept", { intercept });
}
}
async doUpdateDefaultViewport() {
if (!this._options.viewport)
if (!this._options.viewport && !this._options.screen)
return;
const screenSize = this._options.screen || this._options.viewport;
const viewportSize = this._options.viewport || this._options.screen;
await Promise.all([
this._browser._browserSession.send("browsingContext.setViewport", {
viewport: {
width: this._options.viewport.width,
height: this._options.viewport.height
width: viewportSize.width,
height: viewportSize.height
},
devicePixelRatio: this._options.deviceScaleFactor || 1,
userContexts: [this._userContextId()]
}),
this._browser._browserSession.send("emulation.setScreenOrientationOverride", {
screenOrientation: getScreenOrientation(!!this._options.isMobile, this._options.viewport),
screenOrientation: getScreenOrientation(!!this._options.isMobile, screenSize),
userContexts: [this._userContextId()]
}),
this._browser._browserSession.send("emulation.setScreenSettingsOverride", {
screenArea: {
width: screenSize.width,
height: screenSize.height
},
userContexts: [this._userContextId()]
})
]);

View File

@@ -38,6 +38,7 @@ var import_bidiBrowser = require("./bidiBrowser");
var import_bidiConnection = require("./bidiConnection");
var import_chromiumSwitches = require("../chromium/chromiumSwitches");
var import_chromium = require("../chromium/chromium");
var import_hostPlatform = require("../utils/hostPlatform");
class BidiChromium extends import_browserType.BrowserType {
constructor(parent) {
super(parent, "chromium");
@@ -56,14 +57,12 @@ class BidiChromium extends import_browserType.BrowserType {
throw e;
}
}
doRewriteStartupLog(error) {
if (!error.logs)
return error;
if (error.logs.includes("Missing X server"))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
if (!error.logs.includes("crbug.com/357670") && !error.logs.includes("No usable sandbox!") && !error.logs.includes("crbug.com/638180"))
return error;
error.logs = [
doRewriteStartupLog(logs) {
if (logs.includes("Missing X server"))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
if (!logs.includes("crbug.com/357670") && !logs.includes("No usable sandbox!") && !logs.includes("crbug.com/638180"))
return logs;
return [
`Chromium sandboxing failed!`,
`================================`,
`To avoid the sandboxing issue, do either of the following:`,
@@ -72,7 +71,6 @@ class BidiChromium extends import_browserType.BrowserType {
`================================`,
``
].join("\n");
return error;
}
amendEnvironment(env) {
return env;
@@ -109,11 +107,9 @@ class BidiChromium extends import_browserType.BrowserType {
if (args.find((arg) => !arg.startsWith("-")))
throw new Error("Arguments can not specify page to be opened");
const chromeArguments = [...(0, import_chromiumSwitches.chromiumSwitches)(options.assistantMode)];
if (import_os.default.platform() === "darwin") {
if (import_os.default.platform() !== "darwin" || !(0, import_hostPlatform.hasGpuMac)()) {
chromeArguments.push("--enable-unsafe-swiftshader");
}
if (options.devtools)
chromeArguments.push("--auto-open-devtools-for-tabs");
if (options.headless) {
chromeArguments.push("--headless");
chromeArguments.push(

View File

@@ -173,6 +173,7 @@ class BidiSession extends import_events.EventEmitter {
this._browsingContexts.clear();
for (const callback of this._callbacks.values()) {
callback.error.type = this._crashed ? "crashed" : "closed";
callback.error.setMessage(`Internal server error, session ${callback.error.type}.`);
callback.error.logs = this.connection._browserDisconnectedLogs;
callback.reject(callback.error);
}

View File

@@ -0,0 +1,116 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var bidiDeserializer_exports = {};
__export(bidiDeserializer_exports, {
deserializeBidiValue: () => deserializeBidiValue
});
module.exports = __toCommonJS(bidiDeserializer_exports);
var import_javascript = require("../javascript");
function deserializeBidiValue(result, internalIdMap = /* @__PURE__ */ new Map()) {
switch (result.type) {
case "undefined":
return void 0;
case "null":
return null;
case "number":
return typeof result.value === "number" ? result.value : (0, import_javascript.parseUnserializableValue)(result.value);
case "boolean":
return Boolean(result.value);
case "string":
return result.value;
case "bigint":
return BigInt(result.value);
case "array":
return deserializeBidiList(result, internalIdMap);
case "arraybuffer":
return getValue(result, internalIdMap, () => ({}));
case "date":
return getValue(result, internalIdMap, () => new Date(result.value));
case "error":
return getValue(result, internalIdMap, () => {
const error = new Error();
error.stack = "";
return error;
});
case "function":
return void 0;
case "generator":
return getValue(result, internalIdMap, () => ({}));
case "htmlcollection":
return { ...deserializeBidiList(result, internalIdMap) };
case "map":
return getValue(result, internalIdMap, () => ({}));
case "node":
return "ref: <Node>";
case "nodelist":
return { ...deserializeBidiList(result, internalIdMap) };
case "object":
return deserializeBidiMapping(result, internalIdMap);
case "promise":
return getValue(result, internalIdMap, () => ({}));
case "proxy":
return getValue(result, internalIdMap, () => ({}));
case "regexp":
return getValue(result, internalIdMap, () => new RegExp(result.value.pattern, result.value.flags));
case "set":
return getValue(result, internalIdMap, () => ({}));
case "symbol":
return void 0;
case "typedarray":
return void 0;
case "weakmap":
return getValue(result, internalIdMap, () => ({}));
case "weakset":
return getValue(result, internalIdMap, () => ({}));
case "window":
return "ref: <Window>";
}
}
function getValue(bidiValue, internalIdMap, defaultValue) {
if ("internalId" in bidiValue && bidiValue.internalId) {
if (internalIdMap.has(bidiValue.internalId)) {
return internalIdMap.get(bidiValue.internalId);
} else {
const value = defaultValue();
internalIdMap.set(bidiValue.internalId, value);
return value;
}
} else {
return defaultValue();
}
}
function deserializeBidiList(bidiValue, internalIdMap) {
const result = getValue(bidiValue, internalIdMap, () => []);
for (const val of bidiValue.value || [])
result.push(deserializeBidiValue(val, internalIdMap));
return result;
}
function deserializeBidiMapping(bidiValue, internalIdMap) {
const result = getValue(bidiValue, internalIdMap, () => ({}));
for (const [serializedKey, serializedValue] of bidiValue.value || []) {
const key = typeof serializedKey === "string" ? serializedKey : deserializeBidiValue(serializedKey, internalIdMap);
const value = deserializeBidiValue(serializedValue, internalIdMap);
result[key] = value;
}
return result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
deserializeBidiValue
});

View File

@@ -36,9 +36,9 @@ var import_utils = require("../../utils");
var import_utilityScriptSerializers = require("../../utils/isomorphic/utilityScriptSerializers");
var js = __toESM(require("../javascript"));
var dom = __toESM(require("../dom"));
var import_bidiDeserializer = require("./third_party/bidiDeserializer");
var bidi = __toESM(require("./third_party/bidiProtocol"));
var import_bidiSerializer = require("./third_party/bidiSerializer");
var import_bidiDeserializer = require("./bidiDeserializer");
class BidiExecutionContext {
constructor(session, realmInfo) {
this._session = session;
@@ -65,7 +65,7 @@ class BidiExecutionContext {
userActivation: true
});
if (response.type === "success")
return import_bidiDeserializer.BidiDeserializer.deserialize(response.result);
return (0, import_bidiDeserializer.deserializeBidiValue)(response.result);
if (response.type === "exception")
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text);
throw new js.JavaScriptErrorInEvaluate("Unexpected response type: " + JSON.stringify(response));
@@ -108,7 +108,7 @@ class BidiExecutionContext {
throw new js.JavaScriptErrorInEvaluate(response.exceptionDetails.text);
if (response.type === "success") {
if (returnByValue)
return (0, import_utilityScriptSerializers.parseEvaluationResultValue)(import_bidiDeserializer.BidiDeserializer.deserialize(response.result));
return (0, import_utilityScriptSerializers.parseEvaluationResultValue)((0, import_bidiDeserializer.deserializeBidiValue)(response.result));
return createHandle(utilityScript._context, response.result);
}
throw new js.JavaScriptErrorInEvaluate("Unexpected response type: " + JSON.stringify(response));
@@ -123,7 +123,10 @@ class BidiExecutionContext {
}
return names2;
});
const values = await Promise.all(names.map((name) => handle.evaluateHandle((object, name2) => object[name2], name)));
const values = await Promise.all(names.map(async (name) => {
const value = await this._rawCallFunction("(object, name) => object[name]", [{ handle: handle._objectId }, { type: "string", value: name }], true, false);
return createHandle(handle._context, value);
}));
const map = /* @__PURE__ */ new Map();
for (let i = 0; i < names.length; i++)
map.set(names[i], values[i]);
@@ -152,7 +155,7 @@ class BidiExecutionContext {
return createHandle(context, result);
}
async contentFrameIdForFrame(handle) {
const contentWindow = await this._rawCallFunction("e => e.contentWindow", { handle: handle._objectId });
const contentWindow = await this._rawCallFunction("e => e.contentWindow", [{ handle: handle._objectId }]);
if (contentWindow?.type === "window")
return contentWindow.value.context;
return null;
@@ -166,17 +169,17 @@ class BidiExecutionContext {
return null;
}
async _remoteValueForReference(reference, createHandle2) {
return await this._rawCallFunction("e => e", reference, createHandle2);
return await this._rawCallFunction("e => e", [reference], createHandle2);
}
async _rawCallFunction(functionDeclaration, arg, createHandle2) {
async _rawCallFunction(functionDeclaration, args, createHandle2, awaitPromise = true) {
const response = await this._session.send("script.callFunction", {
functionDeclaration,
target: this._target,
arguments: [arg],
arguments: args,
// "Root" is necessary for the handle to be returned.
resultOwnership: createHandle2 ? bidi.Script.ResultOwnership.Root : bidi.Script.ResultOwnership.None,
serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 },
awaitPromise: true,
awaitPromise,
userActivation: true
});
if (response.type === "exception")
@@ -186,25 +189,65 @@ class BidiExecutionContext {
throw new js.JavaScriptErrorInEvaluate("Unexpected response type: " + JSON.stringify(response));
}
}
function renderPreview(remoteObject) {
if (remoteObject.type === "undefined")
return "undefined";
if (remoteObject.type === "null")
return "null";
if ("value" in remoteObject)
return String(remoteObject.value);
return `<${remoteObject.type}>`;
}
function remoteObjectValue(remoteObject) {
if (remoteObject.type === "undefined")
return void 0;
if (remoteObject.type === "null")
return null;
if (remoteObject.type === "number" && typeof remoteObject.value === "string")
return js.parseUnserializableValue(remoteObject.value);
if ("value" in remoteObject)
return remoteObject.value;
return void 0;
function renderPreview(remoteObject, nested = false) {
switch (remoteObject.type) {
case "undefined":
case "null":
return remoteObject.type;
case "number":
case "boolean":
case "string":
return String(remoteObject.value);
case "bigint":
return `${remoteObject.value}n`;
case "date":
return String(new Date(remoteObject.value));
case "regexp":
return String(new RegExp(remoteObject.value.pattern, remoteObject.value.flags));
case "node":
return remoteObject.value?.localName || "Node";
case "object":
if (nested)
return "Object";
const tokens = [];
for (const [name, value] of remoteObject.value || []) {
if (typeof name === "string")
tokens.push(`${name}: ${renderPreview(value, true)}`);
}
return `{${tokens.join(", ")}}`;
case "array":
case "htmlcollection":
case "nodelist":
if (nested || !remoteObject.value)
return remoteObject.value ? `Array(${remoteObject.value.length})` : "Array";
return `[${remoteObject.value.map((v) => renderPreview(v, true)).join(", ")}]`;
case "map":
return remoteObject.value ? `Map(${remoteObject.value.length})` : "Map";
case "set":
return remoteObject.value ? `Set(${remoteObject.value.length})` : "Set";
case "arraybuffer":
return "ArrayBuffer";
case "error":
return "Error";
case "function":
return "Function";
case "generator":
return "Generator";
case "promise":
return "Promise";
case "proxy":
return "Proxy";
case "symbol":
return "Symbol()";
case "typedarray":
return "TypedArray";
case "weakmap":
return "WeakMap";
case "weakset":
return "WeakSet";
case "window":
return "Window";
}
}
function createHandle(context, remoteObject) {
if (remoteObject.type === "node") {
@@ -212,7 +255,10 @@ function createHandle(context, remoteObject) {
return new dom.ElementHandle(context, remoteObject.handle);
}
const objectId = "handle" in remoteObject ? remoteObject.handle : void 0;
return new js.JSHandle(context, remoteObject.type, renderPreview(remoteObject), objectId, remoteObjectValue(remoteObject));
const preview = renderPreview(remoteObject);
const handle = new js.JSHandle(context, remoteObject.type, preview, objectId, (0, import_bidiDeserializer.deserializeBidiValue)(remoteObject));
handle._setPreview(preview);
return handle;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {

View File

@@ -49,15 +49,13 @@ class BidiFirefox extends import_browserType.BrowserType {
async connectToTransport(transport, options) {
return import_bidiBrowser.BidiBrowser.connect(this.attribution.playwright, transport, options);
}
doRewriteStartupLog(error) {
if (!error.logs)
return error;
if (error.logs.includes(`as root in a regular user's session is not supported.`))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(`Firefox is unable to launch if the $HOME folder isn't owned by the current user.
doRewriteStartupLog(logs) {
if (logs.includes(`as root in a regular user's session is not supported.`))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(`Firefox is unable to launch if the $HOME folder isn't owned by the current user.
Workaround: Set the HOME=/root environment variable${process.env.GITHUB_ACTION ? " in your GitHub Actions workflow file" : ""} when running Playwright.`, 1);
if (error.logs.includes("no DISPLAY environment variable specified"))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
return error;
if (logs.includes("no DISPLAY environment variable specified"))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
return logs;
}
amendEnvironment(env) {
if (!import_path.default.isAbsolute(import_os.default.homedir()))

View File

@@ -342,7 +342,7 @@ function resourceTypeFromBidi(requestDestination, requestInitiatorType, eventIni
case "image":
return "image";
case "object":
return "object";
return "other";
case "paintworklet":
return "script";
case "script":

View File

@@ -32,6 +32,7 @@ __export(bidiPage_exports, {
kPlaywrightBindingChannel: () => kPlaywrightBindingChannel
});
module.exports = __toCommonJS(bidiPage_exports);
var import_debugLogger = require("../utils/debugLogger");
var import_eventsHelper = require("../utils/eventsHelper");
var dialog = __toESM(require("../dialog"));
var dom = __toESM(require("../dom"));
@@ -87,8 +88,7 @@ class BidiPage {
async _initialize() {
this._onFrameAttached(this._session.sessionId, null);
await Promise.all([
this.updateHttpCredentials(),
this.updateRequestInterception()
this.updateHttpCredentials()
// If the page is created by the Playwright client's call, some initialization
// may be pending. Wait for it to complete before reporting the page as new.
]);
@@ -179,10 +179,12 @@ class BidiPage {
}
_onNavigationCommitted(params) {
const frameId = params.context;
const frame = this._page.frameManager.frame(frameId);
this._browserContext.doGrantGlobalPermissionsForURL(params.url).catch((error) => import_debugLogger.debugLogger.log("error", error));
this._page.frameManager.frameCommittedNewDocumentNavigation(
frameId,
params.url,
"",
frame._name,
params.navigation,
/* initial */
false
@@ -263,7 +265,7 @@ ${params.stackTrace?.callFrames.map((f) => {
return;
const callFrame = params.stackTrace?.callFrames[0];
const location = callFrame ?? { url: "", lineNumber: 1, columnNumber: 1 };
this._page.addConsoleMessage(null, entry.method, entry.args.map((arg) => (0, import_bidiExecutionContext.createHandle)(context, arg)), location, params.text || void 0);
this._page.addConsoleMessage(null, entry.method, entry.args.map((arg) => (0, import_bidiExecutionContext.createHandle)(context, arg)), location);
}
async _onFileDialogOpened(params) {
if (!params.element)
@@ -272,8 +274,11 @@ ${params.stackTrace?.callFrames.map((f) => {
if (!frame)
return;
const executionContext = await frame._mainContext();
const handle = await toBidiExecutionContext(executionContext).remoteObjectForNodeId(executionContext, { sharedId: params.element.sharedId });
await this._page._onFileChooserOpened(handle);
try {
const handle = await toBidiExecutionContext(executionContext).remoteObjectForNodeId(executionContext, { sharedId: params.element.sharedId });
await this._page._onFileChooserOpened(handle);
} catch {
}
}
async navigateFrame(frame, url, referrer) {
const { navigation } = await this._session.send("browsingContext.navigate", {
@@ -306,6 +311,7 @@ ${params.stackTrace?.callFrames.map((f) => {
const emulatedSize = this._page.emulatedSize();
if (!emulatedSize)
return;
const screenSize = emulatedSize.screen;
const viewportSize = emulatedSize.viewport;
await Promise.all([
this._session.send("browsingContext.setViewport", {
@@ -318,12 +324,19 @@ ${params.stackTrace?.callFrames.map((f) => {
}),
this._session.send("emulation.setScreenOrientationOverride", {
contexts: [this._session.sessionId],
screenOrientation: (0, import_bidiBrowser.getScreenOrientation)(!!options.isMobile, viewportSize)
screenOrientation: (0, import_bidiBrowser.getScreenOrientation)(!!options.isMobile, screenSize)
}),
this._session.send("emulation.setScreenSettingsOverride", {
contexts: [this._session.sessionId],
screenArea: {
width: screenSize.width,
height: screenSize.height
}
})
]);
}
async updateRequestInterception() {
await this._networkManager.setRequestInterception(this._page.needsRequestInterception());
await this._networkManager.setRequestInterception(this._page.requestInterceptors.length > 0);
}
async updateOffline() {
}
@@ -482,7 +495,9 @@ ${params.stackTrace?.callFrames.map((f) => {
throw e;
});
}
async setScreencastOptions(options) {
async startScreencast(options) {
}
async stopScreencast() {
}
rafCountForStablePosition() {
return 1;
@@ -537,26 +552,22 @@ ${params.stackTrace?.callFrames.map((f) => {
const parent = frame.parentFrame();
if (!parent)
throw new Error("Frame has been detached.");
const parentContext = await parent._mainContext();
const list = await parentContext.evaluateHandle(() => {
return [...document.querySelectorAll("iframe,frame")];
});
const length = await list.evaluate((list2) => list2.length);
let foundElement = null;
for (let i = 0; i < length; i++) {
const element = await list.evaluateHandle((list2, i2) => list2[i2], i);
const candidate = await element.contentFrame();
if (frame === candidate) {
foundElement = element;
break;
} else {
element.dispose();
}
}
list.dispose();
if (!foundElement)
const node = await this._getFrameNode(frame);
if (!node?.sharedId)
throw new Error("Frame has been detached.");
return foundElement;
const parentFrameExecutionContext = await parent._mainContext();
return await toBidiExecutionContext(parentFrameExecutionContext).remoteObjectForNodeId(parentFrameExecutionContext, { sharedId: node.sharedId });
}
async _getFrameNode(frame) {
const parent = frame.parentFrame();
if (!parent)
return void 0;
const result = await this._session.send("browsingContext.locateNodes", {
context: parent._id,
locator: { type: "context", value: { context: frame._id } }
});
const node = result.nodes[0];
return node;
}
shouldToggleStyleSheetToSyncAnimations() {
return true;

View File

@@ -1,98 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var bidiDeserializer_exports = {};
__export(bidiDeserializer_exports, {
BidiDeserializer: () => BidiDeserializer
});
module.exports = __toCommonJS(bidiDeserializer_exports);
/**
* @license
* Copyright 2024 Google Inc.
* Modifications copyright (c) Microsoft Corporation.
* SPDX-License-Identifier: Apache-2.0
*/
class BidiDeserializer {
static deserialize(result) {
if (!result)
return void 0;
switch (result.type) {
case "array":
return result.value?.map((value) => {
return BidiDeserializer.deserialize(value);
});
case "set":
return result.value?.reduce((acc, value) => {
return acc.add(BidiDeserializer.deserialize(value));
}, /* @__PURE__ */ new Set());
case "object":
return result.value?.reduce((acc, tuple) => {
const { key, value } = BidiDeserializer._deserializeTuple(tuple);
acc[key] = value;
return acc;
}, {});
case "map":
return result.value?.reduce((acc, tuple) => {
const { key, value } = BidiDeserializer._deserializeTuple(tuple);
return acc.set(key, value);
}, /* @__PURE__ */ new Map());
case "promise":
return {};
case "regexp":
return new RegExp(result.value.pattern, result.value.flags);
case "date":
return new Date(result.value);
case "undefined":
return void 0;
case "null":
return null;
case "number":
return BidiDeserializer._deserializeNumber(result.value);
case "bigint":
return BigInt(result.value);
case "boolean":
return Boolean(result.value);
case "string":
return result.value;
}
throw new Error(`Deserialization of type ${result.type} not supported.`);
}
static _deserializeNumber(value) {
switch (value) {
case "-0":
return -0;
case "NaN":
return NaN;
case "Infinity":
return Infinity;
case "-Infinity":
return -Infinity;
default:
return value;
}
}
static _deserializeTuple([serializedKey, serializedValue]) {
const key = typeof serializedKey === "string" ? serializedKey : BidiDeserializer.deserialize(serializedKey);
const value = BidiDeserializer.deserialize(serializedValue);
return { key, value };
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BidiDeserializer
});

View File

@@ -128,6 +128,7 @@ var Network;
((Network2) => {
let DataType;
((DataType2) => {
DataType2["Request"] = "request";
DataType2["Response"] = "response";
})(DataType = Network2.DataType || (Network2.DataType = {}));
})(Network || (Network = {}));

View File

@@ -55,6 +55,24 @@ var import_recorderApp = require("./recorder/recorderApp");
var import_selectors = require("./selectors");
var import_tracing = require("./trace/recorder/tracing");
var rawStorageSource = __toESM(require("../generated/storageScriptSource"));
const BrowserContextEvent = {
Console: "console",
Close: "close",
Page: "page",
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: "pageerror",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished",
RequestAborted: "requestaborted",
RequestFulfilled: "requestfulfilled",
RequestContinued: "requestcontinued",
BeforeClose: "beforeclose",
VideoStarted: "videostarted",
RecorderEvent: "recorderevent"
};
class BrowserContext extends import_instrumentation.SdkObject {
constructor(browser, options, browserContextId) {
super(browser, "browser-context");
@@ -69,6 +87,7 @@ class BrowserContext extends import_instrumentation.SdkObject {
this._creatingStorageStatePage = false;
this.initScripts = [];
this._routesInFlight = /* @__PURE__ */ new Set();
this._consoleApiExposed = false;
this.attribution.context = this;
this._browser = browser;
this._options = options;
@@ -82,24 +101,7 @@ class BrowserContext extends import_instrumentation.SdkObject {
this.dialogManager = new import_dialog.DialogManager(this.instrumentation);
}
static {
this.Events = {
Console: "console",
Close: "close",
Page: "page",
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: "pageerror",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished",
RequestAborted: "requestaborted",
RequestFulfilled: "requestfulfilled",
RequestContinued: "requestcontinued",
BeforeClose: "beforeclose",
VideoStarted: "videostarted",
RecorderEvent: "recorderevent"
};
this.Events = BrowserContextEvent;
}
isPersistentContext() {
return this._isPersistentContext;
@@ -119,12 +121,8 @@ class BrowserContext extends import_instrumentation.SdkObject {
if (this._debugger.isPaused())
import_recorderApp.RecorderApp.showInspectorNoReply(this);
});
if ((0, import_debug.debugMode)() === "console") {
await this.extendInjectedScript(`
function installConsoleApi(injectedScript) { injectedScript.consoleApi.install(); }
module.exports = { default: () => installConsoleApi };
`);
}
if ((0, import_debug.debugMode)() === "console")
await this.exposeConsoleApi();
if (this._options.serviceWorkers === "block")
await this.addInitScript(void 0, `
if (navigator.serviceWorker) navigator.serviceWorker.register = async () => { console.warn('Service Worker registration blocked by Playwright'); };
@@ -135,6 +133,15 @@ if (navigator.serviceWorker) navigator.serviceWorker.register = async () => { co
debugger() {
return this._debugger;
}
async exposeConsoleApi() {
if (this._consoleApiExposed)
return;
this._consoleApiExposed = true;
await this.extendInjectedScript(`
function installConsoleApi(injectedScript) { injectedScript.consoleApi.install(); }
module.exports = { default: () => installConsoleApi };
`);
}
async _ensureVideosPath() {
if (this._options.recordVideo)
await (0, import_fileUtils.mkdirIfNeeded)(import_path.default.join(this._options.recordVideo.dir, "dummy"));
@@ -322,7 +329,7 @@ if (navigator.serviceWorker) navigator.serviceWorker.register = async () => { co
const pageOrError = await progress.race(page.waitForInitializedOrError());
if (pageOrError instanceof Error)
throw pageOrError;
await page.mainFrame()._waitForLoadState(progress, "load");
await page.mainFrame().waitForLoadState(progress, "load");
return page;
}
async _loadDefaultContext(progress) {

View File

@@ -250,6 +250,13 @@ class BrowserType extends import_instrumentation.SdkObject {
this.waitForReadyState(options, browserLogsCollector),
exitPromise.then(() => ({ wsEndpoint: void 0 }))
]);
if (exitPromise.isDone()) {
const log = import_helper.helper.formatBrowserLogs(browserLogsCollector.recentLogs());
const updatedLog = this.doRewriteStartupLog(log);
throw new Error(`Failed to launch the browser process.
Browser logs:
${updatedLog}`);
}
if (options.cdpPort !== void 0 || !this.supportsPipeTransport()) {
transport = await import_transport.WebSocketTransport.connect(progress, wsEndpoint);
} else {
@@ -276,15 +283,14 @@ class BrowserType extends import_instrumentation.SdkObject {
throw new Error("Connecting to SELENIUM_REMOTE_URL is only supported by Chromium");
}
_validateLaunchOptions(options) {
const { devtools = false } = options;
let { headless = !devtools, downloadsPath, proxy } = options;
let { headless = true, downloadsPath, proxy } = options;
if ((0, import_debug.debugMode)() === "inspector")
headless = false;
if (downloadsPath && !import_path.default.isAbsolute(downloadsPath))
downloadsPath = import_path.default.join(process.cwd(), downloadsPath);
if (options.socksProxyPort)
proxy = { server: `socks5://127.0.0.1:${options.socksProxyPort}` };
return { ...options, devtools, headless, downloadsPath, proxy };
return { ...options, headless, downloadsPath, proxy };
}
_createUserDataDirArgMisuseError(userDataDirArg) {
switch (this.attribution.playwright.options.sdkLanguage) {
@@ -301,7 +307,9 @@ class BrowserType extends import_instrumentation.SdkObject {
_rewriteStartupLog(error) {
if (!(0, import_protocolError.isProtocolError)(error))
return error;
return this.doRewriteStartupLog(error);
if (error.logs)
error.logs = this.doRewriteStartupLog(error.logs);
return error;
}
async waitForReadyState(options, browserLogsCollector) {
return {};

View File

@@ -114,7 +114,8 @@ class Chromium extends import_browserType.BrowserType {
};
(0, import_browserContext.validateBrowserContextOptions)(persistent, browserOptions);
const browser = await progress.race(import_crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions));
browser._isCollocatedWithServer = false;
if (!options.isLocal)
browser._isCollocatedWithServer = false;
browser.on(import_browser.Browser.Events.Disconnected, doCleanup);
return browser;
} catch (error) {
@@ -128,13 +129,8 @@ class Chromium extends import_browserType.BrowserType {
return directory ? new import_crDevTools.CRDevTools(import_path.default.join(directory, "devtools-preferences.json")) : void 0;
}
async connectToTransport(transport, options, browserLogsCollector) {
let devtools = this._devtools;
if (options.__testHookForDevTools) {
devtools = this._createDevTools();
await options.__testHookForDevTools(devtools);
}
try {
return await import_crBrowser.CRBrowser.connect(this.attribution.playwright, transport, options, devtools);
return await import_crBrowser.CRBrowser.connect(this.attribution.playwright, transport, options, this._devtools);
} catch (e) {
if (browserLogsCollector.recentLogs().some((log) => log.includes("Failed to create a ProcessSingleton for your profile directory."))) {
throw new Error(
@@ -144,14 +140,12 @@ class Chromium extends import_browserType.BrowserType {
throw e;
}
}
doRewriteStartupLog(error) {
if (!error.logs)
return error;
if (error.logs.includes("Missing X server"))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
if (!error.logs.includes("crbug.com/357670") && !error.logs.includes("No usable sandbox!") && !error.logs.includes("crbug.com/638180"))
return error;
error.logs = [
doRewriteStartupLog(logs) {
if (logs.includes("Missing X server"))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
if (!logs.includes("crbug.com/357670") && !logs.includes("No usable sandbox!") && !logs.includes("crbug.com/638180"))
return logs;
return [
`Chromium sandboxing failed!`,
`================================`,
`To avoid the sandboxing issue, do either of the following:`,
@@ -160,7 +154,6 @@ class Chromium extends import_browserType.BrowserType {
`================================`,
``
].join("\n");
return error;
}
amendEnvironment(env) {
return env;
@@ -284,11 +277,9 @@ class Chromium extends import_browserType.BrowserType {
if (args.find((arg) => !arg.startsWith("-")))
throw new Error("Arguments can not specify page to be opened");
const chromeArguments = [...(0, import_chromiumSwitches.chromiumSwitches)(options.assistantMode, options.channel)];
if (import_os.default.platform() === "darwin") {
if (import_os.default.platform() !== "darwin" || !(0, import_utils.hasGpuMac)()) {
chromeArguments.push("--enable-unsafe-swiftshader");
}
if (options.devtools)
chromeArguments.push("--auto-open-devtools-for-tabs");
if (options.headless) {
chromeArguments.push("--headless");
chromeArguments.push(
@@ -324,6 +315,10 @@ class Chromium extends import_browserType.BrowserType {
return waitForReadyState(options, browserLogsCollector);
}
getExecutableName(options) {
if (options.channel && import_registry.registry.isChromiumAlias(options.channel))
return "chromium";
if (options.channel === "chromium-tip-of-tree")
return options.headless ? "chromium-tip-of-tree-headless-shell" : "chromium-tip-of-tree";
if (options.channel)
return options.channel;
return options.headless ? "chromium-headless-shell" : "chromium";

View File

@@ -22,10 +22,10 @@ __export(chromiumSwitches_exports, {
});
module.exports = __toCommonJS(chromiumSwitches_exports);
const disabledFeatures = (assistantMode) => [
// See https://github.com/microsoft/playwright/pull/10380
"AcceptCHFrame",
// See https://github.com/microsoft/playwright/issues/14047
"AvoidUnnecessaryBeforeUnloadCheckSync",
// See https://github.com/microsoft/playwright/issues/38568
"BoundaryEventDispatchTracksNodeRemoval",
"DestroyProfileOnBrowserClose",
// See https://github.com/microsoft/playwright/pull/13854
"DialMediaRouteProvider",

View File

@@ -284,11 +284,12 @@ class CRBrowser extends import_browser.Browser {
return this._clientRootSessionPromise;
}
}
const CREvents = {
ServiceWorker: "serviceworker"
};
class CRBrowserContext extends import_browserContext.BrowserContext {
static {
this.CREvents = {
ServiceWorker: "serviceworker"
};
this.CREvents = CREvents;
}
constructor(browser, browserContextId, options) {
super(browser, options, browserContextId);
@@ -388,15 +389,24 @@ class CRBrowserContext extends import_browserContext.BrowserContext {
["midi-sysex", "midiSysex"],
["storage-access", "storageAccess"],
["local-fonts", "localFonts"],
["local-network-access", "localNetworkAccess"]
["local-network-access", ["localNetworkAccess", "localNetwork", "loopbackNetwork"]]
]);
const filtered = permissions.map((permission) => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error("Unknown permission: " + permission);
return protocolPermission;
});
await this._browser._session.send("Browser.grantPermissions", { origin: origin === "*" ? void 0 : origin, browserContextId: this._browserContextId, permissions: filtered });
const grantPermissions = async (mapping) => {
const filtered = permissions.flatMap((permission) => {
const protocolPermission = mapping.get(permission);
if (!protocolPermission)
throw new Error("Unknown permission: " + permission);
return typeof protocolPermission === "string" ? [protocolPermission] : protocolPermission;
});
await this._browser._session.send("Browser.grantPermissions", { origin: origin === "*" ? void 0 : origin, browserContextId: this._browserContextId, permissions: filtered });
};
try {
await grantPermissions(webPermissionToProtocol);
} catch (e) {
const fallbackMapping = new Map(webPermissionToProtocol);
fallbackMapping.set("local-network-access", ["localNetworkAccess"]);
await grantPermissions(fallbackMapping);
}
}
async doClearPermissions() {
await this._browser._session.send("Browser.resetPermissions", { browserContextId: this._browserContextId });
@@ -470,7 +480,7 @@ class CRBrowserContext extends import_browserContext.BrowserContext {
}
}
async stopVideoRecording() {
await Promise.all(this._crPages().map((crPage) => crPage._mainFrameSession._stopVideoRecording()));
await Promise.all(this._crPages().map((crPage) => crPage._page.screencast.stopVideoRecording()));
}
onClosePersistent() {
}

View File

@@ -94,11 +94,6 @@ class CRSession extends import_instrumentation.SdkObject {
this._parentSession = parentSession;
this._sessionId = sessionId;
this._eventListener = eventListener;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
_markAsCrashed() {
this._crashed = true;

View File

@@ -44,8 +44,6 @@ class CRDevTools {
return;
const parsed = JSON.parse(event.payload);
let result = void 0;
if (this.__testHookOnBinding)
this.__testHookOnBinding(parsed);
if (parsed.method === "getPreferences") {
if (this._prefs === void 0) {
try {

View File

@@ -296,6 +296,10 @@ class CRNetworkManager {
if (requestPausedEvent) {
if (redirectedFrom || !this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) {
headersOverride = redirectedFrom?._originalRequestRoute?._alreadyContinuedParams?.headers;
if (headersOverride) {
const originalHeaders = Object.entries(requestPausedEvent.request.headers).map(([name, value]) => ({ name, value }));
headersOverride = network.applyHeadersOverrides(originalHeaders, headersOverride);
}
requestPausedSessionInfo.session._sendMayFail("Fetch.continueRequest", { requestId: requestPausedEvent.requestId, headers: headersOverride });
} else {
route = new RouteImpl(requestPausedSessionInfo.session, requestPausedEvent.requestId);
@@ -482,12 +486,11 @@ class InterceptableRequest {
url,
postDataEntries = null
} = requestPausedEvent ? requestPausedEvent.request : requestWillBeSentEvent.request;
const type = (requestWillBeSentEvent.type || "").toLowerCase();
let postDataBuffer = null;
const entries = postDataEntries?.filter((entry) => entry.bytes);
if (entries && entries.length)
postDataBuffer = Buffer.concat(entries.map((entry) => Buffer.from(entry.bytes, "base64")));
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, type, method, postDataBuffer, headersOverride || (0, import_utils.headersObjectToArray)(headers));
this.request = new network.Request(context, frame, serviceWorker, redirectedFrom?.request || null, documentId, url, toResourceType(requestWillBeSentEvent.type || "Other"), method, postDataBuffer, headersOverride || (0, import_utils.headersObjectToArray)(headers));
}
}
class RouteImpl {
@@ -660,6 +663,44 @@ class ResponseExtraInfoTracker {
this._requests.delete(requestId);
}
}
function toResourceType(type) {
switch (type) {
case "Document":
return "document";
case "Stylesheet":
return "stylesheet";
case "Image":
return "image";
case "Media":
return "media";
case "Font":
return "font";
case "Script":
return "script";
case "TextTrack":
return "texttrack";
case "XHR":
return "xhr";
case "Fetch":
return "fetch";
case "EventSource":
return "eventsource";
case "WebSocket":
return "websocket";
case "Manifest":
return "manifest";
case "Ping":
return "ping";
case "CSPViolationReport":
return "cspreport";
case "Prefetch":
case "SignedExchange":
case "Preflight":
case "FedCM":
default:
return "other";
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CRNetworkManager

View File

@@ -31,9 +31,7 @@ __export(crPage_exports, {
CRPage: () => CRPage
});
module.exports = __toCommonJS(crPage_exports);
var import_path = __toESM(require("path"));
var import_assert = require("../../utils/isomorphic/assert");
var import_crypto = require("../utils/crypto");
var import_eventsHelper = require("../utils/eventsHelper");
var import_stackTrace = require("../../utils/isomorphic/stackTrace");
var dialog = __toESM(require("../dialog"));
@@ -42,7 +40,6 @@ var frames = __toESM(require("../frames"));
var import_helper = require("../helper");
var network = __toESM(require("../network"));
var import_page = require("../page");
var import_registry = require("../registry");
var import_crCoverage = require("./crCoverage");
var import_crDragDrop = require("./crDragDrop");
var import_crExecutionContext = require("./crExecutionContext");
@@ -51,7 +48,6 @@ var import_crNetworkManager = require("./crNetworkManager");
var import_crPdf = require("./crPdf");
var import_crProtocolHelper = require("./crProtocolHelper");
var import_defaultFontFamilies = require("./defaultFontFamilies");
var import_videoRecorder = require("./videoRecorder");
var import_errors = require("../errors");
var import_protocolError = require("../protocolError");
class CRPage {
@@ -236,17 +232,16 @@ class CRPage {
async scrollRectIntoViewIfNeeded(handle, rect) {
return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect);
}
async setScreencastOptions(options) {
if (options) {
await this._mainFrameSession._startScreencast(this, {
format: "jpeg",
quality: options.quality,
maxWidth: options.width,
maxHeight: options.height
});
} else {
await this._mainFrameSession._stopScreencast(this);
}
async startScreencast(options) {
await this._mainFrameSession._client.send("Page.startScreencast", {
format: "jpeg",
quality: options.quality,
maxWidth: options.width,
maxHeight: options.height
});
}
async stopScreencast() {
await this._mainFrameSession._client._sendMayFail("Page.stopScreencast");
}
rafCountForStablePosition() {
return 1;
@@ -311,9 +306,6 @@ class FrameSession {
// Marks the oopif session that remote -> local transition has happened in the parent.
// See Target.detachedFromTarget handler for details.
this._swappedIn = false;
this._videoRecorder = null;
this._screencastId = null;
this._screencastClients = /* @__PURE__ */ new Set();
this._workerSessions = /* @__PURE__ */ new Map();
this._initScriptIds = /* @__PURE__ */ new Map();
this._client = client;
@@ -365,23 +357,9 @@ class FrameSession {
const { windowId } = await this._client.send("Browser.getWindowForTarget");
this._windowId = windowId;
}
let screencastOptions;
if (!this._page.isStorageStatePage && this._isMainFrame() && this._crPage._browserContext._options.recordVideo && hasUIWindow) {
const screencastId = (0, import_crypto.createGuid)();
const outputFile = import_path.default.join(this._crPage._browserContext._options.recordVideo.dir, screencastId + ".webm");
screencastOptions = {
// validateBrowserContextOptions ensures correct video size.
...this._crPage._browserContext._options.recordVideo.size,
outputFile
};
await this._crPage._browserContext._ensureVideosPath();
await this._createVideoRecorder(screencastId, screencastOptions);
this._crPage._page.waitForInitializedOrError().then((p) => {
if (p instanceof Error)
this._stopVideoRecording().catch(() => {
});
});
}
let videoOptions;
if (!this._page.isStorageStatePage && this._isMainFrame() && hasUIWindow)
videoOptions = this._crPage._page.screencast.launchVideoRecorder();
let lifecycleEventsEnabled;
if (!this._isMainFrame())
this._addRendererListeners();
@@ -461,15 +439,15 @@ class FrameSession {
true
/* runImmediately */
));
if (screencastOptions)
promises.push(this._startVideoRecording(screencastOptions));
if (videoOptions)
promises.push(this._crPage._page.screencast.startVideoRecording(videoOptions));
}
promises.push(this._client.send("Runtime.runIfWaitingForDebugger"));
promises.push(this._firstNonInitialNavigationCommittedPromise);
await Promise.all(promises);
}
dispose() {
this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError());
this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError(this._page.closeReason()));
for (const childSession of this._childSessions)
childSession.dispose();
if (this._parentSession)
@@ -730,63 +708,17 @@ class FrameSession {
}
}
_onScreencastFrame(payload) {
this._page.throttleScreencastFrameAck(() => {
this._client.send("Page.screencastFrameAck", { sessionId: payload.sessionId }).catch(() => {
});
this._page.screencast.throttleFrameAck(() => {
this._client._sendMayFail("Page.screencastFrameAck", { sessionId: payload.sessionId });
});
const buffer = Buffer.from(payload.data, "base64");
this._page.emit(import_page.Page.Events.ScreencastFrame, {
buffer,
frameSwapWallTime: payload.metadata.timestamp ? payload.metadata.timestamp * 1e3 : void 0,
frameSwapWallTime: payload.metadata.timestamp ? payload.metadata.timestamp * 1e3 : Date.now(),
width: payload.metadata.deviceWidth,
height: payload.metadata.deviceHeight
});
}
async _createVideoRecorder(screencastId, options) {
(0, import_assert.assert)(!this._screencastId);
const ffmpegPath = import_registry.registry.findExecutable("ffmpeg").executablePathOrDie(this._page.browserContext._browser.sdkLanguage());
this._videoRecorder = await import_videoRecorder.VideoRecorder.launch(this._crPage._page, ffmpegPath, options);
this._screencastId = screencastId;
}
async _startVideoRecording(options) {
const screencastId = this._screencastId;
(0, import_assert.assert)(screencastId);
this._page.once(import_page.Page.Events.Close, () => this._stopVideoRecording().catch(() => {
}));
const gotFirstFrame = new Promise((f) => this._client.once("Page.screencastFrame", f));
await this._startScreencast(this._videoRecorder, {
format: "jpeg",
quality: 90,
maxWidth: options.width,
maxHeight: options.height
});
gotFirstFrame.then(() => {
this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage._page.waitForInitializedOrError());
});
}
async _stopVideoRecording() {
if (!this._screencastId)
return;
const screencastId = this._screencastId;
this._screencastId = null;
const recorder = this._videoRecorder;
this._videoRecorder = null;
await this._stopScreencast(recorder);
await recorder.stop().catch(() => {
});
const video = this._crPage._browserContext._browser._takeVideo(screencastId);
video?.reportFinished();
}
async _startScreencast(client, options = {}) {
this._screencastClients.add(client);
if (this._screencastClients.size === 1)
await this._client.send("Page.startScreencast", options);
}
async _stopScreencast(client) {
this._screencastClients.delete(client);
if (!this._screencastClients.size)
await this._client._sendMayFail("Page.stopScreencast");
}
async _updateGeolocation(initial) {
const geolocation = this._crPage._browserContext._options.geolocation;
if (!initial || geolocation)

View File

@@ -90,7 +90,7 @@ class JavaScriptLanguageGenerator {
case "fill":
return `await ${subject}.${this._asLocator(action.selector)}.fill(${quote(action.text)});`;
case "setInputFiles":
return `await ${subject}.${this._asLocator(action.selector)}.setInputFiles(${formatObject(action.files.length === 1 ? action.files[0] : action.files)});`;
return `await ${subject}.${this._asLocator(action.selector)}.setInputFiles(${(0, import_utils.formatObject)(action.files.length === 1 ? action.files[0] : action.files)});`;
case "press": {
const modifiers = (0, import_language.toKeyboardModifiers)(action.modifiers);
const shortcut = [...modifiers, action.key].join("+");
@@ -99,7 +99,7 @@ class JavaScriptLanguageGenerator {
case "navigate":
return `await ${subject}.goto(${quote(action.url)});`;
case "select":
return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${formatObject(action.options.length === 1 ? action.options[0] : action.options)});`;
return `await ${subject}.${this._asLocator(action.selector)}.selectOption(${(0, import_utils.formatObject)(action.options.length === 1 ? action.options[0] : action.options)});`;
case "assertText":
return `${this._isTest ? "" : "// "}await expect(${subject}.${this._asLocator(action.selector)}).${action.substring ? "toContainText" : "toHaveText"}(${quote(action.text)});`;
case "assertChecked":
@@ -151,7 +151,7 @@ ${useText ? "\ntest.use(" + useText + ");\n" : ""}
const { ${options.browserName}${options.deviceName ? ", devices" : ""} } = require('playwright');
(async () => {
const browser = await ${options.browserName}.launch(${formatObjectOrVoid(options.launchOptions)});
const browser = await ${options.browserName}.launch(${(0, import_utils.formatObjectOrVoid)(options.launchOptions)});
const context = await browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName, false)});`);
if (options.contextOptions.recordHar)
formatter.add(` await context.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
@@ -171,37 +171,14 @@ function formatOptions(value, hasArguments) {
const keys = Object.keys(value).filter((key) => value[key] !== void 0);
if (!keys.length)
return "";
return (hasArguments ? ", " : "") + formatObject(value);
}
function formatObject(value, indent = " ") {
if (typeof value === "string")
return quote(value);
if (Array.isArray(value))
return `[${value.map((o) => formatObject(o)).join(", ")}]`;
if (typeof value === "object") {
const keys = Object.keys(value).filter((key) => value[key] !== void 0).sort();
if (!keys.length)
return "{}";
const tokens = [];
for (const key of keys)
tokens.push(`${key}: ${formatObject(value[key])}`);
return `{
${indent}${tokens.join(`,
${indent}`)}
}`;
}
return String(value);
}
function formatObjectOrVoid(value, indent = " ") {
const result = formatObject(value, indent);
return result === "{}" ? "" : result;
return (hasArguments ? ", " : "") + (0, import_utils.formatObject)(value);
}
function formatContextOptions(options, deviceName, isTest) {
const device = deviceName && import_deviceDescriptors.deviceDescriptors[deviceName];
options = { ...options, recordHar: void 0 };
if (!device)
return formatObjectOrVoid(options);
let serializedObject = formatObjectOrVoid((0, import_language.sanitizeDeviceOptions)(device, options));
return (0, import_utils.formatObjectOrVoid)(options);
let serializedObject = (0, import_utils.formatObjectOrVoid)((0, import_language.sanitizeDeviceOptions)(device, options));
if (!serializedObject)
serializedObject = "{\n}";
const lines = serializedObject.split("\n");

View File

@@ -110,7 +110,7 @@
"defaultBrowserType": "webkit"
},
"Galaxy S5": {
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@@ -121,7 +121,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@@ -132,7 +132,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S8": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 740
@@ -143,7 +143,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S8 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; SM-G950U Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 740,
"height": 360
@@ -154,7 +154,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S9+": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 320,
"height": 658
@@ -165,7 +165,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S9+ landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G965U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 658,
"height": 320
@@ -176,7 +176,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S24": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 780
@@ -187,7 +187,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy S24 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-S921U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 780,
"height": 360
@@ -198,7 +198,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy A55": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 480,
"height": 1040
@@ -209,7 +209,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy A55 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-A556B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 1040,
"height": 480
@@ -220,7 +220,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy Tab S4": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 712,
"height": 1138
@@ -231,7 +231,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy Tab S4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.1.0; SM-T837A) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 1138,
"height": 712
@@ -242,7 +242,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy Tab S9": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 640,
"height": 1024
@@ -253,7 +253,7 @@
"defaultBrowserType": "chromium"
},
"Galaxy Tab S9 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; SM-X710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 1024,
"height": 640
@@ -1208,7 +1208,7 @@
"defaultBrowserType": "webkit"
},
"LG Optimus L70": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 384,
"height": 640
@@ -1219,7 +1219,7 @@
"defaultBrowserType": "chromium"
},
"LG Optimus L70 landscape": {
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 384
@@ -1230,7 +1230,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 550": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 360,
"height": 640
@@ -1241,7 +1241,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 550 landscape": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 640,
"height": 360
@@ -1252,7 +1252,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 950": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 360,
"height": 640
@@ -1263,7 +1263,7 @@
"defaultBrowserType": "chromium"
},
"Microsoft Lumia 950 landscape": {
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36 Edge/14.14263",
"userAgent": "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36 Edge/14.14263",
"viewport": {
"width": 640,
"height": 360
@@ -1274,7 +1274,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 10": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 800,
"height": 1280
@@ -1285,7 +1285,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 10 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 1280,
"height": 800
@@ -1296,7 +1296,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 384,
"height": 640
@@ -1307,7 +1307,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 384
@@ -1318,7 +1318,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@@ -1329,7 +1329,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@@ -1340,7 +1340,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5X": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@@ -1351,7 +1351,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 5X landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@@ -1362,7 +1362,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@@ -1373,7 +1373,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@@ -1384,7 +1384,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6P": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 412,
"height": 732
@@ -1395,7 +1395,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 6P landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 732,
"height": 412
@@ -1406,7 +1406,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 600,
"height": 960
@@ -1417,7 +1417,7 @@
"defaultBrowserType": "chromium"
},
"Nexus 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"viewport": {
"width": 960,
"height": 600
@@ -1472,7 +1472,7 @@
"defaultBrowserType": "webkit"
},
"Pixel 2": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 411,
"height": 731
@@ -1483,7 +1483,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 2 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 731,
"height": 411
@@ -1494,7 +1494,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 2 XL": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 411,
"height": 823
@@ -1505,7 +1505,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 2 XL landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 823,
"height": 411
@@ -1516,7 +1516,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 3": {
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 393,
"height": 786
@@ -1527,7 +1527,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 3 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PQ1A.181105.017.A1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 786,
"height": 393
@@ -1538,7 +1538,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 353,
"height": 745
@@ -1549,7 +1549,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 745,
"height": 353
@@ -1560,7 +1560,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4a (5G)": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"screen": {
"width": 412,
"height": 892
@@ -1575,7 +1575,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 4a (5G) landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 4a (5G)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"screen": {
"height": 892,
"width": 412
@@ -1590,7 +1590,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 5": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"screen": {
"width": 393,
"height": 851
@@ -1605,7 +1605,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 5 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"screen": {
"width": 851,
"height": 393
@@ -1620,7 +1620,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 7": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"screen": {
"width": 412,
"height": 915
@@ -1635,7 +1635,7 @@
"defaultBrowserType": "chromium"
},
"Pixel 7 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 14; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"screen": {
"width": 915,
"height": 412
@@ -1650,7 +1650,7 @@
"defaultBrowserType": "chromium"
},
"Moto G4": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 360,
"height": 640
@@ -1661,7 +1661,7 @@
"defaultBrowserType": "chromium"
},
"Moto G4 landscape": {
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Mobile Safari/537.36",
"userAgent": "Mozilla/5.0 (Linux; Android 7.0; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Mobile Safari/537.36",
"viewport": {
"width": 640,
"height": 360
@@ -1672,7 +1672,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Chrome HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"screen": {
"width": 1792,
"height": 1120
@@ -1687,7 +1687,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Edge HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36 Edg/143.0.7499.4",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36 Edg/145.0.7632.6",
"screen": {
"width": 1792,
"height": 1120
@@ -1702,7 +1702,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox HiDPI": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0.2) Gecko/20100101 Firefox/144.0.2",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0.1) Gecko/20100101 Firefox/146.0.1",
"screen": {
"width": 1792,
"height": 1120
@@ -1732,7 +1732,7 @@
"defaultBrowserType": "webkit"
},
"Desktop Chrome": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36",
"screen": {
"width": 1920,
"height": 1080
@@ -1747,7 +1747,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Edge": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.7499.4 Safari/537.36 Edg/143.0.7499.4",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.7632.6 Safari/537.36 Edg/145.0.7632.6",
"screen": {
"width": 1920,
"height": 1080
@@ -1762,7 +1762,7 @@
"defaultBrowserType": "chromium"
},
"Desktop Firefox": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0.2) Gecko/20100101 Firefox/144.0.2",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0.1) Gecko/20100101 Firefox/146.0.1",
"screen": {
"width": 1920,
"height": 1080

View File

@@ -298,6 +298,9 @@ class BrowserContextDispatcher extends import_dispatcher.Dispatcher {
if (recorder)
recorder.setMode("none");
}
async exposeConsoleApi(params, progress) {
await this._context.exposeConsoleApi();
}
async pause(params, progress) {
}
async newCDPSession(params, progress) {

View File

@@ -85,11 +85,7 @@ class Dispatcher extends import_events.EventEmitter {
this.connection.sendAdopt(this, child);
}
async _runCommand(callMetadata, method, validParams) {
const controller = new import_progress.ProgressController(callMetadata, (message) => {
const logName = this._object.logName || "api";
import_utils.debugLogger.log(logName, message);
this._object.instrumentation.onCallLog(this._object, callMetadata, logName, message);
});
const controller = import_progress.ProgressController.createForSdkObject(this._object, callMetadata);
this._activeProgressControllers.add(controller);
try {
return await controller.run((progress) => this[method](validParams, progress), validParams?.timeout);
@@ -106,7 +102,7 @@ class Dispatcher extends import_events.EventEmitter {
this.connection.sendEvent(this, method, params);
}
_dispose(reason) {
this._disposeRecursively(new import_errors.TargetClosedError());
this._disposeRecursively(new import_errors.TargetClosedError(this._object.closeReason()));
this.connection.sendDispose(this, reason);
}
_onDispose() {
@@ -257,7 +253,7 @@ class DispatcherConnection {
const { id, guid, method, params, metadata } = message;
const dispatcher = this._dispatcherByGuid.get(guid);
if (!dispatcher) {
this.onmessage({ id, error: (0, import_errors.serializeError)(new import_errors.TargetClosedError()) });
this.onmessage({ id, error: (0, import_errors.serializeError)(new import_errors.TargetClosedError(void 0)) });
return;
}
let validParams;
@@ -325,19 +321,19 @@ class DispatcherConnection {
const response = { id };
try {
if (this._dispatcherByGuid.get(guid) !== dispatcher)
throw new import_errors.TargetClosedError(closeReason(sdkObject));
throw new import_errors.TargetClosedError(sdkObject.closeReason());
const result = await dispatcher._runCommand(callMetadata, method, validParams);
const validator = (0, import_validator.findValidator)(dispatcher._type, method, "Result");
response.result = validator(result, "", this._validatorToWireContext());
callMetadata.result = result;
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e)) {
const reason = closeReason(sdkObject);
const reason = sdkObject.closeReason();
if (reason)
(0, import_utils.rewriteErrorMessage)(e, reason);
} else if ((0, import_protocolError.isProtocolError)(e)) {
if (e.type === "closed")
e = new import_errors.TargetClosedError(closeReason(sdkObject), e.browserLogMessage());
e = new import_errors.TargetClosedError(sdkObject.closeReason(), e.browserLogMessage());
else if (e.type === "crashed")
(0, import_utils.rewriteErrorMessage)(e, "Target crashed " + e.browserLogMessage());
}
@@ -359,9 +355,6 @@ class DispatcherConnection {
await new Promise((f) => setTimeout(f, slowMo));
}
}
function closeReason(sdkObject) {
return sdkObject.attribution.page?.closeReason || sdkObject.attribution.context?._closeReason || sdkObject.attribution.browser?._closeReason;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Dispatcher,

View File

@@ -212,7 +212,7 @@ class FrameDispatcher extends import_dispatcher.Dispatcher {
let expectedValue = params.expectedValue ? (0, import_jsHandleDispatcher.parseArgument)(params.expectedValue) : void 0;
if (params.expression === "to.match.aria" && expectedValue)
expectedValue = (0, import_ariaSnapshot.parseAriaSnapshotUnsafe)(import_utilsBundle.yaml, expectedValue);
const result = await this._frame.expect(progress, params.selector, { ...params, expectedValue }, params.timeout);
const result = await this._frame.expect(progress, params.selector, { ...params, expectedValue, timeoutForLogs: params.timeout });
if (result.received !== void 0)
result.received = (0, import_jsHandleDispatcher.serializeResult)(result.received);
return result;

View File

@@ -0,0 +1,96 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var pageAgentDispatcher_exports = {};
__export(pageAgentDispatcher_exports, {
PageAgentDispatcher: () => PageAgentDispatcher
});
module.exports = __toCommonJS(pageAgentDispatcher_exports);
var import_dispatcher = require("./dispatcher");
var import_pageAgent = require("../agent/pageAgent");
var import_instrumentation = require("../instrumentation");
var import_context = require("../agent/context");
class PageAgentDispatcher extends import_dispatcher.Dispatcher {
constructor(scope, options) {
super(scope, new import_instrumentation.SdkObject(scope._object, "pageAgent"), "PageAgent", { page: scope });
this._type_PageAgent = true;
this._type_EventTarget = true;
this._usage = { turns: 0, inputTokens: 0, outputTokens: 0 };
this._page = scope._object;
this._context = new import_context.Context(this._page, options, this._eventSupport());
}
async perform(params, progress) {
try {
await (0, import_pageAgent.pageAgentPerform)(progress, this._context, params.task, params);
} finally {
this._context.pushHistory({ type: "perform", description: params.task });
}
return { usage: this._usage };
}
async expect(params, progress) {
try {
await (0, import_pageAgent.pageAgentExpect)(progress, this._context, params.expectation, params);
} finally {
this._context.pushHistory({ type: "expect", description: params.expectation });
}
return { usage: this._usage };
}
async extract(params, progress) {
const result = await (0, import_pageAgent.pageAgentExtract)(progress, this._context, params.query, params.schema, params);
return { result, usage: this._usage };
}
async usage(params, progress) {
return { usage: this._usage };
}
async dispose(params, progress) {
progress.metadata.potentiallyClosesScope = true;
void this.stopPendingOperations(new Error("The agent is disposed"));
this._dispose();
}
_eventSupport() {
const self = this;
return {
onBeforeTurn(params) {
const userMessage = params.conversation.messages.find((m) => m.role === "user");
self._dispatchEvent("turn", { role: "user", message: userMessage?.content ?? "" });
},
onAfterTurn(params) {
const usage = { inputTokens: params.totalUsage.input, outputTokens: params.totalUsage.output };
const intent = params.assistantMessage.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
self._dispatchEvent("turn", { role: "assistant", message: intent, usage });
if (!params.assistantMessage.content.filter((c) => c.type === "tool_call").length)
self._dispatchEvent("turn", { role: "assistant", message: `no tool calls`, usage });
self._usage = { turns: self._usage.turns + 1, inputTokens: self._usage.inputTokens + usage.inputTokens, outputTokens: self._usage.outputTokens + usage.outputTokens };
},
onBeforeToolCall(params) {
self._dispatchEvent("turn", { role: "assistant", message: `call tool "${params.toolCall.name}"` });
},
onAfterToolCall(params) {
const suffix = params.toolCall.result?.isError ? "failed" : "succeeded";
self._dispatchEvent("turn", { role: "user", message: `tool "${params.toolCall.name}" ${suffix}` });
},
onToolCallError(params) {
self._dispatchEvent("turn", { role: "user", message: `tool "${params.toolCall.name}" failed: ${params.error.message}` });
}
};
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
PageAgentDispatcher
});

View File

@@ -36,6 +36,7 @@ var import_networkDispatchers3 = require("./networkDispatchers");
var import_webSocketRouteDispatcher = require("./webSocketRouteDispatcher");
var import_instrumentation = require("../instrumentation");
var import_urlMatch = require("../../utils/isomorphic/urlMatch");
var import_pageAgentDispatcher = require("./pageAgentDispatcher");
class PageDispatcher extends import_dispatcher.Dispatcher {
constructor(parentScope, page) {
const mainFrame = import_frameDispatcher.FrameDispatcher.from(parentScope, page.mainFrame());
@@ -288,6 +289,9 @@ class PageDispatcher extends import_dispatcher.Dispatcher {
const coverage = this._page.coverage;
return await coverage.stopCSSCoverage();
}
async agent(params, progress) {
return { agent: new import_pageAgentDispatcher.PageAgentDispatcher(this, params) };
}
_onFrameAttached(frame) {
this._dispatchEvent("frameAttached", { frame: import_frameDispatcher.FrameDispatcher.from(this.parentScope(), frame) });
}

View File

@@ -255,6 +255,7 @@ class ElementHandle extends js.JSHandle {
async _retryAction(progress, actionName, action, options) {
let retry = 0;
const waitTime = [0, 20, 100, 100, 500];
const noAutoWaiting = options.__testHookNoAutoWaiting ?? options.noAutoWaiting;
while (true) {
if (retry) {
progress.log(`retrying ${actionName} action${options.trial ? " (trial run)" : ""}`);
@@ -268,35 +269,43 @@ class ElementHandle extends js.JSHandle {
} else {
progress.log(`attempting ${actionName} action${options.trial ? " (trial run)" : ""}`);
}
if (!options.skipActionPreChecks && !options.force)
if (!options.skipActionPreChecks && !options.force && !noAutoWaiting)
await this._frame._page.performActionPreChecks(progress);
const result = await action(retry);
++retry;
if (result === "error:notvisible") {
if (options.force)
if (options.force || noAutoWaiting)
throw new NonRecoverableDOMError("Element is not visible");
progress.log(" element is not visible");
continue;
}
if (result === "error:notinviewport") {
if (options.force)
if (options.force || noAutoWaiting)
throw new NonRecoverableDOMError("Element is outside of the viewport");
progress.log(" element is outside of the viewport");
continue;
}
if (result === "error:optionsnotfound") {
if (noAutoWaiting)
throw new NonRecoverableDOMError("Did not find some options");
progress.log(" did not find some options");
continue;
}
if (result === "error:optionnotenabled") {
if (noAutoWaiting)
throw new NonRecoverableDOMError("Option being selected is not enabled");
progress.log(" option being selected is not enabled");
continue;
}
if (typeof result === "object" && "hitTargetDescription" in result) {
if (noAutoWaiting)
throw new NonRecoverableDOMError(`${result.hitTargetDescription} intercepts pointer events`);
progress.log(` ${result.hitTargetDescription} intercepts pointer events`);
continue;
}
if (typeof result === "object" && "missingState" in result) {
if (noAutoWaiting)
throw new NonRecoverableDOMError(`Element is not ${result.missingState}`);
progress.log(` element is not ${result.missingState}`);
continue;
}

View File

@@ -158,8 +158,8 @@ class Electron extends import_instrumentation.SdkObject {
let shell = false;
if (process.platform === "win32") {
shell = true;
command = `"${command}"`;
electronArguments = electronArguments.map((arg) => `"${arg}"`);
command = [command, ...electronArguments].map((arg) => `"${escapeDoubleQuotes(arg)}"`).join(" ");
electronArguments = [];
}
delete env.NODE_OPTIONS;
const { launchedProcess, gracefullyClose, kill } = await (0, import_processLauncher.launchProcess)({
@@ -263,6 +263,9 @@ async function waitForLine(progress, process2, regex) {
import_eventsHelper.eventsHelper.removeEventListeners(listeners);
}
}
function escapeDoubleQuotes(str) {
return str.replace(/"/g, '\\"');
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Electron,

View File

@@ -54,7 +54,6 @@ class FFBrowser extends import_browser.Browser {
this.session.on("Browser.detachedFromTarget", this._onDetachedFromTarget.bind(this));
this.session.on("Browser.downloadCreated", this._onDownloadCreated.bind(this));
this.session.on("Browser.downloadFinished", this._onDownloadFinished.bind(this));
this.session.on("Browser.videoRecordingFinished", this._onVideoRecordingFinished.bind(this));
}
static async connect(parent, transport, options) {
const connection = new import_ffConnection.FFConnection(transport, options.protocolLogger, options.browserLogsCollector);
@@ -141,12 +140,9 @@ class FFBrowser extends import_browser.Browser {
const error = payload.canceled ? "canceled" : payload.error;
this._downloadFinished(payload.uuid, error);
}
_onVideoRecordingFinished(payload) {
this._takeVideo(payload.screencastId)?.reportFinished();
}
_onDisconnect() {
for (const video of this._idToVideo.values())
video.artifact.reportFinished(new import_errors.TargetClosedError());
video.artifact.reportFinished(new import_errors.TargetClosedError(this.closeReason()));
this._idToVideo.clear();
for (const ffPage of this._ffPages.values())
ffPage.didClose();
@@ -199,15 +195,13 @@ class FFBrowserContext extends import_browserContext.BrowserContext {
promises.push(this.doUpdateOffline());
promises.push(this.doUpdateDefaultEmulatedMedia());
if (this._options.recordVideo) {
promises.push(this._ensureVideosPath().then(() => {
return this._browser.session.send("Browser.setVideoRecordingOptions", {
// validateBrowserContextOptions ensures correct video size.
options: {
...this._options.recordVideo.size,
dir: this._options.recordVideo.dir
},
browserContextId: this._browserContextId
});
promises.push(this._browser.session.send("Browser.setScreencastOptions", {
// validateBrowserContextOptions ensures correct video size.
options: {
...this._options.recordVideo.size,
quality: 90
},
browserContextId: this._browserContextId
}));
}
const proxy = this._options.proxyOverride || this._options.proxy;
@@ -379,12 +373,8 @@ class FFBrowserContext extends import_browserContext.BrowserContext {
}
async doClose(reason) {
if (!this._browserContextId) {
if (this._options.recordVideo) {
await this._browser.session.send("Browser.setVideoRecordingOptions", {
options: void 0,
browserContextId: this._browserContextId
});
}
if (this._options.recordVideo)
await Promise.all(this._ffPages().map((ffPage) => ffPage._page.screencast.stopVideoRecording()));
await this._browser.close({ reason });
} else {
await this._browser.session.send("Browser.removeBrowserContext", { browserContextId: this._browserContextId });

View File

@@ -90,11 +90,6 @@ class FFSession extends import_events.EventEmitter {
this._connection = connection;
this._sessionId = sessionId;
this._rawSend = rawSend;
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
markAsCrashed() {
this._crashed = true;

View File

@@ -170,9 +170,9 @@ const causeToResourceType = {
TYPE_FONT: "font",
TYPE_MEDIA: "media",
TYPE_WEBSOCKET: "websocket",
TYPE_CSP_REPORT: "other",
TYPE_CSP_REPORT: "cspreport",
TYPE_XSLT: "other",
TYPE_BEACON: "other",
TYPE_BEACON: "beacon",
TYPE_FETCH: "fetch",
TYPE_IMAGESET: "image",
TYPE_WEB_MANIFEST: "manifest"

View File

@@ -41,9 +41,9 @@ var import_ffConnection = require("./ffConnection");
var import_ffExecutionContext = require("./ffExecutionContext");
var import_ffInput = require("./ffInput");
var import_ffNetworkManager = require("./ffNetworkManager");
var import_debugLogger = require("../utils/debugLogger");
var import_stackTrace = require("../../utils/isomorphic/stackTrace");
var import_errors = require("../errors");
var import_debugLogger = require("../utils/debugLogger");
const UTILITY_WORLD_NAME = "__playwright_utility_world__";
class FFPage {
constructor(session, browserContext, opener) {
@@ -83,13 +83,16 @@ class FFPage {
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.workerDestroyed", this._onWorkerDestroyed.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.dispatchMessageFromWorker", this._onDispatchMessageFromWorker.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.crashed", this._onCrashed.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.videoRecordingStarted", this._onVideoRecordingStarted.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.webSocketCreated", this._onWebSocketCreated.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.webSocketClosed", this._onWebSocketClosed.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.webSocketFrameReceived", this._onWebSocketFrameReceived.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.webSocketFrameSent", this._onWebSocketFrameSent.bind(this)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Page.screencastFrame", this._onScreencastFrame.bind(this))
];
const screencast = this._page.screencast;
const videoOptions = screencast.launchVideoRecorder();
if (videoOptions)
screencast.startVideoRecording(videoOptions).catch((e) => import_debugLogger.debugLogger.log("error", e));
this._session.once("Page.ready", () => {
if (this._reportedAsNew)
return;
@@ -272,11 +275,8 @@ class FFPage {
this._session.markAsCrashed();
this._page._didCrash();
}
_onVideoRecordingStarted(event) {
this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this._page.waitForInitializedOrError());
}
didClose() {
this._markAsError(new import_errors.TargetClosedError());
this._markAsError(new import_errors.TargetClosedError(this._page.closeReason()));
this._session.dispose();
import_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
this._networkManager.dispose();
@@ -417,24 +417,21 @@ class FFPage {
throw e;
});
}
async setScreencastOptions(options) {
if (options) {
const { screencastId } = await this._session.send("Page.startScreencast", options);
this._screencastId = screencastId;
} else {
await this._session.send("Page.stopScreencast");
}
async startScreencast(options) {
await this._session.send("Page.startScreencast", options);
}
async stopScreencast() {
await this._session.sendMayFail("Page.stopScreencast");
}
_onScreencastFrame(event) {
if (!this._screencastId)
return;
const screencastId = this._screencastId;
this._page.throttleScreencastFrameAck(() => {
this._session.send("Page.screencastFrameAck", { screencastId }).catch((e) => import_debugLogger.debugLogger.log("error", e));
this._page.screencast.throttleFrameAck(() => {
this._session.sendMayFail("Page.screencastFrameAck");
});
const buffer = Buffer.from(event.data, "base64");
this._page.emit(import_page2.Page.Events.ScreencastFrame, {
buffer,
frameSwapWallTime: event.timestamp * 1e3,
// timestamp is in seconds, we need to convert to milliseconds.
width: event.deviceWidth,
height: event.deviceHeight
});

View File

@@ -56,15 +56,13 @@ class Firefox extends import_browserType.BrowserType {
connectToTransport(transport, options) {
return import_ffBrowser.FFBrowser.connect(this.attribution.playwright, transport, options);
}
doRewriteStartupLog(error) {
if (!error.logs)
return error;
if (error.logs.includes(`as root in a regular user's session is not supported.`))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(`Firefox is unable to launch if the $HOME folder isn't owned by the current user.
doRewriteStartupLog(logs) {
if (logs.includes(`as root in a regular user's session is not supported.`))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(`Firefox is unable to launch if the $HOME folder isn't owned by the current user.
Workaround: Set the HOME=/root environment variable${process.env.GITHUB_ACTION ? " in your GitHub Actions workflow file" : ""} when running Playwright.`, 1);
if (error.logs.includes("no DISPLAY environment variable specified"))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
return error;
if (logs.includes("no DISPLAY environment variable specified"))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
return logs;
}
amendEnvironment(env) {
if (!import_path.default.isAbsolute(import_os.default.homedir()))

View File

@@ -50,7 +50,9 @@ class FrameSelectors {
if (!resolved)
throw new Error(`Failed to find frame for selector "${selector}"`);
return await resolved.injected.evaluateHandle((injected, { info, scope: scope2 }) => {
return injected.querySelectorAll(info.parsed, scope2 || document);
const elements = injected.querySelectorAll(info.parsed, scope2 || document);
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return elements;
}, { info: resolved.info, scope: resolved.scope });
}
async queryCount(selector, options) {
@@ -59,7 +61,9 @@ class FrameSelectors {
throw new Error(`Failed to find frame for selector "${selector}"`);
await options.__testHookBeforeQuery?.();
return await resolved.injected.evaluate((injected, { info }) => {
return injected.querySelectorAll(info.parsed, document).length;
const elements = injected.querySelectorAll(info.parsed, document);
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return elements.length;
}, { info: resolved.info });
}
async queryAll(selector, scope) {
@@ -67,7 +71,9 @@ class FrameSelectors {
if (!resolved)
return [];
const arrayHandle = await resolved.injected.evaluateHandle((injected, { info, scope: scope2 }) => {
return injected.querySelectorAll(info.parsed, scope2 || document);
const elements = injected.querySelectorAll(info.parsed, scope2 || document);
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return elements;
}, { info: resolved.info, scope: resolved.scope });
const properties = await arrayHandle.getProperties();
arrayHandle.dispose();

View File

@@ -350,6 +350,11 @@ class FrameManager {
frame.emit(Frame.Events.InternalNavigation, event);
}
}
const FrameEvent = {
InternalNavigation: "internalnavigation",
AddLifecycle: "addlifecycle",
RemoveLifecycle: "removelifecycle"
};
class Frame extends import_instrumentation.SdkObject {
constructor(page, id, parentFrame) {
super(page, "frame");
@@ -382,11 +387,7 @@ class Frame extends import_instrumentation.SdkObject {
this._startNetworkIdleTimer();
}
static {
this.Events = {
InternalNavigation: "internalnavigation",
AddLifecycle: "addlifecycle",
RemoveLifecycle: "removelifecycle"
};
this.Events = FrameEvent;
}
isDetached() {
return this._detachedScope.isClosed();
@@ -569,7 +570,7 @@ class Frame extends import_instrumentation.SdkObject {
const request = navigationEvent.newDocument ? navigationEvent.newDocument.request : void 0;
return request ? progress.race(request._finalRequest().response()) : null;
}
async _waitForLoadState(progress, state) {
async waitForLoadState(progress, state) {
const waitUntil = verifyLifecycle("state", state);
if (!this._firedLifecycleEvents.has(waitUntil))
await import_helper.helper.waitForEvent(progress, this, Frame.Events.AddLifecycle, (e) => e === waitUntil).promise;
@@ -640,6 +641,7 @@ class Frame extends import_instrumentation.SdkObject {
} else if (element2) {
log2 = ` locator resolved to ${visible2 ? "visible" : "hidden"} ${injected.previewNode(element2)}`;
}
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return { log: log2, element: element2, visible: visible2, attached: !!element2 };
}, { info: resolved.info, root: resolved.frame === this ? scope : void 0 }));
const { log, visible, attached } = await progress.race(result.evaluate((r) => ({ log: r.log, visible: r.visible, attached: r.attached })));
@@ -735,7 +737,7 @@ class Frame extends import_instrumentation.SdkObject {
this._onClearLifecycle();
tagPromise.resolve();
});
const lifecyclePromise = progress.race(tagPromise).then(() => this._waitForLoadState(progress, waitUntil));
const lifecyclePromise = progress.race(tagPromise).then(() => this.waitForLoadState(progress, waitUntil));
const contentPromise = progress.race(context.evaluate(({ html: html2, tag: tag2 }) => {
document.open();
console.debug(tag2);
@@ -905,14 +907,19 @@ class Frame extends import_instrumentation.SdkObject {
return true;
return false;
}
async _retryWithProgressIfNotConnected(progress, selector, strict, performActionPreChecks, action) {
async _retryWithProgressIfNotConnected(progress, selector, options, action) {
progress.log(`waiting for ${this._asLocator(selector)}`);
const noAutoWaiting = options.__testHookNoAutoWaiting ?? options.noAutoWaiting;
const performActionPreChecks = (options.performActionPreChecks ?? !options.force) && !noAutoWaiting;
return this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async (continuePolling) => {
if (performActionPreChecks)
await this._page.performActionPreChecks(progress);
const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, { strict }));
if (!resolved)
const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, { strict: options.strict }));
if (!resolved) {
if (noAutoWaiting)
throw new dom.NonRecoverableDOMError("Element(s) not found");
return continuePolling;
}
const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, callId }) => {
const elements = injected.querySelectorAll(info.parsed, document);
if (callId)
@@ -926,12 +933,15 @@ class Frame extends import_instrumentation.SdkObject {
} else if (element2) {
log2 = ` locator resolved to ${injected.previewNode(element2)}`;
}
injected.checkDeprecatedSelectorUsage(info.parsed, elements);
return { log: log2, success: !!element2, element: element2 };
}, { info: resolved.info, callId: progress.metadata.id }));
const { log, success } = await progress.race(result.evaluate((r) => ({ log: r.log, success: r.success })));
if (log)
progress.log(log);
if (!success) {
if (noAutoWaiting)
throw new dom.NonRecoverableDOMError("Element(s) not found");
result.dispose();
return continuePolling;
}
@@ -940,6 +950,8 @@ class Frame extends import_instrumentation.SdkObject {
try {
const result2 = await action(element);
if (result2 === "error:notconnected") {
if (noAutoWaiting)
throw new dom.NonRecoverableDOMError("Element is not attached to the DOM");
progress.log("element was detached from the DOM, retrying");
return continuePolling;
}
@@ -950,19 +962,19 @@ class Frame extends import_instrumentation.SdkObject {
});
}
async rafrafTimeoutScreenshotElementWithProgress(progress, selector, timeout, options) {
return await this._retryWithProgressIfNotConnected(progress, selector, true, true, async (handle) => {
return await this._retryWithProgressIfNotConnected(progress, selector, { strict: true, performActionPreChecks: true }, async (handle) => {
await handle._frame.rafrafTimeout(progress, timeout);
return await this._page.screenshotter.screenshotElement(progress, handle, options);
});
}
async click(progress, selector, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._click(progress, { ...options, waitAfter: !options.noWaitAfter })));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._click(progress, { ...options, waitAfter: !options.noWaitAfter })));
}
async dblclick(progress, selector, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._dblclick(progress, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._dblclick(progress, options)));
}
async dragAndDrop(progress, source, target, options) {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, options.strict, !options.force, async (handle) => {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, source, options, async (handle) => {
return handle._retryPointerAction(progress, "move and down", false, async (point) => {
await this._page.mouse.move(progress, point.x, point.y);
await this._page.mouse.down(progress);
@@ -972,7 +984,7 @@ class Frame extends import_instrumentation.SdkObject {
position: options.sourcePosition
});
}));
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, options.strict, false, async (handle) => {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, target, { ...options, performActionPreChecks: false }, async (handle) => {
return handle._retryPointerAction(progress, "move and up", false, async (point) => {
await this._page.mouse.move(progress, point.x, point.y, { steps: options.steps });
await this._page.mouse.up(progress);
@@ -986,16 +998,16 @@ class Frame extends import_instrumentation.SdkObject {
async tap(progress, selector, options) {
if (!this._page.browserContext._options.hasTouch)
throw new Error("The page does not support tap. Use hasTouch context option to enable touch support.");
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._tap(progress, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._tap(progress, options)));
}
async fill(progress, selector, value, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._fill(progress, value, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._fill(progress, value, options)));
}
async focus(progress, selector, options) {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true, (handle) => handle._focus(progress)));
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._focus(progress)));
}
async blur(progress, selector, options) {
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true, (handle) => handle._blur(progress)));
dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._blur(progress)));
}
async resolveSelector(progress, selector, options = {}) {
const element = await progress.race(this.selectors.query(selector, options));
@@ -1109,35 +1121,35 @@ class Frame extends import_instrumentation.SdkObject {
return this._elementState(progress, selector, "checked", options, scope);
}
async hover(progress, selector, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._hover(progress, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._hover(progress, options)));
}
async selectOption(progress, selector, elements, values, options) {
return await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._selectOption(progress, elements, values, options));
return await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._selectOption(progress, elements, values, options));
}
async setInputFiles(progress, selector, params) {
const inputFileItems = await (0, import_fileUploadUtils.prepareFilesForUpload)(this, params);
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params.strict, true, (handle) => handle._setInputFiles(progress, inputFileItems)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, params, (handle) => handle._setInputFiles(progress, inputFileItems)));
}
async type(progress, selector, text, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true, (handle) => handle._type(progress, text, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._type(progress, text, options)));
}
async press(progress, selector, key, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true, (handle) => handle._press(progress, key, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._press(progress, key, options)));
}
async check(progress, selector, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._setChecked(progress, true, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._setChecked(progress, true, options)));
}
async uncheck(progress, selector, options) {
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, !options.force, (handle) => handle._setChecked(progress, false, options)));
return dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options, (handle) => handle._setChecked(progress, false, options)));
}
async waitForTimeout(progress, timeout) {
return progress.wait(timeout);
}
async ariaSnapshot(progress, selector) {
return await this._retryWithProgressIfNotConnected(progress, selector, true, true, (handle) => progress.race(handle.ariaSnapshot()));
return await this._retryWithProgressIfNotConnected(progress, selector, { strict: true, performActionPreChecks: true }, (handle) => progress.race(handle.ariaSnapshot()));
}
async expect(progress, selector, options, timeout) {
progress.log(`${(0, import_utils.renderTitleForCall)(progress.metadata)}${timeout ? ` with timeout ${timeout}ms` : ""}`);
async expect(progress, selector, options) {
progress.log(`${(0, import_utils.renderTitleForCall)(progress.metadata)}${options.timeoutForLogs ? ` with timeout ${options.timeoutForLogs}ms` : ""}`);
const lastIntermediateResult = { isSet: false };
const fixupMetadataError = (result) => {
if (result.matches === options.isNot)
@@ -1146,17 +1158,19 @@ class Frame extends import_instrumentation.SdkObject {
try {
if (selector)
progress.log(`waiting for ${this._asLocator(selector)}`);
await this._page.performActionPreChecks(progress);
if (!options.noAutoWaiting)
await this._page.performActionPreChecks(progress);
try {
const resultOneShot = await this._expectInternal(progress, selector, options, lastIntermediateResult, true);
if (resultOneShot.matches !== options.isNot)
if (options.noAutoWaiting || resultOneShot.matches !== options.isNot)
return resultOneShot;
} catch (e) {
if (this.isNonRetriableError(e))
if (options.noAutoWaiting || this.isNonRetriableError(e))
throw e;
}
const result = await this.retryWithProgressAndTimeouts(progress, [100, 250, 500, 1e3], async (continuePolling) => {
await this._page.performActionPreChecks(progress);
if (!options.noAutoWaiting)
await this._page.performActionPreChecks(progress);
const { matches, received } = await this._expectInternal(progress, selector, options, lastIntermediateResult, false);
if (matches === options.isNot) {
return continuePolling;
@@ -1200,6 +1214,8 @@ class Frame extends import_instrumentation.SdkObject {
throw injected2.strictModeViolationError(info2.parsed, elements);
else if (elements.length)
log2 = ` locator resolved to ${injected2.previewNode(elements[0])}`;
if (info2)
injected2.checkDeprecatedSelectorUsage(info2.parsed, elements);
return { log: log2, ...await injected2.expect(elements[0], options2, elements) };
}, { info, options, callId: progress.metadata.id }));
if (log)

View File

@@ -33,6 +33,9 @@ class SdkObject extends import_events.EventEmitter {
this.attribution = { ...parent.attribution };
this.instrumentation = parent.instrumentation;
}
closeReason() {
return this.attribution.page?._closeReason || this.attribution.context?._closeReason || this.attribution.browser?._closeReason;
}
}
function createRootSdkObject() {
const fakeParent = { attribution: {}, instrumentation: createInstrumentation() };

View File

@@ -22,6 +22,7 @@ __export(network_exports, {
Response: () => Response,
Route: () => Route,
WebSocket: () => WebSocket,
applyHeadersOverrides: () => applyHeadersOverrides,
filterCookies: () => filterCookies,
isLocalHostname: () => isLocalHostname,
kMaxCookieExpiresDateInSeconds: () => kMaxCookieExpiresDateInSeconds,
@@ -61,6 +62,48 @@ function filterCookies(cookies, urls) {
function isLocalHostname(hostname) {
return hostname === "localhost" || hostname.endsWith(".localhost");
}
const FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
"accept-charset",
"accept-encoding",
"access-control-request-headers",
"access-control-request-method",
"connection",
"content-length",
"cookie",
"date",
"dnt",
"expect",
"host",
"keep-alive",
"origin",
"referer",
"set-cookie",
"te",
"trailer",
"transfer-encoding",
"upgrade",
"via"
]);
const FORBIDDEN_METHODS = /* @__PURE__ */ new Set(["CONNECT", "TRACE", "TRACK"]);
function isForbiddenHeader(name, value) {
const lowerName = name.toLowerCase();
if (FORBIDDEN_HEADER_NAMES.has(lowerName))
return true;
if (lowerName.startsWith("proxy-"))
return true;
if (lowerName.startsWith("sec-"))
return true;
if (lowerName === "x-http-method" || lowerName === "x-http-method-override" || lowerName === "x-method-override") {
if (value && FORBIDDEN_METHODS.has(value.toUpperCase()))
return true;
}
return false;
}
function applyHeadersOverrides(original, overrides) {
const forbiddenHeaders = original.filter((header) => isForbiddenHeader(header.name, header.value));
const allowedHeaders = overrides.filter((header) => !isForbiddenHeader(header.name, header.value));
return mergeHeaders([allowedHeaders, forbiddenHeaders]);
}
const kMaxCookieExpiresDateInSeconds = 253402300799;
function rewriteCookies(cookies) {
return cookies.map((c) => {
@@ -99,7 +142,6 @@ class Request extends import_instrumentation.SdkObject {
this._response = null;
this._redirectedTo = null;
this._failureText = null;
this._headersMap = /* @__PURE__ */ new Map();
this._frame = null;
this._serviceWorker = null;
this._rawRequestHeadersPromise = new import_manualPromise.ManualPromise();
@@ -118,7 +160,6 @@ class Request extends import_instrumentation.SdkObject {
this._method = method;
this._postData = postData;
this._headers = headers;
this._updateHeadersMap();
this._isFavicon = url.endsWith("/favicon.ico") || !!redirectedFrom?._isFavicon;
}
static {
@@ -132,13 +173,8 @@ class Request extends import_instrumentation.SdkObject {
}
_applyOverrides(overrides) {
this._overrides = { ...this._overrides, ...overrides };
this._updateHeadersMap();
return this._overrides;
}
_updateHeadersMap() {
for (const { name, value } of this.headers())
this._headersMap.set(name.toLowerCase(), value);
}
overrides() {
return this._overrides;
}
@@ -158,7 +194,8 @@ class Request extends import_instrumentation.SdkObject {
return this._overrides?.headers || this._headers;
}
headerValue(name) {
return this._headersMap.get(name);
const lowerCaseName = name.toLowerCase();
return this.headers().find((h) => h.name.toLowerCase() === lowerCaseName)?.value;
}
// "null" means no raw headers available - we'll use provisional headers as raw headers.
setRawRequestHeaders(headers) {
@@ -309,10 +346,7 @@ class Route extends import_instrumentation.SdkObject {
throw new Error("New URL must have same protocol as overridden URL");
}
if (overrides.headers) {
overrides.headers = overrides.headers?.filter((header) => {
const headerName = header.name.toLowerCase();
return headerName !== "cookie" && headerName !== "host";
});
overrides.headers = applyHeadersOverrides(this._request._headers, overrides.headers);
}
overrides = this._request._applyOverrides(overrides);
const nextHandler = this._futureHandlers.shift();
@@ -436,6 +470,9 @@ class Response extends import_instrumentation.SdkObject {
request() {
return this._request;
}
finished() {
return this._finishedPromise;
}
frame() {
return this._request.frame();
}
@@ -617,6 +654,7 @@ function mergeHeaders(headers) {
Response,
Route,
WebSocket,
applyHeadersOverrides,
filterCookies,
isLocalHostname,
kMaxCookieExpiresDateInSeconds,

View File

@@ -31,7 +31,8 @@ __export(page_exports, {
InitScript: () => InitScript,
Page: () => Page,
PageBinding: () => PageBinding,
Worker: () => Worker
Worker: () => Worker,
WorkerEvent: () => WorkerEvent
});
module.exports = __toCommonJS(page_exports);
var import_browserContext = require("./browserContext");
@@ -53,6 +54,22 @@ var import_manualPromise = require("../utils/isomorphic/manualPromise");
var import_utilityScriptSerializers = require("../utils/isomorphic/utilityScriptSerializers");
var import_callLog = require("./callLog");
var rawBindingsControllerSource = __toESM(require("../generated/bindingsControllerSource"));
var import_screencast = require("./screencast");
const PageEvent = {
Close: "close",
Crash: "crash",
Download: "download",
EmulatedSizeChanged: "emulatedsizechanged",
FileChooser: "filechooser",
FrameAttached: "frameattached",
FrameDetached: "framedetached",
InternalFrameNavigatedToNewDocument: "internalframenavigatedtonewdocument",
LocatorHandlerTriggered: "locatorhandlertriggered",
ScreencastFrame: "screencastframe",
Video: "video",
WebSocket: "websocket",
Worker: "worker"
};
class Page extends import_instrumentation.SdkObject {
constructor(delegate, browserContext) {
super(browserContext, "page");
@@ -74,9 +91,6 @@ class Page extends import_instrumentation.SdkObject {
this._lastLocatorHandlerUid = 0;
this._locatorHandlerRunningCounter = 0;
this._networkRequests = [];
// Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
// When throttling for tracing, 200ms between frames, except for 10 frames around the action.
this._frameThrottler = new FrameThrottler(10, 35, 200);
this.attribution.page = this;
this.delegate = delegate;
this.browserContext = browserContext;
@@ -85,27 +99,14 @@ class Page extends import_instrumentation.SdkObject {
this.touchscreen = new input.Touchscreen(delegate.rawTouchscreen, this);
this.screenshotter = new import_screenshotter.Screenshotter(this);
this.frameManager = new frames.FrameManager(this);
this.screencast = new import_screencast.Screencast(this);
if (delegate.pdf)
this.pdf = delegate.pdf.bind(delegate);
this.coverage = delegate.coverage ? delegate.coverage() : null;
this.isStorageStatePage = browserContext.isCreatingStorageStatePage();
}
static {
this.Events = {
Close: "close",
Crash: "crash",
Download: "download",
EmulatedSizeChanged: "emulatedsizechanged",
FileChooser: "filechooser",
FrameAttached: "frameattached",
FrameDetached: "framedetached",
InternalFrameNavigatedToNewDocument: "internalframenavigatedtonewdocument",
LocatorHandlerTriggered: "locatorhandlertriggered",
ScreencastFrame: "screencastframe",
Video: "video",
WebSocket: "websocket",
Worker: "worker"
};
this.Events = PageEvent;
}
async reportAsNew(opener, error) {
if (opener) {
@@ -158,17 +159,17 @@ class Page extends import_instrumentation.SdkObject {
}
_didClose() {
this.frameManager.dispose();
this._frameThrottler.dispose();
this.screencast.stopFrameThrottler();
(0, import_utils.assert)(this._closedState !== "closed", "Page closed twice");
this._closedState = "closed";
this.emit(Page.Events.Close);
this._closedPromise.resolve();
this.instrumentation.onPageClose(this);
this.openScope.close(new import_errors.TargetClosedError());
this.openScope.close(new import_errors.TargetClosedError(this.closeReason()));
}
_didCrash() {
this.frameManager.dispose();
this._frameThrottler.dispose();
this.screencast.stopFrameThrottler();
this.emit(Page.Events.Crash);
this._crashed = true;
this.instrumentation.onPageClose(this);
@@ -578,7 +579,7 @@ class Page extends import_instrumentation.SdkObject {
if (this._closedState === "closed")
return;
if (options.reason)
this.closeReason = options.reason;
this._closeReason = options.reason;
const runBeforeUnload = !!options.runBeforeUnload;
if (this._closedState !== "closing") {
if (!runBeforeUnload)
@@ -641,16 +642,6 @@ class Page extends import_instrumentation.SdkObject {
getBinding(name) {
return this._pageBindings.get(name) || this.browserContext._pageBindings.get(name);
}
setScreencastOptions(options) {
this.delegate.setScreencastOptions(options).catch((e) => import_debugLogger.debugLogger.log("error", e));
this._frameThrottler.setThrottlingEnabled(!!options);
}
throttleScreencastFrameAck(ack) {
this._frameThrottler.ack(ack);
}
temporarilyDisableTracingScreencastThrottling() {
this._frameThrottler.recharge();
}
async safeNonStallingEvaluateInAllFrames(expression, world, options = {}) {
await Promise.all(this.frames().map(async (frame) => {
try {
@@ -665,11 +656,14 @@ class Page extends import_instrumentation.SdkObject {
await Promise.all(this.frames().map((frame) => frame.hideHighlight().catch(() => {
})));
}
async snapshotForAI(progress, options) {
async snapshotForAI(progress, options = {}) {
const snapshot = await snapshotFrameForAI(progress, this.mainFrame(), options);
return { full: snapshot.full.join("\n"), incremental: snapshot.incremental?.join("\n") };
}
}
const WorkerEvent = {
Close: "close"
};
class Worker extends import_instrumentation.SdkObject {
constructor(parent, url) {
super(parent, "worker");
@@ -680,9 +674,7 @@ class Worker extends import_instrumentation.SdkObject {
this.url = url;
}
static {
this.Events = {
Close: "close"
};
this.Events = WorkerEvent;
}
createExecutionContext(delegate) {
this.existingExecutionContext = new js.ExecutionContext(this, delegate, "worker");
@@ -763,56 +755,7 @@ class InitScript {
})();`;
}
}
class FrameThrottler {
constructor(nonThrottledFrames, defaultInterval, throttlingInterval) {
this._acks = [];
this._throttlingEnabled = false;
this._nonThrottledFrames = nonThrottledFrames;
this._budget = nonThrottledFrames;
this._defaultInterval = defaultInterval;
this._throttlingInterval = throttlingInterval;
this._tick();
}
dispose() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._timeoutId = void 0;
}
}
setThrottlingEnabled(enabled) {
this._throttlingEnabled = enabled;
}
recharge() {
for (const ack of this._acks)
ack();
this._acks = [];
this._budget = this._nonThrottledFrames;
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._tick();
}
}
ack(ack) {
if (!this._timeoutId) {
ack();
return;
}
this._acks.push(ack);
}
_tick() {
const ack = this._acks.shift();
if (ack) {
--this._budget;
ack();
}
if (this._throttlingEnabled && this._budget <= 0) {
this._timeoutId = setTimeout(() => this._tick(), this._throttlingInterval);
} else {
this._timeoutId = setTimeout(() => this._tick(), this._defaultInterval);
}
}
}
async function snapshotFrameForAI(progress, frame, options) {
async function snapshotFrameForAI(progress, frame, options = {}) {
const snapshot = await frame.retryWithProgressAndTimeouts(progress, [1e3, 2e3, 4e3, 8e3], async (continuePolling) => {
try {
const context = await progress.race(frame._utilityContext());
@@ -822,7 +765,7 @@ async function snapshotFrameForAI(progress, frame, options) {
if (!node)
return true;
return injected.incrementalAriaSnapshot(node, { mode: "ai", ...options2 });
}, { refPrefix: frame.seq ? "f" + frame.seq : "", track: options.track }));
}, { refPrefix: frame.seq ? "f" + frame.seq : "", track: options.track, doNotRenderActive: options.doNotRenderActive }));
if (snapshotOrRetry === true)
return continuePolling;
return snapshotOrRetry;
@@ -882,5 +825,6 @@ function ensureArrayLimit(array, limit) {
InitScript,
Page,
PageBinding,
Worker
Worker,
WorkerEvent
});

View File

@@ -34,19 +34,35 @@ class ProgressController {
this.metadata = metadata || { id: "", startTime: 0, endTime: 0, type: "Internal", method: "", params: {}, log: [], internal: true };
this._onCallLog = onCallLog;
this._forceAbortPromise.catch((e) => null);
this._controller = new AbortController();
}
static createForSdkObject(sdkObject, callMetadata) {
const logName = sdkObject.logName || "api";
return new ProgressController(callMetadata, (message) => {
import_utils.debugLogger.log(logName, message);
sdkObject.instrumentation.onCallLog(sdkObject, callMetadata, logName, message);
});
}
async abort(error) {
if (this._state === "running") {
error[kAbortErrorSymbol] = true;
this._state = { error };
this._forceAbortPromise.reject(error);
this._controller.abort(error);
}
await this._donePromise;
}
async run(task, timeout) {
const deadline = timeout ? (0, import_utils.monotonicTime)() + timeout : 0;
(0, import_utils.assert)(this._state === "before");
this._state = "running";
let timer;
const progress = {
timeout: timeout ?? 0,
deadline,
disableTimeout: () => {
clearTimeout(timer);
},
log: (message) => {
if (this._state === "running")
this.metadata.log.push(message);
@@ -55,24 +71,28 @@ class ProgressController {
metadata: this.metadata,
race: (promise) => {
const promises = Array.isArray(promise) ? promise : [promise];
if (!promises.length)
return Promise.resolve();
return Promise.race([...promises, this._forceAbortPromise]);
},
wait: async (timeout2) => {
let timer2;
const promise = new Promise((f) => timer2 = setTimeout(f, timeout2));
return progress.race(promise).finally(() => clearTimeout(timer2));
}
},
signal: this._controller.signal
};
let timer;
if (timeout) {
if (deadline) {
const timeoutError = new import_errors.TimeoutError(`Timeout ${timeout}ms exceeded.`);
timer = setTimeout(() => {
if (this.metadata.pauseStartTime && !this.metadata.pauseEndTime)
return;
if (this._state === "running") {
timeoutError[kAbortErrorSymbol] = true;
this._state = { error: timeoutError };
this._forceAbortPromise.reject(timeoutError);
this._controller.abort(timeoutError);
}
}, timeout);
}, deadline - (0, import_utils.monotonicTime)());
}
try {
const result = await task(progress);
@@ -89,7 +109,7 @@ class ProgressController {
}
const kAbortErrorSymbol = Symbol("kAbortError");
function isAbortError(error) {
return !!error[kAbortErrorSymbol];
return error instanceof import_errors.TimeoutError || !!error[kAbortErrorSymbol];
}
async function raceUncancellableOperationWithCleanup(progress, run, cleanup) {
let aborted = false;

View File

@@ -53,6 +53,7 @@ class RecorderApp {
this._recorderSources = [];
this._page = page;
this._recorder = recorder;
this._frontend = createRecorderFrontend(page);
this.wsEndpointForTest = wsEndpointForTest;
this._languageGeneratorOptions = {
browserName: params.browserName,
@@ -64,6 +65,10 @@ class RecorderApp {
this._throttledOutputFile = params.outputFile ? new import_throttledFile.ThrottledFile(params.outputFile) : null;
this._primaryGeneratorId = process.env.TEST_INSPECTOR_LANGUAGE || params.language || determinePrimaryGeneratorId(params.sdkLanguage);
this._selectedGeneratorId = this._primaryGeneratorId;
for (const languageGenerator of (0, import_languages.languageSet)()) {
if (languageGenerator.id === this._primaryGeneratorId)
this._recorder.setLanguage(languageGenerator.highlighter);
}
}
async _init(inspectedContext) {
await (0, import_launchApp.syncLocalStorageWithSettings)(this._page, "recorder");
@@ -89,7 +94,7 @@ class RecorderApp {
});
});
});
await this._page.exposeBinding(progress, "dispatch", false, (_, data) => this._handleUIEvent(data));
await this._createDispatcher(progress);
this._page.once("close", () => {
this._recorder.close();
this._page.browserContext.close({ reason: "Recorder window closed" }).catch(() => {
@@ -100,59 +105,56 @@ class RecorderApp {
});
const url = this._recorder.url();
if (url)
this._onPageNavigated(url);
this._onModeChanged(this._recorder.mode());
this._onPausedStateChanged(this._recorder.paused());
this._frontend.pageNavigated({ url });
this._frontend.modeChanged({ mode: this._recorder.mode() });
this._frontend.pauseStateChanged({ paused: this._recorder.paused() });
this._updateActions("reveal");
this._onUserSourcesChanged(this._recorder.userSources(), this._recorder.pausedSourceId());
this._onCallLogsUpdated(this._recorder.callLog());
this._frontend.callLogsUpdated({ callLogs: this._recorder.callLog() });
this._wireListeners(this._recorder);
}
_handleUIEvent(data) {
if (data.event === "clear") {
this._actions = [];
this._updateActions("reveal");
this._recorder.clear();
return;
}
if (data.event === "fileChanged") {
const source = [...this._recorderSources, ...this._userSources].find((s) => s.id === data.params.fileId);
if (source) {
if (source.isRecorded)
this._selectedGeneratorId = source.id;
this._recorder.setLanguage(source.language);
async _createDispatcher(progress) {
const dispatcher = {
clear: async () => {
this._actions = [];
this._updateActions("reveal");
this._recorder.clear();
},
fileChanged: async (params) => {
const source = [...this._recorderSources, ...this._userSources].find((s) => s.id === params.fileId);
if (source) {
if (source.isRecorded)
this._selectedGeneratorId = source.id;
this._recorder.setLanguage(source.language);
}
},
setAutoExpect: async (params) => {
this._languageGeneratorOptions.generateAutoExpect = params.autoExpect;
this._updateActions();
},
setMode: async (params) => {
this._recorder.setMode(params.mode);
},
resume: async () => {
this._recorder.resume();
},
pause: async () => {
this._recorder.pause();
},
step: async () => {
this._recorder.step();
},
highlightRequested: async (params) => {
if (params.selector)
this._recorder.setHighlightedSelector(params.selector);
if (params.ariaTemplate)
this._recorder.setHighlightedAriaTemplate(params.ariaTemplate);
}
return;
}
if (data.event === "setAutoExpect") {
this._languageGeneratorOptions.generateAutoExpect = data.params.autoExpect;
this._updateActions();
return;
}
if (data.event === "setMode") {
this._recorder.setMode(data.params.mode);
return;
}
if (data.event === "resume") {
this._recorder.resume();
return;
}
if (data.event === "pause") {
this._recorder.pause();
return;
}
if (data.event === "step") {
this._recorder.step();
return;
}
if (data.event === "highlightRequested") {
if (data.params.selector)
this._recorder.setHighlightedSelector(data.params.selector);
if (data.params.ariaTemplate)
this._recorder.setHighlightedAriaTemplate(data.params.ariaTemplate);
return;
}
throw new Error(`Unknown event: ${data.event}`);
};
await this._page.exposeBinding(progress, "sendCommand", false, async (_, data) => {
const { method, params } = data;
return await dispatcher[method].call(dispatcher, params);
});
}
static async show(context, params) {
if (process.env.PW_CODEGEN_NO_INSPECTOR)
@@ -220,25 +222,29 @@ class RecorderApp {
this._onSignalAdded(signal);
});
recorder.on(import_recorder.RecorderEvent.PageNavigated, (url) => {
this._onPageNavigated(url);
this._frontend.pageNavigated({ url });
});
recorder.on(import_recorder.RecorderEvent.ContextClosed, () => {
this._onContextClosed();
this._throttledOutputFile?.flush();
this._page.browserContext.close({ reason: "Recorder window closed" }).catch(() => {
});
});
recorder.on(import_recorder.RecorderEvent.ModeChanged, (mode) => {
this._onModeChanged(mode);
this._frontend.modeChanged({ mode });
});
recorder.on(import_recorder.RecorderEvent.PausedStateChanged, (paused) => {
this._onPausedStateChanged(paused);
this._frontend.pauseStateChanged({ paused });
});
recorder.on(import_recorder.RecorderEvent.UserSourcesChanged, (sources, pausedSourceId) => {
this._onUserSourcesChanged(sources, pausedSourceId);
});
recorder.on(import_recorder.RecorderEvent.ElementPicked, (elementInfo, userGesture) => {
this._onElementPicked(elementInfo, userGesture);
if (userGesture)
this._page.bringToFront();
this._frontend.elementPicked({ elementInfo, userGesture });
});
recorder.on(import_recorder.RecorderEvent.CallLogsUpdated, (callLogs) => {
this._onCallLogsUpdated(callLogs);
this._frontend.callLogsUpdated({ callLogs });
});
}
_onActionAdded(action) {
@@ -251,29 +257,6 @@ class RecorderApp {
lastAction.action.signals.push(signal.signal);
this._updateActions();
}
_onPageNavigated(url) {
this._page.mainFrame().evaluateExpression((({ url: url2 }) => {
window.playwrightSetPageURL(url2);
}).toString(), { isFunction: true }, { url }).catch(() => {
});
}
_onContextClosed() {
this._throttledOutputFile?.flush();
this._page.browserContext.close({ reason: "Recorder window closed" }).catch(() => {
});
}
_onModeChanged(mode) {
this._page.mainFrame().evaluateExpression(((mode2) => {
window.playwrightSetMode(mode2);
}).toString(), { isFunction: true }, mode).catch(() => {
});
}
_onPausedStateChanged(paused) {
this._page.mainFrame().evaluateExpression(((paused2) => {
window.playwrightSetPaused(paused2);
}).toString(), { isFunction: true }, paused).catch(() => {
});
}
_onUserSourcesChanged(sources, pausedSourceId) {
if (!sources.length && !this._userSources.length)
return;
@@ -281,34 +264,14 @@ class RecorderApp {
this._pushAllSources();
this._revealSource(pausedSourceId);
}
_onElementPicked(elementInfo, userGesture) {
if (userGesture)
this._page.bringToFront();
this._page.mainFrame().evaluateExpression(((param) => {
window.playwrightElementPicked(param.elementInfo, param.userGesture);
}).toString(), { isFunction: true }, { elementInfo, userGesture }).catch(() => {
});
}
_onCallLogsUpdated(callLogs) {
this._page.mainFrame().evaluateExpression(((callLogs2) => {
window.playwrightUpdateLogs(callLogs2);
}).toString(), { isFunction: true }, callLogs).catch(() => {
});
}
_pushAllSources() {
const sources = [...this._userSources, ...this._recorderSources];
this._page.mainFrame().evaluateExpression((({ sources: sources2 }) => {
window.playwrightSetSources(sources2);
}).toString(), { isFunction: true }, { sources }).catch(() => {
});
this._frontend.sourcesUpdated({ sources });
}
_revealSource(sourceId) {
if (!sourceId)
return;
this._page.mainFrame().evaluateExpression((({ sourceId: sourceId2 }) => {
window.playwrightSelectSource(sourceId2);
}).toString(), { isFunction: true }, { sourceId }).catch(() => {
});
this._frontend.sourceRevealRequested({ sourceId });
}
_updateActions(reveal) {
const recorderSources = [];
@@ -372,6 +335,8 @@ class ProgrammaticRecorderApp {
});
recorder.on(import_recorder.RecorderEvent.SignalAdded, (signal) => {
const page = findPageByGuid(inspectedContext, signal.frame.pageGuid);
if (!page)
return;
inspectedContext.emit(import_browserContext.BrowserContext.Events.RecorderEvent, { event: "signalAdded", data: signal, page, code: "" });
});
}
@@ -379,6 +344,20 @@ class ProgrammaticRecorderApp {
function findPageByGuid(context, guid) {
return context.pages().find((p) => p.guid === guid);
}
function createRecorderFrontend(page) {
return new Proxy({}, {
get: (_target, prop) => {
if (typeof prop !== "string")
return void 0;
return (params) => {
page.mainFrame().evaluateExpression(((event) => {
window.dispatch(event);
}).toString(), { isFunction: true }, { method: prop, params }).catch(() => {
});
};
}
});
}
const recorderAppSymbol = Symbol("recorderApp");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {

View File

@@ -42,10 +42,13 @@ var import_userAgent = require("../utils/userAgent");
var import_utilsBundle = require("../../utilsBundle");
var import_fileUtils = require("../utils/fileUtils");
var import__ = require(".");
async function downloadBrowserWithProgressBar(title, browserDirectory, executablePath, downloadURLs, downloadFileName, downloadSocketTimeout) {
async function downloadBrowserWithProgressBar(title, browserDirectory, executablePath, downloadURLs, downloadFileName, downloadSocketTimeout, force) {
if (await (0, import_fileUtils.existsAsync)((0, import__.browserDirectoryToMarkerFilePath)(browserDirectory))) {
import_debugLogger.debugLogger.log("install", `${title} is already downloaded.`);
return false;
if (force)
import_debugLogger.debugLogger.log("install", `force-downloading ${title}.`);
else
return;
}
const uniqueTempDir = await import_fs.default.promises.mkdtemp(import_path.default.join(import_os.default.tmpdir(), "playwright-download-"));
const zipPath = import_path.default.join(uniqueTempDir, downloadFileName);
@@ -63,7 +66,7 @@ async function downloadBrowserWithProgressBar(title, browserDirectory, executabl
if (await (0, import_fileUtils.existsAsync)(zipPath))
await import_fs.default.promises.unlink(zipPath);
if (await (0, import_fileUtils.existsAsync)(browserDirectory))
await import_fs.default.promises.rmdir(browserDirectory, { recursive: true });
await (0, import_fileUtils.removeFolders)([browserDirectory]);
const errorMessage = error?.message || "";
import_debugLogger.debugLogger.log("install", `attempt #${attempt} - ERROR: ${errorMessage}`);
if (attempt >= retryCount)
@@ -77,7 +80,6 @@ async function downloadBrowserWithProgressBar(title, browserDirectory, executabl
await (0, import_fileUtils.removeFolders)([uniqueTempDir]);
}
logPolitely(`${title} downloaded to ${browserDirectory}`);
return true;
}
function downloadBrowserWithProgressBarOutOfProcess(title, browserDirectory, url, zipPath, executablePath, socketTimeout) {
const cp = childProcess.fork(import_path.default.join(__dirname, "oopDownloadBrowserMain.js"));

View File

@@ -145,130 +145,140 @@ const EXECUTABLE_PATHS = {
"win-x64": ["PrintDeps.exe"]
}
};
function cftUrl(suffix) {
return ({ browserVersion }) => {
return {
path: `builds/cft/${browserVersion}/${suffix}`,
mirrors: [
"https://cdn.playwright.dev"
]
};
};
}
const DOWNLOAD_PATHS = {
"chromium": {
"<unknown>": void 0,
"ubuntu18.04-x64": void 0,
"ubuntu20.04-x64": "builds/chromium/%s/chromium-linux.zip",
"ubuntu22.04-x64": "builds/chromium/%s/chromium-linux.zip",
"ubuntu24.04-x64": "builds/chromium/%s/chromium-linux.zip",
"ubuntu20.04-x64": cftUrl("linux64/chrome-linux64.zip"),
"ubuntu22.04-x64": cftUrl("linux64/chrome-linux64.zip"),
"ubuntu24.04-x64": cftUrl("linux64/chrome-linux64.zip"),
"ubuntu18.04-arm64": void 0,
"ubuntu20.04-arm64": "builds/chromium/%s/chromium-linux-arm64.zip",
"ubuntu22.04-arm64": "builds/chromium/%s/chromium-linux-arm64.zip",
"ubuntu24.04-arm64": "builds/chromium/%s/chromium-linux-arm64.zip",
"debian11-x64": "builds/chromium/%s/chromium-linux.zip",
"debian11-x64": cftUrl("linux64/chrome-linux64.zip"),
"debian11-arm64": "builds/chromium/%s/chromium-linux-arm64.zip",
"debian12-x64": "builds/chromium/%s/chromium-linux.zip",
"debian12-x64": cftUrl("linux64/chrome-linux64.zip"),
"debian12-arm64": "builds/chromium/%s/chromium-linux-arm64.zip",
"debian13-x64": "builds/chromium/%s/chromium-linux.zip",
"debian13-x64": cftUrl("linux64/chrome-linux64.zip"),
"debian13-arm64": "builds/chromium/%s/chromium-linux-arm64.zip",
"mac10.13": "builds/chromium/%s/chromium-mac.zip",
"mac10.14": "builds/chromium/%s/chromium-mac.zip",
"mac10.15": "builds/chromium/%s/chromium-mac.zip",
"mac11": "builds/chromium/%s/chromium-mac.zip",
"mac11-arm64": "builds/chromium/%s/chromium-mac-arm64.zip",
"mac12": "builds/chromium/%s/chromium-mac.zip",
"mac12-arm64": "builds/chromium/%s/chromium-mac-arm64.zip",
"mac13": "builds/chromium/%s/chromium-mac.zip",
"mac13-arm64": "builds/chromium/%s/chromium-mac-arm64.zip",
"mac14": "builds/chromium/%s/chromium-mac.zip",
"mac14-arm64": "builds/chromium/%s/chromium-mac-arm64.zip",
"mac15": "builds/chromium/%s/chromium-mac.zip",
"mac15-arm64": "builds/chromium/%s/chromium-mac-arm64.zip",
"win64": "builds/chromium/%s/chromium-win64.zip"
"mac10.13": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac10.14": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac10.15": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac11": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac11-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac12": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac12-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac13": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac13-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac14": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac14-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac15": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac15-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"win64": cftUrl("win64/chrome-win64.zip")
},
"chromium-headless-shell": {
"<unknown>": void 0,
"ubuntu18.04-x64": void 0,
"ubuntu20.04-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip",
"ubuntu22.04-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip",
"ubuntu24.04-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip",
"ubuntu20.04-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"ubuntu22.04-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"ubuntu24.04-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"ubuntu18.04-arm64": void 0,
"ubuntu20.04-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip",
"ubuntu22.04-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip",
"ubuntu24.04-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip",
"debian11-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip",
"debian11-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"debian11-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip",
"debian12-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip",
"debian12-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"debian12-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip",
"debian13-x64": "builds/chromium/%s/chromium-headless-shell-linux.zip",
"debian13-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"debian13-arm64": "builds/chromium/%s/chromium-headless-shell-linux-arm64.zip",
"mac10.13": void 0,
"mac10.14": void 0,
"mac10.15": void 0,
"mac11": "builds/chromium/%s/chromium-headless-shell-mac.zip",
"mac11-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip",
"mac12": "builds/chromium/%s/chromium-headless-shell-mac.zip",
"mac12-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip",
"mac13": "builds/chromium/%s/chromium-headless-shell-mac.zip",
"mac13-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip",
"mac14": "builds/chromium/%s/chromium-headless-shell-mac.zip",
"mac14-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip",
"mac15": "builds/chromium/%s/chromium-headless-shell-mac.zip",
"mac15-arm64": "builds/chromium/%s/chromium-headless-shell-mac-arm64.zip",
"win64": "builds/chromium/%s/chromium-headless-shell-win64.zip"
"mac11": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac11-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac12": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac12-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac13": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac13-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac14": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac14-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac15": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac15-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"win64": cftUrl("win64/chrome-headless-shell-win64.zip")
},
"chromium-tip-of-tree": {
"<unknown>": void 0,
"ubuntu18.04-x64": void 0,
"ubuntu20.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip",
"ubuntu22.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip",
"ubuntu24.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip",
"ubuntu20.04-x64": cftUrl("linux64/chrome-linux64.zip"),
"ubuntu22.04-x64": cftUrl("linux64/chrome-linux64.zip"),
"ubuntu24.04-x64": cftUrl("linux64/chrome-linux64.zip"),
"ubuntu18.04-arm64": void 0,
"ubuntu20.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip",
"ubuntu22.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip",
"ubuntu24.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip",
"debian11-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip",
"debian11-x64": cftUrl("linux64/chrome-linux64.zip"),
"debian11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip",
"debian12-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip",
"debian12-x64": cftUrl("linux64/chrome-linux64.zip"),
"debian12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip",
"debian13-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux.zip",
"debian13-x64": cftUrl("linux64/chrome-linux64.zip"),
"debian13-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip",
"mac10.13": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac10.14": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac10.15": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac11": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip",
"mac12": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip",
"mac13": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac13-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip",
"mac14": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac14-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip",
"mac15": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac.zip",
"mac15-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-mac-arm64.zip",
"win64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-win64.zip"
"mac10.13": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac10.14": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac10.15": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac11": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac11-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac12": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac12-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac13": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac13-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac14": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac14-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"mac15": cftUrl("mac-x64/chrome-mac-x64.zip"),
"mac15-arm64": cftUrl("mac-arm64/chrome-mac-arm64.zip"),
"win64": cftUrl("win64/chrome-win64.zip")
},
"chromium-tip-of-tree-headless-shell": {
"<unknown>": void 0,
"ubuntu18.04-x64": void 0,
"ubuntu20.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip",
"ubuntu22.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip",
"ubuntu24.04-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip",
"ubuntu20.04-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"ubuntu22.04-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"ubuntu24.04-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"ubuntu18.04-arm64": void 0,
"ubuntu20.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip",
"ubuntu22.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip",
"ubuntu24.04-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip",
"debian11-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip",
"debian11-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"debian11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip",
"debian12-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip",
"debian12-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"debian12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip",
"debian13-x64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux.zip",
"debian13-x64": cftUrl("linux64/chrome-headless-shell-linux64.zip"),
"debian13-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip",
"mac10.13": void 0,
"mac10.14": void 0,
"mac10.15": void 0,
"mac11": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip",
"mac11-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip",
"mac12": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip",
"mac12-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip",
"mac13": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip",
"mac13-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip",
"mac14": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip",
"mac14-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip",
"mac15": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac.zip",
"mac15-arm64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-mac-arm64.zip",
"win64": "builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-win64.zip"
"mac11": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac11-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac12": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac12-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac13": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac13-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac14": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac14-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"mac15": cftUrl("mac-x64/chrome-headless-shell-mac-x64.zip"),
"mac15-arm64": cftUrl("mac-arm64/chrome-headless-shell-mac-arm64.zip"),
"win64": cftUrl("win64/chrome-headless-shell-win64.zip")
},
"firefox": {
"<unknown>": void 0,
@@ -349,14 +359,14 @@ const DOWNLOAD_PATHS = {
"debian13-x64": "builds/webkit/%s/webkit-debian-13.zip",
"debian13-arm64": "builds/webkit/%s/webkit-debian-13-arm64.zip",
"mac10.13": void 0,
"mac10.14": "builds/deprecated-webkit-mac-10.14/%s/deprecated-webkit-mac-10.14.zip",
"mac10.15": "builds/deprecated-webkit-mac-10.15/%s/deprecated-webkit-mac-10.15.zip",
"mac11": "builds/webkit/%s/webkit-mac-11.zip",
"mac11-arm64": "builds/webkit/%s/webkit-mac-11-arm64.zip",
"mac12": "builds/webkit/%s/webkit-mac-12.zip",
"mac12-arm64": "builds/webkit/%s/webkit-mac-12-arm64.zip",
"mac13": "builds/webkit/%s/webkit-mac-13.zip",
"mac13-arm64": "builds/webkit/%s/webkit-mac-13-arm64.zip",
"mac10.14": void 0,
"mac10.15": void 0,
"mac11": void 0,
"mac11-arm64": void 0,
"mac12": void 0,
"mac12-arm64": void 0,
"mac13": void 0,
"mac13-arm64": void 0,
"mac14": "builds/webkit/%s/webkit-mac-14.zip",
"mac14-arm64": "builds/webkit/%s/webkit-mac-14-arm64.zip",
"mac15": "builds/webkit/%s/webkit-mac-15.zip",
@@ -483,7 +493,7 @@ const registryDirectory = (() => {
})();
function isBrowserDirectory(browserDirectory) {
const baseName = import_path.default.basename(browserDirectory);
for (const browserName of allDownloadable) {
for (const browserName of allDownloadableDirectoriesThatEverExisted) {
if (baseName.startsWith(browserName.replace(/-/g, "_") + "-"))
return true;
}
@@ -501,6 +511,7 @@ function readDescriptors(browsersJSON) {
hasRevisionOverride: !!revisionOverride,
// We only put browser version for the supported operating systems.
browserVersion: revisionOverride ? void 0 : obj.browserVersion,
title: obj["title"],
installByDefault: !!obj.installByDefault,
// Method `isBrowserDirectory` determines directory to be browser iff
// it starts with some browser name followed by '-'. Some browser names
@@ -512,7 +523,8 @@ function readDescriptors(browsersJSON) {
return descriptor;
});
}
const allDownloadable = ["android", "chromium", "firefox", "webkit", "ffmpeg", "firefox-beta", "chromium-tip-of-tree", "chromium-headless-shell", "chromium-tip-of-tree-headless-shell"];
const allDownloadableDirectoriesThatEverExisted = ["android", "chromium", "firefox", "webkit", "ffmpeg", "firefox-beta", "chromium-tip-of-tree", "chromium-headless-shell", "chromium-tip-of-tree-headless-shell", "winldd"];
const chromiumAliases = ["bidi-chromium", "chrome-for-testing"];
class Registry {
constructor(browsersJSON) {
const descriptors = readDescriptors(browsersJSON);
@@ -552,7 +564,6 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
const chromium = descriptors.find((d) => d.name === "chromium");
const chromiumExecutable = findExecutablePath(chromium.dir, "chromium");
this._executables.push({
type: "browser",
name: "chromium",
browserName: "chromium",
directory: chromium.dir,
@@ -561,15 +572,16 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: chromium.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromium.dir, ["chrome-linux"], [], ["chrome-win"]),
downloadURLs: this._downloadURLs(chromium),
title: chromium.title,
revision: chromium.revision,
browserVersion: chromium.browserVersion,
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
_install: (force) => this._downloadExecutable(chromium, force, chromiumExecutable),
_dependencyGroup: "chromium",
_isHermeticInstallation: true
});
const chromiumHeadlessShell = descriptors.find((d) => d.name === "chromium-headless-shell");
const chromiumHeadlessShellExecutable = findExecutablePath(chromiumHeadlessShell.dir, "chromium-headless-shell");
this._executables.push({
type: "channel",
name: "chromium-headless-shell",
browserName: "chromium",
directory: chromiumHeadlessShell.dir,
@@ -578,15 +590,16 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: chromiumHeadlessShell.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromiumHeadlessShell.dir, ["chrome-linux"], [], ["chrome-win"]),
downloadURLs: this._downloadURLs(chromiumHeadlessShell),
browserVersion: chromium.browserVersion,
_install: () => this._downloadExecutable(chromiumHeadlessShell, chromiumHeadlessShellExecutable),
title: chromiumHeadlessShell.title,
revision: chromiumHeadlessShell.revision,
browserVersion: chromiumHeadlessShell.browserVersion,
_install: (force) => this._downloadExecutable(chromiumHeadlessShell, force, chromiumHeadlessShellExecutable),
_dependencyGroup: "chromium",
_isHermeticInstallation: true
});
const chromiumTipOfTreeHeadlessShell = descriptors.find((d) => d.name === "chromium-tip-of-tree-headless-shell");
const chromiumTipOfTreeHeadlessShellExecutable = findExecutablePath(chromiumTipOfTreeHeadlessShell.dir, "chromium-tip-of-tree-headless-shell");
this._executables.push({
type: "channel",
name: "chromium-tip-of-tree-headless-shell",
browserName: "chromium",
directory: chromiumTipOfTreeHeadlessShell.dir,
@@ -595,15 +608,16 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: chromiumTipOfTreeHeadlessShell.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromiumTipOfTreeHeadlessShell.dir, ["chrome-linux"], [], ["chrome-win"]),
downloadURLs: this._downloadURLs(chromiumTipOfTreeHeadlessShell),
browserVersion: chromium.browserVersion,
_install: () => this._downloadExecutable(chromiumTipOfTreeHeadlessShell, chromiumTipOfTreeHeadlessShellExecutable),
title: chromiumTipOfTreeHeadlessShell.title,
revision: chromiumTipOfTreeHeadlessShell.revision,
browserVersion: chromiumTipOfTreeHeadlessShell.browserVersion,
_install: (force) => this._downloadExecutable(chromiumTipOfTreeHeadlessShell, force, chromiumTipOfTreeHeadlessShellExecutable),
_dependencyGroup: "chromium",
_isHermeticInstallation: true
});
const chromiumTipOfTree = descriptors.find((d) => d.name === "chromium-tip-of-tree");
const chromiumTipOfTreeExecutable = findExecutablePath(chromiumTipOfTree.dir, "chromium-tip-of-tree");
this._executables.push({
type: "tool",
name: "chromium-tip-of-tree",
browserName: "chromium",
directory: chromiumTipOfTree.dir,
@@ -612,8 +626,10 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: chromiumTipOfTree.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromiumTipOfTree.dir, ["chrome-linux"], [], ["chrome-win"]),
downloadURLs: this._downloadURLs(chromiumTipOfTree),
title: chromiumTipOfTree.title,
revision: chromiumTipOfTree.revision,
browserVersion: chromiumTipOfTree.browserVersion,
_install: () => this._downloadExecutable(chromiumTipOfTree, chromiumTipOfTreeExecutable),
_install: (force) => this._downloadExecutable(chromiumTipOfTree, force, chromiumTipOfTreeExecutable),
_dependencyGroup: "chromium",
_isHermeticInstallation: true
});
@@ -702,25 +718,9 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
"darwin": "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"win32": `\\Google\\Chrome SxS\\Application\\chrome.exe`
}));
this._executables.push({
type: "channel",
name: "bidi-chromium",
browserName: "chromium",
directory: chromium.dir,
executablePath: () => chromiumExecutable,
executablePathOrDie: (sdkLanguage) => executablePathOrDie("chromium", chromiumExecutable, chromium.installByDefault, sdkLanguage),
installType: "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, chromium.dir, ["chrome-linux"], [], ["chrome-win"]),
downloadURLs: this._downloadURLs(chromium),
browserVersion: chromium.browserVersion,
_install: () => this._downloadExecutable(chromium, chromiumExecutable),
_dependencyGroup: "chromium",
_isHermeticInstallation: true
});
const firefox = descriptors.find((d) => d.name === "firefox");
const firefoxExecutable = findExecutablePath(firefox.dir, "firefox");
this._executables.push({
type: "browser",
name: "firefox",
browserName: "firefox",
directory: firefox.dir,
@@ -729,15 +729,16 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: firefox.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, firefox.dir, ["firefox"], [], ["firefox"]),
downloadURLs: this._downloadURLs(firefox),
title: firefox.title,
revision: firefox.revision,
browserVersion: firefox.browserVersion,
_install: () => this._downloadExecutable(firefox, firefoxExecutable),
_install: (force) => this._downloadExecutable(firefox, force, firefoxExecutable),
_dependencyGroup: "firefox",
_isHermeticInstallation: true
});
const firefoxBeta = descriptors.find((d) => d.name === "firefox-beta");
const firefoxBetaExecutable = findExecutablePath(firefoxBeta.dir, "firefox");
this._executables.push({
type: "tool",
name: "firefox-beta",
browserName: "firefox",
directory: firefoxBeta.dir,
@@ -746,8 +747,10 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: firefoxBeta.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, firefoxBeta.dir, ["firefox"], [], ["firefox"]),
downloadURLs: this._downloadURLs(firefoxBeta),
title: firefoxBeta.title,
revision: firefoxBeta.revision,
browserVersion: firefoxBeta.browserVersion,
_install: () => this._downloadExecutable(firefoxBeta, firefoxBetaExecutable),
_install: (force) => this._downloadExecutable(firefoxBeta, force, firefoxBetaExecutable),
_dependencyGroup: "firefox",
_isHermeticInstallation: true
});
@@ -764,7 +767,6 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
import_path.default.join("minibrowser-wpe", "sys", "lib")
];
this._executables.push({
type: "browser",
name: "webkit",
browserName: "webkit",
directory: webkit.dir,
@@ -773,19 +775,21 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: webkit.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: (sdkLanguage) => this._validateHostRequirements(sdkLanguage, webkit.dir, webkitLinuxLddDirectories, ["libGLESv2.so.2", "libx264.so"], [""]),
downloadURLs: this._downloadURLs(webkit),
title: webkit.title,
revision: webkit.revision,
browserVersion: webkit.browserVersion,
_install: () => this._downloadExecutable(webkit, webkitExecutable),
_install: (force) => this._downloadExecutable(webkit, force, webkitExecutable),
_dependencyGroup: "webkit",
_isHermeticInstallation: true
});
this._executables.push({
type: "channel",
name: "webkit-wsl",
browserName: "webkit",
directory: webkit.dir,
executablePath: () => webkitExecutable,
executablePathOrDie: (sdkLanguage) => executablePathOrDie("webkit", webkitExecutable, webkit.installByDefault, sdkLanguage),
installType: "download-on-demand",
title: "Webkit in WSL",
_validateHostRequirements: (sdkLanguage) => Promise.resolve(),
_isHermeticInstallation: true,
_install: async () => {
@@ -807,7 +811,6 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
const ffmpeg = descriptors.find((d) => d.name === "ffmpeg");
const ffmpegExecutable = findExecutablePath(ffmpeg.dir, "ffmpeg");
this._executables.push({
type: "tool",
name: "ffmpeg",
browserName: void 0,
directory: ffmpeg.dir,
@@ -816,14 +819,15 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: ffmpeg.installByDefault ? "download-by-default" : "download-on-demand",
_validateHostRequirements: () => Promise.resolve(),
downloadURLs: this._downloadURLs(ffmpeg),
_install: () => this._downloadExecutable(ffmpeg, ffmpegExecutable),
title: ffmpeg.title,
revision: ffmpeg.revision,
_install: (force) => this._downloadExecutable(ffmpeg, force, ffmpegExecutable),
_dependencyGroup: "tools",
_isHermeticInstallation: true
});
const winldd = descriptors.find((d) => d.name === "winldd");
const winlddExecutable = findExecutablePath(winldd.dir, "winldd");
this._executables.push({
type: "tool",
name: "winldd",
browserName: void 0,
directory: winldd.dir,
@@ -832,13 +836,14 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: process.platform === "win32" ? "download-by-default" : "none",
_validateHostRequirements: () => Promise.resolve(),
downloadURLs: this._downloadURLs(winldd),
_install: () => this._downloadExecutable(winldd, winlddExecutable),
title: winldd.title,
revision: winldd.revision,
_install: (force) => this._downloadExecutable(winldd, force, winlddExecutable),
_dependencyGroup: "tools",
_isHermeticInstallation: true
});
const android = descriptors.find((d) => d.name === "android");
this._executables.push({
type: "tool",
name: "android",
browserName: void 0,
directory: android.dir,
@@ -847,7 +852,9 @@ ${(0, import_ascii.wrapInASCIIBox)(prettyMessage, 1)}`);
installType: "download-on-demand",
_validateHostRequirements: () => Promise.resolve(),
downloadURLs: this._downloadURLs(android),
_install: () => this._downloadExecutable(android),
title: android.title,
revision: android.revision,
_install: (force) => this._downloadExecutable(android, force),
_dependencyGroup: "tools",
_isHermeticInstallation: true
});
@@ -881,7 +888,6 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
throw new Error(`Chromium distribution '${name}' is not found${location}${installation}`);
};
return {
type: "channel",
name,
browserName: "chromium",
directory: void 0,
@@ -920,7 +926,6 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
return void 0;
};
return {
type: "channel",
name,
browserName: "firefox",
directory: void 0,
@@ -932,7 +937,7 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
_install: install
};
}
_createBidiChromiumChannel(name, lookAt, install) {
_createBidiChromiumChannel(name, lookAt) {
const executablePath = (sdkLanguage, shouldThrow) => {
const suffix = lookAt[process.platform];
if (!suffix) {
@@ -956,21 +961,17 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
if (!shouldThrow)
return void 0;
const location = prefixes.length ? ` at ${import_path.default.join(prefixes[0], suffix)}` : ``;
const installation = install ? `
Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
throw new Error(`Chromium distribution '${name}' is not found${location}${installation}`);
throw new Error(`Chromium distribution '${name}' is not found${location}`);
};
return {
type: "channel",
name,
browserName: "chromium",
directory: void 0,
executablePath: (sdkLanguage) => executablePath(sdkLanguage, false),
executablePathOrDie: (sdkLanguage) => executablePath(sdkLanguage, true),
installType: install ? "install-script" : "none",
installType: "none",
_validateHostRequirements: () => Promise.resolve(),
_isHermeticInstallation: false,
_install: install
_isHermeticInstallation: false
};
}
executables() {
@@ -1051,7 +1052,7 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
].join("\n"), 1) + "\n\n");
return;
}
await executable._install();
await executable._install(!!options?.force);
}
} catch (e) {
if (e.code === "ELOCKED") {
@@ -1123,8 +1124,16 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
const downloadPathTemplate = paths[import_hostPlatform.hostPlatform] || paths["<unknown>"];
if (!downloadPathTemplate)
return [];
const downloadPath = util.format(downloadPathTemplate, descriptor.revision);
let downloadURLs = PLAYWRIGHT_CDN_MIRRORS.map((mirror) => `${mirror}/${downloadPath}`);
let downloadPath;
let mirrors;
if (typeof downloadPathTemplate === "function") {
const result = downloadPathTemplate(descriptor);
downloadPath = result.path;
mirrors = result.mirrors;
} else {
downloadPath = util.format(downloadPathTemplate, descriptor.revision);
mirrors = PLAYWRIGHT_CDN_MIRRORS;
}
let downloadHostEnv;
if (descriptor.name.startsWith("chromium"))
downloadHostEnv = "PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST";
@@ -1134,10 +1143,10 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
downloadHostEnv = "PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST";
const customHostOverride = downloadHostEnv && (0, import_utils.getFromENV)(downloadHostEnv) || (0, import_utils.getFromENV)("PLAYWRIGHT_DOWNLOAD_HOST");
if (customHostOverride)
downloadURLs = [`${customHostOverride}/${downloadPath}`];
return downloadURLs;
mirrors = [customHostOverride];
return mirrors.map((mirror) => `${mirror}/${downloadPath}`);
}
async _downloadExecutable(descriptor, executablePath) {
async _downloadExecutable(descriptor, force, executablePath) {
const downloadURLs = this._downloadURLs(descriptor);
if (!downloadURLs.length)
throw new Error(`ERROR: Playwright does not support ${descriptor.name} on ${import_hostPlatform.hostPlatform}`);
@@ -1150,18 +1159,22 @@ Run "${buildPlaywrightCLICommand(sdkLanguage, "install " + name)}"` : "";
else
(0, import_browserFetcher.logPolitely)(message);
}
const displayName = descriptor.name.split("-").map((word) => {
return word === "ffmpeg" ? "FFMPEG" : word.charAt(0).toUpperCase() + word.slice(1);
}).join(" ");
const title = descriptor.browserVersion ? `${displayName} ${descriptor.browserVersion} (playwright build v${descriptor.revision})` : `${displayName} playwright build v${descriptor.revision}`;
const title = this.calculateDownloadTitle(descriptor);
const downloadFileName = `playwright-download-${descriptor.name}-${import_hostPlatform.hostPlatform}-${descriptor.revision}.zip`;
const downloadSocketTimeoutEnv = (0, import_utils.getFromENV)("PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT");
const downloadSocketTimeout = +(downloadSocketTimeoutEnv || "0") || import_network.NET_DEFAULT_TIMEOUT;
await (0, import_browserFetcher.downloadBrowserWithProgressBar)(title, descriptor.dir, executablePath, downloadURLs, downloadFileName, downloadSocketTimeout).catch((e) => {
await (0, import_browserFetcher.downloadBrowserWithProgressBar)(title, descriptor.dir, executablePath, downloadURLs, downloadFileName, downloadSocketTimeout, force).catch((e) => {
throw new Error(`Failed to download ${title}, caused by
${e.stack}`);
});
}
calculateDownloadTitle(descriptor) {
const title = descriptor.title ?? descriptor.name.split("-").map((word) => {
return word === "ffmpeg" ? "FFmpeg" : word.charAt(0).toUpperCase() + word.slice(1);
}).join(" ");
const version = descriptor.browserVersion ? " " + descriptor.browserVersion : "";
return `${title}${version} (playwright ${descriptor.name} v${descriptor.revision})`;
}
async _installMSEdgeChannel(channel, scripts) {
const scriptArgs = [];
if (process.platform !== "linux") {
@@ -1234,7 +1247,7 @@ ${e.stack}`);
linkTarget = (await import_fs.default.promises.readFile(linkPath)).toString();
const browsersJSON = require(import_path.default.join(linkTarget, "browsers.json"));
const descriptors = readDescriptors(browsersJSON);
for (const browserName of allDownloadable) {
for (const browserName of allDownloadableDirectoriesThatEverExisted) {
const descriptor = descriptors.find((d) => d.name === browserName);
if (!descriptor)
continue;
@@ -1279,13 +1292,18 @@ ${e.stack}`);
_defaultBrowsersToInstall(options) {
let executables = this.defaultExecutables();
if (options.shell === "no")
executables = executables.filter((e) => e.name !== "chromium-headless-shell");
executables = executables.filter((e) => e.name !== "chromium-headless-shell" && e.name !== "chromium-tip-of-tree-headless-shell");
if (options.shell === "only")
executables = executables.filter((e) => e.name !== "chromium");
executables = executables.filter((e) => e.name !== "chromium" && e.name !== "chromium-tip-of-tree");
return executables;
}
suggestedBrowsersToInstall() {
return this.executables().filter((e) => e.installType !== "none" && e.type !== "tool").map((e) => e.name).join(", ");
const names = this.executables().filter((e) => e.installType !== "none").map((e) => e.name);
names.push(...chromiumAliases);
return names.sort().join(", ");
}
isChromiumAlias(name) {
return chromiumAliases.includes(name);
}
resolveBrowsers(aliases, options) {
if (aliases.length === 0)
@@ -1298,15 +1316,20 @@ ${e.stack}`);
faultyArguments.push(arg);
else
executables.push(executable);
if (executable?.browserName === "chromium")
if (executable?.browserName)
executables.push(this.findExecutable("ffmpeg"));
};
for (const alias of aliases) {
if (alias === "chromium") {
if (alias === "chromium" || chromiumAliases.includes(alias)) {
if (options.shell !== "only")
handleArgument("chromium");
if (options.shell !== "no")
handleArgument("chromium-headless-shell");
} else if (alias === "chromium-tip-of-tree") {
if (options.shell !== "only")
handleArgument("chromium-tip-of-tree");
if (options.shell !== "no")
handleArgument("chromium-tip-of-tree-headless-shell");
} else {
handleArgument(alias);
}

View File

@@ -29,6 +29,7 @@ var import_path = __toESM(require("path"));
var import_manualPromise = require("../../utils/isomorphic/manualPromise");
var import_network = require("../utils/network");
var import_zipBundle = require("../../zipBundle");
var import_fileUtils = require("../utils/fileUtils");
function log(message) {
process.send?.({ method: "log", params: { message } });
}
@@ -99,6 +100,8 @@ function downloadFile(options) {
async function main(options) {
await downloadFile(options);
log(`SUCCESS downloading ${options.title}`);
log(`removing existing browser directory if any`);
await (0, import_fileUtils.removeFolders)([options.browserDirectory]);
log(`extracting archive`);
await (0, import_zipBundle.extract)(options.zipPath, { dir: options.browserDirectory });
if (options.executablePath) {

190
node_modules/playwright-core/lib/server/screencast.js generated vendored Normal file
View File

@@ -0,0 +1,190 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var screencast_exports = {};
__export(screencast_exports, {
Screencast: () => Screencast
});
module.exports = __toCommonJS(screencast_exports);
var import_path = __toESM(require("path"));
var import_utils = require("../utils");
var import_utils2 = require("../utils");
var import_videoRecorder = require("./videoRecorder");
var import_page = require("./page");
var import_registry = require("./registry");
class Screencast {
constructor(page) {
this._videoRecorder = null;
this._videoId = null;
this._screencastClients = /* @__PURE__ */ new Set();
// Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
// When throttling for tracing, 200ms between frames, except for 10 frames around the action.
this._frameThrottler = new FrameThrottler(10, 35, 200);
this._frameListener = null;
this._page = page;
}
stopFrameThrottler() {
this._frameThrottler.dispose();
}
setOptions(options) {
this._setOptions(options).catch((e) => import_utils2.debugLogger.log("error", e));
this._frameThrottler.setThrottlingEnabled(!!options);
}
throttleFrameAck(ack) {
this._frameThrottler.ack(ack);
}
temporarilyDisableThrottling() {
this._frameThrottler.recharge();
}
launchVideoRecorder() {
const recordVideo = this._page.browserContext._options.recordVideo;
if (!recordVideo)
return void 0;
(0, import_utils.assert)(!this._videoId);
this._videoId = (0, import_utils.createGuid)();
const outputFile = import_path.default.join(recordVideo.dir, this._videoId + ".webm");
const videoOptions = {
// validateBrowserContextOptions ensures correct video size.
...recordVideo.size,
outputFile
};
const ffmpegPath = import_registry.registry.findExecutable("ffmpeg").executablePathOrDie(this._page.browserContext._browser.sdkLanguage());
this._videoRecorder = new import_videoRecorder.VideoRecorder(ffmpegPath, videoOptions);
this._frameListener = import_utils.eventsHelper.addEventListener(this._page, import_page.Page.Events.ScreencastFrame, (frame) => this._videoRecorder.writeFrame(frame.buffer, frame.frameSwapWallTime / 1e3));
this._page.waitForInitializedOrError().then((p) => {
if (p instanceof Error)
this.stopVideoRecording().catch(() => {
});
});
return videoOptions;
}
async startVideoRecording(options) {
const videoId = this._videoId;
(0, import_utils.assert)(videoId);
this._page.once(import_page.Page.Events.Close, () => this.stopVideoRecording().catch(() => {
}));
const gotFirstFrame = new Promise((f) => this._page.once(import_page.Page.Events.ScreencastFrame, f));
await this._startScreencast(this._videoRecorder, {
quality: 90,
width: options.width,
height: options.height
});
gotFirstFrame.then(() => {
this._page.browserContext._browser._videoStarted(this._page.browserContext, videoId, options.outputFile, this._page.waitForInitializedOrError());
});
}
async stopVideoRecording() {
if (!this._videoId)
return;
if (this._frameListener)
import_utils.eventsHelper.removeEventListeners([this._frameListener]);
this._frameListener = null;
const videoId = this._videoId;
this._videoId = null;
const videoRecorder = this._videoRecorder;
this._videoRecorder = null;
await this._stopScreencast(videoRecorder);
await videoRecorder.stop();
const video = this._page.browserContext._browser._takeVideo(videoId);
video?.reportFinished();
}
async _setOptions(options) {
if (options)
await this._startScreencast(this, options);
else
await this._stopScreencast(this);
}
async _startScreencast(client, options) {
this._screencastClients.add(client);
if (this._screencastClients.size === 1) {
await this._page.delegate.startScreencast({
width: options.width,
height: options.height,
quality: options.quality
});
}
}
async _stopScreencast(client) {
this._screencastClients.delete(client);
if (!this._screencastClients.size)
await this._page.delegate.stopScreencast();
}
}
class FrameThrottler {
constructor(nonThrottledFrames, defaultInterval, throttlingInterval) {
this._acks = [];
this._throttlingEnabled = false;
this._nonThrottledFrames = nonThrottledFrames;
this._budget = nonThrottledFrames;
this._defaultInterval = defaultInterval;
this._throttlingInterval = throttlingInterval;
this._tick();
}
dispose() {
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._timeoutId = void 0;
}
}
setThrottlingEnabled(enabled) {
this._throttlingEnabled = enabled;
}
recharge() {
for (const ack of this._acks)
ack();
this._acks = [];
this._budget = this._nonThrottledFrames;
if (this._timeoutId) {
clearTimeout(this._timeoutId);
this._tick();
}
}
ack(ack) {
if (!this._timeoutId) {
ack();
return;
}
this._acks.push(ack);
}
_tick() {
const ack = this._acks.shift();
if (ack) {
--this._budget;
ack();
}
if (this._throttlingEnabled && this._budget <= 0) {
this._timeoutId = setTimeout(() => this._tick(), this._throttlingInterval);
} else {
this._timeoutId = setTimeout(() => this._tick(), this._defaultInterval);
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Screencast
});

View File

@@ -61,6 +61,7 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
constructor() {
this._lastSnapshotNumber = 0;
this._staleStyleSheets = /* @__PURE__ */ new Set();
this._modifiedStyleSheets = /* @__PURE__ */ new Set();
this._readingStyleSheet = false;
const invalidateCSSGroupingRule = (rule) => {
if (rule.parentStyleSheet)
@@ -76,6 +77,10 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
this._interceptNativeMethod(window.CSSGroupingRule.prototype, "insertRule", invalidateCSSGroupingRule);
this._interceptNativeMethod(window.CSSGroupingRule.prototype, "deleteRule", invalidateCSSGroupingRule);
this._interceptNativeGetter(window.CSSGroupingRule.prototype, "cssRules", invalidateCSSGroupingRule);
this._interceptNativeSetter(window.StyleSheet.prototype, "disabled", (sheet) => {
if (sheet instanceof CSSStyleSheet)
this._invalidateStyleSheet(sheet);
});
this._interceptNativeAsyncMethod(window.CSSStyleSheet.prototype, "replace", (sheet) => this._invalidateStyleSheet(sheet));
this._fakeBase = document.createElement("base");
this._observer = new MutationObserver((list) => this._handleMutations(list));
@@ -148,6 +153,17 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
}
});
}
_interceptNativeSetter(obj, prop, cb) {
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
Object.defineProperty(obj, prop, {
...descriptor,
set: function(value) {
const result = descriptor.set.call(this, value);
cb(this, value);
return result;
}
});
}
_handleMutations(list) {
for (const mutation of list)
ensureCachedData(mutation.target).attributesCached = void 0;
@@ -156,6 +172,8 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
if (this._readingStyleSheet)
return;
this._staleStyleSheets.add(sheet);
if (sheet.href !== null)
this._modifiedStyleSheets.add(sheet);
}
_updateStyleElementStyleSheetTextIfNeeded(sheet, forceText) {
const data = ensureCachedData(sheet);
@@ -248,6 +266,8 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
_getSheetText(sheet) {
this._readingStyleSheet = true;
try {
if (sheet.disabled)
return "";
const rules = [];
for (const rule of sheet.cssRules)
rules.push(rule.cssText);
@@ -518,7 +538,7 @@ function frameSnapshotStreamer(snapshotStreamer, removeNoScript) {
wallTime: Date.now(),
collectionTime: 0
};
for (const sheet of this._staleStyleSheets) {
for (const sheet of this._modifiedStyleSheets) {
if (sheet.href === null)
continue;
const content = this._updateLinkStyleSheetTextIfNeeded(sheet, snapshotNumber);

View File

@@ -222,7 +222,7 @@ class Tracing extends import_instrumentation.SdkObject {
if (!(this._context instanceof import_browserContext.BrowserContext))
return;
for (const page of this._context.pages())
page.setScreencastOptions(null);
page.screencast.setOptions(null);
}
_allocateNewTraceFile(state) {
const suffix = state.chunkOrdinal ? `-chunk${state.chunkOrdinal}` : ``;
@@ -326,23 +326,21 @@ class Tracing extends import_instrumentation.SdkObject {
return { artifact };
}
async _captureSnapshot(snapshotName, sdkObject, metadata) {
if (!this._snapshotter)
if (!snapshotName || !sdkObject.attribution.page)
return;
if (!sdkObject.attribution.page)
return;
if (!this._snapshotter.started())
return;
if (!shouldCaptureSnapshot(metadata))
return;
await this._snapshotter.captureSnapshot(sdkObject.attribution.page, metadata.id, snapshotName).catch(() => {
await this._snapshotter?.captureSnapshot(sdkObject.attribution.page, metadata.id, snapshotName).catch(() => {
});
}
onBeforeCall(sdkObject, metadata) {
const event = createBeforeActionTraceEvent(metadata, this._currentGroupId());
_shouldCaptureSnapshot(sdkObject, metadata) {
return !!this._snapshotter?.started() && shouldCaptureSnapshot(metadata) && !!sdkObject.attribution.page;
}
onBeforeCall(sdkObject, metadata, parentId) {
const event = createBeforeActionTraceEvent(metadata, parentId ?? this._currentGroupId());
if (!event)
return Promise.resolve();
sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
event.beforeSnapshot = `before@${metadata.id}`;
sdkObject.attribution.page?.screencast.temporarilyDisableThrottling();
if (this._shouldCaptureSnapshot(sdkObject, metadata))
event.beforeSnapshot = `before@${metadata.id}`;
this._state?.callIds.add(metadata.id);
this._appendTraceEvent(event);
return this._captureSnapshot(event.beforeSnapshot, sdkObject, metadata);
@@ -353,8 +351,9 @@ class Tracing extends import_instrumentation.SdkObject {
const event = createInputActionTraceEvent(metadata);
if (!event)
return Promise.resolve();
sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
event.inputSnapshot = `input@${metadata.id}`;
sdkObject.attribution.page?.screencast.temporarilyDisableThrottling();
if (this._shouldCaptureSnapshot(sdkObject, metadata))
event.inputSnapshot = `input@${metadata.id}`;
this._appendTraceEvent(event);
return this._captureSnapshot(event.inputSnapshot, sdkObject, metadata);
}
@@ -369,15 +368,16 @@ class Tracing extends import_instrumentation.SdkObject {
if (event)
this._appendTraceEvent(event);
}
async onAfterCall(sdkObject, metadata) {
onAfterCall(sdkObject, metadata) {
if (!this._state?.callIds.has(metadata.id))
return;
return Promise.resolve();
this._state?.callIds.delete(metadata.id);
const event = createAfterActionTraceEvent(metadata);
if (!event)
return;
sdkObject.attribution.page?.temporarilyDisableTracingScreencastThrottling();
event.afterSnapshot = `after@${metadata.id}`;
return Promise.resolve();
sdkObject.attribution.page?.screencast.temporarilyDisableThrottling();
if (this._shouldCaptureSnapshot(sdkObject, metadata))
event.afterSnapshot = `after@${metadata.id}`;
this._appendTraceEvent(event);
return this._captureSnapshot(event.afterSnapshot, sdkObject, metadata);
}
@@ -484,7 +484,7 @@ class Tracing extends import_instrumentation.SdkObject {
this._appendTraceEvent(event);
}
_startScreencastInPage(page) {
page.setScreencastOptions(kScreencastOptions);
page.screencast.setOptions(kScreencastOptions);
const prefix = page.guid;
this._screencastListeners.push(
import_eventsHelper.eventsHelper.addEventListener(page, import_page.Page.Events.ScreencastFrame, (params) => {

View File

@@ -1,87 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var inMemorySnapshotter_exports = {};
__export(inMemorySnapshotter_exports, {
InMemorySnapshotter: () => InMemorySnapshotter
});
module.exports = __toCommonJS(inMemorySnapshotter_exports);
var import_snapshotStorage = require("../../../../../trace-viewer/src/sw/snapshotStorage");
var import_utils = require("../../../utils");
var import_harTracer = require("../../har/harTracer");
var import_snapshotter = require("../recorder/snapshotter");
class InMemorySnapshotter {
constructor(context) {
this._blobs = /* @__PURE__ */ new Map();
this._snapshotReadyPromises = /* @__PURE__ */ new Map();
this._snapshotCount = 0;
this._snapshotter = new import_snapshotter.Snapshotter(context, this);
this._harTracer = new import_harTracer.HarTracer(context, null, this, { content: "attach", includeTraceInfo: true, recordRequestOverrides: false, waitForContentOnStop: false });
this._storage = new import_snapshotStorage.SnapshotStorage();
}
async initialize() {
await this._snapshotter.start();
this._harTracer.start({ omitScripts: true });
}
async reset() {
await this._snapshotter.reset();
await this._harTracer.flush();
this._harTracer.stop();
this._harTracer.start({ omitScripts: true });
}
async dispose() {
this._snapshotter.dispose();
await this._harTracer.flush();
this._harTracer.stop();
}
async captureSnapshot(page, callId, snapshotName) {
if (this._snapshotReadyPromises.has(snapshotName))
throw new Error("Duplicate snapshot name: " + snapshotName);
this._snapshotter.captureSnapshot(page, callId, snapshotName).catch(() => {
});
const promise = new import_utils.ManualPromise();
this._snapshotReadyPromises.set(snapshotName, promise);
return promise;
}
onEntryStarted(entry) {
}
onEntryFinished(entry) {
this._storage.addResource("", entry);
}
onContentBlob(sha1, buffer) {
this._blobs.set(sha1, buffer);
}
onSnapshotterBlob(blob) {
this._blobs.set(blob.sha1, blob.buffer);
}
onFrameSnapshot(snapshot) {
++this._snapshotCount;
const renderer = this._storage.addFrameSnapshot("", snapshot, []);
this._snapshotReadyPromises.get(snapshot.snapshotName || "")?.resolve(renderer);
}
async resourceContentForTest(sha1) {
return this._blobs.get(sha1);
}
snapshotCount() {
return this._snapshotCount;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
InMemorySnapshotter
});

View File

@@ -0,0 +1,72 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var traceParser_exports = {};
__export(traceParser_exports, {
ZipTraceLoaderBackend: () => ZipTraceLoaderBackend
});
module.exports = __toCommonJS(traceParser_exports);
var import_url = __toESM(require("url"));
var import_zipFile = require("../../utils/zipFile");
class ZipTraceLoaderBackend {
constructor(traceFile) {
this._traceFile = traceFile;
this._zipFile = new import_zipFile.ZipFile(traceFile);
}
isLive() {
return false;
}
traceURL() {
return import_url.default.pathToFileURL(this._traceFile).toString();
}
async entryNames() {
return await this._zipFile.entries();
}
async hasEntry(entryName) {
const entries = await this.entryNames();
return entries.includes(entryName);
}
async readText(entryName) {
try {
const buffer = await this._zipFile.read(entryName);
return buffer.toString("utf-8");
} catch {
}
}
async readBlob(entryName) {
try {
const buffer = await this._zipFile.read(entryName);
return new Blob([new Uint8Array(buffer)]);
} catch {
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ZipTraceLoaderBackend
});

View File

@@ -47,20 +47,21 @@ var import_launchApp2 = require("../../launchApp");
var import_playwright = require("../../playwright");
var import_progress = require("../../progress");
const tracesDirMarker = "traces.dir";
function validateTraceUrl(traceUrl) {
if (!traceUrl)
return traceUrl;
if (traceUrl.startsWith("http://") || traceUrl.startsWith("https://"))
return traceUrl;
if (traceUrl.endsWith(".json"))
return traceUrl;
function validateTraceUrlOrPath(traceFileOrUrl) {
if (!traceFileOrUrl)
return traceFileOrUrl;
if (traceFileOrUrl.startsWith("http://") || traceFileOrUrl.startsWith("https://"))
return traceFileOrUrl;
let traceFile = traceFileOrUrl;
if (traceFile.endsWith(".json"))
return toFilePathUrl(traceFile);
try {
const stat = import_fs.default.statSync(traceUrl);
const stat = import_fs.default.statSync(traceFile);
if (stat.isDirectory())
return import_path.default.join(traceUrl, tracesDirMarker);
return traceUrl;
traceFile = import_path.default.join(traceFile, tracesDirMarker);
return toFilePathUrl(traceFile);
} catch {
throw new Error(`Trace file ${traceUrl} does not exist!`);
throw new Error(`Trace file ${traceFileOrUrl} does not exist!`);
}
}
async function startTraceViewerServer(options) {
@@ -126,7 +127,7 @@ async function installRootRedirect(server, traceUrl, options) {
});
}
async function runTraceViewerApp(traceUrl, browserName, options, exitOnClose) {
traceUrl = validateTraceUrl(traceUrl);
traceUrl = validateTraceUrlOrPath(traceUrl);
const server = await startTraceViewerServer(options);
await installRootRedirect(server, traceUrl, options);
const page = await openTraceViewerApp(server.urlPrefix("precise"), browserName, options);
@@ -135,7 +136,7 @@ async function runTraceViewerApp(traceUrl, browserName, options, exitOnClose) {
return page;
}
async function runTraceInBrowser(traceUrl, options) {
traceUrl = validateTraceUrl(traceUrl);
traceUrl = validateTraceUrlOrPath(traceUrl);
const server = await startTraceViewerServer(options);
await installRootRedirect(server, traceUrl, options);
await openTraceInBrowser(server.urlPrefix("human-readable"));
@@ -177,8 +178,8 @@ async function openTraceInBrowser(url) {
class StdinServer {
constructor() {
process.stdin.on("data", (data) => {
const url = data.toString().trim();
if (url === this._traceUrl)
const url = validateTraceUrlOrPath(data.toString().trim());
if (!url || url === this._traceUrl)
return;
if (url.endsWith(".json"))
this._pollLoadTrace(url);
@@ -221,15 +222,18 @@ function traceDescriptor(traceDir, tracePrefix) {
};
for (const name of import_fs.default.readdirSync(traceDir)) {
if (!tracePrefix || name.startsWith(tracePrefix))
result.entries.push({ name, path: import_path.default.join(traceDir, name) });
result.entries.push({ name, path: toFilePathUrl(import_path.default.join(traceDir, name)) });
}
const resourcesDir = import_path.default.join(traceDir, "resources");
if (import_fs.default.existsSync(resourcesDir)) {
for (const name of import_fs.default.readdirSync(resourcesDir))
result.entries.push({ name: "resources/" + name, path: import_path.default.join(resourcesDir, name) });
result.entries.push({ name: "resources/" + name, path: toFilePathUrl(import_path.default.join(resourcesDir, name)) });
}
return result;
}
function toFilePathUrl(filePath) {
return `file?path=${encodeURIComponent(filePath)}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
installRootRedirect,

View File

@@ -18,10 +18,16 @@ var __copyProps = (to, from, except, desc) => {
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var expectUtils_exports = {};
__export(expectUtils_exports, {
serializeExpectedTextValues: () => serializeExpectedTextValues
callLogText: () => callLogText,
formatMatcherMessage: () => formatMatcherMessage,
printReceivedStringContainExpectedResult: () => printReceivedStringContainExpectedResult,
printReceivedStringContainExpectedSubstring: () => printReceivedStringContainExpectedSubstring,
serializeExpectedTextValues: () => serializeExpectedTextValues,
simpleMatcherUtils: () => simpleMatcherUtils
});
module.exports = __toCommonJS(expectUtils_exports);
var import_rtti = require("../../utils/isomorphic/rtti");
var import_utilsBundle = require("../../utilsBundle");
function serializeExpectedTextValues(items, options = {}) {
return items.map((i) => ({
string: (0, import_rtti.isString)(i) ? i : void 0,
@@ -32,7 +38,86 @@ function serializeExpectedTextValues(items, options = {}) {
normalizeWhiteSpace: options.normalizeWhiteSpace
}));
}
const printSubstring = (val) => val.replace(/"|\\/g, "\\$&");
const printReceivedStringContainExpectedSubstring = (utils, received, start, length) => utils.RECEIVED_COLOR(
'"' + printSubstring(received.slice(0, start)) + utils.INVERTED_COLOR(printSubstring(received.slice(start, start + length))) + printSubstring(received.slice(start + length)) + '"'
);
const printReceivedStringContainExpectedResult = (utils, received, result) => result === null ? utils.printReceived(received) : printReceivedStringContainExpectedSubstring(
utils,
received,
result.index,
result[0].length
);
function formatMatcherMessage(utils, details) {
const receiver = details.receiver ?? (details.locator ? "locator" : "page");
let message = utils.DIM_COLOR("expect(") + utils.RECEIVED_COLOR(receiver) + utils.DIM_COLOR(")" + (details.promise ? "." + details.promise : "") + (details.isNot ? ".not" : "") + ".") + details.matcherName + utils.DIM_COLOR("(") + utils.EXPECTED_COLOR(details.expectation) + utils.DIM_COLOR(")") + " failed\n\n";
const diffLines = details.printedDiff?.split("\n");
if (diffLines?.length === 2) {
details.printedExpected = diffLines[0];
details.printedReceived = diffLines[1];
details.printedDiff = void 0;
}
const align = !details.errorMessage && details.printedExpected?.startsWith("Expected:") && (!details.printedReceived || details.printedReceived.startsWith("Received:"));
if (details.locator)
message += `Locator: ${align ? " " : ""}${details.locator}
`;
if (details.printedExpected)
message += details.printedExpected + "\n";
if (details.printedReceived)
message += details.printedReceived + "\n";
if (details.timedOut && details.timeout)
message += `Timeout: ${align ? " " : ""}${details.timeout}ms
`;
if (details.printedDiff)
message += details.printedDiff + "\n";
if (details.errorMessage) {
message += details.errorMessage;
if (!details.errorMessage.endsWith("\n"))
message += "\n";
}
message += callLogText(utils, details.log);
return message;
}
const callLogText = (utils, log) => {
if (!log || !log.some((l) => !!l))
return "";
return `
Call log:
${utils.DIM_COLOR(log.join("\n"))}
`;
};
function printValue(value) {
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
function printReceived(value) {
return import_utilsBundle.colors.red(printValue(value));
}
function printExpected(value) {
return import_utilsBundle.colors.green(printValue(value));
}
const simpleMatcherUtils = {
DIM_COLOR: import_utilsBundle.colors.dim,
RECEIVED_COLOR: import_utilsBundle.colors.red,
EXPECTED_COLOR: import_utilsBundle.colors.green,
INVERTED_COLOR: import_utilsBundle.colors.inverse,
printReceived,
printExpected,
printDiffOrStringify: (expected, received, expectedLabel, receivedLabel) => {
const maxLength = Math.max(expectedLabel.length, receivedLabel.length) + 2;
return `${expectedLabel}: `.padEnd(maxLength) + printExpected(expected) + `
` + `${receivedLabel}: `.padEnd(maxLength) + printReceived(received);
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
serializeExpectedTextValues
callLogText,
formatMatcherMessage,
printReceivedStringContainExpectedResult,
printReceivedStringContainExpectedSubstring,
serializeExpectedTextValues,
simpleMatcherUtils
});

View File

@@ -28,11 +28,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var hostPlatform_exports = {};
__export(hostPlatform_exports, {
hasGpuMac: () => hasGpuMac,
hostPlatform: () => hostPlatform,
isOfficiallySupportedPlatform: () => isOfficiallySupportedPlatform,
shortPlatform: () => shortPlatform
});
module.exports = __toCommonJS(hostPlatform_exports);
var import_child_process = require("child_process");
var import_os = __toESM(require("os"));
var import_linuxUtils = require("./linuxUtils");
function calculatePlatform() {
@@ -115,8 +117,21 @@ function toShortPlatform(hostPlatform2) {
return hostPlatform2.endsWith("arm64") ? "linux-arm64" : "linux-x64";
}
const shortPlatform = toShortPlatform(hostPlatform);
let hasGpuMacValue;
function hasGpuMac() {
try {
if (hasGpuMacValue === void 0) {
const output = (0, import_child_process.execSync)("system_profiler SPDisplaysDataType", { stdio: ["ignore", "pipe", "ignore"] }).toString();
hasGpuMacValue = output.includes("Metal: Supported") || output.includes("Metal Support: Metal");
}
return hasGpuMacValue;
} catch (e) {
return false;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
hasGpuMac,
hostPlatform,
isOfficiallySupportedPlatform,
shortPlatform

View File

@@ -36,7 +36,6 @@ var import_path = __toESM(require("path"));
var import_utilsBundle = require("../../utilsBundle");
var import_crypto = require("./crypto");
var import_assert = require("../../utils/isomorphic/assert");
var import_manualPromise = require("../../utils/isomorphic/manualPromise");
var import_network = require("./network");
class HttpServer {
constructor() {
@@ -59,20 +58,6 @@ class HttpServer {
port() {
return this._port;
}
async _tryStart(port, host) {
const errorPromise = new import_manualPromise.ManualPromise();
const errorListener = (error) => errorPromise.reject(error);
this._server.on("error", errorListener);
try {
this._server.listen(port, host);
await Promise.race([
new Promise((cb) => this._server.once("listening", cb)),
errorPromise
]);
} finally {
this._server.removeListener("error", errorListener);
}
}
createWebSocket(transport, guid) {
(0, import_assert.assert)(!this._wsGuid, "can only create one main websocket transport per server");
this._wsGuid = guid || (0, import_crypto.createGuid)();
@@ -100,17 +85,17 @@ class HttpServer {
async start(options = {}) {
(0, import_assert.assert)(!this._started, "server already started");
this._started = true;
const host = options.host || "localhost";
const host = options.host;
if (options.preferredPort) {
try {
await this._tryStart(options.preferredPort, host);
await (0, import_network.startHttpServer)(this._server, { port: options.preferredPort, host });
} catch (e) {
if (!e || !e.message || !e.message.includes("EADDRINUSE"))
throw e;
await this._tryStart(void 0, host);
await (0, import_network.startHttpServer)(this._server, { host });
}
} else {
await this._tryStart(options.port, host);
await (0, import_network.startHttpServer)(this._server, { port: options.port, host });
}
const address = this._server.address();
(0, import_assert.assert)(address, "Could not bind server socket");
@@ -121,7 +106,7 @@ class HttpServer {
this._port = address.port;
const resolvedHost = address.family === "IPv4" ? address.address : `[${address.address}]`;
this._urlPrefixPrecise = `http://${resolvedHost}:${address.port}`;
this._urlPrefixHumanReadable = `http://${host}:${address.port}`;
this._urlPrefixHumanReadable = `http://${host ?? "localhost"}:${address.port}`;
}
}
async stop() {

View File

@@ -35,22 +35,20 @@ __export(network_exports, {
createProxyAgent: () => createProxyAgent,
fetchData: () => fetchData,
httpRequest: () => httpRequest,
isURLAvailable: () => isURLAvailable
isURLAvailable: () => isURLAvailable,
startHttpServer: () => startHttpServer
});
module.exports = __toCommonJS(network_exports);
var import_http = __toESM(require("http"));
var import_http2 = __toESM(require("http2"));
var import_https = __toESM(require("https"));
var import_url = __toESM(require("url"));
var import_utilsBundle = require("../../utilsBundle");
var import_happyEyeballs = require("./happyEyeballs");
var import_manualPromise = require("../../utils/isomorphic/manualPromise");
const NET_DEFAULT_TIMEOUT = 3e4;
function httpRequest(params, onResponse, onError) {
const parsedUrl = import_url.default.parse(params.url);
let options = {
...parsedUrl,
agent: parsedUrl.protocol === "https:" ? import_happyEyeballs.httpsHappyEyeballsAgent : import_happyEyeballs.httpHappyEyeballsAgent,
let url = new URL(params.url);
const options = {
method: params.method || "GET",
headers: params.headers
};
@@ -58,21 +56,16 @@ function httpRequest(params, onResponse, onError) {
options.rejectUnauthorized = params.rejectUnauthorized;
const proxyURL = (0, import_utilsBundle.getProxyForUrl)(params.url);
if (proxyURL) {
const parsedProxyURL = normalizeProxyURL(proxyURL);
if (params.url.startsWith("http:")) {
const parsedProxyURL = import_url.default.parse(proxyURL);
options = {
path: parsedUrl.href,
host: parsedProxyURL.hostname,
port: parsedProxyURL.port,
protocol: parsedProxyURL.protocol || "http:",
headers: options.headers,
method: options.method
};
parsedProxyURL.pathname = url.toString();
url = parsedProxyURL;
} else {
options.agent = new import_utilsBundle.HttpsProxyAgent(normalizeProxyURL(proxyURL));
options.agent = new import_utilsBundle.HttpsProxyAgent(parsedProxyURL);
options.rejectUnauthorized = false;
}
}
options.agent ??= url.protocol === "https:" ? import_happyEyeballs.httpsHappyEyeballsAgent : import_happyEyeballs.httpHappyEyeballsAgent;
let cancelRequest;
const requestCallback = (res) => {
const statusCode = res.statusCode || 0;
@@ -83,7 +76,7 @@ function httpRequest(params, onResponse, onError) {
onResponse(res);
}
};
const request = options.protocol === "https:" ? import_https.default.request(options, requestCallback) : import_http.default.request(options, requestCallback);
const request = url.protocol === "https:" ? import_https.default.request(url, options, requestCallback) : import_http.default.request(url, options, requestCallback);
request.on("error", onError);
if (params.socketTimeout !== void 0) {
request.setTimeout(params.socketTimeout, () => {
@@ -122,7 +115,7 @@ async function fetchData(progress, params, onError) {
throw error;
}
}
function shouldBypassProxy(url2, bypass) {
function shouldBypassProxy(url, bypass) {
if (!bypass)
return false;
const domains = bypass.split(",").map((s) => {
@@ -131,7 +124,7 @@ function shouldBypassProxy(url2, bypass) {
s = "." + s;
return s;
});
const domain = "." + url2.hostname;
const domain = "." + url.hostname;
return domains.some((d) => domain.endsWith(d));
}
function normalizeProxyURL(proxy) {
@@ -177,20 +170,35 @@ function createHttp2Server(...args) {
decorateServer(server);
return server;
}
async function isURLAvailable(url2, ignoreHTTPSErrors, onLog, onStdErr) {
let statusCode = await httpStatusCode(url2, ignoreHTTPSErrors, onLog, onStdErr);
if (statusCode === 404 && url2.pathname === "/") {
const indexUrl = new URL(url2);
async function startHttpServer(server, options) {
const { host = "localhost", port = 0 } = options;
const errorPromise = new import_manualPromise.ManualPromise();
const errorListener = (error) => errorPromise.reject(error);
server.on("error", errorListener);
try {
server.listen(port, host);
await Promise.race([
new Promise((cb) => server.once("listening", cb)),
errorPromise
]);
} finally {
server.removeListener("error", errorListener);
}
}
async function isURLAvailable(url, ignoreHTTPSErrors, onLog, onStdErr) {
let statusCode = await httpStatusCode(url, ignoreHTTPSErrors, onLog, onStdErr);
if (statusCode === 404 && url.pathname === "/") {
const indexUrl = new URL(url);
indexUrl.pathname = "/index.html";
statusCode = await httpStatusCode(indexUrl, ignoreHTTPSErrors, onLog, onStdErr);
}
return statusCode >= 200 && statusCode < 404;
}
async function httpStatusCode(url2, ignoreHTTPSErrors, onLog, onStdErr) {
async function httpStatusCode(url, ignoreHTTPSErrors, onLog, onStdErr) {
return new Promise((resolve) => {
onLog?.(`HTTP GET: ${url2}`);
onLog?.(`HTTP GET: ${url}`);
httpRequest({
url: url2.toString(),
url: url.toString(),
headers: { Accept: "*/*" },
rejectUnauthorized: !ignoreHTTPSErrors
}, (res) => {
@@ -201,7 +209,7 @@ async function httpStatusCode(url2, ignoreHTTPSErrors, onLog, onStdErr) {
}, (error) => {
if (error.code === "DEPTH_ZERO_SELF_SIGNED_CERT")
onStdErr?.(`[WebServer] Self-signed certificate detected. Try adding ignoreHTTPSErrors: true to config.webServer.`);
onLog?.(`Error while checking if ${url2} is available: ${error.message}`);
onLog?.(`Error while checking if ${url} is available: ${error.message}`);
resolve(0);
});
});
@@ -229,5 +237,6 @@ function decorateServer(server) {
createProxyAgent,
fetchData,
httpRequest,
isURLAvailable
isURLAvailable,
startHttpServer
});

View File

@@ -42,6 +42,7 @@ var import_utilsBundle = require("../../utilsBundle");
var import_debugLogger = require("./debugLogger");
var import_zones = require("./zones");
var import_debug = require("./debug");
var import_mcpBundle = require("../../mcpBundle");
const pipelineAsync = util.promisify(import_stream.pipeline);
class NodeZone {
constructor(zone) {
@@ -105,6 +106,11 @@ const nodePlatform = {
streamWritable: (channel) => {
return new WritableStreamImpl(channel);
},
zodToJsonSchema: (schema) => {
if ("_zod" in schema)
return import_mcpBundle.z.toJSONSchema(schema);
return (0, import_mcpBundle.zodToJsonSchema)(schema);
},
zones: {
current: () => new NodeZone((0, import_zones.currentZone)()),
empty: new NodeZone(import_zones.emptyZone)

View File

@@ -21,12 +21,11 @@ __export(videoRecorder_exports, {
VideoRecorder: () => VideoRecorder
});
module.exports = __toCommonJS(videoRecorder_exports);
var import_utils = require("../../utils");
var import_page = require("../page");
var import_processLauncher = require("../utils/processLauncher");
var import_utils = require("../utils");
var import_processLauncher = require("./utils/processLauncher");
const fps = 25;
class VideoRecorder {
constructor(page, ffmpegPath) {
constructor(ffmpegPath, options) {
this._process = null;
this._gracefullyClose = null;
this._lastWritePromise = Promise.resolve();
@@ -36,16 +35,12 @@ class VideoRecorder {
this._frameQueue = [];
this._isStopped = false;
this._ffmpegPath = ffmpegPath;
page.on(import_page.Page.Events.ScreencastFrame, (frame) => this.writeFrame(frame.buffer, frame.frameSwapWallTime / 1e3));
}
static async launch(page, ffmpegPath, options) {
if (!options.outputFile.endsWith(".webm"))
throw new Error("File must have .webm extension");
const recorder = new VideoRecorder(page, ffmpegPath);
await recorder._launch(options);
return recorder;
this._launchPromise = this._launch(options).catch((e) => e);
}
async _launch(options) {
await (0, import_utils.mkdirIfNeeded)(options.outputFile);
const w = options.width;
const h = options.height;
const args = `-loglevel error -f image2pipe -avioflags direct -fpsprobesize 0 -probesize 32 -analyzeduration 0 -c:v mjpeg -i pipe:0 -y -an -r ${fps} -c:v vp8 -qmin 0 -qmax 50 -crf 8 -deadline realtime -speed 8 -b:v 1M -threads 1 -vf pad=${w}:${h}:0:0:gray,crop=${w}:${h}:0:0`.split(" ");
@@ -74,6 +69,13 @@ class VideoRecorder {
this._gracefullyClose = gracefullyClose;
}
writeFrame(frame, timestamp) {
this._launchPromise.then((error) => {
if (error)
return;
this._writeFrame(frame, timestamp);
});
}
_writeFrame(frame, timestamp) {
(0, import_utils.assert)(this._process);
if (this._isStopped)
return;
@@ -100,13 +102,20 @@ class VideoRecorder {
});
}
async stop() {
const error = await this._launchPromise;
if (error)
throw error;
if (this._isStopped || !this._lastFrame)
return;
const addTime = Math.max(((0, import_utils.monotonicTime)() - this._lastWriteNodeTime) / 1e3, 1);
this.writeFrame(Buffer.from([]), this._lastFrame.timestamp + addTime);
this._writeFrame(Buffer.from([]), this._lastFrame.timestamp + addTime);
this._isStopped = true;
await this._lastWritePromise;
await this._gracefullyClose();
try {
await this._lastWritePromise;
await this._gracefullyClose();
} catch (e) {
import_utils.debugLogger.log("error", `ffmpeg failed to stop: ${String(e)}`);
}
}
}
// Annotate the CommonJS export names for ESM import in node:

View File

@@ -51,12 +51,10 @@ class WebKit extends import_browserType.BrowserType {
CURL_COOKIE_JAR_PATH: process.platform === "win32" && isPersistent ? import_path.default.join(userDataDir, "cookiejar.db") : void 0
};
}
doRewriteStartupLog(error) {
if (!error.logs)
return error;
if (error.logs.includes("Failed to open display") || error.logs.includes("cannot open display"))
error.logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
return error;
doRewriteStartupLog(logs) {
if (logs.includes("Failed to open display") || logs.includes("cannot open display"))
logs = "\n" + (0, import_ascii.wrapInASCIIBox)(import_browserType.kNoXServerRunningError, 1);
return logs;
}
attemptToGracefullyCloseBrowser(transport) {
transport.send({ method: "Playwright.close", params: {}, id: import_wkConnection.kBrowserCloseMessageId });

View File

@@ -56,7 +56,6 @@ class WKBrowser extends import_browser.Browser {
this._browserSession.on("Playwright.downloadCreated", this._onDownloadCreated.bind(this));
this._browserSession.on("Playwright.downloadFilenameSuggested", this._onDownloadFilenameSuggested.bind(this));
this._browserSession.on("Playwright.downloadFinished", this._onDownloadFinished.bind(this));
this._browserSession.on("Playwright.screencastFinished", this._onScreencastFinished.bind(this));
this._browserSession.on(import_wkConnection.kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this));
}
static async connect(parent, transport, options) {
@@ -79,7 +78,7 @@ class WKBrowser extends import_browser.Browser {
wkPage.didClose();
this._wkPages.clear();
for (const video of this._idToVideo.values())
video.artifact.reportFinished(new import_errors.TargetClosedError());
video.artifact.reportFinished(new import_errors.TargetClosedError(this.closeReason()));
this._idToVideo.clear();
this._didClose();
}
@@ -131,9 +130,6 @@ class WKBrowser extends import_browser.Browser {
_onDownloadFinished(payload) {
this._downloadFinished(payload.uuid, payload.error);
}
_onScreencastFinished(payload) {
this._takeVideo(payload.screencastId)?.reportFinished();
}
_onPageProxyCreated(event) {
const pageProxyId = event.pageProxyId;
let context = null;
@@ -315,7 +311,7 @@ class WKBrowserContext extends import_browserContext.BrowserContext {
}
async doClose(reason) {
if (!this._browserContextId) {
await Promise.all(this._wkPages().map((wkPage) => wkPage._stopVideo()));
await Promise.all(this._wkPages().map((wkPage) => wkPage._page.screencast.stopVideoRecording()));
await this._browser.close({ reason });
} else {
await this._browser._browserSession.send("Playwright.deleteContext", { browserContextId: this._browserContextId });

View File

@@ -89,11 +89,6 @@ class WKSession extends import_events.EventEmitter {
this.connection = connection;
this.sessionId = sessionId;
this._rawSend = rawSend;
this.on = super.on;
this.off = super.removeListener;
this.addListener = super.addListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
async send(method, params) {
if (this._crashed || this._disposed || this.connection._browserDisconnectedLogs)
@@ -134,7 +129,7 @@ class WKSession extends import_events.EventEmitter {
callback.resolve(object.result);
}
} else if (object.id && !object.error) {
(0, import_utils.assert)(this.isDisposed());
(0, import_utils.assert)(this.isDisposed(), JSON.stringify(object));
} else {
Promise.resolve().then(() => this.emit(object.method, object.params));
}

View File

@@ -54,7 +54,7 @@ class WKInterceptableRequest {
constructor(session, frame, event, redirectedFrom, documentId) {
this._session = session;
this._requestId = event.requestId;
const resourceType = event.type ? event.type.toLowerCase() : redirectedFrom ? redirectedFrom.request.resourceType() : "other";
const resourceType = event.type ? toResourceType(event.type) : redirectedFrom ? redirectedFrom.request.resourceType() : "other";
let postDataBuffer = null;
this._timestamp = event.timestamp;
this._wallTime = event.walltime * 1e3;
@@ -162,6 +162,34 @@ function wkMillisToRoundishMillis(value) {
}
return (value * 1e3 | 0) / 1e3;
}
function toResourceType(type) {
switch (type) {
case "Document":
return "document";
case "StyleSheet":
return "stylesheet";
case "Image":
return "image";
case "Font":
return "font";
case "Script":
return "script";
case "XHR":
return "xhr";
case "Fetch":
return "fetch";
case "Ping":
return "ping";
case "Beacon":
return "beacon";
case "WebSocket":
return "websocket";
case "EventSource":
return "eventsource";
default:
return "other";
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
WKInterceptableRequest,

View File

@@ -31,10 +31,8 @@ __export(wkPage_exports, {
WKPage: () => WKPage
});
module.exports = __toCommonJS(wkPage_exports);
var import_path = __toESM(require("path"));
var import_utils = require("../../utils");
var import_headers = require("../../utils/isomorphic/headers");
var import_crypto = require("../utils/crypto");
var import_eventsHelper = require("../utils/eventsHelper");
var import_hostPlatform = require("../utils/hostPlatform");
var import_stackTrace = require("../../utils/isomorphic/stackTrace");
@@ -51,12 +49,14 @@ var import_wkInput = require("./wkInput");
var import_wkInterceptableRequest = require("./wkInterceptableRequest");
var import_wkProvisionalPage = require("./wkProvisionalPage");
var import_wkWorkers = require("./wkWorkers");
var import_debugLogger = require("../utils/debugLogger");
var import_webkit = require("./webkit");
var import_registry = require("../registry");
const UTILITY_WORLD_NAME = "__playwright_utility_world__";
const enableFrameSessions = !process.env.WK_DISABLE_FRAME_SESSIONS && parseInt(import_registry.registry.findExecutable("webkit").revision, 10) >= 2245;
class WKPage {
constructor(browserContext, pageProxySession, opener) {
this._provisionalPage = null;
this._targetIdToFrameSession = /* @__PURE__ */ new Map();
this._requestIdToRequest = /* @__PURE__ */ new Map();
this._requestIdToRequestWillBeSentEvent = /* @__PURE__ */ new Map();
this._sessionListeners = [];
@@ -66,7 +66,6 @@ class WKPage {
};
this._lastConsoleMessage = null;
this._requestIdToResponseReceivedPayloadEvent = /* @__PURE__ */ new Map();
this._recordingVideoFile = null;
this._screencastGeneration = 0;
this._pageProxySession = pageProxySession;
this._opener = opener;
@@ -116,16 +115,7 @@ class WKPage {
for (const [key, value] of this._browserContext._permissions)
promises.push(this._grantPermissions(key, value));
}
if (this._browserContext._options.recordVideo) {
const outputFile = import_path.default.join(this._browserContext._options.recordVideo.dir, (0, import_crypto.createGuid)() + ".webm");
promises.push(this._browserContext._ensureVideosPath().then(() => {
return this._startVideo({
// validateBrowserContextOptions ensures correct video size.
...this._browserContext._options.recordVideo.size,
outputFile
});
}));
}
promises.push(this._initializeVideoRecording());
await Promise.all(promises);
}
_setSession(session) {
@@ -159,10 +149,13 @@ class WKPage {
session.send("Page.createUserWorld", { name: UTILITY_WORLD_NAME }).catch((_) => {
}),
// Worlds are per-process
session.send("Console.enable"),
session.send("Network.enable"),
this._workers.initializeSession(session)
];
if (enableFrameSessions)
this._initializeFrameSessions(frameTree.frameTree, promises);
else
promises.push(session.send("Console.enable"));
if (this._page.browserContext.needsPlaywrightBinding())
promises.push(session.send("Runtime.addBinding", { name: import_page.PageBinding.kBindingName }));
if (this._page.needsRequestInterception()) {
@@ -219,6 +212,13 @@ class WKPage {
promises.push(session.send("Page.overrideSetting", { setting: "FixedBackgroundsPaintRelativeToDocument", value: contextOptions.isMobile }));
await Promise.all(promises);
}
_initializeFrameSessions(frame, promises) {
const session = this._targetIdToFrameSession.get(`frame-${frame.frame.id}`);
if (session)
promises.push(session.initialize());
for (const childFrame of frame.childFrames || [])
this._initializeFrameSessions(childFrame, promises);
}
_onDidCommitProvisionalTarget(event) {
const { oldTargetId, newTargetId } = event;
(0, import_utils.assert)(this._provisionalPage);
@@ -244,6 +244,9 @@ class WKPage {
this._session.markAsCrashed();
this._page._didCrash();
}
} else if (this._targetIdToFrameSession.has(targetId)) {
this._targetIdToFrameSession.get(targetId).dispose();
this._targetIdToFrameSession.delete(targetId);
}
}
didClose() {
@@ -257,7 +260,7 @@ class WKPage {
this._provisionalPage.dispose();
this._provisionalPage = null;
}
this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError());
this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError(this._page.closeReason()));
this._page._didClose();
}
dispatchMessageToSession(message) {
@@ -288,8 +291,15 @@ class WKPage {
session.dispatchMessage({ id: message.id, error: { message: e.message } });
});
});
if (targetInfo.type === "frame")
if (targetInfo.type === "frame") {
if (enableFrameSessions) {
const wkFrame = new WKFrame(this, session);
this._targetIdToFrameSession.set(targetInfo.targetId, wkFrame);
await wkFrame.initialize().catch((e) => {
});
}
return;
}
(0, import_utils.assert)(targetInfo.type === "page", "Only page targets are expected in WebKit, received: " + targetInfo.type);
if (!targetInfo.isProvisional) {
(0, import_utils.assert)(!this._page.initializedOrUndefined());
@@ -331,6 +341,8 @@ class WKPage {
this._provisionalPage._session.dispatchMessage(JSON.parse(message));
else if (this._session.sessionId === targetId)
this._session.dispatchMessage(JSON.parse(message));
else if (this._targetIdToFrameSession.has(targetId))
this._targetIdToFrameSession.get(targetId)._session.dispatchMessage(JSON.parse(message));
else
throw new Error("Unknown target: " + targetId);
}
@@ -453,7 +465,7 @@ class WKPage {
}
async navigateFrame(frame, url, referrer) {
if (this._pageProxySession.isDisposed())
throw new import_errors.TargetClosedError();
throw new import_errors.TargetClosedError(this._page.closeReason());
const pageProxyId = this._pageProxySession.sessionId;
const result = await this._pageProxySession.connection.browserSession.send("Playwright.navigate", { url, pageProxyId, frameId: frame._id, referrer });
return { newDocumentId: result.loaderId };
@@ -748,7 +760,6 @@ class WKPage {
await this._updateState("Page.setBootstrapScript", { source: this._calculateBootstrapScript() });
}
async closePage(runBeforeUnload) {
await this._stopVideo();
await this._pageProxySession.sendMayFail("Target.close", {
targetId: this._session.sessionId,
runBeforeUnload
@@ -762,22 +773,11 @@ class WKPage {
return import_hostPlatform.hostPlatform === "mac10.15" ? 55 : 59;
return 0;
}
async _startVideo(options) {
(0, import_utils.assert)(!this._recordingVideoFile);
const { screencastId } = await this._pageProxySession.send("Screencast.startVideo", {
file: this._browserContext._browser.options.channel === "webkit-wsl" ? await (0, import_webkit.translatePathToWSL)(options.outputFile) : options.outputFile,
width: options.width,
height: options.height,
toolbarHeight: this._toolbarHeight()
});
this._recordingVideoFile = options.outputFile;
this._browserContext._browser._videoStarted(this._browserContext, screencastId, options.outputFile, this._page.waitForInitializedOrError());
}
async _stopVideo() {
if (!this._recordingVideoFile)
return;
await this._pageProxySession.sendMayFail("Screencast.stopVideo");
this._recordingVideoFile = null;
async _initializeVideoRecording() {
const screencast = this._page.screencast;
const videoOptions = screencast.launchVideoRecorder();
if (videoOptions)
await screencast.startVideoRecording(videoOptions);
}
validateScreenshotDimension(side, omitDeviceScaleFactor) {
if (process.platform === "darwin")
@@ -845,23 +845,27 @@ class WKPage {
throw e;
});
}
async setScreencastOptions(options) {
if (options) {
const so = { ...options, toolbarHeight: this._toolbarHeight() };
const { generation } = await this._pageProxySession.send("Screencast.startScreencast", so);
this._screencastGeneration = generation;
} else {
await this._pageProxySession.send("Screencast.stopScreencast");
}
async startScreencast(options) {
const { generation } = await this._pageProxySession.send("Screencast.startScreencast", {
quality: options.quality,
width: options.width,
height: options.height,
toolbarHeight: this._toolbarHeight()
});
this._screencastGeneration = generation;
}
async stopScreencast() {
await this._pageProxySession.sendMayFail("Screencast.stopScreencast");
}
_onScreencastFrame(event) {
const generation = this._screencastGeneration;
this._page.throttleScreencastFrameAck(() => {
this._pageProxySession.send("Screencast.screencastFrameAck", { generation }).catch((e) => import_debugLogger.debugLogger.log("error", e));
this._page.screencast.throttleFrameAck(() => {
this._pageProxySession.sendMayFail("Screencast.screencastFrameAck", { generation });
});
const buffer = Buffer.from(event.data, "base64");
this._page.emit(import_page.Page.Events.ScreencastFrame, {
buffer,
frameSwapWallTime: event.timestamp ? event.timestamp * 1e3 : Date.now(),
width: event.deviceWidth,
height: event.deviceHeight
});
@@ -886,7 +890,7 @@ class WKPage {
const pageProxyId = this._pageProxySession.sessionId;
const objectId = handle._objectId;
if (this._browserContext._browser?.options.channel === "webkit-wsl")
paths = await Promise.all(paths.map((path2) => (0, import_webkit.translatePathToWSL)(path2)));
paths = await Promise.all(paths.map((path) => (0, import_webkit.translatePathToWSL)(path)));
await Promise.all([
this._pageProxySession.connection.browserSession.send("Playwright.grantFileReadAccess", { pageProxyId, paths }),
this._session.send("DOM.setInputFiles", { objectId, paths })
@@ -1085,6 +1089,31 @@ class WKPage {
return true;
}
}
class WKFrame {
constructor(page, session) {
this._sessionListeners = [];
this._initializePromise = null;
this._page = page;
this._session = session;
}
async initialize() {
if (this._initializePromise)
return this._initializePromise;
this._initializePromise = this._initializeImpl();
return this._initializePromise;
}
async _initializeImpl() {
this._sessionListeners = [
import_eventsHelper.eventsHelper.addEventListener(this._session, "Console.messageAdded", (event) => this._page._onConsoleMessage(event)),
import_eventsHelper.eventsHelper.addEventListener(this._session, "Console.messageRepeatCountUpdated", (event) => this._page._onConsoleRepeatCountUpdated(event))
];
await this._session.send("Console.enable");
}
dispose() {
import_eventsHelper.eventsHelper.removeEventListeners(this._sessionListeners);
this._session.dispose();
}
}
function parseRemoteAddress(value) {
if (!value)
return;

View File

@@ -39,6 +39,7 @@ __reExport(utils_exports, require("./utils/isomorphic/stringUtils"), module.expo
__reExport(utils_exports, require("./utils/isomorphic/time"), module.exports);
__reExport(utils_exports, require("./utils/isomorphic/timeoutRunner"), module.exports);
__reExport(utils_exports, require("./utils/isomorphic/urlMatch"), module.exports);
__reExport(utils_exports, require("./utils/isomorphic/yaml"), module.exports);
__reExport(utils_exports, require("./server/utils/ascii"), module.exports);
__reExport(utils_exports, require("./server/utils/comparators"), module.exports);
__reExport(utils_exports, require("./server/utils/crypto"), module.exports);
@@ -83,6 +84,7 @@ var import_utilsBundle = require("./utilsBundle");
...require("./utils/isomorphic/time"),
...require("./utils/isomorphic/timeoutRunner"),
...require("./utils/isomorphic/urlMatch"),
...require("./utils/isomorphic/yaml"),
...require("./server/utils/ascii"),
...require("./server/utils/comparators"),
...require("./server/utils/crypto"),

View File

@@ -20,12 +20,26 @@ var ariaSnapshot_exports = {};
__export(ariaSnapshot_exports, {
KeyParser: () => KeyParser,
ParserError: () => ParserError,
ariaPropsEqual: () => ariaPropsEqual,
ariaNodesEqual: () => ariaNodesEqual,
findNewNode: () => findNewNode,
hasPointerCursor: () => hasPointerCursor,
parseAriaSnapshot: () => parseAriaSnapshot,
parseAriaSnapshotUnsafe: () => parseAriaSnapshotUnsafe,
textValue: () => textValue
});
module.exports = __toCommonJS(ariaSnapshot_exports);
function ariaNodesEqual(a, b) {
if (a.role !== b.role || a.name !== b.name)
return false;
if (!ariaPropsEqual(a, b) || hasPointerCursor(a) !== hasPointerCursor(b))
return false;
const aKeys = Object.keys(a.props);
const bKeys = Object.keys(b.props);
return aKeys.length === bKeys.length && aKeys.every((k) => a.props[k] === b.props[k]);
}
function hasPointerCursor(ariaNode) {
return ariaNode.box.cursor === "pointer";
}
function ariaPropsEqual(a, b) {
return a.active === b.active && a.checked === b.checked && a.disabled === b.disabled && a.expanded === b.expanded && a.selected === b.selected && a.level === b.level && a.pressed === b.pressed;
}
@@ -386,11 +400,55 @@ class ParserError extends Error {
this.pos = pos;
}
}
function findNewNode(from, to) {
function fillMap(root, map, position) {
let size = 1;
let childPosition = position + size;
for (const child of root.children || []) {
if (typeof child === "string") {
size++;
childPosition++;
} else {
size += fillMap(child, map, childPosition);
childPosition += size;
}
}
if (!["none", "presentation", "fragment", "iframe", "generic"].includes(root.role) && root.name) {
let byRole = map.get(root.role);
if (!byRole) {
byRole = /* @__PURE__ */ new Map();
map.set(root.role, byRole);
}
const existing = byRole.get(root.name);
const sizeAndPosition = size * 100 - position;
if (!existing || existing.sizeAndPosition < sizeAndPosition)
byRole.set(root.name, { node: root, sizeAndPosition });
}
return size;
}
const fromMap = /* @__PURE__ */ new Map();
if (from)
fillMap(from, fromMap, 0);
const toMap = /* @__PURE__ */ new Map();
fillMap(to, toMap, 0);
const result = [];
for (const [role, byRole] of toMap) {
for (const [name, byName] of byRole) {
const inFrom = fromMap.get(role)?.get(name);
if (!inFrom)
result.push(byName);
}
}
result.sort((a, b) => b.sizeAndPosition - a.sizeAndPosition);
return result[0]?.node;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
KeyParser,
ParserError,
ariaPropsEqual,
ariaNodesEqual,
findNewNode,
hasPointerCursor,
parseAriaSnapshot,
parseAriaSnapshotUnsafe,
textValue

View File

@@ -0,0 +1,51 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var lruCache_exports = {};
__export(lruCache_exports, {
LRUCache: () => LRUCache
});
module.exports = __toCommonJS(lruCache_exports);
class LRUCache {
constructor(maxSize) {
this._maxSize = maxSize;
this._map = /* @__PURE__ */ new Map();
this._size = 0;
}
getOrCompute(key, compute) {
if (this._map.has(key)) {
const result2 = this._map.get(key);
this._map.delete(key);
this._map.set(key, result2);
return result2.value;
}
const result = compute();
while (this._map.size && this._size + result.size > this._maxSize) {
const [firstKey, firstValue] = this._map.entries().next().value;
this._size -= firstValue.size;
this._map.delete(firstKey);
}
this._map.set(key, result);
this._size += result.size;
return result.value;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LRUCache
});

View File

@@ -71,6 +71,7 @@ const methodMetainfo = /* @__PURE__ */ new Map([
["WebSocket.waitForEventInfo", { title: 'Wait for event "{info.event}"', snapshot: true }],
["ElectronApplication.waitForEventInfo", { title: 'Wait for event "{info.event}"', snapshot: true }],
["AndroidDevice.waitForEventInfo", { title: 'Wait for event "{info.event}"', snapshot: true }],
["PageAgent.waitForEventInfo", { title: 'Wait for event "{info.event}"', snapshot: true }],
["BrowserContext.addCookies", { title: "Add cookies", group: "configuration" }],
["BrowserContext.addInitScript", { title: "Add init script", group: "configuration" }],
["BrowserContext.clearCookies", { title: "Clear cookies", group: "configuration" }],
@@ -92,6 +93,7 @@ const methodMetainfo = /* @__PURE__ */ new Map([
["BrowserContext.pause", { title: "Pause" }],
["BrowserContext.enableRecorder", { internal: true }],
["BrowserContext.disableRecorder", { internal: true }],
["BrowserContext.exposeConsoleApi", { internal: true }],
["BrowserContext.newCDPSession", { title: "Create CDP session", group: "configuration" }],
["BrowserContext.harStart", { internal: true }],
["BrowserContext.harExport", { internal: true }],
@@ -143,6 +145,7 @@ const methodMetainfo = /* @__PURE__ */ new Map([
["Page.stopCSSCoverage", { title: "Stop CSS coverage", group: "configuration" }],
["Page.bringToFront", { title: "Bring to front" }],
["Page.updateSubscription", { internal: true }],
["Page.agent", { internal: true }],
["Frame.evalOnSelector", { title: "Evaluate", snapshot: true, pausesBeforeAction: true }],
["Frame.evalOnSelectorAll", { title: "Evaluate", snapshot: true, pausesBeforeAction: true }],
["Frame.addScriptTag", { title: "Add script tag", snapshot: true, pausesBeforeAction: true }],
@@ -314,7 +317,12 @@ const methodMetainfo = /* @__PURE__ */ new Map([
["AndroidDevice.connectToWebView", { title: "Connect to Web View" }],
["AndroidDevice.close", { internal: true }],
["JsonPipe.send", { internal: true }],
["JsonPipe.close", { internal: true }]
["JsonPipe.close", { internal: true }],
["PageAgent.perform", { title: 'Perform "{task}"' }],
["PageAgent.expect", { title: 'Expect "{expectation}"' }],
["PageAgent.extract", { title: 'Extract "{query}"' }],
["PageAgent.dispose", { internal: true }],
["PageAgent.usage", { title: "Get agent usage", group: "configuration" }]
]);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {

View File

@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var stringUtils_exports = {};
__export(stringUtils_exports, {
ansiRegex: () => ansiRegex,
cacheNormalizedWhitespaces: () => cacheNormalizedWhitespaces,
escapeForAttributeSelector: () => escapeForAttributeSelector,
escapeForTextSelector: () => escapeForTextSelector,
@@ -26,11 +27,15 @@ __export(stringUtils_exports, {
escapeRegExp: () => escapeRegExp,
escapeTemplateString: () => escapeTemplateString,
escapeWithQuotes: () => escapeWithQuotes,
formatObject: () => formatObject,
formatObjectOrVoid: () => formatObjectOrVoid,
isString: () => isString,
longestCommonSubstring: () => longestCommonSubstring,
normalizeEscapedRegexQuotes: () => normalizeEscapedRegexQuotes,
normalizeWhiteSpace: () => normalizeWhiteSpace,
parseRegex: () => parseRegex,
quoteCSSAttributeValue: () => quoteCSSAttributeValue,
stripAnsiEscapes: () => stripAnsiEscapes,
toSnakeCase: () => toSnakeCase,
toTitleCase: () => toTitleCase,
trimString: () => trimString,
@@ -60,6 +65,31 @@ function toTitleCase(name) {
function toSnakeCase(name) {
return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toLowerCase();
}
function formatObject(value, indent = " ", mode = "multiline") {
if (typeof value === "string")
return escapeWithQuotes(value, "'");
if (Array.isArray(value))
return `[${value.map((o) => formatObject(o)).join(", ")}]`;
if (typeof value === "object") {
const keys = Object.keys(value).filter((key) => value[key] !== void 0).sort();
if (!keys.length)
return "{}";
const tokens = [];
for (const key of keys)
tokens.push(`${key}: ${formatObject(value[key])}`);
if (mode === "multiline")
return `{
${tokens.join(`,
${indent}`)}
}`;
return `{ ${tokens.join(", ")} }`;
}
return String(value);
}
function formatObjectOrVoid(value, indent = " ") {
const result = formatObject(value, indent);
return result === "{}" ? "" : result;
}
function quoteCSSAttributeValue(text) {
return `"${text.replace(/["\\]/g, (char) => "\\" + char)}"`;
}
@@ -133,8 +163,23 @@ function longestCommonSubstring(s1, s2) {
}
return s1.slice(endingIndex - maxLen, endingIndex);
}
function parseRegex(regex) {
if (regex[0] !== "/")
throw new Error(`Invalid regex, must start with '/': ${regex}`);
const lastSlash = regex.lastIndexOf("/");
if (lastSlash <= 0)
throw new Error(`Invalid regex, must end with '/' followed by optional flags: ${regex}`);
const source = regex.slice(1, lastSlash);
const flags = regex.slice(lastSlash + 1);
return new RegExp(source, flags);
}
const ansiRegex = new RegExp("([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))", "g");
function stripAnsiEscapes(str) {
return str.replace(ansiRegex, "");
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ansiRegex,
cacheNormalizedWhitespaces,
escapeForAttributeSelector,
escapeForTextSelector,
@@ -143,11 +188,15 @@ function longestCommonSubstring(s1, s2) {
escapeRegExp,
escapeTemplateString,
escapeWithQuotes,
formatObject,
formatObjectOrVoid,
isString,
longestCommonSubstring,
normalizeEscapedRegexQuotes,
normalizeWhiteSpace,
parseRegex,
quoteCSSAttributeValue,
stripAnsiEscapes,
toSnakeCase,
toTitleCase,
trimString,

View File

@@ -0,0 +1,16 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var entries_exports = {};
module.exports = __toCommonJS(entries_exports);

View File

@@ -0,0 +1,499 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var snapshotRenderer_exports = {};
__export(snapshotRenderer_exports, {
SnapshotRenderer: () => SnapshotRenderer,
rewriteURLForCustomProtocol: () => rewriteURLForCustomProtocol
});
module.exports = __toCommonJS(snapshotRenderer_exports);
var import_stringUtils = require("../stringUtils");
function findClosest(items, metric, target) {
return items.find((item, index) => {
if (index === items.length - 1)
return true;
const next = items[index + 1];
return Math.abs(metric(item) - target) < Math.abs(metric(next) - target);
});
}
function isNodeNameAttributesChildNodesSnapshot(n) {
return Array.isArray(n) && typeof n[0] === "string";
}
function isSubtreeReferenceSnapshot(n) {
return Array.isArray(n) && Array.isArray(n[0]);
}
class SnapshotRenderer {
constructor(htmlCache, resources, snapshots, screencastFrames, index) {
this._htmlCache = htmlCache;
this._resources = resources;
this._snapshots = snapshots;
this._index = index;
this._snapshot = snapshots[index];
this._callId = snapshots[index].callId;
this._screencastFrames = screencastFrames;
this.snapshotName = snapshots[index].snapshotName;
}
snapshot() {
return this._snapshots[this._index];
}
viewport() {
return this._snapshots[this._index].viewport;
}
closestScreenshot() {
const { wallTime, timestamp } = this.snapshot();
const closestFrame = wallTime && this._screencastFrames[0]?.frameSwapWallTime ? findClosest(this._screencastFrames, (frame) => frame.frameSwapWallTime, wallTime) : findClosest(this._screencastFrames, (frame) => frame.timestamp, timestamp);
return closestFrame?.sha1;
}
render() {
const result = [];
const visit = (n, snapshotIndex, parentTag, parentAttrs) => {
if (typeof n === "string") {
if (parentTag === "STYLE" || parentTag === "style")
result.push(escapeURLsInStyleSheet(rewriteURLsInStyleSheetForCustomProtocol(n)));
else
result.push((0, import_stringUtils.escapeHTML)(n));
return;
}
if (isSubtreeReferenceSnapshot(n)) {
const referenceIndex = snapshotIndex - n[0][0];
if (referenceIndex >= 0 && referenceIndex <= snapshotIndex) {
const nodes = snapshotNodes(this._snapshots[referenceIndex]);
const nodeIndex = n[0][1];
if (nodeIndex >= 0 && nodeIndex < nodes.length)
return visit(nodes[nodeIndex], referenceIndex, parentTag, parentAttrs);
}
} else if (isNodeNameAttributesChildNodesSnapshot(n)) {
const [name, nodeAttrs, ...children] = n;
const nodeName = name === "NOSCRIPT" ? "X-NOSCRIPT" : name;
const attrs = Object.entries(nodeAttrs || {});
result.push("<", nodeName);
const kCurrentSrcAttribute = "__playwright_current_src__";
const isFrame = nodeName === "IFRAME" || nodeName === "FRAME";
const isAnchor = nodeName === "A";
const isImg = nodeName === "IMG";
const isImgWithCurrentSrc = isImg && attrs.some((a) => a[0] === kCurrentSrcAttribute);
const isSourceInsidePictureWithCurrentSrc = nodeName === "SOURCE" && parentTag === "PICTURE" && parentAttrs?.some((a) => a[0] === kCurrentSrcAttribute);
for (const [attr, value] of attrs) {
let attrName = attr;
if (isFrame && attr.toLowerCase() === "src") {
attrName = "__playwright_src__";
}
if (isImg && attr === kCurrentSrcAttribute) {
attrName = "src";
}
if (["src", "srcset"].includes(attr.toLowerCase()) && (isImgWithCurrentSrc || isSourceInsidePictureWithCurrentSrc)) {
attrName = "_" + attrName;
}
let attrValue = value;
if (!isAnchor && (attr.toLowerCase() === "href" || attr.toLowerCase() === "src" || attr === kCurrentSrcAttribute))
attrValue = rewriteURLForCustomProtocol(value);
result.push(" ", attrName, '="', (0, import_stringUtils.escapeHTMLAttribute)(attrValue), '"');
}
result.push(">");
for (const child of children)
visit(child, snapshotIndex, nodeName, attrs);
if (!autoClosing.has(nodeName))
result.push("</", nodeName, ">");
return;
} else {
return;
}
};
const snapshot = this._snapshot;
const html = this._htmlCache.getOrCompute(this, () => {
visit(snapshot.html, this._index, void 0, void 0);
const prefix = snapshot.doctype ? `<!DOCTYPE ${snapshot.doctype}>` : "";
const html2 = prefix + [
// Hide the document in order to prevent flickering. We will unhide once script has processed shadow.
"<style>*,*::before,*::after { visibility: hidden }</style>",
`<script>${snapshotScript(this.viewport(), this._callId, this.snapshotName)}</script>`
].join("") + result.join("");
return { value: html2, size: html2.length };
});
return { html, pageId: snapshot.pageId, frameId: snapshot.frameId, index: this._index };
}
resourceByUrl(url, method) {
const snapshot = this._snapshot;
let sameFrameResource;
let otherFrameResource;
for (const resource of this._resources) {
if (typeof resource._monotonicTime === "number" && resource._monotonicTime >= snapshot.timestamp)
break;
if (resource.response.status === 304) {
continue;
}
if (resource.request.url === url && resource.request.method === method) {
if (resource._frameref === snapshot.frameId)
sameFrameResource = resource;
else
otherFrameResource = resource;
}
}
let result = sameFrameResource ?? otherFrameResource;
if (result && method.toUpperCase() === "GET") {
let override = snapshot.resourceOverrides.find((o) => o.url === url);
if (override?.ref) {
const index = this._index - override.ref;
if (index >= 0 && index < this._snapshots.length)
override = this._snapshots[index].resourceOverrides.find((o) => o.url === url);
}
if (override?.sha1) {
result = {
...result,
response: {
...result.response,
content: {
...result.response.content,
_sha1: override.sha1
}
}
};
}
}
return result;
}
}
const autoClosing = /* @__PURE__ */ new Set(["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "MENUITEM", "META", "PARAM", "SOURCE", "TRACK", "WBR"]);
function snapshotNodes(snapshot) {
if (!snapshot._nodes) {
const nodes = [];
const visit = (n) => {
if (typeof n === "string") {
nodes.push(n);
} else if (isNodeNameAttributesChildNodesSnapshot(n)) {
const [, , ...children] = n;
for (const child of children)
visit(child);
nodes.push(n);
}
};
visit(snapshot.html);
snapshot._nodes = nodes;
}
return snapshot._nodes;
}
function snapshotScript(viewport, ...targetIds) {
function applyPlaywrightAttributes(viewport2, ...targetIds2) {
const win = window;
const searchParams = new URLSearchParams(win.location.search);
const shouldPopulateCanvasFromScreenshot = searchParams.has("shouldPopulateCanvasFromScreenshot");
const isUnderTest = searchParams.has("isUnderTest");
const frameBoundingRectsInfo = {
viewport: viewport2,
frames: /* @__PURE__ */ new WeakMap()
};
win["__playwright_frame_bounding_rects__"] = frameBoundingRectsInfo;
const kPointerWarningTitle = "Recorded click position in absolute coordinates did not match the center of the clicked element. This is likely due to a difference between the test runner and the trace viewer operating systems.";
const scrollTops = [];
const scrollLefts = [];
const targetElements = [];
const canvasElements = [];
let topSnapshotWindow = win;
while (topSnapshotWindow !== topSnapshotWindow.parent && !topSnapshotWindow.location.pathname.match(/\/page@[a-z0-9]+$/))
topSnapshotWindow = topSnapshotWindow.parent;
const visit = (root) => {
for (const e of root.querySelectorAll(`[__playwright_scroll_top_]`))
scrollTops.push(e);
for (const e of root.querySelectorAll(`[__playwright_scroll_left_]`))
scrollLefts.push(e);
for (const element of root.querySelectorAll(`[__playwright_value_]`)) {
const inputElement = element;
if (inputElement.type !== "file")
inputElement.value = inputElement.getAttribute("__playwright_value_");
element.removeAttribute("__playwright_value_");
}
for (const element of root.querySelectorAll(`[__playwright_checked_]`)) {
element.checked = element.getAttribute("__playwright_checked_") === "true";
element.removeAttribute("__playwright_checked_");
}
for (const element of root.querySelectorAll(`[__playwright_selected_]`)) {
element.selected = element.getAttribute("__playwright_selected_") === "true";
element.removeAttribute("__playwright_selected_");
}
for (const element of root.querySelectorAll(`[__playwright_popover_open_]`)) {
try {
element.showPopover();
} catch {
}
element.removeAttribute("__playwright_popover_open_");
}
for (const element of root.querySelectorAll(`[__playwright_dialog_open_]`)) {
try {
if (element.getAttribute("__playwright_dialog_open_") === "modal")
element.showModal();
else
element.show();
} catch {
}
element.removeAttribute("__playwright_dialog_open_");
}
for (const targetId of targetIds2) {
for (const target of root.querySelectorAll(`[__playwright_target__="${targetId}"]`)) {
const style = target.style;
style.outline = "2px solid #006ab1";
style.backgroundColor = "#6fa8dc7f";
targetElements.push(target);
}
}
for (const iframe of root.querySelectorAll("iframe, frame")) {
const boundingRectJson = iframe.getAttribute("__playwright_bounding_rect__");
iframe.removeAttribute("__playwright_bounding_rect__");
const boundingRect = boundingRectJson ? JSON.parse(boundingRectJson) : void 0;
if (boundingRect)
frameBoundingRectsInfo.frames.set(iframe, { boundingRect, scrollLeft: 0, scrollTop: 0 });
const src = iframe.getAttribute("__playwright_src__");
if (!src) {
iframe.setAttribute("src", 'data:text/html,<body style="background: #ddd"></body>');
} else {
const url = new URL(win.location.href);
const index = url.pathname.lastIndexOf("/snapshot/");
if (index !== -1)
url.pathname = url.pathname.substring(0, index + 1);
url.pathname += src.substring(1);
iframe.setAttribute("src", url.toString());
}
}
{
const body = root.querySelector(`body[__playwright_custom_elements__]`);
if (body && win.customElements) {
const customElements = (body.getAttribute("__playwright_custom_elements__") || "").split(",");
for (const elementName of customElements)
win.customElements.define(elementName, class extends HTMLElement {
});
}
}
for (const element of root.querySelectorAll(`template[__playwright_shadow_root_]`)) {
const template = element;
const shadowRoot = template.parentElement.attachShadow({ mode: "open" });
shadowRoot.appendChild(template.content);
template.remove();
visit(shadowRoot);
}
for (const element of root.querySelectorAll("a"))
element.addEventListener("click", (event) => {
event.preventDefault();
});
if ("adoptedStyleSheets" in root) {
const adoptedSheets = [...root.adoptedStyleSheets];
for (const element of root.querySelectorAll(`template[__playwright_style_sheet_]`)) {
const template = element;
const sheet = new CSSStyleSheet();
sheet.replaceSync(template.getAttribute("__playwright_style_sheet_"));
adoptedSheets.push(sheet);
}
root.adoptedStyleSheets = adoptedSheets;
}
canvasElements.push(...root.querySelectorAll("canvas"));
};
const onLoad = () => {
win.removeEventListener("load", onLoad);
for (const element of scrollTops) {
element.scrollTop = +element.getAttribute("__playwright_scroll_top_");
element.removeAttribute("__playwright_scroll_top_");
if (frameBoundingRectsInfo.frames.has(element))
frameBoundingRectsInfo.frames.get(element).scrollTop = element.scrollTop;
}
for (const element of scrollLefts) {
element.scrollLeft = +element.getAttribute("__playwright_scroll_left_");
element.removeAttribute("__playwright_scroll_left_");
if (frameBoundingRectsInfo.frames.has(element))
frameBoundingRectsInfo.frames.get(element).scrollLeft = element.scrollLeft;
}
win.document.styleSheets[0].disabled = true;
const search = new URL(win.location.href).searchParams;
const isTopFrame = win === topSnapshotWindow;
if (search.get("pointX") && search.get("pointY")) {
const pointX = +search.get("pointX");
const pointY = +search.get("pointY");
const hasInputTarget = search.has("hasInputTarget");
const hasTargetElements = targetElements.length > 0;
const roots = win.document.documentElement ? [win.document.documentElement] : [];
for (const target of hasTargetElements ? targetElements : roots) {
const pointElement = win.document.createElement("x-pw-pointer");
pointElement.style.position = "fixed";
pointElement.style.backgroundColor = "#f44336";
pointElement.style.width = "20px";
pointElement.style.height = "20px";
pointElement.style.borderRadius = "10px";
pointElement.style.margin = "-10px 0 0 -10px";
pointElement.style.zIndex = "2147483646";
pointElement.style.display = "flex";
pointElement.style.alignItems = "center";
pointElement.style.justifyContent = "center";
if (hasTargetElements) {
const box = target.getBoundingClientRect();
const centerX = box.left + box.width / 2;
const centerY = box.top + box.height / 2;
pointElement.style.left = centerX + "px";
pointElement.style.top = centerY + "px";
if (isTopFrame && (Math.abs(centerX - pointX) >= 10 || Math.abs(centerY - pointY) >= 10)) {
const warningElement = win.document.createElement("x-pw-pointer-warning");
warningElement.textContent = "\u26A0";
warningElement.style.fontSize = "19px";
warningElement.style.color = "white";
warningElement.style.marginTop = "-3.5px";
warningElement.style.userSelect = "none";
pointElement.appendChild(warningElement);
pointElement.setAttribute("title", kPointerWarningTitle);
}
win.document.documentElement.appendChild(pointElement);
} else if (isTopFrame && !hasInputTarget) {
pointElement.style.left = pointX + "px";
pointElement.style.top = pointY + "px";
win.document.documentElement.appendChild(pointElement);
}
}
}
if (canvasElements.length > 0) {
let drawCheckerboard2 = function(context, canvas) {
function createCheckerboardPattern() {
const pattern = win.document.createElement("canvas");
pattern.width = pattern.width / Math.floor(pattern.width / 24);
pattern.height = pattern.height / Math.floor(pattern.height / 24);
const context2 = pattern.getContext("2d");
context2.fillStyle = "lightgray";
context2.fillRect(0, 0, pattern.width, pattern.height);
context2.fillStyle = "white";
context2.fillRect(0, 0, pattern.width / 2, pattern.height / 2);
context2.fillRect(pattern.width / 2, pattern.height / 2, pattern.width, pattern.height);
return context2.createPattern(pattern, "repeat");
}
context.fillStyle = createCheckerboardPattern();
context.fillRect(0, 0, canvas.width, canvas.height);
};
var drawCheckerboard = drawCheckerboard2;
const img = new Image();
img.onload = () => {
for (const canvas of canvasElements) {
const context = canvas.getContext("2d");
const boundingRectAttribute = canvas.getAttribute("__playwright_bounding_rect__");
canvas.removeAttribute("__playwright_bounding_rect__");
if (!boundingRectAttribute)
continue;
let boundingRect;
try {
boundingRect = JSON.parse(boundingRectAttribute);
} catch (e) {
continue;
}
let currWindow = win;
while (currWindow !== topSnapshotWindow) {
const iframe = currWindow.frameElement;
currWindow = currWindow.parent;
const iframeInfo = currWindow["__playwright_frame_bounding_rects__"]?.frames.get(iframe);
if (!iframeInfo?.boundingRect)
break;
const leftOffset = iframeInfo.boundingRect.left - iframeInfo.scrollLeft;
const topOffset = iframeInfo.boundingRect.top - iframeInfo.scrollTop;
boundingRect.left += leftOffset;
boundingRect.top += topOffset;
boundingRect.right += leftOffset;
boundingRect.bottom += topOffset;
}
const { width, height } = topSnapshotWindow["__playwright_frame_bounding_rects__"].viewport;
boundingRect.left = boundingRect.left / width;
boundingRect.top = boundingRect.top / height;
boundingRect.right = boundingRect.right / width;
boundingRect.bottom = boundingRect.bottom / height;
const partiallyUncaptured = boundingRect.right > 1 || boundingRect.bottom > 1;
const fullyUncaptured = boundingRect.left > 1 || boundingRect.top > 1;
if (fullyUncaptured) {
canvas.title = `Playwright couldn't capture canvas contents because it's located outside the viewport.`;
continue;
}
drawCheckerboard2(context, canvas);
if (shouldPopulateCanvasFromScreenshot) {
context.drawImage(img, boundingRect.left * img.width, boundingRect.top * img.height, (boundingRect.right - boundingRect.left) * img.width, (boundingRect.bottom - boundingRect.top) * img.height, 0, 0, canvas.width, canvas.height);
if (partiallyUncaptured)
canvas.title = `Playwright couldn't capture full canvas contents because it's located partially outside the viewport.`;
else
canvas.title = `Canvas contents are displayed on a best-effort basis based on viewport screenshots taken during test execution.`;
} else {
canvas.title = "Canvas content display is disabled.";
}
if (isUnderTest)
console.log(`canvas drawn:`, JSON.stringify([boundingRect.left, boundingRect.top, boundingRect.right - boundingRect.left, boundingRect.bottom - boundingRect.top].map((v) => Math.floor(v * 100))));
}
};
img.onerror = () => {
for (const canvas of canvasElements) {
const context = canvas.getContext("2d");
drawCheckerboard2(context, canvas);
canvas.title = `Playwright couldn't show canvas contents because the screenshot failed to load.`;
}
};
img.src = location.href.replace("/snapshot", "/closest-screenshot");
}
};
const onDOMContentLoaded = () => visit(win.document);
win.addEventListener("load", onLoad);
win.addEventListener("DOMContentLoaded", onDOMContentLoaded);
}
return `
(${applyPlaywrightAttributes.toString()})(${JSON.stringify(viewport)}${targetIds.map((id) => `, "${id}"`).join("")})`;
}
const schemas = ["about:", "blob:", "data:", "file:", "ftp:", "http:", "https:", "mailto:", "sftp:", "ws:", "wss:"];
const kLegacyBlobPrefix = "http://playwright.bloburl/#";
function rewriteURLForCustomProtocol(href) {
if (href.startsWith(kLegacyBlobPrefix))
href = href.substring(kLegacyBlobPrefix.length);
try {
const url = new URL(href);
if (url.protocol === "javascript:" || url.protocol === "vbscript:")
return "javascript:void(0)";
const isBlob = url.protocol === "blob:";
const isFile = url.protocol === "file:";
if (!isBlob && !isFile && schemas.includes(url.protocol))
return href;
const prefix = "pw-" + url.protocol.slice(0, url.protocol.length - 1);
if (!isFile)
url.protocol = "https:";
url.hostname = url.hostname ? `${prefix}--${url.hostname}` : prefix;
if (isFile) {
url.protocol = "https:";
}
return url.toString();
} catch {
return href;
}
}
const urlInCSSRegex = /url\(['"]?([\w-]+:)\/\//ig;
function rewriteURLsInStyleSheetForCustomProtocol(text) {
return text.replace(urlInCSSRegex, (match, protocol) => {
const isBlob = protocol === "blob:";
const isFile = protocol === "file:";
if (!isBlob && !isFile && schemas.includes(protocol))
return match;
return match.replace(protocol + "//", `https://pw-${protocol.slice(0, -1)}--`);
});
}
const urlToEscapeRegex1 = /url\(\s*'([^']*)'\s*\)/ig;
const urlToEscapeRegex2 = /url\(\s*"([^"]*)"\s*\)/ig;
function escapeURLsInStyleSheet(text) {
const replacer = (match, url) => {
if (url.includes("</"))
return match.replace(url, encodeURI(url));
return match;
};
return text.replace(urlToEscapeRegex1, replacer).replace(urlToEscapeRegex2, replacer);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SnapshotRenderer,
rewriteURLForCustomProtocol
});

View File

@@ -0,0 +1,120 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var snapshotServer_exports = {};
__export(snapshotServer_exports, {
SnapshotServer: () => SnapshotServer
});
module.exports = __toCommonJS(snapshotServer_exports);
class SnapshotServer {
constructor(snapshotStorage, resourceLoader) {
this._snapshotIds = /* @__PURE__ */ new Map();
this._snapshotStorage = snapshotStorage;
this._resourceLoader = resourceLoader;
}
serveSnapshot(pageOrFrameId, searchParams, snapshotUrl) {
const snapshot = this._snapshot(pageOrFrameId, searchParams);
if (!snapshot)
return new Response(null, { status: 404 });
const renderedSnapshot = snapshot.render();
this._snapshotIds.set(snapshotUrl, snapshot);
return new Response(renderedSnapshot.html, { status: 200, headers: { "Content-Type": "text/html; charset=utf-8" } });
}
async serveClosestScreenshot(pageOrFrameId, searchParams) {
const snapshot = this._snapshot(pageOrFrameId, searchParams);
const sha1 = snapshot?.closestScreenshot();
if (!sha1)
return new Response(null, { status: 404 });
return new Response(await this._resourceLoader(sha1));
}
serveSnapshotInfo(pageOrFrameId, searchParams) {
const snapshot = this._snapshot(pageOrFrameId, searchParams);
return this._respondWithJson(snapshot ? {
viewport: snapshot.viewport(),
url: snapshot.snapshot().frameUrl,
timestamp: snapshot.snapshot().timestamp,
wallTime: snapshot.snapshot().wallTime
} : {
error: "No snapshot found"
});
}
_snapshot(pageOrFrameId, params) {
const name = params.get("name");
return this._snapshotStorage.snapshotByName(pageOrFrameId, name);
}
_respondWithJson(object) {
return new Response(JSON.stringify(object), {
status: 200,
headers: {
"Cache-Control": "public, max-age=31536000",
"Content-Type": "application/json"
}
});
}
async serveResource(requestUrlAlternatives, method, snapshotUrl) {
let resource;
const snapshot = this._snapshotIds.get(snapshotUrl);
for (const requestUrl of requestUrlAlternatives) {
resource = snapshot?.resourceByUrl(removeHash(requestUrl), method);
if (resource)
break;
}
if (!resource)
return new Response(null, { status: 404 });
const sha1 = resource.response.content._sha1;
const content = sha1 ? await this._resourceLoader(sha1) || new Blob([]) : new Blob([]);
let contentType = resource.response.content.mimeType;
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(contentType);
if (isTextEncoding && !contentType.includes("charset"))
contentType = `${contentType}; charset=utf-8`;
const headers = new Headers();
if (contentType !== "x-unknown")
headers.set("Content-Type", contentType);
for (const { name, value } of resource.response.headers)
headers.set(name, value);
headers.delete("Content-Encoding");
headers.delete("Access-Control-Allow-Origin");
headers.set("Access-Control-Allow-Origin", "*");
headers.delete("Content-Length");
headers.set("Content-Length", String(content.size));
if (this._snapshotStorage.hasResourceOverride(resource.request.url))
headers.set("Cache-Control", "no-store, no-cache, max-age=0");
else
headers.set("Cache-Control", "public, max-age=31536000");
const { status } = resource.response;
const isNullBodyStatus = status === 101 || status === 204 || status === 205 || status === 304;
return new Response(isNullBodyStatus ? null : content, {
headers,
status: resource.response.status,
statusText: resource.response.statusText
});
}
}
function removeHash(url) {
try {
const u = new URL(url);
u.hash = "";
return u.toString();
} catch (e) {
return url;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SnapshotServer
});

View File

@@ -0,0 +1,89 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var snapshotStorage_exports = {};
__export(snapshotStorage_exports, {
SnapshotStorage: () => SnapshotStorage
});
module.exports = __toCommonJS(snapshotStorage_exports);
var import_snapshotRenderer = require("./snapshotRenderer");
var import_lruCache = require("../lruCache");
class SnapshotStorage {
constructor() {
this._frameSnapshots = /* @__PURE__ */ new Map();
this._cache = new import_lruCache.LRUCache(1e8);
// 100MB per each trace
this._contextToResources = /* @__PURE__ */ new Map();
this._resourceUrlsWithOverrides = /* @__PURE__ */ new Set();
}
addResource(contextId, resource) {
resource.request.url = (0, import_snapshotRenderer.rewriteURLForCustomProtocol)(resource.request.url);
this._ensureResourcesForContext(contextId).push(resource);
}
addFrameSnapshot(contextId, snapshot, screencastFrames) {
for (const override of snapshot.resourceOverrides)
override.url = (0, import_snapshotRenderer.rewriteURLForCustomProtocol)(override.url);
let frameSnapshots = this._frameSnapshots.get(snapshot.frameId);
if (!frameSnapshots) {
frameSnapshots = {
raw: [],
renderers: []
};
this._frameSnapshots.set(snapshot.frameId, frameSnapshots);
if (snapshot.isMainFrame)
this._frameSnapshots.set(snapshot.pageId, frameSnapshots);
}
frameSnapshots.raw.push(snapshot);
const resources = this._ensureResourcesForContext(contextId);
const renderer = new import_snapshotRenderer.SnapshotRenderer(this._cache, resources, frameSnapshots.raw, screencastFrames, frameSnapshots.raw.length - 1);
frameSnapshots.renderers.push(renderer);
return renderer;
}
snapshotByName(pageOrFrameId, snapshotName) {
const snapshot = this._frameSnapshots.get(pageOrFrameId);
return snapshot?.renderers.find((r) => r.snapshotName === snapshotName);
}
snapshotsForTest() {
return [...this._frameSnapshots.keys()];
}
finalize() {
for (const resources of this._contextToResources.values())
resources.sort((a, b) => (a._monotonicTime || 0) - (b._monotonicTime || 0));
for (const frameSnapshots of this._frameSnapshots.values()) {
for (const snapshot of frameSnapshots.raw) {
for (const override of snapshot.resourceOverrides)
this._resourceUrlsWithOverrides.add(override.url);
}
}
}
hasResourceOverride(url) {
return this._resourceUrlsWithOverrides.has(url);
}
_ensureResourcesForContext(contextId) {
let resources = this._contextToResources.get(contextId);
if (!resources) {
resources = [];
this._contextToResources.set(contextId, resources);
}
return resources;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SnapshotStorage
});

Some files were not shown because too many files have changed in this diff Show More