/* eslint-disable @typescript-eslint/no-explicit-any */ import logger from '@wdio/logger' import WebDriver, { DEFAULTS } from 'webdriver' import { validateConfig } from '@wdio/config' import { enableFileLogging, wrapCommand, isBidi } from '@wdio/utils' import type { Options, Capabilities } from '@wdio/types' import type * as WebDriverTypes from 'webdriver' import MultiRemote from './multiremote.js' import SevereServiceErrorImport from './utils/SevereServiceError.js' import detectBackend from './utils/detectBackend.js' import { getProtocolDriver } from './utils/driver.js' import { WDIO_DEFAULTS, Key as KeyConstant } from './constants.js' import { getPrototype, addLocatorStrategyHandler, isStub } from './utils/index.js' import { registerSessionManager } from './session/index.js' import { environment } from './environment.js' import type { AttachOptions } from './types.js' import type * as elementCommands from './commands/element.js' export * from './types.js' export const Key = KeyConstant export const SevereServiceError = SevereServiceErrorImport /** * A method to create a new session with WebdriverIO. * * * NOTE: If you hit "error TS2694: Namespace 'global.WebdriverIO' has no exported member 'Browser'" when using typescript, * add "@wdio/globals/types" into tsconfig.json's "types" array will solve it: { "compilerOptions": { "types": ["@wdio/globals/types"] } } * * * @param params Options to create the session with * @param remoteModifier Modifier function to change the monad object * @return browser object with sessionId * @see Typescript setup */ export const remote = async function( params: Capabilities.WebdriverIOConfig, remoteModifier?: (client: WebDriverTypes.Client, options: Capabilities.WebdriverIOConfig) => WebDriverTypes.Client ): Promise { const keysToKeep = Object.keys(environment.value.variables.WDIO_WORKER_ID ? params : DEFAULTS) as (keyof Capabilities.WebdriverIOConfig)[] const config = validateConfig(WDIO_DEFAULTS, params, keysToKeep) await enableFileLogging(config.outputDir) logger.setLogLevelsConfig(config.logLevels, config.logLevel) const modifier = (client: WebDriverTypes.Client, options: Capabilities.WebdriverIOConfig) => { /** * overwrite instance options with default values of the protocol * package (without undefined properties) */ Object.assign(options, Object.entries(config) .reduce((a, [k, v]) => (typeof v === 'undefined' ? a : { ...a, [k]: v }), {})) if (typeof remoteModifier === 'function') { client = remoteModifier(client, options) } return client } const { Driver, options } = await getProtocolDriver({ ...params, ...config }) const prototype = getPrototype('browser') const instance = await Driver.newSession(options, modifier, prototype, wrapCommand) as WebdriverIO.Browser /** * we need to overwrite the original addCommand and overwriteCommand */ if ((params as Options.Testrunner).framework && !isStub(params.automationProtocol)) { instance.addCommand = instance.addCommand.bind(instance) instance.overwriteCommand = instance.overwriteCommand.bind(instance) } instance.addLocatorStrategy = addLocatorStrategyHandler(instance) await registerSessionManager(instance) return instance } export const attach = async function (attachOptions: AttachOptions): Promise { /** * copy instances properties into new object */ const params: Capabilities.WebdriverIOConfig & { requestedCapabilities: Capabilities.RequestedStandaloneCapabilities } = { automationProtocol: 'webdriver', ...attachOptions, ...detectBackend(attachOptions.options), capabilities: attachOptions.capabilities || {}, requestedCapabilities: attachOptions.requestedCapabilities || {} } const prototype = getPrototype('browser') const { Driver } = await getProtocolDriver(params) const driver = Driver.attachToSession( params, undefined, prototype, wrapCommand ) as WebdriverIO.Browser driver.addLocatorStrategy = addLocatorStrategyHandler(driver) /** * Wait for the Bidi handler to be connected before registering the session manager */ if (isBidi(driver.capabilities) && '_bidiHandler' in driver) { await (driver['_bidiHandler'] as WebDriverTypes.BidiHandler).waitForConnected() } await registerSessionManager(driver) return driver } /** * WebdriverIO allows you to run multiple automated sessions in a single test. * This is handy when you're testing features that require multiple users (for example, chat or WebRTC applications). * * Instead of creating a couple of remote instances where you need to execute common commands like newSession() or url() on each instance, * you can simply create a multiremote instance and control all browsers at the same time. * * * NOTE: Multiremote is not meant to execute all your tests in parallel. * It is intended to help coordinate multiple browsers and/or mobile devices for special integration tests (e.g. chat applications). * * * @param params capabilities to choose desired devices. * @param automationProtocol * @return All remote instances, the first result represents the capability defined first in the capability object, * the second result the second capability and so on. * * @see External document and example usage. */ export const multiremote = async function ( params: Capabilities.RequestedMultiremoteCapabilities, { automationProtocol }: { automationProtocol?: string } = {} ): Promise { const multibrowser = new MultiRemote() const browserNames = Object.keys(params) /** * create all instance sessions */ await Promise.all( browserNames.map(async (browserName) => { const instance = await remote(params[browserName]) return multibrowser.addInstance(browserName, instance) }) ) /** * use attachToSession capability to wrap instances around blank pod */ const prototype = getPrototype('browser') const sessionParams = isStub(automationProtocol) ? undefined : { sessionId: '', isW3C: multibrowser.instances[browserNames[0]].isW3C, logLevel: multibrowser.instances[browserNames[0]].options.logLevel } const ProtocolDriver = typeof automationProtocol === 'string' ? (await import(/* @vite-ignore */automationProtocol)).default : WebDriver const driver = ProtocolDriver.attachToSession( sessionParams, multibrowser.modifier.bind(multibrowser), prototype, wrapCommand ) as WebdriverIO.MultiRemoteBrowser /** * in order to get custom command overwritten or added to multiremote instance * we need to pass in the prototype of the multibrowser */ if (!isStub(automationProtocol)) { const origAddCommand = driver.addCommand.bind(driver) driver.addCommand = (name: string, fn, attachToElement) => { driver.instances.forEach(instanceName => driver.getInstance(instanceName).addCommand(name, fn, attachToElement) ) return origAddCommand( name, fn, attachToElement, Object.getPrototypeOf(multibrowser.baseInstance), multibrowser.instances ) } const origOverwriteCommand = driver.overwriteCommand.bind(driver) as typeof driver.overwriteCommand driver.overwriteCommand = (name, fn, attachToElement) => { return origOverwriteCommand( name, fn, attachToElement, Object.getPrototypeOf(multibrowser.baseInstance), multibrowser.instances ) } } driver.addLocatorStrategy = addLocatorStrategyHandler(driver) return driver }