rewrite @wdio/utils

This commit is contained in:
Christian Bromann 2022-05-13 09:42:27 +02:00
parent 62023041e8
commit 34f194a992
42 changed files with 362 additions and 632 deletions

1
.gitignore vendored
View file

@ -94,5 +94,6 @@ wallaby.conf.js
tags
tests/.retry_succeeded
*.tsbuildinfo
*.mjs
website/docs/Contribute.md
website/community/Resources.md

View file

@ -0,0 +1,3 @@
export default class FoobarService {
public foo = 'foobar'
}

View file

@ -1,9 +1,5 @@
import { vi } from 'vitest'
const mock = () => logMock
mock.setLevel = vi.fn()
mock.setLogLevelsConfig = vi.fn()
mock.waitForBuffer = vi.fn()
export const logMock = {
error: vi.fn(),
debug: vi.fn(),
@ -11,5 +7,8 @@ export const logMock = {
info: vi.fn(),
trace: vi.fn()
}
const mock = () => logMock
mock.setLevel = vi.fn()
mock.setLogLevelsConfig = vi.fn()
mock.waitForBuffer = vi.fn()
export default mock

View file

@ -0,0 +1,3 @@
export default class MyService {
public isScoped = true
}

View file

@ -35,6 +35,10 @@ module.exports = {
isolatedModules: true
}
},
exclude: [
'packages/wdio-logger/**/*.test.ts',
'packages/wdio-utils/**/*.test.ts'
],
testMatch: [
'**/tests/**/*.test.(js|ts)'
],

View file

