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:
parent
d8734c50b2
commit
68b011f8e5
10 changed files with 48 additions and 32 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue