rebrowser-playwright-core
Version:
A drop-in replacement for playwright-core patched with rebrowser-patches. It allows to pass modern automation detection tests.
287 lines (286 loc) • 11 kB
JavaScript
"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 contextRecorder_exports = {};
__export(contextRecorder_exports, {
ContextRecorder: () => ContextRecorder,
generateFrameSelector: () => generateFrameSelector
});
module.exports = __toCommonJS(contextRecorder_exports);
var import_events = require("events");
var import_recorderCollection = require("./recorderCollection");
var recorderSource = __toESM(require("../../generated/pollingRecorderSource"));
var import_utils = require("../../utils");
var import_timeoutRunner = require("../../utils/isomorphic/timeoutRunner");
var import_browserContext = require("../browserContext");
var import_languages = require("../codegen/languages");
var import_frames = require("../frames");
var import_page = require("../page");
var import_throttledFile = require("./throttledFile");
var import_language = require("../codegen/language");
class ContextRecorder extends import_events.EventEmitter {
constructor(context, params, delegate) {
super();
this._pageAliases = /* @__PURE__ */ new Map();
this._lastPopupOrdinal = 0;
this._lastDialogOrdinal = -1;
this._lastDownloadOrdinal = -1;
this._throttledOutputFile = null;
this._orderedLanguages = [];
this._listeners = [];
this._context = context;
this._params = params;
this._delegate = delegate;
this._recorderSources = [];
const language = params.language || context.attribution.playwright.options.sdkLanguage;
this.setOutput(language, params.outputFile);
const languageGeneratorOptions = {
browserName: context._browser.options.name,
launchOptions: { headless: false, ...params.launchOptions, tracesDir: void 0 },
contextOptions: { ...params.contextOptions },
deviceName: params.device,
saveStorage: params.saveStorage
};
this._collection = new import_recorderCollection.RecorderCollection(this._pageAliases);
this._collection.on("change", (actions) => {
this._recorderSources = [];
for (const languageGenerator of this._orderedLanguages) {
const { header, footer, actionTexts, text } = (0, import_language.generateCode)(actions, languageGenerator, languageGeneratorOptions);
const source = {
isRecorded: true,
label: languageGenerator.name,
group: languageGenerator.groupName,
id: languageGenerator.id,
text,
header,
footer,
actions: actionTexts,
language: languageGenerator.highlighter,
highlight: []
};
source.revealLine = text.split("\n").length - 1;
this._recorderSources.push(source);
if (languageGenerator === this._orderedLanguages[0])
this._throttledOutputFile?.setContent(source.text);
}
this.emit(ContextRecorder.Events.Change, {
sources: this._recorderSources,
actions
});
});
context.on(import_browserContext.BrowserContext.Events.BeforeClose, () => {
this._throttledOutputFile?.flush();
});
this._listeners.push(import_utils.eventsHelper.addEventListener(process, "exit", () => {
this._throttledOutputFile?.flush();
}));
this.setEnabled(params.mode === "recording");
}
static {
this.Events = {
Change: "change"
};
}
setOutput(codegenId, outputFile) {
const languages = (0, import_languages.languageSet)();
const primaryLanguage = [...languages].find((l) => l.id === codegenId);
if (!primaryLanguage)
throw new Error(`
===============================
Unsupported language: '${codegenId}'
===============================
`);
languages.delete(primaryLanguage);
this._orderedLanguages = [primaryLanguage, ...languages];
this._throttledOutputFile = outputFile ? new import_throttledFile.ThrottledFile(outputFile) : null;
this._collection?.restart();
}
languageName(id) {
for (const lang of this._orderedLanguages) {
if (!id || lang.id === id)
return lang.highlighter;
}
return "javascript";
}
async install() {
this._context.on(import_browserContext.BrowserContext.Events.Page, (page) => this._onPage(page));
for (const page of this._context.pages())
this._onPage(page);
this._context.on(import_browserContext.BrowserContext.Events.Dialog, (dialog) => this._onDialog(dialog.page()));
await this._context.exposeBinding(
"__pw_recorderPerformAction",
false,
(source, action) => this._performAction(source.frame, action)
);
await this._context.exposeBinding(
"__pw_recorderRecordAction",
false,
(source, action) => this._recordAction(source.frame, action)
);
await this._context.extendInjectedScript(recorderSource.source);
}
setEnabled(enabled) {
this._collection.setEnabled(enabled);
}
dispose() {
import_utils.eventsHelper.removeEventListeners(this._listeners);
}
async _onPage(page) {
const frame = page.mainFrame();
page.on("close", () => {
this._collection.addRecordedAction({
frame: this._describeMainFrame(page),
action: {
name: "closePage",
signals: []
},
startTime: (0, import_utils.monotonicTime)()
});
this._pageAliases.delete(page);
});
frame.on(import_frames.Frame.Events.InternalNavigation, (event) => {
if (event.isPublic)
this._onFrameNavigated(frame, page);
});
page.on(import_page.Page.Events.Download, () => this._onDownload(page));
const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : "";
const pageAlias = "page" + suffix;
this._pageAliases.set(page, pageAlias);
if (page.opener()) {
this._onPopup(page.opener(), page);
} else {
this._collection.addRecordedAction({
frame: this._describeMainFrame(page),
action: {
name: "openPage",
url: page.mainFrame().url(),
signals: []
},
startTime: (0, import_utils.monotonicTime)()
});
}
}
clearScript() {
this._collection.restart();
if (this._params.mode === "recording") {
for (const page of this._context.pages())
this._onFrameNavigated(page.mainFrame(), page);
}
}
runTask(task) {
}
_describeMainFrame(page) {
return {
pageAlias: this._pageAliases.get(page),
framePath: []
};
}
async _describeFrame(frame) {
return {
pageAlias: this._pageAliases.get(frame._page),
framePath: await generateFrameSelector(frame)
};
}
testIdAttributeName() {
return this._params.testIdAttributeName || this._context.selectors().testIdAttributeName() || "data-testid";
}
async _createActionInContext(frame, action) {
const frameDescription = await this._describeFrame(frame);
const actionInContext = {
frame: frameDescription,
action,
description: void 0,
startTime: (0, import_utils.monotonicTime)()
};
await this._delegate.rewriteActionInContext?.(this._pageAliases, actionInContext);
return actionInContext;
}
async _performAction(frame, action) {
await this._collection.performAction(await this._createActionInContext(frame, action));
}
async _recordAction(frame, action) {
this._collection.addRecordedAction(await this._createActionInContext(frame, action));
}
_onFrameNavigated(frame, page) {
const pageAlias = this._pageAliases.get(page);
this._collection.signal(pageAlias, frame, { name: "navigation", url: frame.url() });
}
_onPopup(page, popup) {
const pageAlias = this._pageAliases.get(page);
const popupAlias = this._pageAliases.get(popup);
this._collection.signal(pageAlias, page.mainFrame(), { name: "popup", popupAlias });
}
_onDownload(page) {
const pageAlias = this._pageAliases.get(page);
++this._lastDownloadOrdinal;
this._collection.signal(pageAlias, page.mainFrame(), { name: "download", downloadAlias: this._lastDownloadOrdinal ? String(this._lastDownloadOrdinal) : "" });
}
_onDialog(page) {
const pageAlias = this._pageAliases.get(page);
++this._lastDialogOrdinal;
this._collection.signal(pageAlias, page.mainFrame(), { name: "dialog", dialogAlias: this._lastDialogOrdinal ? String(this._lastDialogOrdinal) : "" });
}
}
async function generateFrameSelector(frame) {
const selectorPromises = [];
while (frame) {
const parent = frame.parentFrame();
if (!parent)
break;
selectorPromises.push(generateFrameSelectorInParent(parent, frame));
frame = parent;
}
const result = await Promise.all(selectorPromises);
return result.reverse();
}
async function generateFrameSelectorInParent(parent, frame) {
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
try {
const frameElement = await frame.frameElement();
if (!frameElement || !parent)
return;
const utility = await parent._utilityContext();
const injected = await utility.injectedScript();
const selector = await injected.evaluate((injected2, element) => {
return injected2.generateSelectorSimple(element);
}, frameElement);
return selector;
} catch (e) {
}
}, (0, import_utils.monotonicTime)() + 2e3);
if (!result.timedOut && result.result)
return result.result;
if (frame.name())
return `iframe[name=${(0, import_utils.quoteCSSAttributeValue)(frame.name())}]`;
return `iframe[src=${(0, import_utils.quoteCSSAttributeValue)(frame.url())}]`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ContextRecorder,
generateFrameSelector
});