UNPKG

rebrowser-playwright-core

Version:

A drop-in replacement for playwright-core patched with rebrowser-patches. It allows to pass modern automation detection tests.

299 lines (294 loc) 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ContextRecorder = void 0; exports.generateFrameSelector = generateFrameSelector; var _events = require("events"); var recorderSource = _interopRequireWildcard(require("../../generated/pollingRecorderSource")); var _utils = require("../../utils"); var _timeoutRunner = require("../../utils/timeoutRunner"); var _browserContext = require("../browserContext"); var _languages = require("../codegen/languages"); var _frames = require("../frames"); var _page = require("../page"); var _throttledFile = require("./throttledFile"); var _recorderCollection = require("./recorderCollection"); var _language = require("../codegen/language"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class ContextRecorder extends _events.EventEmitter { constructor(codegenMode, context, params, delegate) { super(); this._collection = void 0; this._pageAliases = new Map(); this._lastPopupOrdinal = 0; this._lastDialogOrdinal = -1; this._lastDownloadOrdinal = -1; this._context = void 0; this._params = void 0; this._delegate = void 0; this._recorderSources = void 0; this._throttledOutputFile = null; this._orderedLanguages = []; this._listeners = []; this._codegenMode = void 0; this._codegenMode = codegenMode; 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); // Make a copy of options to modify them later. const languageGeneratorOptions = { browserName: context._browser.options.name, launchOptions: { headless: false, ...params.launchOptions, tracesDir: undefined }, contextOptions: { ...params.contextOptions }, deviceName: params.device, saveStorage: params.saveStorage }; this._collection = new _recorderCollection.RecorderCollection(this._pageAliases); this._collection.on('change', actions => { this._recorderSources = []; for (const languageGenerator of this._orderedLanguages) { var _this$_throttledOutpu; const { header, footer, actionTexts, text } = (0, _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$_throttledOutpu = this._throttledOutputFile) === null || _this$_throttledOutpu === void 0 || _this$_throttledOutpu.setContent(source.text); } this.emit(ContextRecorder.Events.Change, { sources: this._recorderSources, actions }); }); context.on(_browserContext.BrowserContext.Events.BeforeClose, () => { var _this$_throttledOutpu2; (_this$_throttledOutpu2 = this._throttledOutputFile) === null || _this$_throttledOutpu2 === void 0 || _this$_throttledOutpu2.flush(); }); this._listeners.push(_utils.eventsHelper.addEventListener(process, 'exit', () => { var _this$_throttledOutpu3; (_this$_throttledOutpu3 = this._throttledOutputFile) === null || _this$_throttledOutpu3 === void 0 || _this$_throttledOutpu3.flush(); })); this.setEnabled(true); } setOutput(codegenId, outputFile) { var _this$_collection; const languages = (0, _languages.languageSet)(); const primaryLanguage = [...languages].find(l => l.id === codegenId); if (!primaryLanguage) throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`); languages.delete(primaryLanguage); this._orderedLanguages = [primaryLanguage, ...languages]; this._throttledOutputFile = outputFile ? new _throttledFile.ThrottledFile(outputFile) : null; (_this$_collection = this._collection) === null || _this$_collection === void 0 || _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(_browserContext.BrowserContext.Events.Page, page => this._onPage(page)); for (const page of this._context.pages()) this._onPage(page); this._context.on(_browserContext.BrowserContext.Events.Dialog, dialog => this._onDialog(dialog.page())); // Input actions that potentially lead to navigation are intercepted on the page and are // performed by the Playwright. await this._context.exposeBinding('__pw_recorderPerformAction', false, (source, action) => this._performAction(source.frame, action)); // Other non-essential actions are simply being recorded. 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); if (this._codegenMode === 'trace-events') { if (enabled) this._context.tracing.startChunk({ name: 'trace', title: 'trace' }).catch(() => {});else this._context.tracing.stopChunk({ mode: 'discard' }).catch(() => {}); } } dispose() { _utils.eventsHelper.removeEventListeners(this._listeners); } async _onPage(page) { // First page is called page, others are called popup1, popup2, etc. const frame = page.mainFrame(); page.on('close', () => { this._collection.addRecordedAction({ frame: this._describeMainFrame(page), action: { name: 'closePage', signals: [] }, startTime: (0, _utils.monotonicTime)() }); this._pageAliases.delete(page); }); frame.on(_frames.Frame.Events.InternalNavigation, event => { if (event.isPublic) this._onFrameNavigated(frame, page); }); page.on(_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, _utils.monotonicTime)() }); } } clearScript() { this._collection.restart(); if (this._params.mode === 'recording') { for (const page of this._context.pages()) this._onFrameNavigated(page.mainFrame(), page); } } _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) { var _this$_delegate$rewri, _this$_delegate; const frameDescription = await this._describeFrame(frame); const actionInContext = { frame: frameDescription, action, description: undefined, startTime: (0, _utils.monotonicTime)() }; await ((_this$_delegate$rewri = (_this$_delegate = this._delegate).rewriteActionInContext) === null || _this$_delegate$rewri === void 0 ? void 0 : _this$_delegate$rewri.call(_this$_delegate, 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) : '' }); } } exports.ContextRecorder = ContextRecorder; ContextRecorder.Events = { Change: 'change' }; 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, _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((injected, element) => { return injected.generateSelectorSimple(element); }, frameElement); return selector; } catch (e) { return e.toString(); } }, (0, _utils.monotonicTime)() + 2000); if (!result.timedOut && result.result) return result.result; if (frame.name()) return `iframe[name=${(0, _utils.quoteCSSAttributeValue)(frame.name())}]`; return `iframe[src=${(0, _utils.quoteCSSAttributeValue)(frame.url())}]`; }