Skip to content

Commit

Permalink
fix(webdriver): use undici for requests in Node.js (webdriverio#13852)
Browse files Browse the repository at this point in the history
* fix(webdriver): use undici for requests in Node.js

* apply changes from webdriverio#13783

* conditional environment

* fix tests

* fix interop
  • Loading branch information
christian-bromann authored Nov 26, 2024
1 parent 6e2650c commit 5218796
Show file tree
Hide file tree
Showing 30 changed files with 487 additions and 398 deletions.
4 changes: 2 additions & 2 deletions infra/compiler/src/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export function log(options: BuildOptions, pkg: PackageJson): Plugin {
name: 'LogPlugin',
setup(build) {
build.onStart(() => {
console.log(`${l.name(pkg.name)} 🏗️ Building ${l.format(options.format)} package: ${l.file(srcFile)}${l.file(outFile)}`)
console.log(`${l.name(pkg.name)} 🏗️ Building ${l.format(`${options.platform}:${options.format}`)} package: ${l.file(srcFile)}${l.file(outFile)}`)
})
build.onEnd((result) => {
if (result.errors.length === 0) {
console.log(`${l.name(pkg.name)} ✅ Success building ${l.format(options.format)} package: ${l.file(srcFile)}${l.file(outFile)}`)
console.log(`${l.name(pkg.name)} ✅ Success building ${l.format(`${options.platform}:${options.format}`)} package: ${l.file(srcFile)}${l.file(outFile)}`)
}
})
}
Expand Down
1 change: 1 addition & 0 deletions infra/compiler/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ export function getExternal(pkg: PackageJson) {
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
...Object.keys(pkg.optionalDependencies || {}),
'./node.js'
]
}
2 changes: 1 addition & 1 deletion packages/wdio-webdriver-mock-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@types/uuid": "^10.0.0",
"@wdio/protocols": "workspace:*",
"@wdio/types": "workspace:*",
"nock": "14.0.0-beta.14",
"nock": "14.0.0-beta.18",
"uuid": "^10.0.0",
"webdriverio": "workspace:*"
},
Expand Down
4 changes: 3 additions & 1 deletion packages/wdio-webdriver-mock-service/src/WebDriverMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export default class WebDriverMock {
command: CommandMock
scope: nock.Scope
constructor(host: string = 'localhost', port: number = 4444, public path: string = '/') {
this.scope = nock(`http://${host}:${port}`, { 'encodedQueryParams': true })
this.scope = nock(`http://${host}:${port}`, {
encodedQueryParams: true
})
this.command = new Proxy({}, { get: this.get.bind(this) })
}

Expand Down
4 changes: 4 additions & 0 deletions packages/wdio-webdriver-mock-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,5 +275,9 @@ export const launcher = class WebdriverMockLauncher {
onPrepare(config: WebdriverIO.Config) {
config.hostname = 'localhost'
config.port = 4444
config.runnerEnv = {
...(config.runnerEnv || {}),
WDIO_USE_NATIVE_FETCH: 'true'
}
}
}
8 changes: 6 additions & 2 deletions packages/webdriver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
"author": "Christian Bromann <[email protected]>",
"homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/webdriver",
"license": "MIT",
"main": "./build/index.cjs",
"type": "module",
"main": "./build/index.cjs",
"module": "./build/index.js",
"exports": {
".": {
"types": "./build/index.d.ts",
"import": "./build/index.js",
"browserSource": "./src/browser.js",
"browser": "./build/index.js",
"importSource": "./src/node.ts",
"import": "./build/node.js",
"requireSource": "./src/index.cts",
"require": "./build/index.cjs"
}
Expand Down Expand Up @@ -41,6 +44,7 @@
"@wdio/types": "workspace:*",
"@wdio/utils": "workspace:*",
"deepmerge-ts": "^7.0.3",
"undici": "^6.20.1",
"ws": "^8.8.0"
}
}
6 changes: 3 additions & 3 deletions packages/webdriver/src/bidi/core.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logger from '@wdio/logger'
import type { ClientOptions, RawData, WebSocket } from 'ws'

