Various browser runner improvements (#9673)

* Various browser runner improvements

* fix missing commands

* allow HTMLElement set in $$ command

* fix unit tests
This commit is contained in:
Christian Bromann 2023-01-27 18:56:06 +01:00 committed by GitHub
parent d8734c50b2
commit 68b011f8e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 48 additions and 32 deletions

View file

@ -2,6 +2,7 @@ import { vi } from 'vitest'
const yargs: any = {}
yargs.command = vi.fn(() => yargs)
yargs.example = vi.fn(() => yargs)
yargs.version = vi.fn(() => yargs)
yargs.hideBin = vi.fn(() => yargs)
yargs.argv = { _: ['wdio.conf.js'] }
yargs.epilogue = vi.fn().mockReturnValue(yargs)

View file

@ -1,9 +1,8 @@
import stringify from 'fast-safe-stringify'
import { commands } from 'virtual:wdio'
import { webdriverMonad } from '@wdio/utils'
import { webdriverMonad, sessionEnvironmentDetector } from '@wdio/utils'
import { getEnvironmentVars } from 'webdriver'
import type { ErrorObject } from 'serialize-error'
import { MESSAGE_TYPES } from '../constants.js'
import type { SocketMessage, SocketMessagePayload, ConsoleEvent, CommandRequestEvent } from '../vite/types.js'
@ -12,7 +11,7 @@ const COMMAND_TIMEOUT = 30 * 1000 // 30s
const CONSOLE_METHODS = ['log', 'info', 'warn', 'error', 'debug'] as const
interface CommandMessagePromise {
resolve: (value: unknown) => void
reject: (err: ErrorObject) => void
reject: (err: Error) => void
commandTimeout?: NodeJS.Timeout
}
@ -42,7 +41,8 @@ export default class ProxyDriver {
socket.addEventListener('message', this.#handleServerMessage.bind(this))
let commandId = 0
const environmentPrototype: Record<string, PropertyDescriptor> = getEnvironmentVars(params)
const environment = sessionEnvironmentDetector({ capabilities: params.capabilities, requestedCapabilities: {} })
const environmentPrototype: Record<string, PropertyDescriptor> = getEnvironmentVars(environment)
// have debug command
const commandsProcessedInNodeWorld = [...commands, 'debug']
const protocolCommands = commandsProcessedInNodeWorld.reduce((prev, commandName) => {
@ -126,8 +126,8 @@ export default class ProxyDriver {
return console.error(`Unknown command id "${value.id}"`)
}
if (value.error) {
console.log(`[WDIO] ${(new Date()).toISOString()} - id: ${value.id} - ERROR: ${JSON.stringify(value.result)}`)
return commandMessage.reject(value.error)
console.log(`[WDIO] ${(new Date()).toISOString()} - id: ${value.id} - ERROR: ${JSON.stringify(value.error.message)}`)
return commandMessage.reject(new Error(value.error.message || 'unknown error'))
}
if (commandMessage.commandTimeout) {
clearTimeout(commandMessage.commandTimeout)

View file

@ -2,6 +2,7 @@ import url from 'node:url'
import path from 'node:path'
import logger from '@wdio/logger'
import { deepmerge } from 'deepmerge-ts'
import { resolve } from 'import-meta-resolve'
import type { Plugin } from 'vite'
@ -17,17 +18,11 @@ import { getTemplate, getErrorTemplate } from '../utils.js'
const log = logger('@wdio/browser-runner:plugin')
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const commands = {
...WebDriverProtocol,
...MJsonWProtocol,
...JsonWProtocol,
...AppiumProtocol,
...ChromiumProtocol,
...SauceLabsProtocol,
...SeleniumProtocol,
...GeckoProtocol,
...WebDriverBidiProtocol
}
const commands = deepmerge(
WebDriverProtocol, MJsonWProtocol, JsonWProtocol, AppiumProtocol,
ChromiumProtocol, SauceLabsProtocol, SeleniumProtocol, GeckoProtocol,
WebDriverBidiProtocol
)
const protocolCommandList = Object.values(commands).map(
(endpoint) => Object.values(endpoint).map(
({ command }) => command

View file

@ -175,7 +175,7 @@ export class ViteServer extends EventEmitter {
}
async #handleCommand (ws: WebSocket, payload: CommandRequestEvent) {
log.info(`Received browser message: ${payload}`)
log.debug(`Received browser message: ${JSON.stringify(payload)}`)
const cid = payload.cid
if (typeof cid !== 'string') {
const error = serializeError(new Error(`No "cid" property passed into command message with id "${payload.id}"`))
@ -194,6 +194,14 @@ export class ViteServer extends EventEmitter {
if (payload.commandName === 'debug') {
this.emit('debugState', true)
}
/**
* double check if function is registered
*/
if (typeof browser[payload.commandName as keyof typeof browser] !== 'function') {
throw new Error(`browser.${payload.commandName} is not a function`)
}
const result = await (browser[payload.commandName as keyof typeof browser] as Function)(...payload.args)
const resultMsg = JSON.stringify(this.#commandResponse({ id: payload.id, result }))

View file

@ -54,7 +54,7 @@ test('resolveId', async () => {
test('load', () => {
const plugin = testrunner({})
const js = (plugin.load as Function)('\0virtual:wdio')
expect(js).toContain('export const commands = ["newSession","getSession","deleteSession"')
expect(js).toContain('export const commands = ["newSession","deleteSession","getSession"')
expect(js).toContain('export const automationProtocolPath =')
expect((plugin.load as Function)('something else')).toBe(undefined)
})

View file

@ -7,7 +7,7 @@ import { hideBin } from 'yargs/helpers'
import { commands } from './commands/index.js'
import { handler, cmdArgs } from './commands/run.js'
import { CLI_EPILOGUE } from './constants.js'
import { CLI_EPILOGUE, pkg } from './constants.js'
import type { RunCommandArguments } from './types.js'
const __dirname = path.dirname(url.fileURLToPath(import.meta.url))
@ -37,6 +37,7 @@ export default async function run() {
.example('wdio install reporter spec', 'Install @wdio/spec-reporter')
.example('wdio repl chrome -u <SAUCE_USERNAME> -k <SAUCE_ACCESS_KEY>', 'Run repl in Sauce Labs cloud')
.updateStrings({ 'Commands:': `${DESCRIPTION.join('\n')}\n\nCommands:` })
.version(pkg.version)
.epilogue(CLI_EPILOGUE)
/**

View file

@ -28,7 +28,6 @@ declare global {
}
const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms))
const TEST_TIMEOUT = 15 * 1000
export default class BrowserFramework implements Omit<TestFramework, 'init'> {
#inDebugMode = false
@ -75,11 +74,12 @@ export default class BrowserFramework implements Omit<TestFramework, 'init'> {
}
async #loop () {
const timeout = this._config.mochaOpts!.timeout
/**
* start tests
*/
let failures = 0
let uid = 0
for (const spec of this._specs) {
log.info(`Run spec file ${spec} for cid ${this._cid}`)
@ -101,7 +101,7 @@ export default class BrowserFramework implements Omit<TestFramework, 'init'> {
const now = Date.now()
await browser.waitUntil(async () => {
while (typeof state.failures !== 'number' && (!state.errors || state.errors.length === 0)) {
if ((Date.now() - now) > TEST_TIMEOUT) {
if ((Date.now() - now) > timeout) {
return false
}
@ -137,7 +137,7 @@ export default class BrowserFramework implements Omit<TestFramework, 'init'> {
return true
}, {
timeoutMsg: 'browser test timed out',
timeout: TEST_TIMEOUT
timeout
})
if (state.errors?.length) {

View file

@ -1,6 +1,6 @@
import type { ElementReference } from '@wdio/protocols'
import { findElements, enhanceElementsArray } from '../../utils/index.js'
import { findElements, enhanceElementsArray, isElement, findElement } from '../../utils/index.js'
import { getElements } from '../../utils/getElementObject.js'
import type { Selector, ElementArray } from '../../types.js'
@ -61,11 +61,22 @@ import type { Selector, ElementArray } from '../../types.js'
*/
export async function $$ (
this: WebdriverIO.Browser | WebdriverIO.Element,
selector: Selector | ElementReference[] | WebdriverIO.Element[]
selector: Selector | ElementReference[] | WebdriverIO.Element[] | HTMLElement[]
) {
const res = Array.isArray(selector)
let res: (ElementReference | Error)[] = Array.isArray(selector)
? selector as ElementReference[]
: await findElements.call(this, selector)
const elements = await getElements.call(this, selector, res)
return enhanceElementsArray(elements, this, selector) as ElementArray
/**
* allow user to transform a set of HTMLElements into a set of WebdriverIO elements
*/
if (Array.isArray(selector) && isElement(selector[0])) {
res = []
for (const el of selector) {
res.push(await findElement.call(this, el))
}
}
const elements = await getElements.call(this, selector as Selector, res)
return enhanceElementsArray(elements, this, selector as Selector) as ElementArray
}

View file

@ -85,7 +85,7 @@ export const getElement = function findElement(
export const getElements = function getElements(
this: WebdriverIO.Browser | WebdriverIO.Element,
selector: Selector | ElementReference[] | WebdriverIO.Element[],
elemResponse: ElementReference[],
elemResponse: (ElementReference | Error)[],
isReactElement = false
): ElementArray {
const browser = getBrowserObject(this as WebdriverIO.Element)

View file

@ -81,7 +81,7 @@ export const getElementFromResponse = (res: ElementReference) => {
* deprecated JSONWireProtocol response
*/
if ((res as any).ELEMENT) {
return (res as any).ELEMENT
return (res as any as { ELEMENT: string }).ELEMENT
}
/**
@ -207,7 +207,7 @@ function fetchElementByJSFunction (
return getBrowserObject(scope).execute(`return (${script}).apply(null, arguments)`, scope, referenceId)
}
function isElement (o: Selector){
export function isElement (o: Selector){
return (
typeof HTMLElement === 'object'
? o instanceof HTMLElement