/*!
 * playwright-extra v4.3.5 by berstend
 * https://github.com/berstend/puppeteer-extra/tree/master/packages/playwright-extra#readme
 * @license MIT
 */
import Debug from '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
};

export default moduleExports;
export { PlaywrightExtra, PlaywrightExtraClass, PluginList, _android, _electron, addExtra, chromium, devices, errors, firefox, request, selectors, webkit };
//# sourceMappingURL=index.esm.js.map
