/*! * playwright-extra v4.3.5 by berstend * https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra#readme * @license MIT */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var Debug = _interopDefault(require('debug')); /** Node.js module loader helper */ class Loader { constructor(moduleName, packageNames) { this.moduleName = moduleName; this.packageNames = packageNames; } /** * Lazy load a top level export from another module by wrapping it in a JS proxy. * * This allows us to re-export e.g. `devices` from `playwright` while redirecting direct calls * to it to the module version the user has installed, rather than shipping with a hardcoded version. * * If we don't do this and the user doesn't have the target module installed we'd throw immediately when our code is imported. * * We use a "super" Proxy defining all traps, so calls like `Object.keys(playwright.devices).length` will return the correct value. */ lazyloadExportOrDie(exportName) { const that = this; const trapHandler = Object.fromEntries(Object.getOwnPropertyNames(Reflect).map((name) => [ name, function (target, ...args) { const moduleExport = that.loadModuleOrDie()[exportName]; const customTarget = moduleExport; const result = Reflect[name](customTarget || target, ...args); return result; } ])); return new Proxy({}, trapHandler); } /** Load the module if possible */ loadModule() { return requirePackages(this.packageNames); } /** Load the module if possible or throw */ loadModuleOrDie() { const module = requirePackages(this.packageNames); if (module) { return module; } throw this.requireError; } get requireError() { const moduleNamePretty = this.moduleName.charAt(0).toUpperCase() + this.moduleName.slice(1); return new Error(` ${moduleNamePretty} is missing. :-) I've tried loading ${this.packageNames .map(p => `"${p}"`) .join(', ')} - no luck. Make sure you install one of those packages or use the named 'addExtra' export, to patch a specific (and maybe non-standard) implementation of ${moduleNamePretty}. To get the latest stable version of ${moduleNamePretty} run: 'yarn add ${this.moduleName}' or 'npm i ${this.moduleName}' `); } } function requirePackages(packageNames) { for (const name of packageNames) { try { return require(name); } catch (_) { continue; // noop } } return; } /** Playwright specific module loader */ const playwrightLoader = new Loader('playwright', [ 'playwright-core', 'playwright' ]); const debug = Debug('playwright-extra:puppeteer-compat'); const isPlaywrightPage = (obj) => { return 'unroute' in obj; }; const isPlaywrightFrame = (obj) => { return ['parentFrame', 'frameLocator'].every(x => x in obj); }; const isPlaywrightBrowser = (obj) => { return 'newContext' in obj; }; const isPuppeteerCompat = (obj) => { return !!obj && typeof obj === 'object' && !!obj.isCompatShim; }; const cache = { objectToShim: new Map(), cdpSession: { page: new Map(), browser: new Map() } }; /** Augment a Playwright object with compatibility with certain Puppeteer methods */ function addPuppeteerCompat(object) { if (!object || typeof object !== 'object') { return object; } if (cache.objectToShim.has(object)) { return cache.objectToShim.get(object); } if (isPuppeteerCompat(object)) { return object; } debug('addPuppeteerCompat', cache.objectToShim.size); if (isPlaywrightPage(object) || isPlaywrightFrame(object)) { const shim = createPageShim(object); cache.objectToShim.set(object, shim); return shim; } if (isPlaywrightBrowser(object)) { const shim = createBrowserShim(object); cache.objectToShim.set(object, shim); return shim; } debug('Received unknown object:', Reflect.ownKeys(object)); return object; } // Only chromium browsers support CDP const dummyCDPClient = { send: async (...args) => { debug('dummy CDP client called', 'send', args); }, on: (...args) => { debug('dummy CDP client called', 'on', args); } }; async function getPageCDPSession(page) { let session = cache.cdpSession.page.get(page); if (session) { debug('getPageCDPSession: use existing'); return session; } debug('getPageCDPSession: use new'); const context = isPlaywrightFrame(page) ? page.page().context() : page.context(); try { session = await context.newCDPSession(page); cache.cdpSession.page.set(page, session); return session; } catch (err) { debug('getPageCDPSession: error while creating session:', err.message); debug('getPageCDPSession: Unable create CDP session (most likely a different browser than chromium) - returning a dummy'); } return dummyCDPClient; } async function getBrowserCDPSession(browser) { let session = cache.cdpSession.browser.get(browser); if (session) { debug('getBrowserCDPSession: use existing'); return session; } debug('getBrowserCDPSession: use new'); try { session = await browser.newBrowserCDPSession(); cache.cdpSession.browser.set(browser, session); return session; } catch (err) { debug('getBrowserCDPSession: error while creating session:', err.message); debug('getBrowserCDPSession: Unable create CDP session (most likely a different browser than chromium) - returning a dummy'); } return dummyCDPClient; } function createPageShim(page) { const objId = Math.random().toString(36).substring(2, 7); const shim = new Proxy(page, { get(target, prop) { if (prop === 'isCompatShim' || prop === 'isPlaywright') { return true; } debug('page - get', objId, prop); if (prop === '_client') { return () => ({ send: async (method, params) => { const session = await getPageCDPSession(page); return await session.send(method, params); }, on: (event, listener) => { getPageCDPSession(page).then(session => { session.on(event, listener); }); } }); } if (prop === 'setBypassCSP') { return async (enabled) => { const session = await getPageCDPSession(page); return await session.send('Page.setBypassCSP', { enabled }); }; } if (prop === 'setUserAgent') { return async (userAgent, userAgentMetadata) => { const session = await getPageCDPSession(page); return await session.send('Emulation.setUserAgentOverride', { userAgent, userAgentMetadata }); }; } if (prop === 'browser') { if (isPlaywrightPage(page)) { return () => { let browser = page.context().browser(); if (!browser) { debug('page.browser() - not available, most likely due to launchPersistentContext'); // Use a page shim as quick drop-in (so browser.userAgent() still works) browser = page; } return addPuppeteerCompat(browser); }; } } if (prop === 'evaluateOnNewDocument') { if (isPlaywrightPage(page)) { return async function (pageFunction, ...args) { return await page.addInitScript(pageFunction, args[0]); }; } } // Only relevant when page is being used a pseudo stand-in for the browser object (launchPersistentContext) if (prop === 'userAgent') { return async (enabled) => { const session = await getPageCDPSession(page); const data = await session.send('Browser.getVersion'); return data.userAgent; }; } return Reflect.get(target, prop); } }); return shim; } function createBrowserShim(browser) { const objId = Math.random().toString(36).substring(2, 7); const shim = new Proxy(browser, { get(target, prop) { if (prop === 'isCompatShim' || prop === 'isPlaywright') { return true; } debug('browser - get', objId, prop); if (prop === 'pages') { return () => browser .contexts() .flatMap(c => c.pages().map(page => addPuppeteerCompat(page))); } if (prop === 'userAgent') { return async () => { const session = await getBrowserCDPSession(browser); const data = await session.send('Browser.getVersion'); return data.userAgent; }; } return Reflect.get(target, prop); } }); return shim; } const debug$1 = Debug('playwright-extra:plugins'); class PluginList { constructor() { this._plugins = []; this._dependencyDefaults = new Map(); this._dependencyResolution = new Map(); } /** * Get a list of all registered plugins. */ get list() { return this._plugins; } /** * Get the names of all registered plugins. */ get names() { return this._plugins.map(p => p.name); } /** * Add a new plugin to the list (after checking if it's well-formed). * * @param plugin * @internal */ add(plugin) { var _a; if (!this.isValidPluginInstance(plugin)) { return false; } if (!!plugin.onPluginRegistered) { plugin.onPluginRegistered({ framework: 'playwright' }); } // PuppeteerExtraPlugin: Populate `_childClassMembers` list containing methods defined by the plugin if (!!plugin._registerChildClassMembers) { plugin._registerChildClassMembers(Object.getPrototypeOf(plugin)); } if ((_a = plugin.requirements) === null || _a === void 0 ? void 0 : _a.has('dataFromPlugins')) { plugin.getDataFromPlugins = this.getData.bind(this); } this._plugins.push(plugin); return true; } /** Check if the shape of a plugin is correct or warn */ isValidPluginInstance(plugin) { if (!plugin || typeof plugin !== 'object' || !plugin._isPuppeteerExtraPlugin) { console.error(`Warning: Plugin is not derived from PuppeteerExtraPlugin, ignoring.`, plugin); return false; } if (!plugin.name) { console.error(`Warning: Plugin with no name registering, ignoring.`, plugin); return false; } return true; } /** Error callback in case calling a plugin method throws an error. Can be overwritten. */ onPluginError(plugin, method, err) { console.warn(`An error occured while executing "${method}" in plugin "${plugin.name}":`, err); } /** * Define default values for plugins implicitly required through the `dependencies` plugin stanza. * * @param dependencyPath - The string by which the dependency is listed (not the plugin name) * * @example * chromium.use(stealth) * chromium.plugins.setDependencyDefaults('stealth/evasions/webgl.vendor', { vendor: 'Bob', renderer: 'Alice' }) */ setDependencyDefaults(dependencyPath, opts) { this._dependencyDefaults.set(dependencyPath, opts); return this; } /** * Define custom plugin modules for plugins implicitly required through the `dependencies` plugin stanza. * * Using this will prevent dynamic imports from being used, which JS bundlers often have issues with. * * @example * chromium.use(stealth) * chromium.plugins.setDependencyResolution('stealth/evasions/webgl.vendor', VendorPlugin) */ setDependencyResolution(dependencyPath, pluginModule) { this._dependencyResolution.set(dependencyPath, pluginModule); return this; } /** * Prepare plugins to be used (resolve dependencies, ordering) * @internal */ prepare() { this.resolveDependencies(); this.order(); } /** Return all plugins using the supplied method */ filterByMethod(methodName) { return this._plugins.filter(plugin => { // PuppeteerExtraPlugin: The base class will already define all methods, hence we need to do a different check if (!!plugin._childClassMembers && Array.isArray(plugin._childClassMembers)) { return plugin._childClassMembers.includes(methodName); } return methodName in plugin; }); } /** Conditionally add puppeteer compatibility to values provided to the plugins */ _addPuppeteerCompatIfNeeded(plugin, method, args) { const canUseShim = plugin._isPuppeteerExtraPlugin && !plugin.noPuppeteerShim; const methodWhitelist = [ 'onBrowser', 'onPageCreated', 'onPageClose', 'afterConnect', 'afterLaunch' ]; const shouldUseShim = methodWhitelist.includes(method); if (!canUseShim || !shouldUseShim) { return args; } debug$1('add puppeteer compatibility', plugin.name, method); return [...args.map(arg => addPuppeteerCompat(arg))]; } /** * Dispatch plugin lifecycle events in a typesafe way. * Only Plugins that expose the supplied property will be called. * * Will not await results to dispatch events as fast as possible to all plugins. * * @param method - The lifecycle method name * @param args - Optional: Any arguments to be supplied to the plugin methods * @internal */ dispatch(method, ...args) { var _a, _b; const plugins = this.filterByMethod(method); debug$1('dispatch', method, { all: this._plugins.length, filteredByMethod: plugins.length }); for (const plugin of plugins) { try { args = this._addPuppeteerCompatIfNeeded.bind(this)(plugin, method, args); const fnType = (_b = (_a = plugin[method]) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.name; debug$1('dispatch to plugin', { plugin: plugin.name, method, fnType }); if (fnType === 'AsyncFunction') { ; plugin[method](...args).catch((err) => this.onPluginError(plugin, method, err)); } else { ; plugin[method](...args); } } catch (err) { this.onPluginError(plugin, method, err); } } } /** * Dispatch plugin lifecycle events in a typesafe way. * Only Plugins that expose the supplied property will be called. * * Can also be used to get a definite return value after passing it to plugins: * Calls plugins sequentially and passes on a value (waterfall style). * * The plugins can either modify the value or return an updated one. * Will return the latest, updated value which ran through all plugins. * * By convention only the first argument will be used as the updated value. * * @param method - The lifecycle method name * @param args - Optional: Any arguments to be supplied to the plugin methods * @internal */ async dispatchBlocking(method, ...args) { const plugins = this.filterByMethod(method); debug$1('dispatchBlocking', method, { all: this._plugins.length, filteredByMethod: plugins.length }); let retValue = null; for (const plugin of plugins) { try { args = this._addPuppeteerCompatIfNeeded.bind(this)(plugin, method, args); retValue = await plugin[method](...args); // In case we got a return value use that as new first argument for followup function calls if (retValue !== undefined) { args[0] = retValue; } } catch (err) { this.onPluginError(plugin, method, err); return retValue; } } return retValue; } /** * Order plugins that have expressed a special placement requirement. * * This is useful/necessary for e.g. plugins that depend on the data from other plugins. * * @private */ order() { debug$1('order:before', this.names); const runLast = this._plugins .filter(p => { var _a; return (_a = p.requirements) === null || _a === void 0 ? void 0 : _a.has('runLast'); }) .map(p => p.name); for (const name of runLast) { const index = this._plugins.findIndex(p => p.name === name); this._plugins.push(this._plugins.splice(index, 1)[0]); } debug$1('order:after', this.names); } /** * Collects the exposed `data` property of all registered plugins. * Will be reduced/flattened to a single array. * * Can be accessed by plugins that listed the `dataFromPlugins` requirement. * * Implemented mainly for plugins that need data from other plugins (e.g. `user-preferences`). * * @see [PuppeteerExtraPlugin]/data * @param name - Filter data by optional name * * @private */ getData(name) { const data = this._plugins .filter((p) => !!p.data) .map((p) => (Array.isArray(p.data) ? p.data : [p.data])) .reduce((acc, arr) => [...acc, ...arr], []); return name ? data.filter((d) => d.name === name) : data; } /** * Handle `plugins` stanza (already instantiated plugins that don't require dynamic imports) */ resolvePluginsStanza() { debug$1('resolvePluginsStanza'); const pluginNames = new Set(this.names); this._plugins .filter(p => !!p.plugins && p.plugins.length) .filter(p => !pluginNames.has(p.name)) // TBD: Do we want to filter out existing? .forEach(parent => { (parent.plugins || []).forEach(p => { debug$1(parent.name, 'adding missing plugin', p.name); this.add(p); }); }); } /** * Handle `dependencies` stanza (which requires dynamic imports) * * Plugins can define `dependencies` as a Set or Array of dependency paths, or a Map with additional opts * * @note * - The default opts for implicit dependencies can be defined using `setDependencyDefaults()` * - Dynamic imports can be avoided by providing plugin modules with `setDependencyResolution()` */ resolveDependenciesStanza() { debug$1('resolveDependenciesStanza'); /** Attempt to dynamically require a plugin module */ const requireDependencyOrDie = (parentName, dependencyPath) => { // If the user provided the plugin module already we use that if (this._dependencyResolution.has(dependencyPath)) { return this._dependencyResolution.get(dependencyPath); } const possiblePrefixes = ['puppeteer-extra-plugin-']; // could be extended later const isAlreadyPrefixed = possiblePrefixes.some(prefix => dependencyPath.startsWith(prefix)); const packagePaths = []; // If the dependency is not already prefixed we attempt to require all possible combinations to find one that works if (!isAlreadyPrefixed) { packagePaths.push(...possiblePrefixes.map(prefix => prefix + dependencyPath)); } // We always attempt to require the path verbatim (as a last resort) packagePaths.push(dependencyPath); const pluginModule = requirePackages(packagePaths); if (pluginModule) { return pluginModule; } const explanation = ` The plugin '${parentName}' listed '${dependencyPath}' as dependency, which could not be found. Please install it: ${packagePaths .map(packagePath => `yarn add ${packagePath.split('/')[0]}`) .join(`\n or:\n`)} Note: You don't need to require the plugin yourself, unless you want to modify it's default settings. If your bundler has issues with dynamic imports take a look at '.plugins.setDependencyResolution()'. `; console.warn(explanation); throw new Error('Plugin dependency not found'); }; const existingPluginNames = new Set(this.names); const recursivelyLoadMissingDependencies = ({ name: parentName, dependencies }) => { if (!dependencies) { return; } const processDependency = (dependencyPath, opts) => { const pluginModule = requireDependencyOrDie(parentName, dependencyPath); opts = opts || this._dependencyDefaults.get(dependencyPath) || {}; const plugin = pluginModule(opts); if (existingPluginNames.has(plugin.name)) { debug$1(parentName, '=> dependency already exists:', plugin.name); return; } existingPluginNames.add(plugin.name); debug$1(parentName, '=> adding new dependency:', plugin.name, opts); this.add(plugin); return recursivelyLoadMissingDependencies(plugin); }; if (dependencies instanceof Set || Array.isArray(dependencies)) { return [...dependencies].forEach(dependencyPath => processDependency(dependencyPath)); } if (dependencies instanceof Map) { // Note: `k,v => v,k` (Map + forEach will reverse the order) return dependencies.forEach((v, k) => processDependency(k, v)); } }; this.list.forEach(recursivelyLoadMissingDependencies); } /** * Lightweight plugin dependency management to require plugins and code mods on demand. * @private */ resolveDependencies() { debug$1('resolveDependencies'); this.resolvePluginsStanza(); this.resolveDependenciesStanza(); } } const debug$2 = Debug('playwright-extra'); /** * Modular plugin framework to teach `playwright` new tricks. */ class PlaywrightExtraClass { constructor(_launcher) { this._launcher = _launcher; this.plugins = new PluginList(); } /** * The **main interface** to register plugins. * * Can be called multiple times to enable multiple plugins. * * Plugins derived from `PuppeteerExtraPlugin` will be used with a compatiblity layer. * * @example * chromium.use(plugin1).use(plugin2) * firefox.use(plugin1).use(plugin2) * * @see [PuppeteerExtraPlugin] * * @return The same `PlaywrightExtra` instance (for optional chaining) */ use(plugin) { const isValid = plugin && 'name' in plugin; if (!isValid) { throw new Error('A plugin must be provided to .use()'); } if (this.plugins.add(plugin)) { debug$2('Plugin registered', plugin.name); } return this; } /** * In order to support a default export which will require vanilla playwright automatically, * as well as `addExtra` to patch a provided launcher, we need to so some gymnastics here. * * Otherwise this would throw immediately, even when only using the `addExtra` export with an arbitrary compatible launcher. * * The solution is to make the vanilla launcher optional and only throw once we try to effectively use and can't find it. * * @internal */ get launcher() { if (!this._launcher) { throw playwrightLoader.requireError; } return this._launcher; } async launch(...args) { if (!this.launcher.launch) { throw new Error('Launcher does not support "launch"'); } let [options] = args; options = Object.assign({ args: [] }, (options || {})); // Initialize args array debug$2('launch', options); this.plugins.prepare(); // Give plugins the chance to modify the options before continuing options = (await this.plugins.dispatchBlocking('beforeLaunch', options)) || options; debug$2('launch with options', options); if ('userDataDir' in options) { debug$2("A plugin defined userDataDir during .launch, which isn't supported by playwright - ignoring"); delete options.userDataDir; } const browser = await this.launcher['launch'](options); await this.plugins.dispatchBlocking('onBrowser', browser); await this._bindBrowserEvents(browser); await this.plugins.dispatchBlocking('afterLaunch', browser); return browser; } async launchPersistentContext(...args) { if (!this.launcher.launchPersistentContext) { throw new Error('Launcher does not support "launchPersistentContext"'); } let [userDataDir, options] = args; options = Object.assign({ args: [] }, (options || {})); // Initialize args array debug$2('launchPersistentContext', options); this.plugins.prepare(); // Give plugins the chance to modify the options before continuing options = (await this.plugins.dispatchBlocking('beforeLaunch', options)) || options; const context = await this.launcher['launchPersistentContext'](userDataDir, options); await this.plugins.dispatchBlocking('afterLaunch', context); this._bindBrowserContextEvents(context); return context; } async connect(wsEndpointOrOptions, wsOptions = {}) { if (!this.launcher.connect) { throw new Error('Launcher does not support "connect"'); } this.plugins.prepare(); // Playwright currently supports two function signatures for .connect let options = {}; let wsEndpointAsString = false; if (typeof wsEndpointOrOptions === 'object') { options = Object.assign(Object.assign({}, wsEndpointOrOptions), wsOptions); } else { wsEndpointAsString = true; options = Object.assign({ wsEndpoint: wsEndpointOrOptions }, wsOptions); } debug$2('connect', options); // Give plugins the chance to modify the options before launch/connect options = (await this.plugins.dispatchBlocking('beforeConnect', options)) || options; // Follow call signature of end user const args = []; const wsEndpoint = options.wsEndpoint; if (wsEndpointAsString) { delete options.wsEndpoint; args.push(wsEndpoint, options); } else { args.push(options); } const browser = (await this.launcher['connect'](...args)); await this.plugins.dispatchBlocking('onBrowser', browser); await this._bindBrowserEvents(browser); await this.plugins.dispatchBlocking('afterConnect', browser); return browser; } async connectOverCDP(wsEndpointOrOptions, wsOptions = {}) { if (!this.launcher.connectOverCDP) { throw new Error(`Launcher does not implement 'connectOverCDP'`); } this.plugins.prepare(); // Playwright currently supports two function signatures for .connectOverCDP let options = {}; let wsEndpointAsString = false; if (typeof wsEndpointOrOptions === 'object') { options = Object.assign(Object.assign({}, wsEndpointOrOptions), wsOptions); } else { wsEndpointAsString = true; options = Object.assign({ endpointURL: wsEndpointOrOptions }, wsOptions); } debug$2('connectOverCDP'), options; // Give plugins the chance to modify the options before launch/connect options = (await this.plugins.dispatchBlocking('beforeConnect', options)) || options; // Follow call signature of end user const args = []; const endpointURL = options.endpointURL; if (wsEndpointAsString) { delete options.endpointURL; args.push(endpointURL, options); } else { args.push(options); } const browser = (await this.launcher['connectOverCDP'](...args)); await this.plugins.dispatchBlocking('onBrowser', browser); await this._bindBrowserEvents(browser); await this.plugins.dispatchBlocking('afterConnect', browser); return browser; } async _bindBrowserContextEvents(context, contextOptions) { debug$2('_bindBrowserContextEvents'); this.plugins.dispatch('onContextCreated', context, contextOptions); // Make sure things like `addInitScript` show an effect on the very first page as well context.newPage = ((originalMethod, ctx) => { return async () => { const page = await originalMethod.call(ctx); await page.goto('about:blank'); return page; }; })(context.newPage, context); context.on('close', () => { // When using `launchPersistentContext` context closing is the same as browser closing if (!context.browser()) { this.plugins.dispatch('onDisconnected'); } }); context.on('page', page => { this.plugins.dispatch('onPageCreated', page); page.on('close', () => { this.plugins.dispatch('onPageClose', page); }); }); } async _bindBrowserEvents(browser) { debug$2('_bindPlaywrightBrowserEvents'); browser.on('disconnected', () => { this.plugins.dispatch('onDisconnected', browser); }); // Note: `browser.newPage` will implicitly call `browser.newContext` as well browser.newContext = ((originalMethod, ctx) => { return async (options = {}) => { const contextOptions = (await this.plugins.dispatchBlocking('beforeContext', options, browser)) || options; const context = await originalMethod.call(ctx, contextOptions); this._bindBrowserContextEvents(context, contextOptions); return context; }; })(browser.newContext, browser); } } /** * PlaywrightExtra class with additional launcher methods. * * Augments the class with an instance proxy to pass on methods that are not augmented to the original target. * */ const PlaywrightExtra = new Proxy(PlaywrightExtraClass, { construct(classTarget, args) { debug$2(`create instance of ${classTarget.name}`); const result = Reflect.construct(classTarget, args); return new Proxy(result, { get(target, prop) { if (prop in target) { return Reflect.get(target, prop); } debug$2('proxying property to original launcher: ', prop); return Reflect.get(target.launcher, prop); } }); } }); /** * Augment the provided Playwright browser launcher with plugin functionality. * * Using `addExtra` will always create a fresh PlaywrightExtra instance. * * @example * import playwright from 'playwright' * import { addExtra } from 'playwright-extra' * * const chromium = addExtra(playwright.chromium) * chromium.use(plugin) * * @param launcher - Playwright (or compatible) browser launcher */ const addExtra = (launcher) => new PlaywrightExtra(launcher); /** * This object can be used to launch or connect to Chromium with plugin functionality. * * This default export will behave exactly the same as the regular playwright * (just with extra plugin functionality) and can be used as a drop-in replacement. * * Behind the scenes it will try to require either the `playwright-core` * or `playwright` module from the installed dependencies. * * @note * Due to Node.js import caching this will result in a single * PlaywrightExtra instance, even when used in different files. If you need multiple * instances with different plugins please use `addExtra`. * * @example * // javascript import * const { chromium } = require('playwright-extra') * * // typescript/es6 module import * import { chromium } from 'playwright-extra' * * // Add plugins * chromium.use(...) */ const chromium = addExtra((playwrightLoader.loadModule() || {}).chromium); /** * This object can be used to launch or connect to Firefox with plugin functionality * @note This export will always return the same instance, if you wish to use multiple instances with different plugins use `addExtra` */ const firefox = addExtra((playwrightLoader.loadModule() || {}).firefox); /** * This object can be used to launch or connect to Webkit with plugin functionality * @note This export will always return the same instance, if you wish to use multiple instances with different plugins use `addExtra` */ const webkit = addExtra((playwrightLoader.loadModule() || {}).webkit); // Other playwright module exports we simply re-export with lazy loading const _android = playwrightLoader.lazyloadExportOrDie('_android'); const _electron = playwrightLoader.lazyloadExportOrDie('_electron'); const request = playwrightLoader.lazyloadExportOrDie('request'); const selectors = playwrightLoader.lazyloadExportOrDie('selectors'); const devices = playwrightLoader.lazyloadExportOrDie('devices'); const errors = playwrightLoader.lazyloadExportOrDie('errors'); /** Playwright with plugin functionality */ const moduleExports = { // custom exports PlaywrightExtra, PlaywrightExtraClass, PluginList, addExtra, chromium, firefox, webkit, // vanilla exports _android, _electron, request, selectors, devices, errors }; exports.PlaywrightExtra = PlaywrightExtra; exports.PlaywrightExtraClass = PlaywrightExtraClass; exports.PluginList = PluginList; exports._android = _android; exports._electron = _electron; exports.addExtra = addExtra; exports.chromium = chromium; exports.default = moduleExports; exports.devices = devices; exports.errors = errors; exports.firefox = firefox; exports.request = request; exports.selectors = selectors; exports.webkit = webkit; module.exports = exports.default || {} Object.entries(exports).forEach(([key, value]) => { module.exports[key] = value }) //# sourceMappingURL=index.cjs.js.map