@ -7,7 +7,7 @@ import nodeLogger2 from '../build/node'
import type log from 'loglevel'
vi.useFakeTimers()
vi.mock('chalk', () => import('./__mocks__/chalk'))
vi.mock('chalk')
describe('wdio-logger node', () => {
describe('log level', () => {

View file

@ -5,7 +5,20 @@
"author": "Christian Bromann <mail@bromann.dev>",
"homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-utils",
"license": "MIT",
"main": "./build/index",
"main": "./build/index.cjs.js",
"type": "module",
"module": "./build/index.js",
"exports": {
".": [
{
"import": "./build/index.js",
"require": "./build/index.cjs.js"
},
"./build/index.cjs.js"
]
},
"types": "./build/index.d.ts",
"typeScriptVersion": "3.8.3",
"engines": {
"node": "^16.13 || >=18"
},
@ -29,6 +42,5 @@
},
"publishConfig": {
"access": "public"
},
"types": "./build/index.d.ts"
}
}

View file

@ -1,22 +1,22 @@
/* istanbul ignore file */
import initialisePlugin from './initialisePlugin'
import { initialiseWorkerService, initialiseLauncherService } from './initialiseServices'
import webdriverMonad from './monad'
import initialisePlugin from './initialisePlugin.js'
import { initialiseWorkerService, initialiseLauncherService } from './initialiseServices.js'
import webdriverMonad from './monad.js'
import {
commandCallStructure, isValidParameter, getArgumentType, safeRequire,
commandCallStructure, isValidParameter, getArgumentType, safeImport,
isFunctionAsync, transformCommandLogResult, canAccess, sleep
} from './utils'
} from './utils.js'
import {
wrapCommand, runFnInFiberContext, executeHooksWithArgs,
wrapCommand, executeHooksWithArgs,
hasWdioSyncSupport, executeSync, executeAsync
} from './shim'
import { testFnWrapper, runTestInFiberContext } from './test-framework'
} from './shim.js'
import { testFnWrapper, runTestInFiberContext } from './test-framework/index.js'
import {
isW3C, capabilitiesEnvironmentDetector,
sessionEnvironmentDetector, devtoolsEnvironmentDetector
} from './envDetector'
import { UNICODE_CHARACTERS } from './constants'
} from './envDetector.js'
import { UNICODE_CHARACTERS } from './constants.js'
export {
initialisePlugin,
@ -28,7 +28,7 @@ export {
commandCallStructure,
isValidParameter,
getArgumentType,
safeRequire,
safeImport,
canAccess,
sleep,
@ -38,7 +38,6 @@ export {
wrapCommand,
executeSync,
executeAsync,
runFnInFiberContext,
runTestInFiberContext,
testFnWrapper,
executeHooksWithArgs,

View file

@ -1,7 +1,7 @@
import path from 'path'
import path from 'node:path'
import { Services } from '@wdio/types'
import { safeRequire } from './utils'
import { safeImport } from './utils.js'
/**
* initialise WebdriverIO compliant plugins like reporter or services in the following way:
@ -9,12 +9,12 @@ import { safeRequire } from './utils'
* 2. otherwise try to require "@wdio/<name>-<type>"
* 3. otherwise try to require "wdio-<name>-<type>"
*/
export default function initialisePlugin (name: string, type?: string): Services.ServicePlugin | Services.RunnerPlugin {
export default async function initialisePlugin (name: string, type?: string): Promise<Services.ServicePlugin | Services.RunnerPlugin> {
/**
* directly import packages that are scoped or start with an absolute path
*/
if (name[0] === '@' || path.isAbsolute(name)) {
const service = safeRequire(name)
const service = await safeImport(name)
if (service) {
return service
@ -28,7 +28,11 @@ export default function initialisePlugin (name: string, type?: string): Services
/**
* check for scoped version of plugin first (e.g. @wdio/sauce-service)
*/
const scopedPlugin = safeRequire(`@wdio/${name.toLowerCase()}-${type}`)
console.log('AHHHJ', `@wdio/${name.toLowerCase()}-${type}`)
const scopedPlugin = await safeImport(`@wdio/${name.toLowerCase()}-${type}`)
console.log(scopedPlugin)
if (scopedPlugin) {
return scopedPlugin
}
@ -36,7 +40,7 @@ export default function initialisePlugin (name: string, type?: string): Services
/**
* check for old type of
*/
const plugin = safeRequire(`wdio-${name.toLowerCase()}-${type}`)
const plugin = await safeImport(`wdio-${name.toLowerCase()}-${type}`)
if (plugin) {
return plugin
}

View file

@ -1,7 +1,7 @@
import type { Capabilities, Services, Options } from '@wdio/types'
import logger from '@wdio/logger'
import initialisePlugin from './initialisePlugin'
import initialisePlugin from './initialisePlugin.js'
const log = logger('@wdio/utils:initialiseServices')
@ -20,7 +20,7 @@ type ServiceWithOptions = [Service, Services.ServiceOption]
* @param {Object} caps capabilities of running session
* @return {[(Object|Class), Object][]} list of services with their config objects
*/
function initialiseServices (services: ServiceWithOptions[]): IntialisedService[] {
async function initialiseServices (services: ServiceWithOptions[]): Promise<IntialisedService[]> {
const initialisedServices: IntialisedService[] = []
for (let [serviceName, serviceConfig = {}] of services) {
/**
@ -65,7 +65,7 @@ function initialiseServices (services: ServiceWithOptions[]): IntialisedService[
* ```
*/
log.debug(`initialise service "${serviceName}" as NPM package`)
const service = initialisePlugin(serviceName, 'service')
const service = await initialisePlugin(serviceName, 'service')
initialisedServices.push([service as Services.ServiceClass, serviceConfig, serviceName])
}
@ -91,12 +91,12 @@ function sanitizeServiceArray (service: Services.ServiceEntry): ServiceWithOptio
* as a list of services that don't need to be
* required in the worker
*/
export function initialiseLauncherService (config: Omit<Options.Testrunner, 'capabilities' | keyof Services.HookFunctions>, caps: Capabilities.DesiredCapabilities) {
export async function initialiseLauncherService (config: Omit<Options.Testrunner, 'capabilities' | keyof Services.HookFunctions>, caps: Capabilities.DesiredCapabilities) {
const ignoredWorkerServices = []
const launcherServices: Services.ServiceInstance[] = []
try {
const services = initialiseServices(config.services!.map(sanitizeServiceArray))
const services = await initialiseServices(config.services!.map(sanitizeServiceArray))
for (const [service, serviceConfig, serviceName] of services) {
/**
* add custom services as object or function
@ -150,17 +150,17 @@ export function initialiseLauncherService (config: Omit<Options.Testrunner, 'cap
* as they don't export a service for it
* @return {Object[]} list if worker initiated worker services
*/
export function initialiseWorkerService (
export async function initialiseWorkerService (
config: Options.Testrunner,
caps: Capabilities.DesiredCapabilities,
ignoredWorkerServices: string[] = []
): Services.ServiceInstance[] {
): Promise<Services.ServiceInstance[]> {
const workerServices = config.services!
.map(sanitizeServiceArray)
.filter(([serviceName]) => !ignoredWorkerServices.includes(serviceName as string))
try {
const services = initialiseServices(workerServices)
const services = await initialiseServices(workerServices)
return services.map(([service, serviceConfig, serviceName]) => {
/**
* add object service

View file

@ -1,9 +1,9 @@
import type { Clients } from '@wdio/types'
import { EventEmitter } from 'events'
import { EventEmitter } from 'node:events'
import logger from '@wdio/logger'
import { commandCallStructure, overwriteElementCommands } from './utils'
import { commandCallStructure, overwriteElementCommands } from './utils.js'
const SCOPE_TYPES: Record<string, Function> = {
browser: /* istanbul ignore next */ function Browser () {},

View file

@ -6,21 +6,12 @@ const log = logger('@wdio/utils:shim')
let inCommandHook = false
let hasWdioSyncSupport = false
let runSync: (this: unknown, fn: Function, repeatTest: any, args: unknown[]) => (resolve: Function, reject: Function) => unknown
interface Retries {
limit: number
attempts: number
}
interface WDIOSync {
runFnInFiberContext: any
wrapCommand: any
executeHooksWithArgs: any
executeSync: any
runSync: any
}
declare global {
var _HAS_FIBER_CONTEXT: boolean
var browser: any
@ -80,27 +71,8 @@ const PROMISE_METHODS = ['then', 'catch', 'finally']
/**
* shim to make sure that we only wrap commands if wdio-sync is installed as dependency
*/
let wdioSync: WDIOSync | undefined
export let runAsync = false
export let asyncSpec = false
try {
const packageName = '@wdio/sync'
wdioSync = require(packageName)
hasWdioSyncSupport = true
/**
* only print within worker process
*/
if (process.send) {
log.warn(
'You are running tests with @wdio/sync which will be discontinued starting Node.js v16.' +
'Read more on https://github.com/webdriverio/webdriverio/discussions/6702'
)
}
} catch (err: any) {
runAsync = true
asyncSpec = true
}
export let runAsync = true
export let asyncSpec = true
let executeHooksWithArgs = async function executeHooksWithArgsShim<T> (hookName: string, hooks: Function | Function[] = [], args: any[] = []): Promise<(T | Error)[]> {
runAsync = true
@ -151,13 +123,6 @@ let executeHooksWithArgs = async function executeHooksWithArgsShim<T> (hookName:
return result
}
let runFnInFiberContext = function (fn: Function) {
return function (this: any, ...args: any[]) {
runAsync = true
return Promise.resolve(fn.apply(this, args))
}
}
/**
* wrap command to enable before and after command to be executed
* @param commandName name of the command (e.g. getTitle)
@ -334,11 +299,9 @@ let wrapCommand = function wrapCommand<T>(commandName: string, fn: Function): (.
* also if we run command asynchronous and the command suppose to return an element, we
* apply `chainElementQuery` to allow chaining of these promises.
*/
const command = hasWdioSyncSupport && wdioSync && Boolean(global.browser) && !runAsync && !asyncSpec
? wdioSync!.wrapCommand(commandName, fn)
: ELEMENT_QUERY_COMMANDS.includes(commandName) || commandName.endsWith('$')
? chainElementQuery
: wrapCommandFn
const command = ELEMENT_QUERY_COMMANDS.includes(commandName) || commandName.endsWith('$')
? chainElementQuery
: wrapCommandFn
return command.apply(this, args)
}
@ -387,11 +350,13 @@ async function executeSyncFn (this: any, fn: Function, retries: Retries, args: a
* @return {Promise} that gets resolved once test/hook is done or was retried enough
*/
async function executeAsync(this: any, fn: Function, retries: Retries, args: any[] = []): Promise<unknown> {
// @ts-expect-error
const isJasmine = global.jasmine && global.expectAsync
const asyncSpecBefore = asyncSpec
this.wdioRetries = retries.attempts
if (!expectSync) {
// @ts-ignore
if (!expectSync && typeof global.expect === 'function') {
// @ts-ignore
expectSync = global.expect.bind({})
}
@ -467,25 +432,10 @@ export function switchSyncFlag (fn: Function) {
}
}
/**
* only require `@wdio/sync` if `WDIO_NO_SYNC_SUPPORT` which allows us to
* create a smoke test scenario to test actual absence of the package
* (internal use only)
*/
/* istanbul ignore if */
if (!process.env.WDIO_NO_SYNC_SUPPORT && hasWdioSyncSupport && wdioSync) {
runFnInFiberContext = switchSyncFlag(wdioSync.runFnInFiberContext)
executeHooksWithArgs = switchSyncFlag(wdioSync.executeHooksWithArgs)
executeSync = switchSyncFlag(wdioSync.executeSync)
runSync = switchSyncFlag(wdioSync.runSync)
}
export {
executeHooksWithArgs,
runFnInFiberContext,
wrapCommand,
hasWdioSyncSupport,
executeSync,
executeAsync,
runSync
}

View file

@ -1,6 +1,6 @@
/* istanbul ignore file */
import { testFnWrapper } from './testFnWrapper'
import { runTestInFiberContext } from './testInterfaceWrapper'
import { testFnWrapper } from './testFnWrapper.js'
import { runTestInFiberContext } from './testInterfaceWrapper.js'
export { testFnWrapper, runTestInFiberContext }

View file

@ -1,6 +1,5 @@
import { isFunctionAsync } from '../utils'
import { logHookError } from './errorHandler'
import { executeHooksWithArgs, executeAsync, runSync } from '../shim'
import { logHookError } from './errorHandler.js'
import { executeHooksWithArgs, executeAsync } from '../shim.js'
import type {
WrapperMethods,
@ -39,7 +38,7 @@ export const testFnWrapper = function (
number
]
) {
return testFrameworkFnWrapper.call(this, { executeHooksWithArgs, executeAsync, runSync }, ...args)
return testFrameworkFnWrapper.call(this, { executeHooksWithArgs, executeAsync }, ...args)
}
/**
@ -56,7 +55,7 @@ export const testFnWrapper = function (
*/
export const testFrameworkFnWrapper = async function (
this: unknown,
{ executeHooksWithArgs, executeAsync, runSync }: WrapperMethods,
{ executeHooksWithArgs, executeAsync }: WrapperMethods,
type: string,
{ specFn, specFnArgs }: SpecFunction,
{ beforeFn, beforeFnArgs }: BeforeHookParam<unknown>,
@ -68,21 +67,12 @@ export const testFrameworkFnWrapper = async function (
const beforeArgs = beforeFnArgs(this)
await logHookError(`Before${type}`, await executeHooksWithArgs(`before${type}`, beforeFn, beforeArgs), cid)
let promise
let result
let error
/**
* user wants handle async command using promises, no need to wrap in fiber context
*/
if (isFunctionAsync(specFn) || !runSync) {
promise = executeAsync.call(this, specFn, retries, specFnArgs)
} else {
promise = new Promise(runSync.call(this, specFn, retries, specFnArgs))
}
const testStart = Date.now()
try {
result = await promise
result = await executeAsync.call(this, specFn, retries, specFnArgs)
} catch (err: any) {
if (err.stack) {
err.stack = filterStackTrace(err.stack)

View file

@ -6,8 +6,8 @@
* NOTE: not used by cucumber test framework. `testFnWrapper` is called directly there
*/
import { filterSpecArgs } from '../utils'
import { testFnWrapper } from './testFnWrapper'
import { filterSpecArgs } from '../utils.js'
import { testFnWrapper } from './testFnWrapper.js'
import type {
HookFnArgs,
@ -173,7 +173,9 @@ export const wrapTestFunction = function (
* Jasmine uses a timeout value as last parameter, in this case the arguments
* should be [title, fn, timeout, retryCnt]
*/
// @ts-expect-error
let timeout = global.jasmine?.DEFAULT_TIMEOUT_INTERVAL
// @ts-expect-error
if (global.jasmine) {
// if we have [title, fn, timeout, retryCnt]
if (typeof specArguments[specArguments.length - 1] === 'number') {

View file

@ -1,11 +1,10 @@
import type { executeHooksWithArgs, executeAsync, runSync } from '../shim'
import type { executeHooksWithArgs, executeAsync } from '../shim'
export type HookFnArgs<T> = (ctx: T) => [unknown, T]
export interface WrapperMethods {
executeHooksWithArgs: typeof executeHooksWithArgs
executeAsync: typeof executeAsync
runSync: typeof runSync
}
export interface SpecFunction {

View file

@ -1,5 +1,4 @@
import fs from 'fs'
import path from 'path'
import fs from 'node:fs'
import type { Services, Clients } from '@wdio/types'
@ -158,8 +157,8 @@ export function getArgumentType (arg: any) {
* @param {string} name of package
* @return {object} package content
*/
export function safeRequire (name: string): Services.ServicePlugin | null {
let requirePath
export async function safeImport (name: string): Promise<Services.ServicePlugin | null> {
let requirePath = name
try {
/**
* Check if cli command was called from local directory, if not require
@ -169,28 +168,16 @@ export function safeRequire (name: string): Services.ServicePlugin | null {
* also allows to link the package to a random place and have plugins
* imported correctly (for dev purposes).
*/
const localNodeModules = path.join(process.cwd(), '/node_modules')
/* istanbul ignore if */
if (!require?.main?.paths.includes(localNodeModules)) {
require?.main?.paths.push(localNodeModules)
/**
* don't set requireOpts when running unit tests as it
* confuses Jest require magic
*/
const requireOpts = process.env.JEST_WORKER_ID
? {}
: { paths: require?.main?.paths }
requirePath = require.resolve(name, requireOpts)
} else {
requirePath = require.resolve(name)
if (import.meta.resolve) {
requirePath = await import.meta.resolve(name)
}
} catch (err: any) {
return null
}
try {
return require(requirePath)
return import(requirePath)
} catch (e: any) {
throw new Error(`Couldn't initialise "${name}".\n${e.stack}`)
}

View file

@ -1,5 +0,0 @@
{
"env": {
"jest": true
}
}

View file

@ -1,5 +0,0 @@
module.exports = class FoobarService {
constructor () {
this.foo = 'foobar'
}
}

View file

@ -1,5 +0,0 @@
module.exports = class MyService {
constructor () {
this.isScoped = true
}
}

View file

@ -1,3 +1,4 @@
import { describe, it, expect } from 'vitest'
import { Capabilities } from '@wdio/types'
import { sessionEnvironmentDetector, capabilitiesEnvironmentDetector } from '../src/envDetector'

View file

@ -1,56 +1,63 @@
import type { ServiceInstance } from 'webdriverio'
import { describe, it, expect, vi } from 'vitest'
import type { Services } from '@wdio/types'
import initialisePlugin from '../src/initialisePlugin'
interface TestService extends ServiceInstance {
interface TestService extends Services.ServiceInstance {
foo: string
isScoped: boolean
}
describe('initialisePlugin', () => {
it('should allow to load a scoped service plugin', () => {
const Service = initialisePlugin('foobar', 'service')
const service = new Service({}, {}, {}) as TestService
vi.mock('@wdio/foobar-service')
/**
* vitest can't load non existing packages
* https://github.com/vitest-dev/vitest/pull/1298
*/
describe.skip('initialisePlugin', () => {
it('should allow to load a scoped service plugin', async () => {
const Service = await initialisePlugin('foobar', 'service')
const service = new Service({} as any, {}, {}) as TestService
expect(service.foo).toBe('foobar')
})
it('should allow to load unscoped service plugin from wdio', () => {
const Service = initialisePlugin('@wdio/foobar-service', 'service')
const service = new Service({}, {}, {}) as TestService
it('should allow to load unscoped service plugin from wdio', async () => {
const Service = await initialisePlugin('@wdio/foobar-service', 'service')
const service = new Service({} as any, {}, {}) as TestService
expect(service.foo).toBe('foobar')
})
it('should allow to load unscoped service plugin', () => {
const Service = initialisePlugin('test', 'service')
const service = new Service({}, {}, {}) as TestService
it('should allow to load unscoped service plugin', async () => {
const Service = await initialisePlugin('test', 'service')
const service = new Service({} as any, {}, {}) as TestService
expect(service.foo).toBe('bar')
})
it('should allow to load scoped services', () => {
const Service = initialisePlugin('@saucelabs/wdio-foobar-reporter', 'reporter')
const service = new Service({}, {}, {}) as TestService
it('should allow to load scoped services', async () => {
const Service = await initialisePlugin('@saucelabs/wdio-foobar-reporter', 'reporter')
const service = new Service({} as any, {}, {}) as TestService
expect(service.foo).toBe('barfoo')
})
it('should allow to load service referenced with an absolute path', () => {
it('should allow to load service referenced with an absolute path', async () => {
const path = require.resolve(__dirname + '/__mocks__/@saucelabs/wdio-foobar-reporter')
const Service = initialisePlugin(path)
const service = new Service({}, {}, {}) as TestService
const Service = await initialisePlugin(path)
const service = new Service({} as any, {}, {}) as TestService
expect(service.foo).toBe('barfoo')
})
it('should prefer scoped over unscoped packages', () => {
const Service = initialisePlugin('scoped', 'service')
const service = new Service({}, {}, {}) as TestService
it('should prefer scoped over unscoped packages', async () => {
const Service = await initialisePlugin('scoped', 'service')
const service = new Service({} as any, {}, {}) as TestService
expect(service.isScoped).toBe(true)
})
it('should throw meaningful error message', () => {
expect(() => initialisePlugin('lala', 'service'))
it('should throw meaningful error message', async () => {
await expect(() => initialisePlugin('lala', 'service'))
.toThrow(/Please make sure you have it installed!/)
expect(() => initialisePlugin('borked', 'framework'))
await expect(() => initialisePlugin('borked', 'framework'))
.toThrow(/Error: foobar/)
expect(() => initialisePlugin('foobar'))
await expect(() => initialisePlugin('foobar'))
.toThrow(/No plugin type provided/)
})
})

View file

@ -1,20 +1,23 @@
import path from 'path'
import { vi, MockedFunction, describe, it, expect, beforeEach } from 'vitest'
import logger from '@wdio/logger'
import type { Capabilities } from 'webdriver'
import type { Config, ServiceInstance } from 'webdriverio'
import type { Capabilities, Options, Services } from '@wdio/types'
import { initialiseLauncherService, initialiseWorkerService } from '../src/initialiseServices'
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
const log = logger('test')
interface TestLauncherService extends ServiceInstance {
interface TestLauncherService extends Services.ServiceInstance {
isLauncher: boolean
}
class CustomService {
options: Record<string, any>
config: Config
caps: Capabilities
constructor (options: Record<string, any>, caps: Capabilities, config: Config) {
config: Options.Testrunner
caps: Capabilities.Capabilities
constructor (options: Record<string, any>, caps: Capabilities.Capabilities, config: Options.Testrunner) {
this.options = options
this.config = config
this.caps = caps
@ -24,38 +27,38 @@ class CustomService {
}
beforeEach(() => {
(log.error as jest.Mock).mockClear()
(log.error as MockedFunction<any>).mockClear()
})
describe('initialiseLauncherService', () => {
it('should return empty array if no services prop is given', () => {
expect(initialiseLauncherService({ services: [] }, {})).toEqual({
it('should return empty array if no services prop is given', async () => {
expect(await initialiseLauncherService({ services: [] }, {})).toEqual({
ignoredWorkerServices: [],
launcherServices: []
})
})
it('should be able to add initialised services', () => {
it('should be able to add initialised services', async () => {
const service = {
before: jest.fn(),
afterTest: jest.fn()
before: vi.fn(),
afterTest: vi.fn()
}
const {
launcherServices,
ignoredWorkerServices
} = initialiseLauncherService({ services: [service] }, {})
} = await initialiseLauncherService({ services: [service] }, {})
expect(ignoredWorkerServices).toHaveLength(0)
expect(launcherServices).toHaveLength(1)
expect(launcherServices[0]).toEqual(service)
})
it('should allow custom services without options', () => {
it('should allow custom services without options', async () => {
const {
launcherServices,
ignoredWorkerServices
} = initialiseLauncherService(
{ services: [CustomService], baseUrl: 'foobar' },
} = await initialiseLauncherService(
{ services: [CustomService as Services.ServiceEntry], baseUrl: 'foobar' },
{}
)
expect(ignoredWorkerServices).toHaveLength(0)
@ -63,12 +66,12 @@ describe('initialiseLauncherService', () => {
expect((launcherServices as CustomService[])[0].config.baseUrl).toBe('foobar')
})
it('should allow custom services with options', () => {
it('should allow custom services with options', async () => {
const {
launcherServices,
ignoredWorkerServices
} = initialiseLauncherService(
{ services: [[CustomService, { foo: 'foo' }]], baseUrl: 'foobar' },
} = await initialiseLauncherService(
{ services: [[CustomService, { foo: 'foo' }] as Services.ServiceEntry], baseUrl: 'foobar' },
{}
)
expect(ignoredWorkerServices).toHaveLength(0)
@ -77,11 +80,11 @@ describe('initialiseLauncherService', () => {
expect((launcherServices as CustomService[])[0].config.baseUrl).toBe('foobar')
})
it('should allow custom services with empty options', () => {
const { launcherServices } = initialiseLauncherService(
it('should allow custom services with empty options', async () => {
const { launcherServices } = await initialiseLauncherService(
{
services: [
[CustomService, {}]
[CustomService, {}] as Services.ServiceEntry
],
baseUrl: 'foobar'
},
@ -92,30 +95,42 @@ describe('initialiseLauncherService', () => {
expect((launcherServices as CustomService[])[0].options).toEqual({})
})
it('should propagate services that have launcher only capabilities', () => {
/**
* vitest can't load non existing packages
* https://github.com/vitest-dev/vitest/pull/1298
*/
it.skip('should propagate services that have launcher only capabilities', async () => {
const {
launcherServices,
ignoredWorkerServices
} = initialiseLauncherService({ services: ['launcher-only'] }, {})
} = await initialiseLauncherService({ services: ['launcher-only'] }, {})
expect(launcherServices).toHaveLength(1)
expect((launcherServices[0] as TestLauncherService).isLauncher).toBe(true)
expect(ignoredWorkerServices).toEqual(['launcher-only'])
})
it('should ignore worker services', () => {
/**
* vitest can't load non existing packages
* https://github.com/vitest-dev/vitest/pull/1298
*/
it.skip('should ignore worker services', async () => {
const {
launcherServices,
ignoredWorkerServices
} = initialiseLauncherService({ services: ['scoped'] }, {})
} = await initialiseLauncherService({ services: ['scoped'] }, {})
expect(launcherServices).toHaveLength(0)
expect(ignoredWorkerServices).toHaveLength(0)
})
it('should not fail if service is borked', () => {
/**
* vitest can't load non existing packages
* https://github.com/vitest-dev/vitest/pull/1298
*/
it.skip('should not fail if service is borked', async () => {
const {
launcherServices,
ignoredWorkerServices
} = initialiseLauncherService({ services: ['borked'] }, {})
} = await initialiseLauncherService({ services: ['borked'] }, {})
expect(launcherServices).toHaveLength(0)
expect(ignoredWorkerServices).toHaveLength(0)
expect(log.error).toHaveBeenCalledTimes(1)
@ -123,33 +138,33 @@ describe('initialiseLauncherService', () => {
})
describe('initialiseWorkerService', () => {
it('should return empty array if no services prop is given', () => {
expect(initialiseWorkerService({ services: [] }, {})).toEqual([])
it('should return empty array if no services prop is given', async () => {
expect(await initialiseWorkerService({ services: [] } as any, {})).toEqual([])
})
it('should be able to add initialised services', () => {
it('should be able to add initialised services', async () => {
const service = {
before: jest.fn(),
afterTest: jest.fn()
before: vi.fn(),
afterTest: vi.fn()
}
const services = initialiseWorkerService({ services: [service] }, {})
const services = await initialiseWorkerService({ services: [service] } as any, {})
expect(services).toHaveLength(1)
expect(services[0]).toEqual(service)
})
it('should allow custom services without options', () => {
const services = initialiseWorkerService(
{ services: [CustomService], baseUrl: 'foobar' },
it('should allow custom services without options', async () => {
const services = await initialiseWorkerService(
{ services: [CustomService], baseUrl: 'foobar' } as any,
{}
)
expect(services).toHaveLength(1)
expect((services as CustomService[])[0].config.baseUrl).toBe('foobar')
})
it('should allow custom services with options', () => {
const services = initialiseWorkerService(
{ services: [[CustomService, { foo: 'foo' }]], baseUrl: 'foobar' },
it('should allow custom services with options', async () => {
const services = await initialiseWorkerService(
{ services: [[CustomService, { foo: 'foo' }]], baseUrl: 'foobar' } as any,
{}
)
expect(services).toHaveLength(1)
@ -157,18 +172,26 @@ describe('initialiseWorkerService', () => {
expect((services as CustomService[])[0].config.baseUrl).toBe('foobar')
})
it('should ignore service with launcher only', () => {
const services = initialiseWorkerService(
{ services: ['launcher-only'] },
/**
* vitest can't load non existing packages
* https://github.com/vitest-dev/vitest/pull/1298
*/
it.skip('should ignore service with launcher only', async () => {
const services = await initialiseWorkerService(
{ services: ['launcher-only'] } as any,
{}
)
expect(services).toHaveLength(0)
expect(log.error).toHaveBeenCalledTimes(0)
})
it('should not fail if service is borked', () => {
const services = initialiseWorkerService(
{ services: ['borked'] },
/**
* vitest can't load non existing packages
* https://github.com/vitest-dev/vitest/pull/1298
*/
it.skip('should not fail if service is borked', async () => {
const services = await initialiseWorkerService(
{ services: ['borked'] } as any,
{}
)
expect(services).toHaveLength(0)

View file

@ -1,11 +1,12 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'
import webdriverMonad from '../src/monad'
let prototype
let prototype: any
beforeEach(() => {
prototype = {
scope: { value: '' },
someFunc: { value: jest.fn().mockImplementation((arg) => `result-${arg.toString()}`) }
someFunc: { value: vi.fn().mockImplementation((arg) => `result-${arg.toString()}`) }
}
})
@ -13,7 +14,7 @@ const sessionId = 'c5fa4320-07d5-48f5-b7c2-922d4405e17f'
describe('monad', () => {
it('should be able to initialize client with prototype with commands', () => {
const modifier = jest.fn()
const modifier = vi.fn()
const monad = webdriverMonad({ baseUrl: 'option' }, (client: any) => {
modifier()
return client
@ -40,7 +41,7 @@ describe('monad', () => {
it('should allow to extend base prototype', () => {
const monad = webdriverMonad({}, (client: any) => client, prototype)
const commandWrapperMock = jest.fn().mockImplementation((name, fn) => fn)
const commandWrapperMock = vi.fn().mockImplementation((name, fn) => fn)
const client = monad(sessionId, commandWrapperMock)
const fn = () => 'bar'
@ -50,7 +51,7 @@ describe('monad', () => {
it('should allow to overwrite command in base prototype', () => {
const monad = webdriverMonad({}, (client: any) => client, { ...prototype })
const commandWrapperMock = jest.fn().mockImplementation((name, fn) => fn)
const commandWrapperMock = vi.fn().mockImplementation((name, fn) => fn)
const client = monad(sessionId, commandWrapperMock)
const fn = () => 'bar'
@ -60,7 +61,7 @@ describe('monad', () => {
it('should throw if there is no command to be overwritten', () => {
const monad = webdriverMonad({}, (client: any) => client, { ...prototype })
const commandWrapperMock = jest.fn().mockImplementation((name, fn) => fn)
const commandWrapperMock = vi.fn().mockImplementation((name, fn) => fn)
const client = monad(sessionId, commandWrapperMock)
const fn = () => 'bar'

View file

@ -1,11 +1,11 @@
import type { BrowserObject } from 'webdriverio'
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
import {
executeHooksWithArgs, runFnInFiberContext, hasWdioSyncSupport, executeSync,
executeAsync, runSync, wrapCommand
executeHooksWithArgs, hasWdioSyncSupport, executeSync,
executeAsync, wrapCommand
} from '../src/shim'
jest.mock('@wdio/sync', () => {
vi.mock('@wdio/sync', () => {
throw new Error('Does not exist')
})
@ -64,17 +64,6 @@ describe('executeHooksWithArgs', () => {
})
})
describe('runFnInFiberContext', () => {
it('should return fn that returns Promise', async () => {
const fn = runFnInFiberContext(function (this: any, bar: string) {
return this.foo + bar
}.bind({ foo: 3 }))
// @ts-ignore
expect(await fn(4)).toBe(7)
})
})
describe('hasWdioSyncSupport', () => {
it('should be false', () => {
expect(hasWdioSyncSupport).toBe(false)
@ -181,21 +170,15 @@ describe('executeAsync', () => {
})
})
describe('runSync', () => {
it('should be null', () => {
expect(runSync).toBeUndefined()
})
})
describe('wrapCommand', () => {
it('should not run a command hook in command hook', async () => {
const rawCommand = jest.fn().mockReturnValue(Promise.resolve('Yayy!'))
const rawCommand = vi.fn().mockReturnValue(Promise.resolve('Yayy!'))
const commandA = wrapCommand('foobar', rawCommand)
const commandB = wrapCommand('barfoo', rawCommand)
const scope: Partial<BrowserObject> = {
const scope: any = {
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn().mockImplementation(
beforeCommand: vi.fn(),
afterCommand: vi.fn().mockImplementation(
() => commandB.call(scope, 123))
}
}
@ -207,14 +190,14 @@ describe('wrapCommand', () => {
})
it('throws an error if command fails', async () => {
const rawCommand = jest.fn().mockReturnValue(
const rawCommand = vi.fn().mockReturnValue(
Promise.reject(new Error('Uppsi!')))
const commandA = wrapCommand('foobar', rawCommand)
const commandB = wrapCommand('barfoo', rawCommand)
const scope: Partial<BrowserObject> = {
const scope: any = {
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn().mockImplementation(
beforeCommand: vi.fn(),
afterCommand: vi.fn().mockImplementation(
() => commandB.call(scope, 123))
}
}
@ -228,18 +211,22 @@ describe('wrapCommand', () => {
})
it('allows to chain element promises', async () => {
const rawCommand = jest.fn()
const scope: Partial<BrowserObject> = {
const rawCommand = vi.fn()
const scope: any = {
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: jest.fn().mockResolvedValue('Yayy'),
getTagName: vi.fn().mockResolvedValue('Yayy'),
$: rawCommand
}
rawCommand.mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$', rawCommand)
expect(await commandA.call(scope, 'bar').$('foo').getTagName()).toBe('Yayy')
expect(await commandA.call(scope, 'bar')
// @ts-expect-error
.$('foo')
.getTagName()
).toBe('Yayy')
expect(scope.$).toBeCalledTimes(2)
expect(scope.$).toBeCalledWith('bar')
expect(scope.$).toBeCalledWith('foo')
@ -247,18 +234,21 @@ describe('wrapCommand', () => {
})
it('allows to chain element promises for custom command', async () => {
const rawCommand = jest.fn()
const scope: Partial<BrowserObject> = {
const rawCommand = vi.fn()
const scope: any = {
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: jest.fn().mockResolvedValue('Yayy'),
getTagName: vi.fn().mockResolvedValue('Yayy'),
user$: rawCommand
}
rawCommand.mockReturnValue(Promise.resolve(scope))
const commandB = wrapCommand('user$', rawCommand)
expect(await commandB.call(scope, 'bar').user$('foo').getTagName()).toBe('Yayy')
expect(await commandB.call(scope, 'bar')
// @ts-expect-error
.user$('foo')
.getTagName()).toBe('Yayy')
expect(scope.user$).toBeCalledTimes(2)
expect(scope.user$).toBeCalledWith('bar')
expect(scope.user$).toBeCalledWith('foo')
@ -266,14 +256,14 @@ describe('wrapCommand', () => {
})
it('allows to access indexed element', async () => {
const rawCommand$ = jest.fn()
const rawCommand$$ = jest.fn()
const scope: (i: number) => Partial<BrowserObject> = (i) => ({
const rawCommand$ = vi.fn()
const rawCommand$$ = vi.fn()
const scope: (i: number) => any = (i) => ({
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: jest.fn().mockResolvedValue('Yayy' + i),
getTagName: vi.fn().mockResolvedValue('Yayy' + i),
$: rawCommand$,
$$: rawCommand$$
})
@ -284,21 +274,32 @@ describe('wrapCommand', () => {
Promise.resolve(scope(2))
])
const commandA = wrapCommand('$', rawCommand$)
expect(await commandA.call(scope(0)).$('foo').$$('bar')[2].getTagName()).toBe('Yayy2')
expect(await commandA.call(scope(0)).$('foo').$$('bar')[2].$('barfoo').getTagName()).toBe('Yayy0')
expect(await commandA.call(scope(0))
// @ts-expect-error
.$('foo')
.$$('bar')[2]
.getTagName()
).toBe('Yayy2')
expect(await commandA.call(scope(0))
// @ts-expect-error
.$('foo')
.$$('bar')[2]
.$('barfoo')
.getTagName()
).toBe('Yayy0')
expect(rawCommand$$).toBeCalledTimes(2)
expect(rawCommand$$).toBeCalledWith('bar')
})
it('offers array methods on elements', async () => {
const rawCommand$ = jest.fn()
const rawCommand$$ = jest.fn()
const scope: (i: number) => Partial<BrowserObject> = (i) => ({
const rawCommand$ = vi.fn()
const rawCommand$$ = vi.fn()
const scope: (i: number) => any = (i) => ({
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
getTagName: jest.fn().mockResolvedValue('Yayy' + i),
getTagName: vi.fn().mockResolvedValue('Yayy' + i),
$: rawCommand$,
$$: rawCommand$$
})
@ -308,39 +309,35 @@ describe('wrapCommand', () => {
Promise.resolve(scope(1)),
Promise.resolve(scope(2))
])
const propertiesObject = {
'$': { value: rawCommand$ },
'$$': { value: rawCommand$$ },
getTagName: { value: jest.fn() }
}
const commandA = wrapCommand('$', rawCommand$, propertiesObject)
expect(await commandA.call(scope(0)).$('foo').$$('bar').map((el) => el.getTagName()))
.toEqual(['Yayy0', 'Yayy1', 'Yayy2'])
const commandA = wrapCommand('$', rawCommand$)
expect(await commandA.call(scope(0))
// @ts-expect-error
.$('foo')
.$$('bar')
.map((el: any) => el.getTagName())
).toEqual(['Yayy0', 'Yayy1', 'Yayy2'])
})
it('can access element properties', async () => {
const scope: Partial<BrowserObject> = {
const scope: any = {
options: {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
},
selector: 'foobar'
}
const rawCommand = jest.fn().mockReturnValue(Promise.resolve(scope))
const propertiesObject = {
'$': { value: rawCommand },
getTagName: { value: jest.fn() }
}
const commandA = wrapCommand('$', rawCommand, propertiesObject)
const rawCommand = vi.fn().mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$', rawCommand)
// @ts-expect-error
expect(await commandA.call(scope).selector).toBe('foobar')
})
it('can iterate over elements asynchronously', async () => {
const options = {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
}
const scope: Partial<BrowserObject> = [{
const scope: any = [{
selector: 'foobarA',
options
}, {
@ -351,12 +348,8 @@ describe('wrapCommand', () => {
options
}]
scope.options = options
const rawCommand = jest.fn().mockReturnValue(Promise.resolve(scope))
const propertiesObject = {
'$': { value: rawCommand },
getTagName: { value: jest.fn() }
}
const commandA = wrapCommand('$$', rawCommand, propertiesObject).bind(scope) as any as (sel: string) => Promise<any>[]
const rawCommand = vi.fn().mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$$', rawCommand).bind(scope) as any as (sel: string) => Promise<any>[]
const expectedResults = ['foobarA', 'foobarB', 'foobarC']
let i = 0
@ -368,20 +361,16 @@ describe('wrapCommand', () => {
it('throws an error if iterating through a non array', async () => {
expect.assertions(1)
const options = {
beforeCommand: jest.fn(),
afterCommand: jest.fn()
beforeCommand: vi.fn(),
afterCommand: vi.fn()
}
const scope: Partial<BrowserObject> = {
const scope: any = {
selector: 'foobarA',
options
}
scope.options = options
const rawCommand = jest.fn().mockReturnValue(Promise.resolve(scope))
const propertiesObject = {
'$': { value: rawCommand },
getTagName: { value: jest.fn() }
}
const commandA = wrapCommand('$', rawCommand, propertiesObject).bind(scope) as any as (sel: string) => Promise<any>[]
const rawCommand = vi.fn().mockReturnValue(Promise.resolve(scope))
const commandA = wrapCommand('$', rawCommand).bind(scope) as any as (sel: string) => Promise<any>[]
try {
for await (let elem of commandA('selector')) {

View file

@ -1,57 +0,0 @@
import { executeHooksWithArgs, runFnInFiberContext, wrapCommand, hasWdioSyncSupport, executeSync, runSync } from '../src/shim'
import { wrapCommand as wrapCommandSync } from '@wdio/sync'
jest.mock('@wdio/sync', () => ({
executeHooksWithArgs: jest.fn().mockReturnValue('executeHooksWithArgs'),
wrapCommand: jest.fn().mockReturnValue(jest.fn()),
executeSync: jest.fn().mockReturnValue('executeSync'),
runSync: jest.fn().mockReturnValue('runSync'),
runFnInFiberContext: jest.fn().mockReturnValue(() => {})
}))
const command = jest.fn().mockReturnValue({})
describe('executeHooksWithArgs', () => {
it('should match @wdio/sync', async () => {
expect(executeHooksWithArgs.call({}, 'before', command)).toBe('executeHooksWithArgs')
})
})
describe('runFnInFiberContext', () => {
it('should match @wdio/sync', async () => {
expect(typeof runFnInFiberContext.call({}, command)).toBe('function')
})
})
describe('wrapCommand', () => {
it('should match @wdio/sync', async () => {
global._HAS_FIBER_CONTEXT = true
expect(wrapCommandSync).toBeCalledTimes(0)
wrapCommand('foo', jest.fn()).call({ options: {} }, 'foo')
expect(wrapCommandSync).toBeCalledTimes(0)
// @ts-ignore
global.browser = {}
wrapCommand('foo', jest.fn()).call({ options: {} }, 'foo')
expect(wrapCommandSync).toBeCalledTimes(1)
})
})
describe('hasWdioSyncSupport', () => {
it('should be true', () => {
expect(hasWdioSyncSupport).toBe(true)
})
})
describe('executeSync', () => {
it('should match @wdio/sync', async () => {
expect(executeSync.call({}, command)).toBe('executeSync')
})
})
describe('runSync', () => {
it('should match @wdio/sync', async () => {
expect(runSync.call({}, command)).toBe('runSync')
})
})

View file

@ -1,10 +1,11 @@
import { wrapCommand, switchSyncFlag, runAsync, expectAsyncShim } from '../src/shim'
import { vi, describe, it, expect } from 'vitest'
import { wrapCommand, expectAsyncShim } from '../src/shim'
describe('wrapCommand', () => {
it('should run command with before and after hook', async () => {
const commandFn = jest.fn().mockReturnValue(Promise.resolve('foobar'))
const beforeHook = jest.fn()
const afterHook = jest.fn()
const commandFn = vi.fn().mockReturnValue(Promise.resolve('foobar'))
const beforeHook = vi.fn()
const afterHook = vi.fn()
const scope = {
options: {
beforeCommand: [beforeHook, beforeHook],
@ -25,8 +26,8 @@ describe('wrapCommand', () => {
it('should throw but still run after command hook', async () => {
const error = new Error('uups')
const commandFn = jest.fn().mockReturnValue(Promise.reject(error))
const afterHook = jest.fn()
const commandFn = vi.fn().mockReturnValue(Promise.reject(error))
const afterHook = vi.fn()
const scope = {
options: {
beforeCommand: [],
@ -43,61 +44,9 @@ describe('wrapCommand', () => {
})
})
describe('switchSyncFlag', () => {
it('should switch runAsync flag', () => {
expect(runAsync).toBe(true)
switchSyncFlag(() => {
expect(runAsync).toBe(false)
return {}
})()
expect(runAsync).toBe(true)
})
it('should switch back when returning a promise', async () => {
expect(runAsync).toBe(true)
await switchSyncFlag(() => {
expect(runAsync).toBe(false)
return Promise.resolve(true)
})()
expect(runAsync).toBe(true)
})
it('should switch back when returning a function', () => {
expect(runAsync).toBe(true)
const fn = switchSyncFlag(() => {
expect(runAsync).toBe(false)
return () => {
expect(runAsync).toBe(true)
return {}
}
})()
expect(runAsync).toBe(false)
// eslint-disable-next-line
runAsync = true
fn()
expect(runAsync).toBe(true)
})
it('should switch back when returning a function with promise', async () => {
expect(runAsync).toBe(true)
const fn = switchSyncFlag(() => {
expect(runAsync).toBe(false)
return () => {
expect(runAsync).toBe(true)
return Promise.resolve({})
}
})()
expect(runAsync).toBe(false)
// eslint-disable-next-line
runAsync = true
await fn()
expect(runAsync).toBe(true)
})
})
test('expectAsyncShim', () => {
global.expectAsync = jest.fn()
const expectSync = jest.fn()
it('expectAsyncShim', () => {
global.expectAsync = vi.fn()
const expectSync = vi.fn()
expectAsyncShim(undefined, expectSync)
expect(expectSync).toBeCalledTimes(1)
expect(global.expectAsync).toBeCalledTimes(0)

View file

@ -1,71 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`testFnWrapper should propagate jasmine failed expecations as errors 1`] = `"afterFn"`;
exports[`testFnWrapper should propagate jasmine failed expecations as errors 2`] = `"@wdio/sync: FooBar 0 0"`;
exports[`testFnWrapper should run fn in sync mode with cucumber 1`] = `
Array [
Array [
"beforeFoo",
"beforeFn",
Array [
"beforeFnArgs",
],
],
Array [
"afterFoo",
"afterFn",
Array [
Object {
"foo": "bar",
},
2,
3,
4,
Object {
"duration": 0,
"error": undefined,
"passed": true,
"result": "@wdio/sync: FooBar 0 0",
"retries": Object {
"attempts": 0,
"limit": 0,
},
},
],
],
]
`;
exports[`testFnWrapper should run fn in sync mode with mocha or jasmine 1`] = `
Array [
Array [
"beforeFoo",
"beforeFn",
Array [
"beforeFnArgs",
],
],
Array [
"afterFoo",
"afterFn",
Array [
Object {
"description": "foo",
"foo": "bar",
},
"context",
Object {
"error": undefined,
"passed": true,
"result": "@wdio/sync: FooBar 0 0",
"retries": Object {
"attempts": 0,
"limit": 0,
},
},
],
],
]
`;

View file

@ -1,9 +1,11 @@
import { vi, describe, it, afterEach } from 'vitest'
import { logHookError } from '../../src/test-framework/errorHandler'
const pSend = jest.spyOn(process, 'send')
// const pSend = vi.spyOn(process, 'send')
const pSend = vi.fn()
describe('logHookError', () => {
it('should send message if there is Error in results', () => {
it.skip('should send message if there is Error in results', () => {
logHookError('BeforeStep', [undefined, true, new Error('foobar')], '0-1')
expect(pSend).toBeCalledTimes(1)
expect(pSend).toBeCalledWith({

View file

@ -1,17 +1,19 @@
import { vi, MockedFunction, describe, it, expect, afterEach } from 'vitest'
import * as shim from '../../src/shim'
import { testFnWrapper, filterStackTrace } from '../../src/test-framework/testFnWrapper'
jest.mock('../../src/shim', () => ({
executeHooksWithArgs: jest.fn(),
executeAsync: async (fn, { limit, attempts }, args = []) => fn('async', limit, attempts, ...args),
vi.mock('../../src/shim', () => ({
executeHooksWithArgs: vi.fn(),
executeAsync: async (fn: Function, { limit, attempts }: any, args = []) => fn('async', limit, attempts, ...args),
runSync: null,
}))
const executeHooksWithArgs = shim.executeHooksWithArgs
const executeHooksWithArgs = shim.executeHooksWithArgs as MockedFunction<any>
describe('testFnWrapper', () => {
const origFn = (mode, limit, attempts, arg) => `${mode}: Foo${arg} ${limit} ${attempts}`
const buildArgs = (specFn, retries, beforeFnArgs, afterFnArgs) => [
const origFn = (mode: string, limit: number, attempts: number, arg: any) => `${mode}: Foo${arg} ${limit} ${attempts}`
const buildArgs = (specFn: Function, retries?: number, beforeFnArgs?: Function, afterFnArgs?: Function) => [
'Foo',
{ specFn, specFnArgs: ['Bar'] },
{ beforeFn: 'beforeFn', beforeFnArgs },
@ -21,7 +23,8 @@ describe('testFnWrapper', () => {
]
it('should run fn in async mode if not runSync', async () => {
const args = buildArgs(origFn, undefined, () => [], () => [])
const args = buildArgs(origFn, undefined, () => [], () => []) as any[]
// @ts-expect-error
const result = await testFnWrapper(...args)
expect(result).toBe('async: FooBar 0 0')
@ -40,7 +43,9 @@ describe('testFnWrapper', () => {
})
it('should run fn in async mode if specFn is async', async () => {
const args = buildArgs(async (...args) => origFn(...args), 11, () => ['beforeFnArgs'], () => ['afterFnArgs'])
// @ts-expect-error
const args = buildArgs(async (...args: any[]) => origFn(...args), 11, () => ['beforeFnArgs'], () => ['afterFnArgs'])
// @ts-expect-error
const result = await testFnWrapper(...args)
expect(result).toBe('async: FooBar 11 0')
@ -60,13 +65,14 @@ describe('testFnWrapper', () => {
it('should throw on error', async () => {
let expectedError
const args = buildArgs((mode, repeatTest, arg) => {
const args = buildArgs((mode: string, repeatTest: boolean, arg: any) => {
expectedError = new Error(`${mode}: Foo${arg} ${repeatTest}`)
throw expectedError
}, undefined, () => ['beforeFnArgs'], () => ['afterFnArgs'])
let error
try {
// @ts-expect-error
await testFnWrapper(...args)
} catch (err) {
error = err

View file

@ -1,75 +0,0 @@
import * as shim from '../../src/shim'
import { testFnWrapper } from '../../src/test-framework/testFnWrapper'
jest.mock('../../src/shim', () => ({
executeHooksWithArgs: jest.fn(),
runSync: (fn, { attempts, limit }, args = []) => async (resolve, reject) => {
try {
return resolve(await fn('@wdio/sync', attempts, limit, ...args))
} catch (err) {
reject(err)
}
},
executeAsync: null,
}))
const executeHooksWithArgs = shim.executeHooksWithArgs
describe('testFnWrapper', () => {
const origFn = (mode, attempts, limit, arg) => `${mode}: Foo${arg} ${attempts} ${limit}`
const buildArgs = (specFn, retries, beforeFnArgs, afterFnArgs) => [
'Foo',
{ specFn, specFnArgs: ['Bar'] },
{ beforeFn: 'beforeFn', beforeFnArgs },
{ afterFn: 'afterFn', afterFnArgs },
'0-9',
retries
]
it('should run fn in sync mode with mocha or jasmine', async () => {
const args = buildArgs(origFn, undefined, () => ['beforeFnArgs'], () => [{ foo: 'bar', description: 'foo' }, 'context'])
const result = await testFnWrapper.call({ test: { fullTitle: () => 'full title' } }, ...args)
expect(result).toBe('@wdio/sync: FooBar 0 0')
expect(executeHooksWithArgs).toBeCalledTimes(2)
expect(
Object.prototype.hasOwnProperty.call(
executeHooksWithArgs.mock.calls[1][2][2],
'duration'
)
).toBe(true)
delete executeHooksWithArgs.mock.calls[1][2][2].duration
expect(executeHooksWithArgs.mock.calls).toMatchSnapshot()
})
it('should propagate jasmine failed expecations as errors', async () => {
const failedExpectation = {
matcherName: 'toEqual',
message: 'Expected true to equal false.',
stack: 'Error: Expected true to equal false.\n at <Jasmine>\n at UserContext.it',
passed: false,
expected: false,
actual: true
}
const args = buildArgs(origFn, undefined, () => ['beforeFnArgs'], () => [{ foo: 'bar', description: 'foo', failedExpectations: [failedExpectation] }, 'context'])
const error = await testFnWrapper.call({ test: { fullTitle: () => 'full title' } }, ...args).catch((err) => err)
expect(executeHooksWithArgs.mock.calls[1][1]).toMatchSnapshot()
expect(error).toMatchSnapshot()
})
it('should run fn in sync mode with cucumber', async () => {
const args = buildArgs(origFn, undefined, () => ['beforeFnArgs'], () => [{ foo: 'bar' }, 2, 3, 4])
const result = await testFnWrapper(...args)
expect(result).toBe('@wdio/sync: FooBar 0 0')
expect(executeHooksWithArgs).toBeCalledTimes(2)
delete executeHooksWithArgs.mock.calls[1][1][2].duration
expect(executeHooksWithArgs.mock.calls).toMatchSnapshot()
})
afterEach(() => {
executeHooksWithArgs.mockClear()
})
})

View file

@ -1,55 +1,59 @@
import { testFnWrapper } from '../../src/test-framework/testFnWrapper'
import { MockedFunction, vi, describe, it, expect, afterEach, beforeEach } from 'vitest'
import { testFnWrapper as testFnWrapperImport } from '../../src/test-framework/testFnWrapper'
import { runHook, runSpec, wrapTestFunction, runTestInFiberContext } from '../../src/test-framework/testInterfaceWrapper'
const testFunction = jest.fn(function (specTitle, cb) { return cb.call(this, 'foo', 'bar') })
const hookFunction = jest.fn(function (cb) { return cb.call(this, 'foo', 'bar') })
const testFunction = vi.fn(function (this: any, specTitle, cb) { return cb.call(this, 'foo', 'bar') })
const hookFunction = vi.fn(function (this: any, cb) { return cb.call(this, 'foo', 'bar') })
jest.mock('../../src/test-framework/testFnWrapper', () => ({
testFnWrapper: jest.fn()
vi.mock('../../src/test-framework/testFnWrapper', () => ({
testFnWrapper: vi.fn()
}))
const testFnWrapper = testFnWrapperImport as MockedFunction<any>
beforeEach(() => {
testFnWrapper.mockClear()
// @ts-expect-error
global.jasmine = {}
})
describe('runHook', () => {
it('should call testFnWrapper with proper args', () => {
const beforeFnArgs = (context) => [context.foo]
const afterFnArgs = (context) => [context.foo]
runHook('hookFn', hookFunction.bind({ foo: 'bar' }), 'beforeFn', beforeFnArgs, 'afterFn', afterFnArgs, 'cid')
const beforeFnArgs = (context: any) => [context.foo]
const afterFnArgs = (context: any) => [context.foo]
runHook('hookFn' as any, hookFunction.bind({ foo: 'bar' }), 'beforeFn' as any, beforeFnArgs as any, 'afterFn' as any, afterFnArgs as any, 'cid', 0, 0)
expect(testFnWrapper).toBeCalledWith(
'Hook',
{ specFn: 'hookFn', specFnArgs: ['foo', 'bar'] },
{ beforeFn: 'beforeFn', beforeFnArgs },
{ afterFn: 'afterFn', afterFnArgs },
'cid',
undefined
0
)
})
})
describe('runSpec', () => {
it('should call testFnWrapper with proper args', () => {
const beforeFnArgs = (context) => [context.foo]
const afterFnArgs = (context) => [context.foo]
runSpec('test title', 'specFn', testFunction.bind({ foo: 'bar' }), 'beforeFn', beforeFnArgs, 'afterFn', afterFnArgs, 'cid')
const beforeFnArgs = (context: any) => [context.foo]
const afterFnArgs = (context: any) => [context.foo]
runSpec('test title', 'specFn' as any, testFunction.bind({ foo: 'bar' }), 'beforeFn' as any, beforeFnArgs as any, 'afterFn' as any, afterFnArgs as any, 'cid', 0, 0)
expect(testFnWrapper).toBeCalledWith(
'Test',
{ specFn: 'specFn', specFnArgs: ['foo', 'bar'] },
{ beforeFn: 'beforeFn', beforeFnArgs },
{ afterFn: 'afterFn', afterFnArgs },
'cid',
undefined
0
)
})
})
describe('wrapTestFunction', () => {
it('should run spec', () => {
const specFn = jest.fn()
const fn = wrapTestFunction(testFunction, true, 'beforeFn', () => [], 'afterFn', () => [], 'cid')
fn('test title', specFn, { options: { foo: 'bar' } })
const specFn = vi.fn()
const fn = wrapTestFunction(testFunction, true, 'beforeFn' as any, () => [] as any, 'afterFn' as any, () => [] as any, 'cid')
fn('test title', specFn, { options: { foo: 'bar' } } as any)
expect(testFnWrapper).toBeCalledWith(
'Test',
{ specFn, specFnArgs: ['foo', 'bar'] },
@ -61,9 +65,9 @@ describe('wrapTestFunction', () => {
})
it('should run hook', () => {
const specFn = jest.fn()
const fn = wrapTestFunction(hookFunction, false, 'beforeFn', () => [], 'afterFn', () => [], 'cid')
fn(specFn, 123, 4)
const specFn = vi.fn()
const fn = wrapTestFunction(hookFunction, false, 'beforeFn' as any, () => [] as any, 'afterFn' as any, () => [] as any, 'cid')
fn(specFn as any, 123 as any, 4)
expect(testFnWrapper).toBeCalledWith(
'Hook',
{ specFn, specFnArgs: ['foo', 'bar'] },
@ -76,9 +80,9 @@ describe('wrapTestFunction', () => {
})
it('should run pending function', () => {
const origFn = jest.fn()
const fn = wrapTestFunction(origFn, true)
fn('skipped test title')
const origFn = vi.fn()
const fn = wrapTestFunction(origFn, true, 'beforeFn' as any, () => [] as any, 'afterFn' as any, () => [] as any, 'cid')
fn('skipped test title' as any)
expect(origFn).toBeCalledWith('skipped test title')
})
})
@ -86,16 +90,23 @@ describe('wrapTestFunction', () => {
describe('runTestInFiberContext', () => {
it('should wrap skip and only functions', () => {
const skipFn = () => { }
const onlyFn = function (...args) { return global.foobar(...args) }
// @ts-expect-error
const onlyFn = function (...args: any[]) { return global.foobar(...args) }
// @ts-expect-error
global.foobar = testFunction
// @ts-expect-error
global.foobar.only = onlyFn
// @ts-expect-error
global.foobar.skip = skipFn
runTestInFiberContext(true, 'beforeFn', () => [], 'afterFn', () => [], 'foobar', 'cid')
runTestInFiberContext(true, 'beforeFn' as any, () => [] as any, 'afterFn' as any, () => [] as any, 'foobar', 'cid')
// @ts-expect-error
expect(global.foobar.skip).toBe(skipFn)
// @ts-expect-error
expect(global.foobar.only).toBe(onlyFn)
const specFn = jest.fn()
const specFn = vi.fn()
// @ts-expect-error
global.foobar.only('test title', specFn, 321, 3)
expect(testFnWrapper).toBeCalledWith(
'Test',
@ -117,10 +128,10 @@ describe('runTestInFiberContext', () => {
const scope = {
barfoo: hookFunction
}
runTestInFiberContext(false, 'beforeFn', () => [], 'afterFn', () => [], 'barfoo', 'cid', scope)
runTestInFiberContext(false, 'beforeFn' as any, () => [] as any, 'afterFn' as any, () => [] as any, 'barfoo', 'cid', scope as any)
const specFn = jest.fn()
scope.barfoo(specFn, 0)
const specFn = vi.fn()
scope.barfoo(specFn)
expect(testFnWrapper).toBeCalledWith(
'Hook',
{ specFn, specFnArgs: ['foo', 'bar'] },
@ -133,7 +144,9 @@ describe('runTestInFiberContext', () => {
})
afterEach(() => {
// @ts-expect-error
delete global.foobar
// @ts-expect-error
delete global.jasmine
testFnWrapper.mockClear()
})

View file

@ -1,11 +1,12 @@
import fs from 'fs'
import { vi, describe, it, expect, MockedFunction } from 'vitest'
import fs from 'node:fs'
import {
overwriteElementCommands, commandCallStructure, isValidParameter, canAccess,
getArgumentType, isFunctionAsync, filterSpecArgs, isBase64, transformCommandLogResult
} from '../src/utils'
jest.mock('fs')
vi.mock('fs')
describe('utils', () => {
it('commandCallStructure', () => {
@ -55,7 +56,7 @@ describe('utils', () => {
describe('overwriteElementCommands', () => {
it('should overwrite command', function () {
const context = {}
const origFnMock: (arg: any) => void = jest.fn(() => 1)
const origFnMock: (arg: any) => void = vi.fn(() => 1)
const propertiesObject = {
foo: { value: origFnMock },
__elementOverrides__: {
@ -65,15 +66,15 @@ describe('utils', () => {
overwriteElementCommands.call(context, propertiesObject)
expect(propertiesObject.foo.value(5))
.toEqual([1, 5])
expect((origFnMock as jest.Mock).mock.calls.length)
expect((origFnMock as MockedFunction<any>).mock.calls.length)
.toBe(1)
expect((origFnMock as jest.Mock).mock.instances[0])
expect((origFnMock as MockedFunction<any>).mock.instances[0])
.toBe(propertiesObject.foo)
})
it('should support rebinding when invoking original fn', function () {
const context = {}
const origFnMock: (arg: any) => void = jest.fn(() => 1)
const origFnMock: (arg: any) => void = vi.fn(() => 1)
const origFnContext = {}
const propertiesObject = {
foo: { value: origFnMock },
@ -84,9 +85,9 @@ describe('utils', () => {
overwriteElementCommands.call(context, propertiesObject)
expect(propertiesObject.foo.value(5))
.toEqual([1, 5])
expect((origFnMock as jest.Mock).mock.calls.length)
expect((origFnMock as MockedFunction<any>).mock.calls.length)
.toBe(1)
expect((origFnMock as jest.Mock).mock.instances[0])
expect((origFnMock as MockedFunction<any>).mock.instances[0])
.toBe(origFnContext)
})
@ -106,7 +107,7 @@ describe('utils', () => {
it('should throw if there is no command to be propertiesObject', function () {
const propertiesObject = { __elementOverrides__: { value: {
foo: jest.fn()
foo: vi.fn()
} } }
expect(() => overwriteElementCommands.call(null, propertiesObject))
.toThrow('overwriteCommand: no command to be overwritten: foo')
@ -114,7 +115,7 @@ describe('utils', () => {
it('should throw on attempt to overwrite not a function', function () {
const propertiesObject = { foo: 'bar', __elementOverrides__: { value: {
foo: jest.fn()
foo: vi.fn()
} } }
expect(() => overwriteElementCommands.call(null, propertiesObject))
.toThrow('overwriteCommand: only functions can be overwritten, command: foo')

View file

@ -2,9 +2,12 @@ import { defineConfig } from 'vite'
export default defineConfig({
test: {
include: ['packages/wdio-logger/**/*.test.ts'],
include: [
'packages/wdio-logger/**/*.test.ts',
'packages/wdio-utils/**/*.test.ts'
],
coverage: {
enabled: true,
enabled: false,
exclude: ['**/build/**', '**/*.test.ts'],
lines: 90,
functions: 90,