import Socket from './socket.js'
import { environment } from '../environment.js'
import type * as remote from './remoteTypes.js'
import type { CommandData } from './remoteTypes.js'
import type { CommandResponse } from './localTypes.js'
Expand All @@ -27,7 +27,7 @@ export class BidiCore {
constructor (webSocketUrl: string, opts?: ClientOptions) {
this.#webSocketUrl = webSocketUrl
log.info(`Connect to webSocketUrl ${this.#webSocketUrl}`)
this.#ws = new Socket(this.#webSocketUrl, opts) as WebSocket
this.#ws = new environment.value.Socket(this.#webSocketUrl, opts) as unknown as WebSocket
this.#ws.on('message', this.#handleResponse.bind(this))
}

Expand Down Expand Up @@ -81,7 +81,7 @@ export class BidiCore {
log.info(`Reconnect to new Bidi session at ${webSocketUrl}`)
this.close()
this.#webSocketUrl = webSocketUrl
this.#ws = new Socket(this.#webSocketUrl, opts) as WebSocket
this.#ws = new environment.value.Socket(this.#webSocketUrl, opts) as unknown as WebSocket
this.#ws.on('message', this.#handleResponse.bind(this))
return this.connect()
}
Expand Down
11 changes: 2 additions & 9 deletions packages/webdriver/src/bidi/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
* A WebSocket implementation that wraps the browser native WebSocket
* interface and exposes a similar interface to the Node.js WebSocket
*/
class BrowserSocket {
export class BrowserSocket {
#callbacks = new Set<any>()
#ws: WebSocket

// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor (wsUrl: string, opts: any) {
this.#ws = new globalThis.WebSocket(wsUrl) as any
this.#ws = new WebSocket(wsUrl) as any
this.#ws.onmessage = this.handleMessage.bind(this)
}

Expand Down Expand Up @@ -45,10 +45,3 @@ class BrowserSocket {
this.#ws.close()
}
}

/**
* make sure to use the correct WebSocket implementation based on the environment
*/
export default globalThis.window
? BrowserSocket
: (await import('ws')).default
15 changes: 15 additions & 0 deletions packages/webdriver/src/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import WebDriver from './index.js'
import { BrowserSocket } from './bidi/socket.js'
import { FetchRequest } from './request/web.js'

export default WebDriver
export * from './index.js'

import { environment } from './environment.js'

environment.value = {
Request: FetchRequest,
Socket: BrowserSocket,
variables: {}
}

4 changes: 2 additions & 2 deletions packages/webdriver/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import logger from '@wdio/logger'
import { commandCallStructure, isValidParameter, getArgumentType } from '@wdio/utils'
import { WebDriverBidiProtocol, type CommandEndpoint } from '@wdio/protocols'

import Request from './request/request.js'
import { environment } from './environment.js'
import type { BidiHandler } from './bidi/handler.js'
import type { WebDriverResponse } from './request/types.js'
import type { BaseClient, BidiCommands, BidiResponses } from './types.js'
Expand Down Expand Up @@ -120,7 +120,7 @@ export default function (
body[commandParams[i].name] = arg
}

const request = new Request(method, endpoint, body, isHubCommand)
const request = new environment.value.Request(method, endpoint, body, isHubCommand)
request.on('performance', (...args) => this.emit('request.performance', ...args))
this.emit('command', { command, method, endpoint, body })
log.info('COMMAND', commandCallStructure(command, args))
Expand Down
5 changes: 3 additions & 2 deletions packages/webdriver/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os from 'node:os'
import type { Options } from '@wdio/types'

import { environment } from './environment.js'
import type { RemoteConfig } from './types.js'

export const DEFAULTS: Options.Definition<Required<RemoteConfig>> = {
Expand Down Expand Up @@ -139,7 +140,7 @@ export const DEFAULTS: Options.Definition<Required<RemoteConfig>> = {
*/
cacheDir: {
type: 'string',
default: process.env.WEBDRIVER_CACHE_DIR || os.tmpdir()
default: environment.value.variables.WEBDRIVER_CACHE_DIR
}
}

Expand Down
38 changes: 38 additions & 0 deletions packages/webdriver/src/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { BrowserSocket } from './bidi/socket.js'
import type { FetchRequest } from './request/web.js'

/**
* @internal
*/
export const isNode = !!(typeof process !== 'undefined' && process.version)

export interface EnvironmentVariables {
WEBDRIVER_CACHE_DIR?: string
PROXY_URL?: string
}

export interface EnvironmentDependencies {
Request: typeof FetchRequest,
Socket: typeof BrowserSocket,
variables: EnvironmentVariables
}

/**
* Holder for environment dependencies. These dependencies cannot
* be used during the module instantiation.
*/
export const environment: {
value: EnvironmentDependencies;
} = {
value: {
get Request(): EnvironmentDependencies['Request'] {
throw new Error('Request is not available in this environment')
},
get Socket(): EnvironmentDependencies['Socket'] {
throw new Error('Socket is not available in this environment')
},
get variables(): EnvironmentDependencies['variables'] {
return {}
}
}
}
14 changes: 8 additions & 6 deletions packages/webdriver/src/index.cts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const esmModule = import('./node.js')

function command (method: string, encodeUri: string, commandInfo: any, doubleEncodeVariables = false) {
return async function protocolCommand(this: unknown, ...args: unknown[]) {
const commandESM = await import('./command.js')
return commandESM.default(method, encodeUri, commandInfo, doubleEncodeVariables).apply(this, args)
const commandESM = await esmModule
return commandESM.command(method, encodeUri, commandInfo, doubleEncodeVariables).apply(this, args)
}
}

Expand All @@ -12,7 +14,7 @@ class WebDriver {
userPrototype = {},
customCommandWrapper?: () => any
): Promise<any> {
const WebDriver = (await import('./index.js')).default
const WebDriver = (await esmModule).WebDriver
return WebDriver.newSession(options, modifier, userPrototype, customCommandWrapper)
}

Expand All @@ -25,19 +27,19 @@ class WebDriver {
userPrototype = {},
commandWrapper?: () => any
): Promise<any> {
const WebDriver = (await import('./index.js')).default
const WebDriver = (await esmModule).WebDriver
return WebDriver.attachToSession(options, modifier, userPrototype, commandWrapper)
}

/**
* Changes The instance session id and browser capabilities for the new session
* directly into the passed in browser object
* directly into the passed in browser object!!
*
* @param {object} instance the object we get from a new browser session.
* @returns {string} the new session id of the browser
*/
static async reloadSession (instance: any) {
const WebDriver = (await import('./index.js')).default
const WebDriver = (await esmModule).WebDriver
return WebDriver.reloadSession(instance)
}

Expand Down
2 changes: 1 addition & 1 deletion packages/webdriver/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export default class WebDriver {
/**
* Helper methods consumed by webdriverio package
*/
export { getPrototype, DEFAULTS, command, getEnvironmentVars, initiateBidi, parseBidiMessage }
export { getPrototype, DEFAULTS, command, getEnvironmentVars, initiateBidi, parseBidiMessage, WebDriver }
export * from './types.js'
export * from './constants.js'
export * from './bidi/handler.js'
Expand Down
35 changes: 35 additions & 0 deletions packages/webdriver/src/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import os from 'node:os'
import ws from 'ws'

import WebDriver from './index.js'
import { FetchRequest } from './request/node.js'
import { FetchRequest as WebFetchRequest } from './request/web.js'
import type { BrowserSocket } from './bidi/socket.js'

export default WebDriver
export * from './index.js'

import { environment } from './environment.js'

environment.value = {
Request: (
/**
* Currently Nock doesn't support the mocking of undici requests, therefore for all
* Smoke test we use the native fetch implementation.
*
* @see https://github.com/nock/nock/issues/2183#issuecomment-2252525890
*/
process.env.WDIO_USE_NATIVE_FETCH ||
/**
* For unit tests we use the WebFetchRequest implementation as we can better mock the
* requests in the unit tests.
*/
process.env.WDIO_UNIT_TESTS
) ? WebFetchRequest : FetchRequest,
Socket: ws as unknown as typeof BrowserSocket,
variables: {
WEBDRIVER_CACHE_DIR: process.env.WEBDRIVER_CACHE_DIR || os.tmpdir(),
PROXY_URL: process.env.HTTP_PROXY || process.env.HTTPS_PROXY
}
}

Loading

0 comments on commit 5218796

Please sign in to comment.