chore: upgrade to Eslint v9 (#13867)

* chore: upgrade to Eslint v9

* fix remaining issues

* more type fixes

* resolve remaining issues

* use wdio eslint package

* PR feedback
This commit is contained in:
Christian Bromann 2024-12-18 11:55:43 -08:00 committed by GitHub
parent 34e6d647e8
commit a2ae35332f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
305 changed files with 7459 additions and 9255 deletions

View file

@ -1,9 +0,0 @@
/packages/**/build
/packages/**/cjs
/packages/**/node_modules
/packages/node_modules
/packages/**/coverage
/tests/typings
*.d.ts
packages/wdio-protocols/src/protocols/webdriverBidi.ts
packages/webdriverio/third_party/fake-timers.js

View file

@ -1,76 +0,0 @@
/** @type {import('eslint').Linter.Config} */
const config = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'unicorn', 'import'],
extends: ['eslint:recommended'],
env: {
node: true,
es6: true,
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
rules: {
quotes: ['error', 'single', { avoidEscape: true }],
camelcase: ['error', { properties: 'never' }],
semi: ['error', 'never'],
indent: [2, 4],
eqeqeq: ['error', 'always'],
'prefer-const': 'error',
'no-multiple-empty-lines': [2, { max: 1, maxEOF: 1 }],
'array-bracket-spacing': ['error', 'never'],
'brace-style': ['error', '1tbs', { allowSingleLine: true }],
'comma-spacing': ['error', { before: false, after: true }],
'no-lonely-if': 'error',
'dot-notation': 'error',
'no-else-return': 'error',
'no-tabs': 'error',
'no-trailing-spaces': [
'error',
{
skipBlankLines: false,
ignoreComments: false,
},
],
'no-var': 'error',
'unicode-bom': ['error', 'never'],
curly: ['error', 'all'],
'object-curly-spacing': ['error', 'always'],
'keyword-spacing': ['error'],
'require-atomic-updates': 0,
'linebreak-style': ['error', 'unix'],
'unicorn/prefer-node-protocol': ['error'],
'import/extensions': ['error', 'ignorePackages'],
'no-restricted-syntax': [
'error',
'IfStatement > ExpressionStatement > AssignmentExpression',
],
'unicorn/prefer-ternary': 'error',
'no-dupe-class-members': 'off'
},
overrides: [
{
files: ['*.ts'],
rules: {
// see https://stackoverflow.com/questions/55280555/typescript-eslint-eslint-plugin-error-route-is-defined-but-never-used-no-un
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'no-undef': 'off',
// allow overloads
'no-redeclare': 'off',
},
},
{
files: ['*.test.ts'],
rules: {
'dot-notation': 'off',
},
},
],
}
module.exports = config

1
.gitignore vendored
View file

@ -72,6 +72,7 @@ packages/**/cjs/
/*.ts
!.eslintrc.js
!vitest.config.ts
!eslint.config.mjs
packages/**/package-lock.json
.DS_Store
e2e/screenshot.pdf

72
eslint.config.mjs Normal file
View file

@ -0,0 +1,72 @@
import wdioEslint, { globals } from '@wdio/eslint'
export default wdioEslint.config([
{
/**
* Eslint ignore patterns for the whole project
*/
ignores: [
'packages/**/build',
'packages/**/cjs',
'packages/**/node_modules',
'packages/node_modules',
'packages/**/coverage',
'tests/typings',
'**/*.d.ts',
'packages/wdio-protocols/src/protocols/webdriverBidi.ts',
'packages/webdriverio/third_party/fake-timers.js',
'packages/wdio-protocols/src/commands'
],
},
{
/**
* Eslint rules for /example directory and unit tests
*/
files: [
'examples/**/*.ts',
'examples/**/*.js',
'packages/**/*.test.ts',
'packages/**/tests/**/*.ts'
],
languageOptions: {
globals: {
...globals.mocha,
...globals.jasmine,
browser: true,
$: true
}
},
rules: {
'no-undef': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'wdio/no-pause': 'off',
'wdio/no-debug': 'off',
}
},
{
/**
* Eslint rules for /tests folder
*/
files: [
'tests/**/*.ts',
'tests/**/*.js',
],
languageOptions: {
globals: {
...globals.mocha,
...globals.jasmine,
browser: true,
$: true
}
},
rules: {
'dot-notation': 'off',
'wdio/no-pause': 'off',
'wdio/no-debug': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-require-imports': 'off',
}
}
])

View file

@ -44,6 +44,6 @@ const webviewApp = '/path/to/app'
])
await driver.deleteSession()
})().catch(
// eslint-disable-next-line no-console
console.error
)

View file

@ -13,6 +13,6 @@ await browser.scriptAddPreloadScript({
await browser.url('https://webdriver.io')
await browser.pause(2000)
// eslint-disable-next-line no-undef
console.log(await browser.execute(() => window.bar))
await browser.deleteSession()

View file

@ -26,7 +26,7 @@ await resultLabel.click()
await browser.pause(1000)
const title = await browser.getTitle()
// eslint-disable-next-line
console.log(title) // returns "should return "WebdriverIO - click""
await browser.deleteSession()

View file

@ -31,7 +31,7 @@ await resultLabel.click()
await browser.pause(1000)
const title = await browser.getTitle()
// eslint-disable-next-line
console.log(title) // returns "should return "WebdriverIO - click""
await browser.deleteSession()

View file

@ -32,7 +32,7 @@ await resultLabel.click()
await browser.pause(1000)
const title = await browser.getTitle()
// eslint-disable-next-line
console.log(title) // returns "should return "WebdriverIO - click""
await browser.deleteSession()

View file

@ -25,7 +25,7 @@ await resultLabel.click()
await browser.pause(1000)
const title = await browser.getTitle()
// eslint-disable-next-line
console.log(title) // returns "should return "WebdriverIO - click""
await browser.deleteSession()

View file

@ -1,4 +1,3 @@
/* eslint-disable no-unused-vars */
exports.config = {
// =====================

View file

@ -1,4 +1,3 @@
/* eslint-disable no-console */
describe('my app', () => {
before(() => console.log('inner before hook'))

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console */
import WDIOReporter from '@wdio/reporter'
export default class CustomReporter extends WDIOReporter {

View file

@ -36,11 +36,11 @@ export const config: WebdriverIO.Config = {
* hooks
*/
onPrepare: function() {
// eslint-disable-next-line
console.log('let\'s go')
},
onComplete: function() {
// eslint-disable-next-line
console.log('that\'s it')
}
}

View file

@ -1,11 +0,0 @@
{
"env": {
"mocha": true
},
"globals": {
"browser": true,
"browserA": true,
"browserB": true,
"$": true
}
}

View file

@ -1,4 +1,4 @@
const path = require('node:path')
import path from 'node:path'
exports.config = {
/**

View file

@ -75,24 +75,22 @@
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@types/archiver": "^6.0.2",
"@types/cheerio": "^0.22.35",
"@types/eslint": "^8.56.10",
"@types/inquirer": "^9.0.7",
"@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.zip": "^4.2.9",
"@types/mime-types": "^2.1.4",
"@types/mocha": "^10.0.7",
"@types/node": "^20.12.7",
"@types/recursive-readdir": "^2.2.4",
"@types/shelljs": "^0.8.15",
"@types/split2": "^4.2.3",
"@types/unzipper": "^0.10.9",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.7.0",
"@typescript-eslint/parser": "^7.7.0",
"@typescript-eslint/utils": "^7.7.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-vue": "^5.1.2",
"@vitest/coverage-v8": "^2.0.5",
"@wdio/config": "workspace:*",
"@wdio/eslint": "^0.0.5",
"@wdio/repl": "workspace:*",
"camelcase": "^8.0.0",
"cddl": "^0.8.5",
@ -105,12 +103,9 @@
"dox": "^1.0.0",
"ejs": "^3.1.10",
"ejs-lint": "^2.0.0",
"eslint": "^8.57.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-unicorn": "^55.0.0",
"eslint-plugin-wdio": "workspace:*",
"eta": "^3.4.0",
"glob": "^11.0.0",
"globals": "^15.12.0",
"husky": "^9.1.4",
"inquirer": "^11.0.1",
"jsdom": "^25.0.0",

View file

@ -1,10 +1,18 @@
export const isCommand = function(expression: any, command: 'pause' | 'debug'): boolean {
import type { Rule } from 'eslint'
type CallExpression = Parameters<NonNullable<Rule.NodeListener['CallExpression']>>[0]
export const isCommand = function(expression: CallExpression, command: 'pause' | 'debug'): boolean {
const callee = expression?.callee
return (
callee &&
callee?.object?.name === 'browser' &&
callee?.property?.name === command
'object' in callee &&
'name' in callee.object &&
callee.object?.name === 'browser' &&
'property' in callee &&
'name' in callee.property &&
callee.property?.name === command
)
}

View file

@ -8,7 +8,9 @@ import { events } from '../constants.js'
* @param {object} msg - event payload
* @private
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const tellReporter = (event: string, msg: any = {}) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
process.emit(event as any, msg)
}
@ -212,6 +214,7 @@ export function addStep (
content,
name = 'attachment',
type = 'text/plain'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}: any = {},
status: Status = Status.PASSED
) {

View file

@ -62,7 +62,8 @@ export default class AllureReporter extends WDIOReporter {
this.registerListeners()
const processObj:any = process
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const processObj: any = process
if (options.addConsoleLogs) {
processObj.stdout.write = (chunk: string, encoding: BufferEncoding, callback: ((err?: Error) => void)) => {
@ -75,7 +76,9 @@ export default class AllureReporter extends WDIOReporter {
}
const { reportedEnvironmentVars } = this._options
reportedEnvironmentVars && this._allure.writeEnvironmentInfo(reportedEnvironmentVars)
if (reportedEnvironmentVars) {
this._allure.writeEnvironmentInfo(reportedEnvironmentVars)
}
}
attachLogs() {
@ -111,7 +114,7 @@ export default class AllureReporter extends WDIOReporter {
)
}
attachJSON(name: string, json: any) {
attachJSON(name: string, json: unknown) {
const isStr = typeof json === 'string'
const content = isStr ? json : JSON.stringify(json, null, 2)
@ -502,7 +505,7 @@ export default class AllureReporter extends WDIOReporter {
this._startStep(stepName as string)
if (!isEmpty(payload)) {
if (typeof payload === 'object' && !isEmpty(payload as object)) {
this.attachJSON('Request', payload)
}
}
@ -510,10 +513,14 @@ export default class AllureReporter extends WDIOReporter {
onAfterCommand(command: AfterCommandArgs) {
const { disableWebdriverStepsReporting, disableWebdriverScreenshotsReporting } = this._options
const commandResult = command?.result?.value || command?.result?.error?.name || {}
const commandResult = (
(command?.result as { value?: unknown } | undefined)?.value ||
(command?.result as { error?: Error } | undefined)?.error?.name ||
{}
)
const isScreenshot = isScreenshotCommand(command)
if (!disableWebdriverScreenshotsReporting && isScreenshot && commandResult) {
this.attachScreenshot('Screenshot', Buffer.from(commandResult, 'base64'))
this.attachScreenshot('Screenshot', Buffer.from(commandResult as string, 'base64'))
}
if (disableWebdriverStepsReporting || this._isMultiremote || !this._state.currentStep) {
@ -666,7 +673,11 @@ export default class AllureReporter extends WDIOReporter {
*/
if (useCucumberStepReporter && !disableMochaHooks) {
// closing the cucumber hook (in this case, it's reported as a step)
hook.error ? this.onTestFail(hook) : this.onTestPass()
if (hook.error) {
this.onTestFail(hook)
} else {
this.onTestPass()
}
// remove cucumber hook (reported as a step) from a suite if it has no steps or attachments.
const currentItem = this._state.currentAllureStepableEntity?.wrappedItem
@ -885,7 +896,7 @@ export default class AllureReporter extends WDIOReporter {
addStep({
step
}: any) {
}: { step: { title: string, attachment: { name: string, content: string, type: ContentType }, status: AllureStatus } }) {
if (!this._state.currentAllureStepableEntity) {
return
}
@ -902,7 +913,7 @@ export default class AllureReporter extends WDIOReporter {
addArgument({
name,
value
}: any) {
}: { name: string, value: string }) {
if (!this._state.currentTest) {
return
}

View file

@ -50,7 +50,7 @@ export const getTestStatus = (
* @param object {Object}
* @private
*/
export const isEmpty = (object: any) =>
export const isEmpty = (object: object) =>
!object || Object.keys(object).length === 0
/**

View file

@ -9,7 +9,7 @@ import { temporaryDirectory } from 'tempy'
* this is not a real package and only used to utilize helper
* methods without having to ignore them for test coverage
*/
// eslint-disable-next-line
import { clean, getResults, mapBy } from './helpers/wdio-allure-helper'
import AllureReporter from '../src/reporter.js'

View file

@ -1,3 +1,4 @@
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'

View file

@ -13,7 +13,7 @@ import { TYPE } from '../src/types.js'
* this is not a real package and only used to utilize helper
* methods without having to ignore them for test coverage
*/
// eslint-disable-next-line
import { clean, getResults, mapBy } from './helpers/wdio-allure-helper.js'
import { runnerEnd, runnerStart } from './__fixtures__/runner.js'

View file

@ -304,12 +304,12 @@ export default class AppiumLauncher implements Services.ServiceInstance {
try {
const entryPath = await resolve(command, import.meta.url)
return url.fileURLToPath(entryPath)
} catch (err: any) {
} catch (err) {
const errorMessage = (
'Appium is not installed locally. Please install via e.g. `npm i --save-dev appium`.\n' +
'If you use globally installed appium please add: `appium: { command: \'appium\' }`\n' +
'to your wdio.conf.js!\n\n' +
err.stack
(err as Error).stack
)
log.error(errorMessage)
throw new SevereServiceError(errorMessage)

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/// <reference types="@wdio/globals/types" />
import { commands } from 'virtual:wdio'
import { webdriverMonad, sessionEnvironmentDetector } from '@wdio/utils'
@ -80,7 +81,7 @@ export default class ProxyDriver {
? params.capabilities.alwaysMatch?.webSocketUrl
: params.capabilities!.webSocketUrl
if (webSocketUrl) {
Object.assign(bidiPrototype, initiateBidi(webSocketUrl as any as string))
Object.assign(bidiPrototype, initiateBidi(webSocketUrl as unknown as string))
}
/**

View file

@ -7,7 +7,7 @@ import { getCID } from './utils.js'
import { WDIO_EVENT_NAME } from '../constants.js'
declare type RawMatcherFn<Context extends MatcherContext = MatcherContext> = {
(this: Context, actual: any, ...expected: Array<any>): ExpectationResult;
(this: Context, actual: unknown, ...expected: Array<unknown>): ExpectationResult;
};
interface MatcherPayload {
@ -31,6 +31,7 @@ const COMMAND_TIMEOUT = 30 * 1000 // 30s
* @returns a matcher result computed in the Node.js environment
*/
function createMatcher (matcherName: string) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return async function (this: MatcherContext, context: WebdriverIO.Browser | WebdriverIO.Element | ChainablePromiseElement | ChainablePromiseArray, ...args: any[]) {
const cid = getCID()
if (!import.meta.hot || !cid) {
@ -68,7 +69,7 @@ function createMatcher (matcherName: string) {
/**
* Check if context is ChainablePromiseElement
*/
if (isContextObject && 'then' in context && typeof (context as any).selector === 'object') {
if (isContextObject && 'then' in context && typeof (context as { selector: string }).selector === 'object') {
expectRequest.element = await context
}
@ -76,7 +77,7 @@ function createMatcher (matcherName: string) {
* Check if context is a `Element` and transform it into a WebdriverIO.Element
*/
if (context instanceof Element) {
expectRequest.element = await $(context as any as HTMLElement)
expectRequest.element = await $(context as unknown as HTMLElement)
} else if (isContextObject && !('sessionId' in context)) {
/**
* check if context is an object or promise and resolve it

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import safeStringify from 'safe-stringify'
import { setupEnv, formatMessage } from '@wdio/mocha-framework/common'
import { MESSAGE_TYPES, type Workers } from '@wdio/types'
@ -19,6 +20,7 @@ class HTMLReporter extends BaseReporter {
* to the element within the Shadow DOM
*/
const getElementById = document.getElementById.bind(document)
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
document.getElementById = () => document.querySelector('mocha-framework')?.shadowRoot?.querySelector('#mocha')!
super(runner, options)
document.getElementById = getElementById

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { h } from '@stencil/core'
import { $ } from '@wdio/globals'
import type { StencilEnvironment } from '../../../stencil/index.d.ts'

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/// <reference types="vite/client" />
import { automationProtocolPath } from 'virtual:wdio'

View file

@ -26,6 +26,7 @@ export async function mock (path: string, factory?: MockFactoryWithHelper) {
/**
* parameter is added by hoisting plugin if factory comes with a parameter
*/
// eslint-disable-next-line prefer-rest-params
const actualImport = arguments[2]
const mockLocalFile = path.startsWith('/') || path.startsWith('./') || path.startsWith('../')
@ -37,7 +38,7 @@ export async function mock (path: string, factory?: MockFactoryWithHelper) {
try {
const resolvedMock = await factory(actualImport)
window.__wdioMockCache__.set(mockPath, resolvedMock)
} catch (err: unknown) {
} catch (err) {
const error = err as Error
throw new Error(`${ERROR_MESSAGE}\n${error.message}: ${error.stack}`)
}
@ -51,4 +52,4 @@ export function unmock(moduleName: string) {
/**
* utility helper for type conversions
*/
export function mocked<T>(item: T) { return item as any as MaybeMocked<T> }
export function mocked<T>(item: T) { return item as unknown as MaybeMocked<T> }

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export function getCID() {
const urlParamString = new URLSearchParams(window.location.search)
const cid = (
@ -50,7 +51,7 @@ export function sanitizeConsoleArgs(args: unknown[]) {
if (arg && typeof arg.sessionId === 'string') {
return `WebdriverIO.Browser<${arg.capabilities.browserName}>`
}
} catch (err) {
} catch {
// ignore
}

View file

@ -106,6 +106,6 @@ export class ServerWorkerCommunicator {
const msg: WorkerMessage = { id, client }
this.#pendingMessages.set(id, msg)
const args: Workers.WorkerRequest['args'] = { id, message }
return worker.postMessage('workerRequest', args, true)
return worker.postMessage('workerRequest', args as unknown as Workers.WorkerMessageArgs, true)
}
}

View file

@ -110,8 +110,8 @@ export default class BrowserRunner extends LocalRunner {
allowOrigins: [`http://localhost:${port}`]
}
}
} catch (err: any) {
throw new Error(`Vite server failed to start: ${err.stack}`)
} catch (err) {
throw new Error(`Vite server failed to start: ${(err as Error).stack}`)
}
if (!runArgs.args.baseUrl && runArgs.command === 'run') {
@ -183,13 +183,14 @@ export default class BrowserRunner extends LocalRunner {
...this.#coverageOptions.perFile
? Object.entries(summary)
.filter(([source]) => source !== 'total')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.map(([source, summary]: any) => (
getCoverageByFactor(this.#coverageOptions, summary, source.replace(this.#config.rootDir, '')))
)
.flat()
: getCoverageByFactor(this.#coverageOptions, summary.total)
)
} catch (err: unknown) {
} catch (err) {
console.error(`Failed to generate code coverage report: ${(err as Error).message}`)
return false
}
@ -278,5 +279,4 @@ export function mocked<T>(item: T, options: {
partial: true;
deep: true;
}): MaybePartiallyMockedDeep<T>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function mocked (item: any, options: any) {}
export function mocked (_item: unknown, _options: unknown) {}

View file

@ -126,7 +126,7 @@ export interface BrowserRunnerOptions {
export interface RunArgs extends Workers.WorkerRunPayload {
command: string
args: any
args: Record<string, string>
cid: string
}
@ -138,4 +138,4 @@ export interface Environment {
injectGlobals: boolean
}
export type MockFactoryWithHelper = (importOriginal: <T = unknown>() => Promise<T>) => any;
export type MockFactoryWithHelper = (importOriginal: <T = unknown>() => Promise<T>) => unknown;

View file

@ -11,7 +11,7 @@ const log = logger('@wdio/browser-runner:vite')
export const DEFAULT_PROTOCOL = 'http'
export const DEFAULT_HOSTNAME = 'localhost'
export const DEFAULT_HOST = `${DEFAULT_PROTOCOL}://${DEFAULT_HOSTNAME}`
export const PRESET_DEPENDENCIES: Record<FrameworkPreset, [string, string, any] | undefined> = {
export const PRESET_DEPENDENCIES: Record<FrameworkPreset, [string, string, unknown] | undefined> = {
react: ['@vitejs/plugin-react', 'default', {
babel: {
assumptions: {
@ -63,6 +63,7 @@ export const DEFAULT_VITE_CONFIG: Partial<InlineConfig> = {
* cast to "any" here as Vite's esbuild dependency and WebdriverIOs one
* may differ and cause type issues here.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
codeFrameFix() as any
],
},

View file

@ -28,7 +28,7 @@ export async function optimizeForStencil(rootDir: string) {
}
if (stencilPlugins) {
const esbuildPlugin = stencilPlugins.find((plugin: any) => plugin.name === 'esbuild-plugin')
const esbuildPlugin = stencilPlugins.find((plugin: { name: string }) => plugin.name === 'esbuild-plugin')
if (esbuildPlugin) {
stencilOptimizations.optimizeDeps?.include?.push(...esbuildPlugin.options.include)
}
@ -163,6 +163,7 @@ let _tsCompilerOptions: CompilerOptions | null = null
* @param rootDir the location to search for the config file
* @returns the configuration, or `null` if the file cannot be found
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getCompilerOptions(ts: any, rootDir: string): CompilerOptions | null {
if (_tsCompilerOptions) {
return _tsCompilerOptions

View file

@ -33,7 +33,7 @@ export function mockHoisting(mockHandler: MockHandler): Plugin[] {
try {
const orig = await fs.readFile(id.slice(MOCK_PREFIX.length + (os.platform() === 'win32' ? 1 : 0)))
return orig.toString()
} catch (err: unknown) {
} catch (err) {
log.error(`Failed to read file (${id}) for mocking: ${(err as Error).message}`)
return ''
}
@ -75,7 +75,7 @@ export function mockHoisting(mockHandler: MockHandler): Plugin[] {
sourceRoot: path.dirname(id)
})
log.trace(`Parsed file for mocking: ${id} in ${Date.now() - start}ms`)
} catch (err) {
} catch {
return { code }
}
@ -327,7 +327,7 @@ export function mockHoisting(mockHandler: MockHandler): Plugin[] {
const newCode = print(ast, { sourceMapName: id })
log.trace(`Transformed file for mocking: ${id} in ${Date.now() - start}ms`)
return newCode
} catch (err: any) {
} catch (err) {
log.trace(`Failed to transformed file (${id}) for mocking: ${(err as Error).stack}`)
return { code }
}

View file

@ -19,6 +19,7 @@ import { getTemplate, getErrorTemplate, normalizeId } from '../utils.js'
const log = logger('@wdio/browser-runner:plugin')
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const commands = deepmerge<any>(
WebDriverProtocol, MJsonWProtocol, AppiumProtocol,
ChromiumProtocol, SauceLabsProtocol, SeleniumProtocol, GeckoProtocol
@ -157,9 +158,9 @@ export function testrunner(options: WebdriverIO.BrowserRunnerOptions): Plugin[]
const template = await getTemplate(options, env, spec)
log.debug(`Render template for ${req.originalUrl}`)
res.end(await server.transformIndexHtml(`${req.originalUrl}`, template))
} catch (err: any) {
const template = getErrorTemplate(req.originalUrl, err)
log.error(`Failed to render template: ${err.message}`)
} catch (err) {
const template = getErrorTemplate(req.originalUrl, err as Error)
log.error(`Failed to render template: ${(err as Error).message}`)
res.end(await server.transformIndexHtml(`${req.originalUrl}`, template))
}

View file

@ -34,7 +34,7 @@ export async function getTemplate(options: WebdriverIO.BrowserRunnerOptions, env
const sourceMapSupportDir = await resolve('source-map-support', import.meta.url)
sourceMapScript = /*html*/`<script src="/@fs/${url.fileURLToPath(path.dirname(sourceMapSupportDir))}/browser-source-map-support.js"></script>`
sourceMapSetupCommand = 'sourceMapSupport.install()'
} catch (err: unknown) {
} catch (err) {
log.error(`Failed to setup source-map-support: ${(err as Error).message}`)
}
@ -127,7 +127,7 @@ export async function userfriendlyImport(preset: FrameworkPreset, pkg?: string)
try {
return await import(pkg)
} catch (err: any) {
} catch {
throw new Error(
`Couldn't load preset "${preset}" given important dependency ("${pkg}") is not installed.\n` +
`Please run:\n\n\tnpm install ${pkg}\n\tor\n\tyarn add --dev ${pkg}`

View file

@ -13,12 +13,11 @@ import { PercyLogger } from './PercyLogger.js'
import { PERCY_DOM_CHANGING_COMMANDS_ENDPOINTS, CAPTURE_MODES } from '../constants.js'
class _PercyHandler {
private _testMetadata: { [key: string]: any } = {}
private _sessionName?: string
private _isPercyCleanupProcessingUnderway?: boolean = false
private _percyScreenshotCounter: any = 0
private _percyDeferredScreenshots: any = []
private _percyScreenshotInterval: any = null
private _percyScreenshotCounter = 0
private _percyDeferredScreenshots: ({ sessionName: string, eventName: string | null })[] = []
private _percyScreenshotInterval: NodeJS.Timeout | null = null
private _percyCaptureMap?: PercyCaptureMap
constructor (
@ -59,7 +58,7 @@ class _PercyHandler {
await (this._isAppAutomate ? PercySDK.screenshotApp(this._percyCaptureMap?.getName( sessionName ? sessionName : (this._sessionName as string), eventName)) : await PercySDK.screenshot(this._browser, this._percyCaptureMap?.getName( sessionName ? sessionName : (this._sessionName as string), eventName)))
this._percyScreenshotCounter -= 1
}
} catch (err: any) {
} catch (err) {
this._percyScreenshotCounter -= 1
this._percyCaptureMap?.decrement(sessionName ? sessionName : (this._sessionName as string), eventName as string)
PercyLogger.error(`Error while trying to auto capture Percy screenshot ${err}`)
@ -97,7 +96,7 @@ class _PercyHandler {
)
) ||
/* execute script sync / async */
(args.endpoint.includes('/session/:sessionId/execute') && args.body?.script) ||
Boolean(args.endpoint.includes('/session/:sessionId/execute') && (args.body as { script: string }).script) ||
/* Touch action for Appium */
(args.endpoint.includes('/session/:sessionId/touch'))
)
@ -126,12 +125,14 @@ class _PercyHandler {
} while (this._percyScreenshotInterval)
this._percyScreenshotInterval = setInterval(async () => {
if (!this._isPercyCleanupProcessingUnderway) {
clearInterval(this._percyScreenshotInterval)
if (this._percyScreenshotInterval) {
clearInterval(this._percyScreenshotInterval)
}
await this.cleanupDeferredScreenshots()
this._percyScreenshotInterval = null
}
}, 1000)
} catch (err: any) {
} catch (err) {
PercyLogger.error(`Error while trying to cleanup deferred screenshots ${err}`)
}
}
@ -148,7 +149,8 @@ class _PercyHandler {
} else if (endpoint.includes('screenshot') && ['screenshot', 'auto'].includes(this._percyAutoCaptureMode as string)) {
eventName = 'screenshot'
} else if (endpoint.includes('actions') && ['auto'].includes(this._percyAutoCaptureMode as string)) {
if (args.body && args.body.actions && Array.isArray(args.body.actions) && args.body.actions.length && args.body.actions[0].type === 'key') {
const actionsBody = (args.body as { actions: { type: string }[] }).actions
if (actionsBody && Array.isArray(actionsBody) && actionsBody.length && actionsBody[0].type === 'key') {
eventName = 'keys'
}
} else if (endpoint.includes('/session/:sessionId/element') && endpoint.includes('value') && ['auto'].includes(this._percyAutoCaptureMode as string)) {
@ -157,7 +159,7 @@ class _PercyHandler {
if (eventName) {
this.deferCapture(this._sessionName as string, eventName)
}
} catch (err: any) {
} catch (err) {
PercyLogger.error(`Error while trying to calculate auto capture parameters ${err}`)
}
}

View file

@ -1,6 +1,7 @@
import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import type { ChildProcessWithoutNullStreams } from 'node:child_process'
import { spawn } from 'node:child_process'
@ -19,10 +20,10 @@ class Percy {
#logfile: string = path.join(logDir, 'percy.log')
#address: string = process.env.PERCY_SERVER_ADDRESS || 'http://127.0.0.1:5338'
#binaryPath: string | any = null
#binaryPath: string | null = null
#options: BrowserstackConfig & Options.Testrunner
#config: Options.Testrunner
#proc: any = null
#proc: ChildProcessWithoutNullStreams | null = null
#isApp: boolean
#projectName: string | undefined = undefined
@ -51,12 +52,12 @@ class Percy {
async healthcheck() {
try {
const resp = await nodeRequest('GET', 'percy/healthcheck', null, this.#address)
const resp = await nodeRequest('GET', 'percy/healthcheck', {}, this.#address)
if (resp) {
this.buildId = resp.build.id
return true
}
} catch (err) {
} catch {
return false
}
}
@ -109,7 +110,7 @@ class Percy {
const binaryPath = await this.#getBinaryPath()
return new Promise( (resolve) => {
const proc = spawn(binaryPath, ['exec:stop'])
proc.on('close', (code: any) => {
proc.on('close', (code: number) => {
this.isProcessRunning = false
resolve(code)
})
@ -140,7 +141,7 @@ class Percy {
{
username: getBrowserStackUser(this.#config),
password: getBrowserStackKey(this.#config)
},
} as RequestInit,
'https://api.browserstack.com'
)
PercyLogger.debug('Percy fetch token success : ' + response.token)
@ -149,7 +150,7 @@ class Percy {
}
this.percyCaptureMode = response.percy_capture_mode
this.percy = response.success
} catch (err: any) {
} catch (err) {
PercyLogger.error(`Percy unable to fetch project token: ${err}`)
return null
}
@ -173,7 +174,7 @@ class Percy {
JSON.stringify(
percyOptions
),
(err: any) => {
(err: unknown) => {
if (err) {
PercyLogger.error(`Error creating percy config: ${err}`)
resolve(null)

View file

@ -10,7 +10,7 @@ import { PercyLogger } from './PercyLogger.js'
class PercyBinary {
#hostOS = process.platform
#httpPath: any = null
#httpPath: string | null = null
#binaryName = 'percy'
#orderedPaths = [
@ -44,7 +44,7 @@ class PercyBinary {
if (hasDir) {
return true
}
} catch (err) {
} catch {
return false
}
}
@ -92,7 +92,7 @@ class PercyBinary {
})
}
async download(destParentDir: any): Promise<string> {
async download(destParentDir: string): Promise<string> {
if (!await this.#checkPath(destParentDir)){
await fsp.mkdir(destParentDir)
}
@ -101,9 +101,10 @@ class PercyBinary {
const binaryPath = path.join(destParentDir, binaryName)
const downloadedFileStream = fs.createWriteStream(zipFilePath)
const response = await fetch(this.#httpPath)
const response = await fetch(this.#httpPath as unknown as URL)
await pipeline(response.body as any, downloadedFileStream)
// @ts-expect-error stream type
await pipeline(response.body as unknown as RequestInit, downloadedFileStream)
return new Promise((resolve, reject) => {
yauzl.open(zipFilePath, { lazyEntries: true }, function (err, zipfile) {
@ -142,7 +143,7 @@ class PercyBinary {
})
zipfile.once('end', () => {
fs.chmod(binaryPath, '0755', function (zipErr: any) {
fs.chmod(binaryPath, '0755', function (zipErr: Error) {
if (zipErr) {
reject(zipErr)
}

View file

@ -3,8 +3,12 @@
* unique screenshot names for percy
*/
interface Map {
[key: string]: Record<string, number>
}
class PercyCaptureMap {
#map: any = {}
#map: Map = {}
increment(sessionName: string, eventName: string) {
if (!this.#map[sessionName]) {

View file

@ -23,12 +23,12 @@ export const stopPercy = async (percy: Percy) => {
return percy.stop()
}
export const getBestPlatformForPercySnapshot = (capabilities?: Capabilities.TestrunnerCapabilities) : any => {
export const getBestPlatformForPercySnapshot = (capabilities?: Capabilities.TestrunnerCapabilities) => {
try {
const percyBrowserPreference: any = { 'chrome': 0, 'firefox': 1, 'edge': 2, 'safari': 3 }
const percyBrowserPreference = { 'chrome': 0, 'firefox': 1, 'edge': 2, 'safari': 3 }
let bestPlatformCaps: any = null
let bestBrowser: any = null
let bestPlatformCaps: WebdriverIO.Capabilities | undefined
let bestBrowser: string | undefined
if (Array.isArray(capabilities)) {
capabilities
@ -46,10 +46,11 @@ export const getBestPlatformForPercySnapshot = (capabilities?: Capabilities.Test
if (capability['bstack:options']) {
currBrowserName = capability['bstack:options'].browserName || currBrowserName
}
// @ts-expect-error
if (!bestBrowser || !bestPlatformCaps || (bestPlatformCaps.deviceName || bestPlatformCaps['bstack:options']?.deviceName)) {
bestBrowser = currBrowserName
bestPlatformCaps = capability
} else if (currBrowserName && percyBrowserPreference[currBrowserName.toLowerCase()] < percyBrowserPreference[bestBrowser.toLowerCase()]) {
} else if (currBrowserName && percyBrowserPreference[currBrowserName.toLowerCase() as keyof typeof percyBrowserPreference] < percyBrowserPreference[bestBrowser.toLowerCase() as keyof typeof percyBrowserPreference]) {
bestBrowser = currBrowserName
bestPlatformCaps = capability
}
@ -61,17 +62,18 @@ export const getBestPlatformForPercySnapshot = (capabilities?: Capabilities.Test
if ((caps.capabilities as WebdriverIO.Capabilities)['bstack:options']) {
currBrowserName = (caps.capabilities as WebdriverIO.Capabilities)['bstack:options']?.browserName || currBrowserName
}
// @ts-expect-error
if (!bestBrowser || !bestPlatformCaps || (bestPlatformCaps.deviceName || bestPlatformCaps['bstack:options']?.deviceName)) {
bestBrowser = currBrowserName
bestPlatformCaps = (caps.capabilities as WebdriverIO.Capabilities)
} else if (currBrowserName && percyBrowserPreference[currBrowserName.toLowerCase()] < percyBrowserPreference[bestBrowser.toLowerCase()]) {
} else if (currBrowserName && percyBrowserPreference[currBrowserName.toLowerCase() as keyof typeof percyBrowserPreference] < percyBrowserPreference[bestBrowser.toLowerCase() as keyof typeof percyBrowserPreference]) {
bestBrowser = currBrowserName
bestPlatformCaps = (caps.capabilities as WebdriverIO.Capabilities)
}
})
return bestPlatformCaps
}
} catch (err: any) {
} catch (err) {
PercyLogger.error(`Error while trying to determine best platform for Percy snapshot ${err}`)
return null
}

View file

@ -44,7 +44,7 @@ export class PercyLogger {
log.error(message)
}
public static debug(message: string, param?: any) {
public static debug(message: string, param?: unknown) {
this.logToFile(message, 'debug')
if (param) {
log.debug(message, param)

View file

@ -3,7 +3,7 @@ import TestReporter from '../reporter.js'
import { PercyLogger } from './PercyLogger.js'
import { isUndefined } from '../util.js'
const tryRequire = async function (pkg: string, fallback: any) {
const tryRequire = async function (pkg: string, fallback: unknown) {
try {
return (await import(pkg)).default
} catch {
@ -16,11 +16,11 @@ const percySnapshot = await tryRequire('@percy/selenium-webdriver', null)
const percyAppScreenshot = await tryRequire('@percy/appium-app', {})
/* eslint-disable @typescript-eslint/no-unused-vars */
let snapshotHandler = (...args: any[]) => {
let snapshotHandler = (...args: unknown[]) => {
PercyLogger.error('Unsupported driver for percy')
}
if (percySnapshot) {
snapshotHandler = (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, snapshotName: string, options?: { [key: string]: any }) => {
snapshotHandler = (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, snapshotName: string, options?: { [key: string]: unknown }) => {
if (process.env.PERCY_SNAPSHOT === 'true') {
let { name, uuid } = InsightsHandler.currentTest
if (isUndefined(name)) {
@ -42,7 +42,7 @@ export const snapshot = snapshotHandler
This is a helper method which appends some internal fields
to the options object being sent to Percy methods
*/
const screenshotHelper = (type: string, driverOrName: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser | string, nameOrOptions?: string | { [key: string]: any }, options?: { [key: string]: any }) => {
const screenshotHelper = (type: string, driverOrName: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser | string, nameOrOptions?: string | { [key: string]: unknown }, options?: { [key: string]: unknown }) => {
let { name, uuid } = InsightsHandler.currentTest
if (isUndefined(name)) {
({ name, uuid } = TestReporter.currentTest)
@ -71,22 +71,22 @@ const screenshotHelper = (type: string, driverOrName: WebdriverIO.Browser | Webd
}
/* eslint-disable @typescript-eslint/no-unused-vars */
let screenshotHandler = async (...args: any[]) => {
let screenshotHandler = async (...args: unknown[]) => {
PercyLogger.error('Unsupported driver for percy')
}
if (percySnapshot && percySnapshot.percyScreenshot) {
screenshotHandler = (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser | string, screenshotName?: string | { [key: string]: any }, options?: { [key: string]: any }) => {
screenshotHandler = (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser | string, screenshotName?: string | { [key: string]: unknown }, options?: { [key: string]: unknown }) => {
return screenshotHelper('web', browser, screenshotName, options)
}
}
export const screenshot = screenshotHandler
/* eslint-disable @typescript-eslint/no-unused-vars */
let screenshotAppHandler = async (...args: any[]) => {
let screenshotAppHandler = async (...args: unknown[]) => {
PercyLogger.error('Unsupported driver for percy')
}
if (percyAppScreenshot) {
screenshotAppHandler = (driverOrName: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser | string, nameOrOptions?: string | { [key: string]: any }, options?: { [key: string]: any }) => {
screenshotAppHandler = (driverOrName: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser | string, nameOrOptions?: string | { [key: string]: unknown }, options?: { [key: string]: unknown }) => {
return screenshotHelper('app', driverOrName, nameOrOptions, options)
}
}

View file

@ -24,23 +24,23 @@ import accessibilityScripts from './scripts/accessibility-scripts.js'
import { BStackLogger } from './bstackLogger.js'
class _AccessibilityHandler {
private _platformA11yMeta: { [key: string]: any; }
private _platformA11yMeta: { [key: string]: unknown; }
private _caps: Capabilities.ResolvedTestrunnerCapabilities
private _suiteFile?: string
private _accessibility?: boolean
private _accessibilityOptions?: { [key: string]: any; }
private _testMetadata: { [key: string]: any; } = {}
private static _a11yScanSessionMap: { [key: string]: any; } = {}
private _accessibilityOptions?: { [key: string]: unknown; }
private _testMetadata: { [key: string]: unknown; } = {}
private static _a11yScanSessionMap: { [key: string]: unknown; } = {}
private _sessionId: string | null = null
private listener = Listener.getInstance()
constructor (
private _browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser,
private _capabilities: Capabilities.ResolvedTestrunnerCapabilities,
_capabilities: Capabilities.ResolvedTestrunnerCapabilities,
isAppAutomate?: boolean,
private _framework?: string,
private _accessibilityAutomation?: boolean | string,
private _accessibilityOpts?: { [key: string]: any; }
_accessibilityAutomation?: boolean | string,
_accessibilityOpts?: { [key: string]: unknown; }
) {
const caps = (this._browser as WebdriverIO.Browser).capabilities as WebdriverIO.Capabilities
@ -96,9 +96,9 @@ class _AccessibilityHandler {
if (isBrowserstackSession(this._browser) && isAccessibilityAutomationSession(this._accessibility)) {
const deviceName = this._getCapabilityValue(this._caps, 'deviceName', 'device')
const chromeOptions = this._getCapabilityValue(this._caps, 'goog:chromeOptions', '')
const chromeOptions = this._getCapabilityValue(this._caps, 'goog:chromeOptions', '') as Capabilities.ChromeOptions
this._accessibility = validateCapsWithA11y(deviceName, this._platformA11yMeta, chromeOptions)
this._accessibility = validateCapsWithA11y(deviceName as string, this._platformA11yMeta as unknown as Record<string, string>, chromeOptions)
}
(this._browser as WebdriverIO.Browser).getAccessibilityResultsSummary = async () => {
@ -124,6 +124,7 @@ class _AccessibilityHandler {
.filter((command) => command.name && command.class)
.forEach((command) => {
const browser = this._browser as WebdriverIO.Browser
// @ts-expect-error fix type
browser.overwriteCommand(command.name, this.commandWrapper.bind(this, command), command.class === 'Element')
})
}
@ -139,6 +140,7 @@ class _AccessibilityHandler {
return
}
// @ts-expect-error fix type
const shouldScanTest = shouldScanTestForAccessibility(suiteTitle, test.title, this._accessibilityOptions)
const testIdentifier = this.getIdentifier(test)
const isPageOpened = await this.checkIfPageOpened(this._browser, testIdentifier, shouldScanTest)
@ -155,6 +157,7 @@ class _AccessibilityHandler {
return
}
// @ts-expect-error fix type
this._testMetadata[testIdentifier].accessibilityScanStarted = shouldScanTest
if (shouldScanTest) {
@ -176,7 +179,9 @@ class _AccessibilityHandler {
try {
const testIdentifier = this.getIdentifier(test)
// @ts-expect-error fix type
const accessibilityScanStarted = this._testMetadata[testIdentifier]?.accessibilityScanStarted
// @ts-expect-error fix type
const shouldScanTestForAccessibility = this._testMetadata[testIdentifier]?.scanTestForAccessibility
if (!accessibilityScanStarted) {
@ -216,6 +221,7 @@ class _AccessibilityHandler {
}
try {
// @ts-expect-error fix type
const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true)
const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario)
@ -231,6 +237,7 @@ class _AccessibilityHandler {
return
}
// @ts-expect-error fix type
this._testMetadata[uniqueId].accessibilityScanStarted = shouldScanScenario
if (shouldScanScenario) {
@ -250,7 +257,9 @@ class _AccessibilityHandler {
const pickleData = world.pickle
try {
const uniqueId = getUniqueIdentifierForCucumber(world)
// @ts-expect-error fix type
const accessibilityScanStarted = this._testMetadata[uniqueId]?.accessibilityScanStarted
// @ts-expect-error fix type
const shouldScanTestForAccessibility = this._testMetadata[uniqueId]?.scanTestForAccessibility
if (!accessibilityScanStarted) {
@ -279,7 +288,7 @@ class _AccessibilityHandler {
* private methods
*/
private async commandWrapper (command: any, origFunction: Function, ...args: any[]) {
private async commandWrapper (command: { name: string, class: string }, origFunction: Function, ...args: string[]) {
if (
this._sessionId && AccessibilityHandler._a11yScanSessionMap[this._sessionId] &&
(
@ -293,7 +302,7 @@ class _AccessibilityHandler {
return origFunction(...args)
}
private async sendTestStopEvent(browser: WebdriverIO.Browser, dataForExtension: any) {
private async sendTestStopEvent(browser: WebdriverIO.Browser, dataForExtension: unknown) {
BStackLogger.debug('Performing scan before saving results')
await performA11yScan(browser, true, true)
const results: unknown = await (browser as WebdriverIO.Browser).executeAsync(accessibilityScripts.saveTestResults as string, dataForExtension)
@ -307,7 +316,7 @@ class _AccessibilityHandler {
return getUniqueIdentifier(test, this._framework)
}
private shouldRunTestHooks(browser: any, isAccessibility?: boolean | string) {
private shouldRunTestHooks(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isAccessibility?: boolean | string) {
if (!browser) {
return false
}
@ -325,14 +334,14 @@ class _AccessibilityHandler {
const currentURL = await (browser as WebdriverIO.Browser).getUrl()
const url = new URL(currentURL)
pageOpen = url?.protocol === 'http:' || url?.protocol === 'https:'
} catch (e) {
} catch {
pageOpen = false
}
return pageOpen
}
private static shouldPatchExecuteScript(script: string | null): Boolean {
private static shouldPatchExecuteScript(script: string | null): boolean {
if (!script || typeof script !== 'string') {
return true
}

View file

@ -57,7 +57,7 @@ class AiHandler {
await browser.installAddOn(extFile.toString('base64'), true)
}
async handleHealing(orginalFunc: (arg0: string, arg1: string) => any, using: string, value: string, browser: WebdriverIO.Browser, options: BrowserstackOptions){
async handleHealing(orginalFunc: (arg0: string, arg1: string) => { error?: string }, using: string, value: string, browser: WebdriverIO.Browser, options: BrowserstackOptions){
const sessionId = browser.sessionId
// a utility function to escape single and double quotes
@ -114,17 +114,18 @@ class AiHandler {
config: Options.Testrunner,
browserStackConfig: BrowserStackConfig,
options: BrowserstackOptions,
caps: any,
caps: Capabilities.RequestedMultiremoteCapabilities,
browser: string
) {
if ( caps[browser].capabilities &&
!(isBrowserstackInfra(caps[browser])) &&
SUPPORTED_BROWSERS_FOR_AI.includes(caps[browser]?.capabilities?.browserName?.toLowerCase())
SUPPORTED_BROWSERS_FOR_AI.includes((caps[browser]?.capabilities as WebdriverIO.Capabilities)?.browserName?.toLowerCase() || 'unknown browser')
) {
const innerConfig = getBrowserStackUserAndKey(config, options)
if (innerConfig?.user && innerConfig.key) {
// @ts-expect-error fix type
handleHealingInstrumentation(authResult, browserStackConfig, options.selfHeal)
caps[browser].capabilities = this.updateCaps(authResult, options, caps[browser].capabilities)
caps[browser].capabilities = this.updateCaps(authResult, options, caps[browser].capabilities as WebdriverIO.Capabilities) as WebdriverIO.Capabilities
}
}
}
@ -134,7 +135,7 @@ class AiHandler {
config: Options.Testrunner,
browserStackConfig: BrowserStackConfig,
options: BrowserstackOptions,
caps: any,
caps: Capabilities.RequestedMultiremoteCapabilities,
) {
const browserNames = Object.keys(caps)
for (let i = 0; i < browserNames.length; i++) {
@ -147,7 +148,7 @@ class AiHandler {
config: Options.Testrunner,
browserStackConfig: BrowserStackConfig,
options: BrowserstackOptions,
caps: any,
caps: WebdriverIO.Capabilities,
isMultiremote: boolean
) {
try {
@ -155,13 +156,14 @@ class AiHandler {
if (innerConfig?.user && innerConfig.key) {
const authResult = await this.authenticateUser(innerConfig.user, innerConfig.key)
process.env[BSTACK_TCG_AUTH_RESULT] = JSON.stringify(authResult)
if (!isMultiremote && SUPPORTED_BROWSERS_FOR_AI.includes(caps?.browserName?.toLowerCase())) {
if (!isMultiremote && SUPPORTED_BROWSERS_FOR_AI.includes(caps.browserName?.toLowerCase() || 'unknown browser')) {
// @ts-expect-error fix type
handleHealingInstrumentation(authResult, browserStackConfig, options.selfHeal)
this.updateCaps(authResult, options, caps)
} else if (isMultiremote) {
this.handleMultiRemoteSetup(authResult, config, browserStackConfig, options, caps)
this.handleMultiRemoteSetup(authResult, config, browserStackConfig, options, caps as unknown as Capabilities.RequestedMultiremoteCapabilities)
}
}
@ -193,7 +195,9 @@ class AiHandler {
await this.installFirefoxExtension(browser)
}
browser.overwriteCommand('findElement' as any, async (orginalFunc: (arg0: string, arg1: string) => any, using: string, value: string) => {
// @ts-expect-error fix type
browser.overwriteCommand('findElement', async (orginalFunc: unknown, using: string, value: string) => {
// @ts-expect-error fix type
return await this.handleHealing(orginalFunc, using, value, browser, options)
})
}
@ -206,6 +210,7 @@ class AiHandler {
const multiRemoteBrowsers = Object.keys(caps).filter(e => Object.keys(browser).includes(e))
if (multiRemoteBrowsers.length > 0) {
for (let i = 0; i < multiRemoteBrowsers.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const remoteBrowser = (browser as any)[multiRemoteBrowsers[i]]
await this.handleSelfHeal(options, remoteBrowser)
}

View file

@ -42,7 +42,7 @@ export class BStackLogger {
log.error(message)
}
public static debug(message: string, param?: any) {
public static debug(message: string, param?: unknown) {
this.logToFile(message, 'debug')
if (param) {
log.debug(message, param)

View file

@ -3,20 +3,21 @@ import { BStackLogger } from './bstackLogger.js'
import fs from 'node:fs'
import { fireFunnelRequest } from './instrumentation/funnelInstrumentation.js'
import { BROWSERSTACK_TESTHUB_UUID, BROWSERSTACK_TESTHUB_JWT, BROWSERSTACK_OBSERVABILITY } from './constants.js'
import type { FunnelData } from './types.js'
export default class BStackCleanup {
static async startCleanup() {
try {
// Get funnel data object from saved file
const funnelDataCleanup = process.argv.includes('--funnelData')
let funnelData = null
let funnelData: FunnelData | null = null
if (funnelDataCleanup) {
const index = process.argv.indexOf('--funnelData')
const filePath = process.argv[index + 1]
funnelData = this.getFunnelDataFromFile(filePath)
funnelData = BStackCleanup.getFunnelDataFromFile(filePath)
}
if (process.argv.includes('--observability')) {
if (process.argv.includes('--observability') && funnelData) {
await this.executeObservabilityCleanup(funnelData)
}
@ -28,7 +29,7 @@ export default class BStackCleanup {
BStackLogger.error(error)
}
}
static async executeObservabilityCleanup(funnelData: any) {
static async executeObservabilityCleanup(funnelData: FunnelData) {
if (!process.env[BROWSERSTACK_TESTHUB_JWT]) {
return
}
@ -47,7 +48,7 @@ export default class BStackCleanup {
}
}
static updateO11yStopData(funnelData: any, status: string, error: unknown = undefined) {
static updateO11yStopData(funnelData: FunnelData, status: string, error: unknown = undefined) {
const toData = funnelData?.event_properties?.productUsage?.testObservability
// Return if no O11y data in funnel data
if (!toData) {
@ -65,7 +66,7 @@ export default class BStackCleanup {
toData.events.buildEvents.finished = existingStopData
}
static async sendFunnelData(funnelData: any) {
static async sendFunnelData(funnelData: FunnelData) {
try {
await fireFunnelRequest(funnelData)
BStackLogger.debug('Funnel data sent successfully from cleanup')

View file

@ -5,7 +5,7 @@ import type { BrowserstackConfig, CredentialsForCrashReportUpload, UserConfigfor
import { DEFAULT_REQUEST_CONFIG, getObservabilityKey, getObservabilityUser } from './util.js'
import { BStackLogger } from './bstackLogger.js'
type Dict = Record<string, any>
type Dict = Record<string, string>
export default class CrashReporter {
/* User test config for build run minus PII */
@ -38,7 +38,7 @@ export default class CrashReporter {
this.setCredentialsForCrashReportUpload(options, userConfig)
}
static async uploadCrashReport(exception: any, stackTrace: string) {
static async uploadCrashReport(exception: string, stackTrace: string) {
try {
if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
this.credentialsForCrashReportUpload = process.env.CREDENTIALS_FOR_CRASH_REPORTING !== undefined ? JSON.parse(process.env.CREDENTIALS_FOR_CRASH_REPORTING) : this.credentialsForCrashReportUpload
@ -74,7 +74,7 @@ export default class CrashReporter {
const url = `${DATA_ENDPOINT}/api/v1/analytics`
const encodedAuth = Buffer.from(`${this.credentialsForCrashReportUpload.username}:${this.credentialsForCrashReportUpload.password}`, 'utf8').toString('base64')
const headers: any = {
const headers: Record<string, string> = {
...DEFAULT_REQUEST_CONFIG.headers,
Authorization: `Basic ${encodedAuth}`,
}
@ -109,7 +109,7 @@ export default class CrashReporter {
}
}
static deletePIIKeysFromObject(obj: {[key: string]: any}) {
static deletePIIKeysFromObject(obj: {[key: string]: unknown}) {
if (!obj) {
return
}
@ -133,15 +133,17 @@ export default class CrashReporter {
if (Array.isArray(serviceArray) && serviceArray.length >= 2 && serviceArray[0] === 'browserstack') {
for (let idx = 1; idx < serviceArray.length; idx++) {
this.deletePIIKeysFromObject(serviceArray[idx])
serviceArray[idx] && this.deletePIIKeysFromObject(serviceArray[idx].testObservabilityOptions)
if (serviceArray[idx]) {
this.deletePIIKeysFromObject(serviceArray[idx].testObservabilityOptions)
}
}
finalServices.push(serviceArray)
break
}
}
} catch (err: any) {
} catch (err) {
/* Wrong configuration like strings instead of json objects could break this method, needs no action */
BStackLogger.error(`Error in parsing user config PII with error ${err ? (err.stack || err) : err}`)
BStackLogger.error(`Error in parsing user config PII with error ${err ? ((err as Error).stack || err) : err}`)
return configWithoutPII
}
configWithoutPII.services = finalServices

View file

@ -27,7 +27,7 @@ export function getDataFromWorkers(){
return workersData
}
export function saveWorkerData(data: Record<string, any>) {
export function saveWorkerData(data: Record<string, unknown>) {
const filePath = path.join(workersDataDirPath, 'worker-data-' + process.pid + '.json')
try {

View file

@ -30,7 +30,8 @@ import type {
PlatformMeta,
CurrentRunInfo,
StdLog,
CBTData
CBTData,
IntegrationObject
} from './types.js'
import { BStackLogger } from './bstackLogger.js'
import type { Capabilities } from '@wdio/types'
@ -76,8 +77,8 @@ class _InsightsHandler {
_isAppAutomate(): boolean {
const browserDesiredCapabilities = (this._browser?.capabilities ?? {})
const desiredCapabilities = (this._userCaps ?? {})
return !!browserDesiredCapabilities['appium:app'] || !!desiredCapabilities['appium:app'] || !!(( desiredCapabilities as any)['appium:options']?.app)
const desiredCapabilities = (this._userCaps ?? {}) as WebdriverIO.Capabilities
return !!browserDesiredCapabilities['appium:app'] || !!desiredCapabilities['appium:app'] || !!(desiredCapabilities['appium:options']?.app)
}
registerListeners() {
@ -238,7 +239,7 @@ class _InsightsHandler {
}
}
async beforeHook (test: Frameworks.Test|CucumberHook|undefined, context: any) {
async beforeHook (test: Frameworks.Test|CucumberHook|undefined, context: unknown) {
if (!frameworkSupportsHook('before', this._framework)) {
return
}
@ -291,6 +292,7 @@ class _InsightsHandler {
This won't be needed for `afterAll`, as even if `afterAll` fails all the tests that we need are already run by then, so we don't need to send the stats for them separately
*/
if (!result.passed && (hookType === 'BEFORE_EACH' || hookType === 'BEFORE_ALL' || hookType === 'AFTER_EACH')) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sendTestSkip = async (skippedTest: any) => {
// We only need to send the tests that whose state is not determined yet. The state of tests which is determined will already be sent.
@ -308,11 +310,12 @@ class _InsightsHandler {
/*
Recursively send the tests as skipped for all suites below the hook. This is to handle nested describe blocks
*/
const sendSuiteSkipped = async (suite: any) => {
const sendSuiteSkipped = async (suite: { tests: unknown[], suites: unknown[] }) => {
for (const skippedTest of suite.tests) {
await sendTestSkip(skippedTest)
}
for (const skippedSuite of suite.suites) {
// @ts-expect-error fix types here
await sendSuiteSkipped(skippedSuite)
}
}
@ -532,7 +535,7 @@ class _InsightsHandler {
// log screenshot
const body = 'body' in args ? args.body : undefined
const result = 'result' in args ? args.result : undefined
const result = 'result' in args ? args.result as { value: string } : undefined
if (Boolean(process.env[TESTOPS_SCREENSHOT_ENV]) && isScreenshotCommand(args) && result?.value) {
await this.listener.onScreenshot([{
test_run_uuid: testMeta.uuid,
@ -566,6 +569,7 @@ class _InsightsHandler {
* private methods
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private attachHookData (context: any, hookId: string): void {
if (context.currentTest && context.currentTest.parent) {
const parentTest = `${context.currentTest.parent.title} - ${context.currentTest.title}`
@ -580,6 +584,7 @@ class _InsightsHandler {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private setHooksFromSuite(parent: any, hookId: string): boolean {
if (!parent) {
return false
@ -697,6 +702,7 @@ class _InsightsHandler {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getTestRunId(context: any): string|undefined {
if (!context) {
return
@ -713,6 +719,7 @@ class _InsightsHandler {
return this.getTestRunIdFromSuite(context.test.parent)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getTestRunIdFromSuite(parent: any): string|undefined {
if (!parent) {
return
@ -833,10 +840,10 @@ class _InsightsHandler {
}
async sendCBTInfo() {
const integrationsData: any = {}
const integrationsData: Record<string, IntegrationObject> = {}
if (this._browser && this._platformMeta) {
const provider = getCloudProvider(this._browser)
const provider = getCloudProvider(this._browser) as keyof IntegrationObject
integrationsData[provider] = this.getIntegrationsObject()
}

View file

@ -2,7 +2,7 @@ import os from 'node:os'
import util, { format } from 'node:util'
import path from 'node:path'
import fs from 'node:fs'
import UsageStats from '../testOps/usageStats.js'
import UsageStats, { type UsageStat } from '../testOps/usageStats.js'
import { BStackLogger } from '../bstackLogger.js'
import type BrowserStackConfig from '../config.js'
import { BSTACK_SERVICE_VERSION, FUNNEL_INSTRUMENTATION_URL } from '../constants.js'
@ -10,6 +10,7 @@ import { getDataFromWorkers } from '../data-store.js'
import { getProductMap } from '../testHub/utils.js'
import fetchWrap from '../fetchWrapper.js'
import type { BrowserstackHealing } from '@browserstack/ai-sdk-node'
import type { FunnelData, EventProperties } from '../types.js'
async function fireFunnelTestEvent(eventType: string, config: BrowserStackConfig) {
if (!config.userName || !config.accessKey) {
@ -44,7 +45,7 @@ export function saveFunnelData(eventType: string, config: BrowserStackConfig): s
return filePath
}
function redactCredentialsFromFunnelData(data: any) {
function redactCredentialsFromFunnelData(data: FunnelData) {
if (data) {
if (data.userName) {
data.userName = '[REDACTED]'
@ -57,7 +58,7 @@ function redactCredentialsFromFunnelData(data: any) {
}
// Called from two different process
export async function fireFunnelRequest(data: any): Promise<void> {
export async function fireFunnelRequest(data: FunnelData): Promise<void> {
const { userName, accessKey } = data
redactCredentialsFromFunnelData(data)
@ -99,8 +100,8 @@ function getProductList(config: BrowserStackConfig) {
return products
}
function buildEventData(eventType: string, config: BrowserStackConfig): any {
const eventProperties: any = {
function buildEventData(eventType: string, config: BrowserStackConfig) {
const eventProperties: EventProperties = {
// Framework Details
language_framework: getLanguageFramework(config.framework),
referrer: getReferrer(config.framework),
@ -117,11 +118,12 @@ function buildEventData(eventType: string, config: BrowserStackConfig): any {
// Product Details
productMap: getProductMap(config),
product: getProductList(config),
product: getProductList(config)
}
if (eventType === 'SDKTestSuccessful') {
const workerData = getDataFromWorkers()
// @ts-expect-error
eventProperties.productUsage = getProductUsage(workerData)
}
@ -131,11 +133,11 @@ function buildEventData(eventType: string, config: BrowserStackConfig): any {
event_type: eventType,
detectedFramework: 'WebdriverIO-' + config.framework,
event_properties: eventProperties
}
} as unknown as FunnelData
}
function getProductUsage(workersData: any[]) {
function getProductUsage(workersData: { usageStats: UsageStat }[]) {
return {
testObservability: UsageStats.getInstance().getFormattedData(workersData)
}
@ -159,8 +161,8 @@ const sendEvent = {
tcgProxyFailure: (config: BrowserStackConfig) => fireFunnelTestEvent('SDKTestTcgProxyFailure', config),
}
function isProxyError(authResult: any): boolean {
return (authResult as BrowserstackHealing.InitErrorResponse).status === 502
function isProxyError(authResult: { status?: number }): boolean {
return (authResult as BrowserstackHealing.InitErrorResponse)?.status === 502
}
function handleProxyError(config: BrowserStackConfig, isSelfHealEnabled: boolean | undefined) {
@ -215,8 +217,17 @@ function handleInitializationFailure(status: number, config: BrowserStackConfig,
}
}
interface AuthResult {
message: string
isAuthenticated: boolean
status: number
userId: string
groupId: string
isHealingEnabled: boolean
}
export function handleHealingInstrumentation(
authResult: BrowserstackHealing.InitErrorResponse | BrowserstackHealing.InitSuccessResponse,
authResult: AuthResult,
config: BrowserStackConfig,
isSelfHealEnabled: boolean | undefined,
) {
@ -226,7 +237,7 @@ export function handleHealingInstrumentation(
return
}
const { message, isAuthenticated, status, userId, groupId, isHealingEnabled: isHealingEnabledForUser } = authResult as any
const { message, isAuthenticated, status, userId, groupId, isHealingEnabled: isHealingEnabledForUser } = authResult
if (message === 'Upgrade required') {
handleUpgradeRequired(isSelfHealEnabled)

View file

@ -76,7 +76,9 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
PercyLogger.clearLogFile()
setupExitHandlers()
// added to maintain backward compatibility with webdriverIO v5
this._config || (this._config = _options)
if (!this._config) {
this._config = _options
}
this.browserStackConfig = BrowserStackConfig.getInstance(_options, _config)
if (Array.isArray(capabilities)) {
capabilities
@ -180,12 +182,12 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
}
try {
CrashReporter.setConfigDetails(this._config, capabilities, this._options)
} catch (error: any) {
} catch (error: unknown) {
BStackLogger.error(`[Crash_Report_Upload] Config processing failed due to ${error}`)
}
}
async onWorkerStart (cid: any, caps: any) {
async onWorkerStart (cid: string, caps: WebdriverIO.Capabilities) {
try {
if (this._options.percy && this._percyBestPlatformCaps) {
const isThisBestPercyPlatform = ObjectsAreEqual(caps, this._percyBestPlatformCaps)
@ -193,12 +195,12 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
process.env.BEST_PLATFORM_CID = cid
}
}
} catch (err: unknown) {
} catch (err) {
PercyLogger.error(`Error while setting best platform for Percy snapshot at worker start ${err}`)
}
}
async onPrepare (config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities) {
async onPrepare (config: Options.Testrunner, capabilities: Capabilities.TestrunnerCapabilities | WebdriverIO.Capabilities) {
// Send Funnel start request
await sendStart(this.browserStackConfig)
@ -206,16 +208,16 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
if (!shouldAddServiceVersion(this._config, this._options.testObservability, capabilities as Capabilities.BrowserStackCapabilities)) {
try {
if ((capabilities as Capabilities.BrowserStackCapabilities).browserName) {
capabilities = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities, false)
capabilities = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities as WebdriverIO.Capabilities, false)
} else if ( Array.isArray(capabilities)){
for (let i = 0; i < capabilities.length; i++) {
if ((capabilities[i] as Capabilities.BrowserStackCapabilities).browserName) {
capabilities[i] = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities[i], false)
capabilities[i] = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities[i] as WebdriverIO.Capabilities, false)
}
}
} else if (isValidCapsForHealing(capabilities as any)) {
} else if (isValidCapsForHealing(capabilities)) {
// setting up healing in case capabilities.xyz.capabilities.browserName where xyz can be anything:
capabilities = await AiHandler.setup(this._config, this.browserStackConfig, this._options, capabilities, true)
}
@ -238,8 +240,8 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
try {
app = await this._validateApp(appConfig)
} catch (error: any){
throw new SevereServiceError(error)
} catch (error: unknown){
throw new SevereServiceError((error as Error).message)
}
if (VALID_APP_EXTENSION.includes(path.extname(app.app!))){
@ -255,7 +257,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
}
BStackLogger.info(`Using app: ${app.app}`)
this._updateCaps(capabilities, 'app', app.app)
this._updateCaps(capabilities as Capabilities.TestrunnerCapabilities, 'app', app.app)
}
/**
@ -263,17 +265,17 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
*/
if (this._options.buildIdentifier) {
this._buildIdentifier = this._options.buildIdentifier
this._updateCaps(capabilities, 'buildIdentifier', this._buildIdentifier)
this._updateCaps(capabilities as Capabilities.TestrunnerCapabilities, 'buildIdentifier', this._buildIdentifier)
}
/**
* evaluate buildIdentifier in case unique execution identifiers are present
* e.g., ${BUILD_NUMBER} and ${DATE_TIME}
*/
this._handleBuildIdentifier(capabilities)
this._handleBuildIdentifier(capabilities as Capabilities.TestrunnerCapabilities)
// remove accessibilityOptions from the capabilities if present
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions')
this._updateObjectTypeCaps(capabilities as Capabilities.TestrunnerCapabilities, 'accessibilityOptions')
const shouldSetupPercy = this._options.percy || (isUndefined(this._options.percy) && this._options.app)
if (this._options.testObservability || this._accessibilityAutomation || shouldSetupPercy) {
@ -298,27 +300,27 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
}
}, {})
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', filteredOpts)
this._updateObjectTypeCaps(capabilities as Capabilities.TestrunnerCapabilities, 'accessibilityOptions', filteredOpts)
} else if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', {})
this._updateObjectTypeCaps(capabilities as Capabilities.TestrunnerCapabilities, 'accessibilityOptions', {})
}
if (shouldSetupPercy) {
try {
const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities)
this._percyBestPlatformCaps = bestPlatformPercyCaps
const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities as Capabilities.TestrunnerCapabilities)
this._percyBestPlatformCaps = bestPlatformPercyCaps as WebdriverIO.Capabilities
process.env[BROWSERSTACK_PERCY] = 'false'
await this.setupPercy(this._options, this._config, {
projectName: this._projectName
})
this._updateBrowserStackPercyConfig()
} catch (err: unknown) {
} catch (err) {
PercyLogger.error(`Error while setting up Percy ${err}`)
}
}
this._updateCaps(capabilities, 'testhubBuildUuid')
this._updateCaps(capabilities, 'buildProductMap')
this._updateCaps(capabilities as Capabilities.TestrunnerCapabilities, 'testhubBuildUuid')
this._updateCaps(capabilities as Capabilities.TestrunnerCapabilities, 'buildProductMap')
if (!this._options.browserstackLocal) {
return BStackLogger.info('browserstackLocal is not enabled - skipping...')
@ -331,9 +333,9 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
this.browserstackLocal = new BrowserstackLocalLauncher.Local()
this._updateCaps(capabilities, 'local')
this._updateCaps(capabilities as Capabilities.TestrunnerCapabilities, 'local')
if (opts.localIdentifier) {
this._updateCaps(capabilities, 'localIdentifier', opts.localIdentifier)
this._updateCaps(capabilities as Capabilities.TestrunnerCapabilities, 'localIdentifier', opts.localIdentifier)
}
/**
@ -452,12 +454,14 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
let signal = 0
const handler = async () => {
signal++
signal === 1 && await this.stopPercy()
if (signal === 1) {
await this.stopPercy()
}
}
process.on('beforeExit', handler)
process.on('SIGINT', handler)
process.on('SIGTERM', handler)
} catch (err: unknown) {
} catch (err) {
PercyLogger.debug(`Error in percy setup ${format(err)}`)
process.env[BROWSERSTACK_PERCY] = 'false'
}
@ -488,7 +492,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
form.append('custom_id', app.customId)
}
const headers: any = {
const headers: Record<string, string> = {
Authorization: getBasicAuthHeader(this._config.user as string, this._config.key as string),
}
@ -540,7 +544,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
BStackLogger.logToFile(`Response - ${format(response)}`, 'debug')
}
_updateObjectTypeCaps(capabilities?: Capabilities.TestrunnerCapabilities, capType?: string, value?: { [key: string]: any }) {
_updateObjectTypeCaps(capabilities?: Capabilities.TestrunnerCapabilities, capType?: string, value?: { [key: string]: unknown }) {
try {
if (Array.isArray(capabilities)) {
capabilities
@ -831,7 +835,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
const newIdentifier = 1
this._updateLocalBuildCache(filePath, this._buildName, 1)
return newIdentifier.toString()
} catch (error: any) {
} catch {
return null
}
}

View file

@ -4,8 +4,8 @@ const BSTestOpsLogger = new logReportingAPI({})
//Disabling eslint here as there params can be used later
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function BSTestOpsLog4JSAppender(layout: Function, timezoneOffset: any): Function {
return (loggingEvent: any) => {
function BSTestOpsLog4JSAppender(layout: Function, timezoneOffset: unknown): Function {
return (loggingEvent: { level?: { levelStr: string }, data: string[] }) => {
BSTestOpsLogger.log({
level: loggingEvent.level ? loggingEvent.level.levelStr : null,
message: loggingEvent.data ? loggingEvent.data.join(' ') : null
@ -13,7 +13,8 @@ function BSTestOpsLog4JSAppender(layout: Function, timezoneOffset: any): Functio
}
}
export const configure = (config: any, layouts: any): Function => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const configure = (config: { timezoneOffset: string, layout: { type: string } }, layouts: any): Function => {
let layout = layouts.colouredLayout
if (config.layout) {
layout = layouts.layout(config.layout.type, config.layout)

View file

@ -5,11 +5,7 @@ const LOG_LEVELS = {
}
class logPatcher extends Transport {
constructor(opts: any) {
super(opts)
}
logToTestOps = (level = LOG_LEVELS.INFO, message = ['']) => {
logToTestOps = (level = LOG_LEVELS.INFO, message = [''] as unknown[]) => {
(process.emit as Function)(`bs:addLog:${process.pid}`, {
timestamp: new Date().toISOString(),
level: level.toUpperCase(),
@ -20,27 +16,27 @@ class logPatcher extends Transport {
}
/* Patching this would show user an extended trace on their cli */
trace = (...message: any) => {
trace = (...message: unknown[]) => {
this.logToTestOps(LOG_LEVELS.TRACE, message)
}
debug = (...message:any) => {
debug = (...message: unknown[]) => {
this.logToTestOps(LOG_LEVELS.DEBUG, message)
}
info = (...message: any) => {
info = (...message: unknown[]) => {
this.logToTestOps(LOG_LEVELS.INFO, message)
}
warn = (...message: any) => {
warn = (...message: unknown[]) => {
this.logToTestOps(LOG_LEVELS.WARN, message)
}
error = (...message: any) => {
error = (...message: unknown[]) => {
this.logToTestOps(LOG_LEVELS.ERROR, message)
}
log = (...message:any) => {
log = (...message: unknown[]) => {
this.logToTestOps(LOG_LEVELS.INFO, message)
}
}

View file

@ -10,7 +10,7 @@ const LOG_LEVELS = {
}
class logReportingAPI extends Transport {
log(info: any, callback: undefined|Function = undefined) {
log(info: { level: string | null, message: string | null }, callback: undefined|Function = undefined) {
setImmediate(() => {
this.emit('logged', info)
})
@ -28,9 +28,9 @@ class logReportingAPI extends Transport {
}
}
logToTestOps = (level = LOG_LEVELS.INFO, message = '', consoleLog = true) => {
logToTestOps = (level = LOG_LEVELS.INFO, message: string | null = '', consoleLog = true) => {
if (consoleLog) {
(consoleHolder as any)[level.toLowerCase()](message)
consoleHolder[level.toLowerCase() as 'info' | 'log'](message)
}
(process.emit as Function)(`bs:addLog:${process.pid}`, {
timestamp: new Date().toISOString(),
@ -42,23 +42,23 @@ class logReportingAPI extends Transport {
}
/* Patching this would show user an extended trace on their cli */
trace = (message: any) => {
trace = (message: string | null) => {
this.logToTestOps(LOG_LEVELS.TRACE, message)
}
debug = (message: any) => {
debug = (message: string | null) => {
this.logToTestOps(LOG_LEVELS.DEBUG, message)
}
info = (message: any) => {
info = (message: string | null) => {
this.logToTestOps(LOG_LEVELS.INFO, message)
}
warn = (message: any) => {
warn = (message: string | null) => {
this.logToTestOps(LOG_LEVELS.WARN, message)
}
error = (message: any) => {
error = (message: string | null) => {
this.logToTestOps(LOG_LEVELS.ERROR, message)
}
}

View file

@ -6,7 +6,7 @@ import { BStackLogger } from './bstackLogger.js'
export default class PerformanceTester {
static _observer: PerformanceObserver
static _csvWriter: any
static _csvWriter: { writeRecords: (arg0: unknown) => Promise<void> }
private static _events: PerformanceEntry[] = []
static started = false
@ -98,6 +98,6 @@ export default class PerformanceTester {
})
this._csvWriter.writeRecords(dat)
.then(() => BStackLogger.info('Performance CSV report generated successfully'))
.catch((error: any) => console.error(error))
.catch((error: unknown) => console.error(error))
}
}

View file

@ -111,7 +111,7 @@ class _TestReporter extends WDIOReporter {
// Sometimes in cases where a file has two suites. Then the file name be unknown for second suite, so getting the filename from first suite
filename = this._suiteName || suiteStats.file
}
} catch (e) {
} catch {
BStackLogger.debug('Error in decoding file name of suite')
}
}

View file

@ -67,7 +67,7 @@ export default class RequestQueueHandler {
callCallback = async (data: UploadType[], kind: string) => {
BStackLogger.debug('calling callback with kind ' + kind)
this.callback && await this.callback(data)
await this.callback?.(data)
}
resetEventBatchPolling () {

View file

@ -2,6 +2,18 @@ import path from 'node:path'
import fs from 'node:fs'
import os from 'node:os'
interface Scripts {
scan: string
getResults: string
getResultsSummary: string
saveResults: string
}
interface Command {
name: string
class: string
}
class AccessibilityScripts {
private static instance: AccessibilityScripts | null = null
@ -9,7 +21,7 @@ class AccessibilityScripts {
public getResults: string | null = null
public getResultsSummary: string | null = null
public saveTestResults: string | null = null
public commandsToWrap: Array<any> | null = null
public commandsToWrap: Array<Command> | null = null
public browserstackFolderPath = path.join(os.homedir(), '.browserstack')
public commandsPath = path.join(this.browserstackFolderPath, 'commands.json')
@ -33,12 +45,12 @@ class AccessibilityScripts {
this.update(JSON.parse(data))
}
}
} catch (error: any) {
} catch {
/* Do nothing */
}
}
public update(data: { commands: any[], scripts: Record<string, any> }) {
public update(data: { commands: [], scripts: Scripts }) {
if (data.scripts) {
this.performScan = data.scripts.scan
this.getResults = data.scripts.getResults

View file

@ -53,7 +53,10 @@ export default class BrowserstackService implements Services.ServiceInstance {
) {
this._options = { ...DEFAULT_OPTIONS, ...options }
// added to maintain backward compatibility with webdriverIO v5
this._config || (this._config = this._options)
if (!this._config) {
this._config = this._options
}
this._observability = this._options.testObservability
this._accessibility = this._options.accessibility
this._percy = isTrue(process.env.BROWSERSTACK_PERCY)
@ -205,7 +208,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
} catch (err) {
BStackLogger.error(`Error in service class before function: ${err}`)
if (shouldProcessEventForTesthub('')) {
CrashReporter.uploadCrashReport(`Error in service class before function: ${err}`, err && (err as any).stack)
CrashReporter.uploadCrashReport(`Error in service class before function: ${err}`, err ? (err as Error).stack as string : 'unknown error')
}
}
@ -241,7 +244,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
}
}
async beforeHook (test: Frameworks.Test|CucumberHook, context: any) {
async beforeHook (test: Frameworks.Test|CucumberHook, context: unknown) {
if (this._config.framework !== 'cucumber') {
this._currentTest = test as Frameworks.Test // not update currentTest when this is called for cucumber step
}
@ -385,8 +388,8 @@ export default class BrowserstackService implements Services.ServiceInstance {
if (!this._browser.isMultiremote) {
BStackLogger.info(`Update (reloaded) job with sessionId ${oldSessionId}, ${status}`)
} else {
const browserName = (this._browser as any as WebdriverIO.MultiRemoteBrowser).instances.filter(
(browserName: string) => this._browser && (this._browser as any as WebdriverIO.MultiRemoteBrowser).getInstance(browserName).sessionId === newSessionId)[0]
const browserName = (this._browser as unknown as WebdriverIO.MultiRemoteBrowser).instances.filter(
(browserName: string) => this._browser && (this._browser as unknown as WebdriverIO.MultiRemoteBrowser).getInstance(browserName).sessionId === newSessionId)[0]
BStackLogger.info(`Update (reloaded) multiremote job for browser "${browserName}" and sessionId ${oldSessionId}, ${status}`)
}
@ -410,11 +413,15 @@ export default class BrowserstackService implements Services.ServiceInstance {
_isAppAutomate(): boolean {
const browserDesiredCapabilities = (this._browser?.capabilities ?? {})
const desiredCapabilities = (this._caps ?? {})
return !!browserDesiredCapabilities['appium:app'] || !!desiredCapabilities['appium:app'] || !!(( desiredCapabilities as any)['appium:options']?.app)
const desiredCapabilities = (this._caps ?? {}) as WebdriverIO.Capabilities
return (
!!browserDesiredCapabilities['appium:app'] ||
!!desiredCapabilities['appium:app'] ||
!!desiredCapabilities['appium:options']?.app
)
}
_updateJob (requestBody: any) {
_updateJob (requestBody: unknown) {
return this._multiRemoteAction((sessionId: string, browserName: string) => {
BStackLogger.info(browserName
? `Update multiremote job for browser "${browserName}" and sessionId ${sessionId}`
@ -433,7 +440,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
return action(this._browser.sessionId)
}
const multiremotebrowser = this._browser as any as WebdriverIO.MultiRemoteBrowser
const multiremotebrowser = this._browser as unknown as WebdriverIO.MultiRemoteBrowser
return Promise.all(multiremotebrowser.instances
.filter((browserName: string) => {
const cap = getBrowserCapabilities(multiremotebrowser, this._caps, browserName)
@ -445,7 +452,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
)
}
_update(sessionId: string, requestBody: any) {
_update(sessionId: string, requestBody: unknown) {
if (!isBrowserstackSession(this._browser)) {
return Promise.resolve()
}
@ -453,7 +460,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
BStackLogger.debug(`Updating Browserstack session at ${sessionUrl} with request body: `, requestBody)
const encodedAuth = Buffer.from(`${this._config.user}:${this._config.key}`, 'utf8').toString('base64')
const headers: any = {
const headers: Record<string, string> = {
'Content-Type': 'application/json; charset=utf-8',
Authorization: `Basic ${encodedAuth}`,
}
@ -483,7 +490,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
let browserUrl
const encodedAuth = Buffer.from(`${this._config.user}:${this._config.key}`, 'utf8').toString('base64')
const headers: any = {
const headers: Record<string, string> = {
'Content-Type': 'application/json; charset=utf-8',
Authorization: `Basic ${encodedAuth}`,
}
@ -546,7 +553,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
return this._executeCommand('annotate', { data, level: 'info' })
}
private async _executeCommand<T = any>(
private async _executeCommand<T = unknown>(
action: string,
args?: object,
) {
@ -558,7 +565,7 @@ export default class BrowserstackService implements Services.ServiceInstance {
const script = `browserstack_executor: ${JSON.stringify(cmd)}`
if (this._browser.isMultiremote) {
const multiRemoteBrowser = this._browser as any as WebdriverIO.MultiRemoteBrowser
const multiRemoteBrowser = this._browser as unknown as WebdriverIO.MultiRemoteBrowser
return Promise.all(Object.keys(this._caps).map(async (browserName) => {
const browser = multiRemoteBrowser.getInstance(browserName)
return (await browser.execute<T, []>(script))

View file

@ -4,13 +4,22 @@ import type BrowserStackConfig from '../config.js'
import { BStackLogger } from '../bstackLogger.js'
import { isTrue } from '../util.js'
export const getProductMap = (config: BrowserStackConfig): any => {
interface ErrorType {
key: string
message: string
}
export interface Errors {
errors: ErrorType[]
}
export const getProductMap = (config: BrowserStackConfig): { [key: string]: boolean } => {
return {
'observability': config.testObservability.enabled,
'accessibility': config.accessibility,
'percy': config.percy,
'automate': config.automate,
'app_automate': config.appAutomate
observability: config.testObservability.enabled,
accessibility: config.accessibility,
percy: config.percy,
automate: config.automate,
app_automate: config.appAutomate
}
}
@ -27,17 +36,17 @@ export const shouldProcessEventForTesthub = (eventType: string): boolean => {
return Boolean(process.env[BROWSERSTACK_ACCESSIBILITY] || process.env[BROWSERSTACK_OBSERVABILITY] || process.env[BROWSERSTACK_PERCY])!
}
export const handleErrorForObservability = (error: any): void => {
export const handleErrorForObservability = (error: Errors | null): void => {
process.env[BROWSERSTACK_OBSERVABILITY] = 'false'
logBuildError(error, 'observability')
}
export const handleErrorForAccessibility = (error: any): void => {
export const handleErrorForAccessibility = (error: Errors | null): void => {
process.env[BROWSERSTACK_ACCESSIBILITY] = 'false'
logBuildError(error, 'accessibility')
}
export const logBuildError = (error: any, product: string = ''): void => {
export const logBuildError = (error: Errors | null, product: string = ''): void => {
if (!error || !error.errors) {
BStackLogger.error(`${product.toUpperCase()} Build creation failed ${error!}`)
return

View file

@ -12,6 +12,13 @@ interface JSONConversionSettings {
nestedGroups?: boolean
}
export interface Feature {
triggeredCount: number
sentCount: number
failedCount: number
groups: Feature[]
}
class FeatureStats {
private triggeredCount: number = 0
private sentCount: number = 0
@ -126,7 +133,7 @@ class FeatureStats {
}
}
public static fromJSON(json: any): FeatureStats {
public static fromJSON(json: Feature): FeatureStats {
const stats = new FeatureStats()
if (!json || isObjectEmpty(json)) {

View file

@ -26,7 +26,7 @@ class Listener {
private readonly logEvents: FeatureStats = this.usageStats.logStats
private requestBatcher?: RequestQueueHandler
private pendingUploads = 0
private static _accessibilityOptions?: { [key: string]: any; }
private static _accessibilityOptions?: { [key: string]: unknown; }
private static _testRunAccessibilityVar?: boolean = false
// Making the constructor private to use singleton pattern
@ -40,7 +40,7 @@ class Listener {
return Listener.instance
}
public static setAccessibilityOptions(options: { [key: string]: any; } | undefined) {
public static setAccessibilityOptions(options: { [key: string]: unknown; } | undefined) {
Listener._accessibilityOptions = options
}
@ -225,7 +225,7 @@ class Listener {
await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'BATCH_DATA', data)
BStackLogger.debug('callback: marking events success ' + data.length)
this.eventsSuccess(data)
} catch (e) {
} catch {
BStackLogger.debug('callback: marking events failed ' + data.length)
this.eventsFailed(data)
} finally {

View file

@ -3,8 +3,9 @@ class TestOpsConfig {
public buildStopped: boolean = false
public buildHashedId?: string
static getInstance(...args: any[]) {
static getInstance(...args: unknown[]) {
if (!this._instance) {
// @ts-expect-error passing args to constructor
this._instance = new TestOpsConfig(...args)
}
return this._instance

View file

@ -1,9 +1,23 @@
import FeatureStats from './featureStats.js'
import FeatureStats, { type Feature } from './featureStats.js'
import FeatureUsage from './featureUsage.js'
import { BStackLogger } from '../bstackLogger.js'
import TestOpsConfig from './testOpsConfig.js'
import type { TOUsageStats } from '../types.js'
export interface UsageStat {
testEvents: {
started: Feature
finished: Feature
}
hookEvents: {
started: Feature
finished: Feature
}
logEvents: Feature
cbtSessionEvents: Feature
cbtSessionStats: Feature
}
class UsageStats {
public static instance: UsageStats
public testStartedStats: FeatureStats
@ -42,7 +56,7 @@ class UsageStats {
this.logStats.add(usageStats.logStats)
}
public getFormattedData(workersData: any[]) {
public getFormattedData(workersData: { usageStats: UsageStat }[]) {
this.addDataFromWorkers(workersData)
const testOpsConfig = TestOpsConfig.getInstance()
const usage: TOUsageStats = {
@ -64,7 +78,7 @@ class UsageStats {
return usage
}
public addDataFromWorkers(workersData: any[]) {
public addDataFromWorkers(workersData: { usageStats: UsageStat }[]) {
workersData.map(workerData => {
try {
const usageStatsForWorker = UsageStats.fromJSON(workerData.usageStats)
@ -111,7 +125,7 @@ class UsageStats {
}
}
public static fromJSON(data: any) {
public static fromJSON(data: UsageStat) {
const usageStats = new UsageStats()
usageStats.testStartedStats = FeatureStats.fromJSON(data.testEvents.started)
usageStats.testFinishedStats = FeatureStats.fromJSON(data.testEvents.finished)

View file

@ -1,7 +1,7 @@
import type { Capabilities, Options, Frameworks } from '@wdio/types'
import type { Options as BSOptions } from 'browserstack-local'
export type MultiRemoteAction = (sessionId: string, browserName?: string) => Promise<any>;
export type MultiRemoteAction = (sessionId: string, browserName?: string) => Promise<unknown>;
export type AppConfig = {
id?: string,
@ -67,7 +67,9 @@ export interface BrowserstackConfig {
/**
* Set the Percy related config options under this key.
*/
percyOptions?: any;
percyOptions?: {
version?: string,
};
/**
* Set this to true to enable BrowserStack Accessibility Automation which will
* automically conduct accessibility testing on your pre-existing test builds
@ -79,7 +81,7 @@ export interface BrowserstackConfig {
* Customise the Accessibility-related config options under this key.
* For e.g. wcagVersion, bestPractice issues, needsReview issues etc.
*/
accessibilityOptions?: { [key: string]: any; };
accessibilityOptions?: { [key: string]: unknown; };
/**
* Set this with app file path present locally on your device or
* app hashed id returned after uploading app to BrowserStack or
@ -225,7 +227,7 @@ export interface UserConfig {
buildTag?: string,
bstackServiceVersion?: string,
buildIdentifier?: string,
accessibilityOptions?: { [key: string]: any; }
accessibilityOptions?: { [key: string]: unknown; }
}
export interface UploadType {
@ -242,7 +244,7 @@ export interface LogData {
hook_run_uuid?: string
message?: string
level?: string
http_response?: any
http_response?: unknown
}
export interface StdLog extends LogData {
@ -276,7 +278,7 @@ export interface LaunchResponse {
status: string;
commandsToWrap: {
scriptsToRun: string[];
commands: any[];
commands: unknown[];
};
scripts: {
name: string;
@ -284,7 +286,7 @@ export interface LaunchResponse {
}[];
capabilities: {
name: string,
value: any
value: unknown
}[];
}
};
@ -292,7 +294,7 @@ export interface LaunchResponse {
export interface UserConfigforReporting {
framework?: string,
services?: any[],
services?: unknown[],
capabilities?: WebdriverIO.Capabilities,
env?: {
'BROWSERSTACK_BUILD': string | undefined,
@ -306,7 +308,7 @@ export interface CredentialsForCrashReportUpload {
password?: string
}
interface IntegrationObject {
export interface IntegrationObject {
capabilities?: WebdriverIO.Capabilities,
session_id?: string
browser?: string
@ -351,5 +353,39 @@ export interface TOUsageStats {
enabled: boolean
manuallySet: boolean
buildHashedId?: string
events?: any
events?: unknown
}
export interface EventProperties {
language_framework: string
referrer: string
language: string
languageVersion: string
buildName: string
buildIdentifier: string
os: string
hostname: string
productMap: { [key: string]: boolean }
product: string[]
productUsage?: {
testObservability: {
events: {
buildEvents: {
finished: {
status: string
error?: string
stoppedFrom: string
}
}
}
}
}
}
export interface FunnelData {
userName?: string
accessKey?: string
event_type?: string,
detectedFramework?: string,
event_properties: EventProperties
}

View file

@ -17,6 +17,7 @@ import logPatcher from './logPatcher.js'
import PerformanceTester from './performance-tester.js'
import { getProductMap, logBuildError, handleErrorForObservability, handleErrorForAccessibility } from './testHub/utils.js'
import type BrowserStackConfig from './config.js'
import type { Errors } from './testHub/utils.js'
import type { UserConfig, UploadType, BrowserstackConfig, BrowserstackOptions, LaunchResponse } from './types.js'
import type { ITestCaseHookParameter } from './cucumber-types.js'
@ -96,7 +97,7 @@ export function getBrowserDescription(cap: WebdriverIO.Capabilities) {
* These keys describe the browser the test was run on
*/
return BROWSER_DESCRIPTION
.map((k) => (cap as any)[k])
.map((k) => (cap)[k as keyof typeof cap])
.filter(Boolean)
.join(' ')
}
@ -147,22 +148,23 @@ export function getParentSuiteName(fullTitle: string, testSuiteTitle: string): s
return parentSuiteName.trim()
}
function processError(error: any, fn: Function, args: any[]) {
function processError(error: Error, fn: Function, args: unknown[]) {
BStackLogger.error(`Error in executing ${fn.name} with args ${args}: ${error}`)
let argsString: string
try {
argsString = JSON.stringify(args)
} catch (e) {
} catch {
argsString = util.inspect(args, { depth: 2 })
}
CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack)
CrashReporter.uploadCrashReport(`Error in executing ${fn.name} with args ${argsString} : ${error}`, error && error.stack || 'unknown error')
}
export function o11yErrorHandler(fn: Function) {
return function (...args: any) {
return function (...args: unknown[]) {
try {
let functionToHandle = fn
if (process.env[PERF_MEASUREMENT_ENV]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
functionToHandle = PerformanceTester.getPerformance().timerify(functionToHandle as any)
}
const result = functionToHandle(...args)
@ -171,13 +173,13 @@ export function o11yErrorHandler(fn: Function) {
}
return result
} catch (error) {
processError(error, fn, args)
processError(error as Error, fn, args)
}
}
}
export function errorHandler(fn: Function) {
return function (...args: any) {
return function (...args: unknown[]) {
try {
const functionToHandle = fn
const result = functionToHandle(...args)
@ -191,7 +193,7 @@ export function errorHandler(fn: Function) {
}
}
export async function nodeRequest(requestType: string, apiEndpoint: string, options: any, apiUrl: string, timeout: number = 120000) {
export async function nodeRequest(requestType: string, apiEndpoint: string, options: RequestInit, apiUrl: string, timeout: number = 120000) {
try {
const controller = new AbortController()
@ -207,17 +209,22 @@ export async function nodeRequest(requestType: string, apiEndpoint: string, opti
clearTimeout(timeoutId)
return await response.json()
} catch (error : any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
BStackLogger.debug(`Error in firing request ${apiUrl}/${apiEndpoint}: ${format(error)}`)
const isLogUpload = apiEndpoint === UPLOAD_LOGS_ENDPOINT
if (error && error.response) {
const errorMessageJson = error.response.body ? JSON.parse(error.response.body.toString()) : null
const errorMessage = errorMessageJson ? errorMessageJson.message : null
if (errorMessage) {
isLogUpload ? BStackLogger.debug(`${errorMessage} - ${error.stack}`) : BStackLogger.error(`${errorMessage} - ${error.stack}`)
} else {
isLogUpload ? BStackLogger.debug(`${error.stack}`) : BStackLogger.error(`${error.stack}`)
const message = `${errorMessage} - ${error.stack}`
if (isLogUpload) {
BStackLogger.debug(message)
} else {
BStackLogger.error(message)
}
}
if (isLogUpload) {
return
}
@ -238,7 +245,7 @@ export async function nodeRequest(requestType: string, apiEndpoint: string, opti
A class wrapper for error handling. The wrapper wraps all the methods of the class with a error handler function.
If any exception occurs in any of the class method, that will get caught in the wrapper which logs and reports the error.
*/
type ClassType = { new(...args: any[]): any; }; // A generic type for a class
type ClassType = { new(...args: unknown[]): unknown; }; // A generic type for a class
export function o11yClassErrorHandler<T extends ClassType>(errorClass: T): T {
const prototype = errorClass.prototype
@ -252,7 +259,7 @@ export function o11yClassErrorHandler<T extends ClassType>(errorClass: T): T {
// In order to preserve this context, need to define like this
Object.defineProperty(prototype, methodName, {
writable: true,
value: function(...args: any) {
value: function(...args: unknown[]) {
try {
const result = (process.env[PERF_MEASUREMENT_ENV] ? PerformanceTester.getPerformance().timerify(method) : method).call(this, ...args)
if (result instanceof Promise) {
@ -261,7 +268,7 @@ export function o11yClassErrorHandler<T extends ClassType>(errorClass: T): T {
return result
} catch (err) {
processError(err, method, args)
processError(err as Error, method, args)
}
}
})
@ -277,7 +284,7 @@ export const processTestObservabilityResponse = (response: LaunchResponse) => {
return
}
if (!response.observability.success) {
handleErrorForObservability(response.observability)
handleErrorForObservability(response.observability as Errors)
return
}
process.env[BROWSERSTACK_OBSERVABILITY] = 'true'
@ -287,16 +294,16 @@ export const processTestObservabilityResponse = (response: LaunchResponse) => {
}
interface DataElement {
[key: string]: any
[key: string]: unknown
}
export const jsonifyAccessibilityArray = (
dataArray: DataElement[],
keyName: keyof DataElement,
valueName: keyof DataElement
): Record<string, any> => {
const result: Record<string, any> = {}
dataArray.forEach((element: DataElement) => {
): Record<string, unknown> => {
const result: Record<string, unknown> = {}
dataArray.forEach((element: Record<string, string>) => {
result[element[keyName]] = element[valueName]
})
return result
@ -308,7 +315,7 @@ export const processAccessibilityResponse = (response: LaunchResponse) => {
return
}
if (!response.accessibility.success) {
handleErrorForAccessibility(response.accessibility)
handleErrorForAccessibility(response.accessibility as Errors)
return
}
@ -319,14 +326,15 @@ export const processAccessibilityResponse = (response: LaunchResponse) => {
'commands': response.accessibility.options.commandsToWrap.commands
}
if (scannerVersion) {
process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion
process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion as string
}
BStackLogger.debug(`Accessibility scannerVersion ${scannerVersion}`)
if (accessibilityToken) {
process.env.BSTACK_A11Y_JWT = accessibilityToken
process.env.BSTACK_A11Y_JWT = accessibilityToken as string
process.env[BROWSERSTACK_ACCESSIBILITY] = 'true'
}
if (scriptsJson) {
// @ts-expect-error fix type
AccessibilityScripts.update(scriptsJson)
AccessibilityScripts.store()
}
@ -415,17 +423,17 @@ export const launchTestSession = o11yErrorHandler(async function launchTestSessi
}
processLaunchBuildResponse(jsonResponse, options)
launchBuildUsage.success()
} catch (error: any) {
} catch (error: unknown) {
BStackLogger.debug(`TestHub build start failed: ${format(error)}`)
if (!error.success) {
if (!(error as Error & { success: boolean }).success) {
launchBuildUsage.failed(error)
logBuildError(error)
logBuildError(error as Errors)
return
}
}
})
export const validateCapsWithA11y = (deviceName?: any, platformMeta?: { [key: string]: any; }, chromeOptions?: any) => {
export const validateCapsWithA11y = (deviceName?: string, platformMeta?: { [key: string]: string; }, chromeOptions?: Capabilities.ChromeOptions) => {
/* Check if the current driver platform is eligible for Accessibility scan */
try {
if (deviceName) {
@ -454,14 +462,14 @@ export const validateCapsWithA11y = (deviceName?: any, platformMeta?: { [key: st
return false
}
export const shouldScanTestForAccessibility = (suiteTitle: string | undefined, testTitle: string, accessibilityOptions?: { [key: string]: any; }, world?: { [key: string]: any; }, isCucumber?: boolean ) => {
export const shouldScanTestForAccessibility = (suiteTitle: string | undefined, testTitle: string, accessibilityOptions?: { [key: string]: string; }, world?: { [key: string]: unknown; }, isCucumber?: boolean ) => {
try {
const includeTags = Array.isArray(accessibilityOptions?.includeTagsInTestingScope) ? accessibilityOptions?.includeTagsInTestingScope : []
const excludeTags = Array.isArray(accessibilityOptions?.excludeTagsInTestingScope) ? accessibilityOptions?.excludeTagsInTestingScope : []
if (isCucumber) {
const tagsList: string[] = []
world?.pickle?.tags.map((tag: { [key: string]: any; }) => tagsList.push(tag.name))
;(world?.pickle as { tags: { name: string }[] })?.tags.map((tag: { [key: string]: string; }) => tagsList.push(tag.name))
const excluded = excludeTags?.some((exclude) => tagsList.includes(exclude))
const included = includeTags?.length === 0 || includeTags?.some((include) => tagsList.includes(include))
@ -489,7 +497,7 @@ export const isAccessibilityAutomationSession = (accessibilityFlag?: boolean | s
return false
}
export const performA11yScan = async (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string, commandName?: string) : Promise<{ [key: string]: any; } | undefined> => {
export const performA11yScan = async (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string, commandName?: string) : Promise<{ [key: string]: unknown; } | undefined> => {
if (!isBrowserStackSession) {
BStackLogger.warn('Not a BrowserStack Automate session, cannot perform Accessibility scan.')
return // since we are running only on Automate as of now
@ -503,14 +511,14 @@ export const performA11yScan = async (browser: WebdriverIO.Browser | WebdriverIO
try {
const results: unknown = await (browser as WebdriverIO.Browser).executeAsync(AccessibilityScripts.performScan as string, { 'method': commandName || '' })
BStackLogger.debug(util.format(results as string))
return ( results as { [key: string]: any; } | undefined )
} catch (err : any) {
return ( results as { [key: string]: unknown; } | undefined )
} catch (err) {
BStackLogger.error('Accessibility Scan could not be performed : ' + err)
return
}
}
export const getA11yResults = async (browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise<Array<{ [key: string]: any; }>> => {
export const getA11yResults = async (browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise<Array<{ [key: string]: unknown; }>> => {
if (!isBrowserStackSession) {
BStackLogger.warn('Not a BrowserStack Automate session, cannot retrieve Accessibility results.')
return [] // since we are running only on Automate as of now
@ -524,7 +532,7 @@ export const getA11yResults = async (browser: WebdriverIO.Browser, isBrowserStac
try {
BStackLogger.debug('Performing scan before getting results')
await performA11yScan(browser, isBrowserStackSession, isAccessibility)
const results: Array<{ [key: string]: any; }> = await (browser as WebdriverIO.Browser).executeAsync(AccessibilityScripts.getResults as string)
const results: Array<{ [key: string]: unknown }> = await (browser as WebdriverIO.Browser).executeAsync(AccessibilityScripts.getResults as string)
return results
} catch {
BStackLogger.error('No accessibility results were found.')
@ -532,7 +540,7 @@ export const getA11yResults = async (browser: WebdriverIO.Browser, isBrowserStac
}
}
export const getA11yResultsSummary = async (browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise<{ [key: string]: any; }> => {
export const getA11yResultsSummary = async (browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise<{ [key: string]: unknown; }> => {
if (!isBrowserStackSession) {
return {} // since we are running only on Automate as of now
}
@ -545,7 +553,7 @@ export const getA11yResultsSummary = async (browser: WebdriverIO.Browser, isBrow
try {
BStackLogger.debug('Performing scan before getting results summary')
await performA11yScan(browser, isBrowserStackSession, isAccessibility)
const summaryResults: { [key: string]: any; } = await (browser as WebdriverIO.Browser).executeAsync(AccessibilityScripts.getResultsSummary as string)
const summaryResults: { [key: string]: unknown; } = await (browser as WebdriverIO.Browser).executeAsync(AccessibilityScripts.getResultsSummary as string)
return summaryResults
} catch {
BStackLogger.error('No accessibility summary was found.')
@ -592,12 +600,12 @@ export const stopBuildUpstream = o11yErrorHandler(async function stopBuildUpstre
status: 'success',
message: ''
}
} catch (error: any) {
} catch (error: unknown) {
stopBuildUsage.failed(error)
BStackLogger.debug(`[STOP_BUILD] Failed. Error: ${error}`)
return {
status: 'error',
message: error.message
message: (error as Error).message
}
}
})
@ -864,7 +872,7 @@ export function getUniqueIdentifier(test: Frameworks.Test, framework?: string):
let parentTitle = test.parent
// Sometimes parent will be an object instead of a string
if (typeof parentTitle === 'object') {
parentTitle = (parentTitle as any).title
parentTitle = (parentTitle as { title: string }).title
}
return `${parentTitle} - ${test.title}`
}
@ -999,7 +1007,7 @@ export function isBrowserstackInfra(config: BrowserstackConfig & Options.Testrun
}
} else {
for (const key in caps) {
const capability = (caps as any)[key]
const capability = caps[key as keyof Capabilities.BrowserStackCapabilities]
if (((capability as Options.Testrunner).hostname) && !isBrowserstack((capability as Options.Testrunner).hostname as string)) {
return false
}
@ -1125,9 +1133,9 @@ export function getObservabilityBuildTags(options: BrowserstackConfig & Options.
export function getBrowserStackUser(config: Options.Testrunner) {
if (process.env.BROWSERSTACK_USERNAME) {
return process.env.BROWSERSTACK_USERNAME
return process.env.BROWSERSTACK_USERNAME as string
}
return config.user
return config.user as string
}
export function getBrowserStackKey(config: Options.Testrunner) {
@ -1137,7 +1145,7 @@ export function getBrowserStackKey(config: Options.Testrunner) {
return config.key
}
export function isUndefined(value: any) {
export function isUndefined(value: unknown) {
let res = (value === undefined || value === null)
if (typeof value === 'string') {
res = res || value === ''
@ -1145,7 +1153,7 @@ export function isUndefined(value: any) {
return res
}
export function isTrue(value?: any) {
export function isTrue(value?: unknown) {
return (value + '').toLowerCase() === 'true'
}
@ -1165,16 +1173,18 @@ export const patchConsoleLogs = o11yErrorHandler(() => {
const BSTestOpsPatcher = new logPatcher({})
Object.keys(consoleHolder).forEach((method: keyof typeof console) => {
const origMethod = (console[method] as any).bind(console)
// @ts-expect-error
const origMethod = console[method].bind(console)
// Make sure we don't override Constructors
// Arrow functions are not construable
if (typeof console[method] === 'function'
&& method !== 'Console'
) {
(console as any)[method] = (...args: unknown[]) => {
origMethod(...args);
(BSTestOpsPatcher as any)[method](...args)
console[method] = (...args: unknown[]) => {
origMethod(...args)
// @ts-expect-error
BSTestOpsPatcher[method](...args)
}
}
})
@ -1208,8 +1218,8 @@ export async function uploadLogs(user: string | undefined, key: string | undefin
formData.append('data', new FileStream(zip), 'logs.gz')
formData.append('clientBuildUuid', clientBuildUuid)
const requestOptions = {
body: formData,
const requestOptions: RequestInit = {
body: formData as BodyInit,
headers: {
'Authorization': getBasicAuthHeader(user, key)
}
@ -1222,19 +1232,19 @@ export async function uploadLogs(user: string | undefined, key: string | undefin
return response
}
export const isObject = (object: any) => {
export const isObject = (object: unknown) => {
return object !== null && typeof object === 'object' && !Array.isArray(object)
}
export const ObjectsAreEqual = (object1: any, object2: any) => {
export const ObjectsAreEqual = (object1: object, object2: object) => {
const objectKeys1 = Object.keys(object1)
const objectKeys2 = Object.keys(object2)
if (objectKeys1.length !== objectKeys2.length) {
return false
}
for (const key of objectKeys1) {
const value1 = object1[key]
const value2 = object2[key]
const value1 = object1[key as keyof typeof object1]
const value2 = object2[key as keyof typeof object1]
const isBothAreObjects = isObject(value1) && isObject(value2)
if ((isBothAreObjects && !ObjectsAreEqual(value1, value2)) || (!isBothAreObjects && value1 !== value2)) {
return false
@ -1334,7 +1344,7 @@ export const hasBrowserName = (cap: Capabilities.WebdriverIOConfig): boolean =>
return browserStackCapabilities.browserName !== undefined
}
export const isValidCapsForHealing = (caps: { [key: string]: Options.Testrunner }): boolean => {
export const isValidCapsForHealing = (caps: WebdriverIO.Capabilities): boolean => {
// Get all capability values
const capValues = Object.values(caps)

View file

@ -61,7 +61,7 @@ beforeEach(() => {
executeAsync: async () => { 'done' },
getUrl: () => { return 'https://www.google.com/'},
on: vi.fn(),
} as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
caps = {
browserName: 'chrome',
'bstack:options': {
@ -97,7 +97,7 @@ describe('teardown', () => {
it('resolves promise if _percyScreenshotCounter is 0', async () => {
percyHandler.teardown().then(() => {
expect(percyHandler['_percyScreenshotCounter']).toEqual(0)
/* eslint-disable @typescript-eslint/no-unused-vars */
}).catch((err: any) => {
expect(percyHandler['_percyScreenshotCounter']).not.equal(0)
})

View file

@ -55,7 +55,7 @@ beforeEach(() => {
executeAsync: async () => { 'done' },
getUrl: () => { return 'https://www.google.com/'},
on: vi.fn(),
} as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
})
describe('startPercy', () => {

View file

@ -57,7 +57,7 @@ beforeEach(() => {
executeAsync: async () => { 'done' },
getUrl: () => { return 'https://www.google.com/'},
on: vi.fn(),
} as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
caps = {
browserName: 'chrome',
'bstack:options': {

View file

@ -30,9 +30,10 @@ describe('BStackCleanup', () => {
describe('startCleanup', () => {
it('executes observability cleanup if --observability is present in argv', async () => {
vi.spyOn(utils, 'stopBuildUpstream')
process.argv.push('--observability')
process.argv.push('--observability', '--funnelData')
process.env[BROWSERSTACK_TESTHUB_JWT] = 'some jwt'
vi.spyOn(BStackCleanup, 'getFunnelDataFromFile').mockReturnValue({ data: 123 })
await BStackCleanup.startCleanup()
expect(utils.stopBuildUpstream).toHaveBeenCalledTimes(1)

View file

@ -53,7 +53,7 @@ beforeEach(() => {
browserB: {},
execute: vi.fn(),
on: vi.fn(),
} as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
insightsHandler = new InsightsHandler(browser, 'framework')
})

View file

@ -90,7 +90,6 @@ describe('funnelInstrumentation', () => {
'percy': false,
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const finishExpectedEventData = {
...expectedEventData,
event_type: 'SDKTestSuccessful',

View file

@ -33,9 +33,6 @@ vi.mock('node:fs', () => ({
}))
vi.spyOn(utils, 'uploadLogs').mockImplementation((_user, _key, _uuid) => new Promise((resolve) => {
_user
_key
_uuid
resolve('success')
}))

View file

@ -340,7 +340,7 @@ describe('before', () => {
it('should set auth to default values if not provided', async () => {
let service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
await service.beforeSession({} as any as any)
await service.beforeSession({} as unknown as any)
await service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
@ -348,7 +348,7 @@ describe('before', () => {
expect(service['_config'].key).toEqual('NotSetKey')
service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
service.beforeSession({ user: 'blah' } as any as any)
service.beforeSession({ user: 'blah' } as unknown as any)
await service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])
@ -356,7 +356,7 @@ describe('before', () => {
expect(service['_config'].user).toEqual('blah')
expect(service['_config'].key).toEqual('NotSetKey')
service = new BrowserstackService({} as any, [{}] as any, { capabilities: {} })
service.beforeSession({ key: 'blah' } as any as any)
service.beforeSession({ key: 'blah' } as unknown as any)
await service.before(service['_config'] as any, [], browser)
expect(service['_failReasons']).toEqual([])

View file

@ -101,7 +101,7 @@ describe('getBrowserCapabilities', () => {
browser: 'browser'
}
}
} as any as WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {}, 'browserA'))
.toEqual(browser.browserA.capabilities as any)
})
@ -111,7 +111,7 @@ describe('getBrowserCapabilities', () => {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {}
} as any as WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {}, 'browserB')).toEqual({})
})
@ -121,7 +121,7 @@ describe('getBrowserCapabilities', () => {
browser: 'browser',
os: 'OS X',
}
} as any as WebdriverIO.Browser
} as unknown as WebdriverIO.Browser
expect(getBrowserCapabilities(browser, { os: 'Windows' }))
.toEqual({ os:'Windows', browser: 'browser' } as any)
})
@ -136,7 +136,7 @@ describe('getBrowserCapabilities', () => {
os: 'OS X',
}
}
} as any as WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {
browserA: { capabilities: { os: 'Windows' } } }, 'browserA'))
.toEqual({ os:'Windows', browser: 'browser' } as any)
@ -147,7 +147,7 @@ describe('getBrowserCapabilities', () => {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {}
} as any as WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {}, 'browserB'))
.toEqual({})
})
@ -157,7 +157,7 @@ describe('getBrowserCapabilities', () => {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {}
} as any as WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, { browserB: {} } as any, 'browserB'))
.toEqual({})
})
@ -327,6 +327,7 @@ describe('getCiInfo', () => {
process.env.bamboo_buildNumber = '123'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.APviwPVEYOR
delete process.env.bamboo_buildNumber
})
it('should return object if any CI being used - Wercker', () => {
@ -390,7 +391,7 @@ describe('getCiInfo', () => {
})
it('should return null if no CI being used', () => {
expect(getCiInfo()).toBeNull
expect(getCiInfo()).toBeNull()
})
})
@ -1065,7 +1066,7 @@ describe('getA11yResults', () => {
execute: vi.fn(),
executeAsync: vi.fn(),
on: vi.fn(),
} as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
it('return false if BrowserStack Session', async () => {
const result: any = await utils.getA11yResults((browser as WebdriverIO.Browser), false, false)
@ -1113,7 +1114,7 @@ describe('getA11yResultsSummary', () => {
execute: vi.fn(),
executeAsync: vi.fn(),
on: vi.fn(),
} as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
it('return false if BrowserStack Session', async () => {
const result: any = await utils.getA11yResultsSummary((browser as WebdriverIO.Browser), false, false)

View file

@ -3,4 +3,5 @@ import * as install from './install.js'
import * as repl from './repl.js'
import * as run from './run.js'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const commands: any = [config, install, repl, run]

View file

@ -1,4 +1,4 @@
/* eslint-disable no-console */
import fs from 'node:fs/promises'
import path from 'node:path'
@ -138,4 +138,4 @@ export async function handler(argv: InstallCommandArguments) {
process.exit(0)
}
/* eslint-enable no-console */

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
class Launcher {
#esmLauncher: any

View file

@ -22,7 +22,9 @@ interface CLIInterfaceEvent {
name: string
cid?: string
fullTitle?: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content?: any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params?: any
error?: TestError
}
@ -215,8 +217,7 @@ export default class WDIOCLInterface extends EventEmitter {
/**
* for testing purposes call console log in a static method
*/
log (...args: any[]) {
// eslint-disable-next-line no-console
log (...args: unknown[]) {
console.log(...args)
return args
}

View file

@ -93,7 +93,7 @@ class Launcher {
const capabilities = this.configParser.getCapabilities()
this.isParallelMultiremote = Array.isArray(capabilities) &&
capabilities.every(cap => Object.values(cap).length > 0 && Object.values(cap).every(c => typeof c === 'object' && (c as any).capabilities))
capabilities.every(cap => Object.values(cap).length > 0 && Object.values(cap).every(c => typeof c === 'object' && (c as { capabilities: WebdriverIO.Capabilities }).capabilities))
this.isMultiremote = this.isParallelMultiremote || !Array.isArray(capabilities)
validateConfig(TESTRUNNER_DEFAULTS, { ...config, capabilities })
@ -119,7 +119,7 @@ class Launcher {
: 1
this.interface = new CLInterface(config, totalWorkerCnt, this._isWatchMode)
config.runnerEnv!.FORCE_COLOR = Number(this.interface.hasAnsiSupport)
config.runnerEnv!.FORCE_COLOR = Number(this.interface.hasAnsiSupport).toString()
const [runnerName, runnerOptions] = Array.isArray(config.runner) ? config.runner : [config.runner, {} as WebdriverIO.BrowserRunnerOptions]
const Runner = (await initializePlugin(runnerName, 'runner') as Services.RunnerPlugin).default

View file

@ -65,16 +65,16 @@ export default async function run() {
try {
const cp = await handler(args)
return cp
} catch (err: any) {
} catch (err) {
const output = await new Promise((resolve) => (
yargs(hideBin(process.argv)).parse('--help', (
err: Error,
argv: Record<string, any>,
argv: Record<string, string>,
output: string
) => resolve(output)))
)
console.error(`${output}\n\n${err.stack}`)
console.error(`${output}\n\n${(err as Error).stack}`)
/**
* only exit process if we are run by a user and not running unit tests

View file

@ -19,9 +19,9 @@ export interface Questionnair {
expEnvHostname?: string
expEnvPort?: string
expEnvProtocol?: 'http' | 'https'
// eslint-disable-next-line
env_user?: string
// eslint-disable-next-line
env_key?: string
region?: RegionOptions
useSauceConnect?: boolean
@ -145,6 +145,7 @@ export type ValueKeyIteratee<T> =
| IterateeShorthand<T>;
type IterateeShorthand<T> =
| PropertyName
// eslint-disable-next-line @typescript-eslint/no-explicit-any
| [PropertyName, any]
| PartialShallow<T>;
type PropertyName = string | number | symbol;

View file

@ -47,7 +47,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
const NPM_COMMAND = /^win/.test(process.platform) ? 'npm.cmd' : 'npm'
const VERSION_REGEXP = /(\d+)\.(\d+)\.(\d+)-(alpha|beta|)\.(\d+)\+(.+)/g
const TEMPLATE_ROOT_DIR = path.join(__dirname, 'templates', 'exampleFiles')
export const renderFile = promisify(ejs.renderFile) as (path: string, data: Record<string, any>) => Promise<string>
export const renderFile = promisify(ejs.renderFile) as (path: string, data: Record<string, unknown>) => Promise<string>
export class HookError extends SevereServiceError {
public origin: string
@ -63,7 +63,7 @@ export class HookError extends SevereServiceError {
export async function runServiceHook(
launcher: Services.ServiceInstance[],
hookName: keyof Services.HookFunctions,
...args: any[]
...args: unknown[]
) {
const start = Date.now()
return Promise.all(launcher.map(async (service: Services.ServiceInstance) => {
@ -71,10 +71,10 @@ export async function runServiceHook(
if (typeof service[hookName] === 'function') {
await (service[hookName] as Function)(...args)
}
} catch (err: any) {
const message = `A service failed in the '${hookName}' hook\n${err.stack}\n\n`
} catch (err) {
const message = `A service failed in the '${hookName}' hook\n${(err as Error).stack}\n\n`
if (err instanceof SevereServiceError || err.name === 'SevereServiceError') {
if (err instanceof SevereServiceError || (err as Error).name === 'SevereServiceError') {
return { status: 'rejected', reason: message, origin: hookName }
}
@ -98,7 +98,7 @@ export async function runServiceHook(
* @param {object} config
* @param {object} capabilities
*/
export async function runLauncherHook(hook: Function | Function[], ...args: any[]) {
export async function runLauncherHook(hook: Function | Function[], ...args: unknown[]) {
if (typeof hook === 'function') {
hook = [hook]
}
@ -113,8 +113,8 @@ export async function runLauncherHook(hook: Function | Function[], ...args: any[
return Promise.all(hook.map((hook) => {
try {
return hook(...args)
} catch (err: any) {
return catchFn(err)
} catch (err) {
return catchFn(err as Error)
}
})).catch(catchFn)
}
@ -142,8 +142,8 @@ export async function runOnCompleteHook(
try {
await hook(exitCode, config, capabilities, results)
return 0
} catch (err: any) {
log.error(`Error in onCompleteHook: ${err.stack}`)
} catch (err) {
log.error(`Error in onCompleteHook: ${(err as Error).stack}`)
if (err instanceof SevereServiceError) {
throw new HookError(err.message, 'onComplete')
}
@ -225,7 +225,7 @@ export function addServiceDeps(names: SupportedPackage[], packages: string[], up
if (result === 'APPIUM_MISSING') {
packages.push('appium')
} else if (update) {
// eslint-disable-next-line no-console
console.log(
'\n=======',
'\nUsing globally installed appium', result,
@ -315,7 +315,7 @@ export async function getCapabilities(arg: ReplCommandArguments) {
try {
await config.initialize()
} catch (e) {
throw Error((e as any).code === 'MODULE_NOT_FOUND' ? `Config File not found: ${arg.option}` :
throw Error((e as { code: string }).code === 'MODULE_NOT_FOUND' ? `Config File not found: ${arg.option}` :
`Could not parse ${arg.option}, failed with error: ${(e as Error).message}`)
}
if (typeof arg.capabilities === 'undefined') {
@ -371,7 +371,7 @@ export async function hasPackage(pkg: string) {
try {
await resolve(pkg, import.meta.url)
return true
} catch (err: any) {
} catch {
return false
}
}
@ -511,13 +511,13 @@ export async function getAnswers(yes: boolean): Promise<Questionnair> {
* pick first choice, select value if it exists
*/
? typeof question.choices === 'function'
? (question.choices(answers)[0] as any as { value: any }).value
? (question.choices(answers)[0] as any as { value: any }).value
? (question.choices(answers)[0] as unknown as { value: unknown }).value
? (question.choices(answers)[0] as unknown as { value: unknown }).value
: question.choices(answers)[0]
: (question.choices[0] as { value: any }).value
: (question.choices[0] as { value: unknown }).value
? question.type === 'checkbox'
? [(question.choices[0] as { value: any }).value]
: (question.choices[0] as { value: any }).value
? [(question.choices[0] as { value: unknown }).value]
: (question.choices[0] as { value: unknown }).value
: question.choices[0]
: {}
})
@ -665,7 +665,7 @@ export async function getProjectProps(cwd = process.cwd()): Promise<ProjectProps
packageJson,
path: path.dirname(packageJsonPath)
}
} catch (err) {
} catch {
return undefined
}
}
@ -983,8 +983,8 @@ export async function createWDIOConfig(parsedAnswers: ParsedAnswers) {
await generateTestFiles(parsedAnswers)
console.log(chalk.green(chalk.bold('✔ Success!\n')))
}
} catch (err: any) {
throw new Error(`⚠️ Couldn't write config file: ${err.stack}`)
} catch (err) {
throw new Error(`⚠️ Couldn't write config file: ${(err as Error).stack}`)
}
}
@ -1029,10 +1029,10 @@ export async function createWDIOScript(parsedAnswers: ParsedAnswers) {
try {
console.log(`Adding ${chalk.bold(`"${ script }"`)} script to package.json`)
await runProgram(NPM_COMMAND, args, { cwd: parsedAnswers.projectRootDir })
} catch (err: any) {
} catch (err) {
const [preArgs, scriptPath] = args.join(' ').split('=')
console.error(
`⚠️ Couldn't add script to package.json: "${err.message}", you can add it manually ` +
`⚠️ Couldn't add script to package.json: "${(err as Error).message}", you can add it manually ` +
`by running:\n\n\t${NPM_COMMAND} ${preArgs}="${scriptPath}"`
)
return false

View file

@ -103,7 +103,7 @@ export default class Watcher {
}
// Do not pass the `spec` command line option to `this.run()`
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { spec: _, ...args } = this._args
return runSpecs.map((spec) => {
return this.run({

View file

@ -631,6 +631,7 @@ describe('launcher', () => {
expect(sleep).not.toHaveBeenCalled()
expect(launcher['_runnerStarted']).toBe(1)
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
expect(vi.mocked(launcher.runner?.run!).mock.calls[0][0]).toHaveProperty('cid', '0-5')
expect(launcher['_getRunnerId'](0)).toBe('0-0')

View file

@ -338,7 +338,7 @@ describe('addServiceDeps', () => {
it('should not add appium if globally installed', () => {
// @ts-ignore
// eslint-disable-next-line no-import-assign, @typescript-eslint/no-unused-vars
cp.execSyncRes = '1.13.0'
const packages: any = []
addServiceDeps([{ package: '@wdio/appium-service', short: 'appium', purpose: 'e2e' }], packages)

View file

@ -93,7 +93,9 @@ export default class ConfigParser {
coverage: { enabled: this._initialConfig.coverage }
}]
} else if (Array.isArray(this._config.runner) && this._config.runner[0] === 'browser') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this._config.runner[1] as any).coverage = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(this._config.runner[1] as any).coverage,
enabled: this._initialConfig.coverage
}
@ -160,8 +162,8 @@ export default class ConfigParser {
* remove `watch` from config as far as it can be only passed as command line argument
*/
delete this._config.watch
} catch (e: any) {
log.error(`Failed loading configuration file: ${filePath}:`, e.message)
} catch (e: unknown) {
log.error(`Failed loading configuration file: ${filePath}:`, (e as Error).message)
throw e
}
}
@ -214,7 +216,7 @@ export default class ConfigParser {
/**
* overwrite capabilities
*/
this._capabilities = validObjectOrArray(this._config.capabilities) ? this._config.capabilities : this._capabilities
this._capabilities = validObjectOrArray(this._config.capabilities as object) ? this._config.capabilities : this._capabilities
/**
* save original specs if Cucumber's feature line number is provided

View file

@ -1,6 +1,6 @@
import type { Options } from '@wdio/types'
export const validObjectOrArray = (object: any): object is object | Array<any> => (Array.isArray(object) && object.length > 0) ||
export const validObjectOrArray = (object: object): object is object | Array<unknown> => (Array.isArray(object) && object.length > 0) ||
(typeof object === 'object' && Object.keys(object).length > 0)
/**
@ -61,8 +61,8 @@ export function validateConfig<T>(defaults: Options.Definition<T>, options: T, k
if (typeof expectedOption.validate === 'function') {
try {
expectedOption.validate(optValue)
} catch (e: any) {
throw new Error(`Type check for option "${name.toString()}" failed: ${e.message}`)
} catch (e: unknown) {
throw new Error(`Type check for option "${name.toString()}" failed: ${(e as Error).message}`)
}
}
@ -74,7 +74,7 @@ export function validateConfig<T>(defaults: Options.Definition<T>, options: T, k
}
}
for (const [name, option] of Object.entries(options as any) as [keyof T, T[keyof T]][]) {
for (const [name, option] of Object.entries(options as object) as [keyof T, T[keyof T]][]) {
/**
* keep keys from source object if desired
*/

View file

@ -36,10 +36,10 @@ export default class MockPathService implements PathService {
*/
getMocks() {
return {
getcwdMock: this.getcwdMock as any as Function,
loadFileMock: this.loadFileMock as any as Function,
isFileMock: this.isFileMock as any as Function,
globMock: this.globMock as any as Function
getcwdMock: this.getcwdMock as unknown as Function,
loadFileMock: this.loadFileMock as unknown as Function,
isFileMock: this.isFileMock as unknown as Function,
globMock: this.globMock as unknown as Function
}
}

View file

@ -5,6 +5,8 @@ import path from 'node:path'
import logger from '@wdio/logger'
import type { Payload } from './types.js'
const log = logger('CucumberFormatter')
import type {
@ -122,7 +124,7 @@ export default class CucumberFormatter extends Formatter {
return path.isAbsolute(uri) ? uri : path.resolve(uri)
}
emit(event: string, payload: any) {
emit(event: string, payload: Payload) {
const message = formatMessage({ payload })
message.cid = this.cid
@ -170,7 +172,9 @@ export default class CucumberFormatter extends Formatter {
type: 'hook',
state: result.status,
error,
duration: Date.now() - this._testStart!?.getTime(),
duration: this._testStart
? Date.now() - this._testStart.getTime()
: 0,
})
this.emit('hook:end', payload)
@ -236,7 +240,7 @@ export default class CucumberFormatter extends Formatter {
error = err
}
} else if (result.status === Status.FAILED && !(result as any as TestCaseFinished).willBeRetried) {
} else if (result.status === Status.FAILED && !(result as unknown as TestCaseFinished).willBeRetried) {
error = new Error(result.message?.split('\n')[0])
error.stack = result.message as string
this.failedCount++
@ -251,7 +255,9 @@ export default class CucumberFormatter extends Formatter {
title: title,
state,
error,
duration: Date.now() - this._testStart!?.getTime(),
duration: this._testStart
? Date.now() - this._testStart.getTime()
: 0,
passed: ['pass', 'skip'].includes(state),
file: uri,
}
@ -352,7 +358,9 @@ export default class CucumberFormatter extends Formatter {
title: this.getTitle(this._currentDoc.feature),
type: 'feature',
file: this._currentDoc.uri,
duration: Date.now() - this._featureStart!?.getTime(),
duration: this._featureStart
? Date.now() - this._featureStart.getTime()
: 0,
tags: this._currentDoc.feature?.tags,
}
this.emit('suite:end', payload)
@ -394,7 +402,7 @@ export default class CucumberFormatter extends Formatter {
const reporterScenario: ReporterScenario = scenario
reporterScenario.rule = getRule(
doc?.feature!,
doc?.feature as Feature,
this._pickleMap.get(scenario.id)!
)
@ -555,7 +563,9 @@ export default class CucumberFormatter extends Formatter {
parent: getFeatureId(doc?.uri as string, doc?.feature as Feature),
type: 'scenario',
file: doc?.uri,
duration: Date.now() - this._scenarioStart!?.getTime(),
duration: this._scenarioStart
? Date.now() - this._scenarioStart.getTime()
: 0,
tags: scenario.tags,
}
@ -579,7 +589,9 @@ export default class CucumberFormatter extends Formatter {
title: this.getTitle(this._currentDoc.feature as Feature),
type: 'feature',
file: this._currentDoc.uri,
duration: Date.now() - this._featureStart!?.getTime(),
duration: this._featureStart
? Date.now() - this._featureStart.getTime()
: 0,
tags: this._currentDoc.feature?.tags,
}
@ -602,7 +614,9 @@ export default class CucumberFormatter extends Formatter {
title: this.getTitle(gherkinDocEvent.feature as Feature),
type: 'feature',
file: gherkinDocEvent.uri,
duration: Date.now() - this._featureStart!?.getTime(),
duration: this._featureStart
? Date.now() - this._featureStart.getTime()
: 0,
tags: gherkinDocEvent.feature?.tags,
}

View file

@ -21,13 +21,14 @@ const {
defineParameterType,
defineStep
// eslint-disable-next-line @typescript-eslint/no-require-imports
} = require('@cucumber/cucumber')
const adapterFactory = {
init: async function initCucumberFramework (...args: any[]) {
init: async function initCucumberFramework (...args: unknown[]) {
const { CucumberAdapter } = await import('./index.js')
// @ts-ignore just passing through args
const adapter = new CucumberAdapter(...args as any)
// @ts-expect-error just passing through args
const adapter = new CucumberAdapter(...args as unknown[])
const instance = await adapter.init()
return instance
},

View file

@ -198,7 +198,8 @@ export class CucumberAdapter {
this._specs = plan?.map((pl) => path.resolve(pl.uri))
// Filter features (of which at least some have line numbers) against the already filtered specs
if (this._config.cucumberFeaturesWithLineNumbers?.length! > 0) {
const lineNumbers = this._config.cucumberFeaturesWithLineNumbers?.length ?? 0
if (lineNumbers > 0) {
this._specs = this._config.cucumberFeaturesWithLineNumbers!.filter(feature =>
this._specs.some(spec => path.resolve(feature).startsWith(spec))
)
@ -276,7 +277,7 @@ export class CucumberAdapter {
if (this._cucumberOpts.ignoreUndefinedDefinitions && result) {
result = failedCount
}
} catch (err: any) {
} catch (err) {
runtimeError = err
result = 1
} finally {
@ -443,7 +444,7 @@ export class CucumberAdapter {
const wrapStep = this.wrapStep
const cid = this._cid
let params: any
let params: unknown
this._eventEmitter.on('getHookParams', (payload) => {
params = payload
})
@ -500,7 +501,7 @@ export class CucumberAdapter {
timeout?: number,
hookName: string | undefined = undefined,
): Function {
return function (this: Cucumber.World, ...args: any[]) {
return function (this: Cucumber.World, ...args: unknown[]) {
const hookParams = getHookParams()
const retryTest = isStep && isFinite(options.retry) ? options.retry : 0
@ -512,8 +513,8 @@ export class CucumberAdapter {
return testFnWrapper.call(this,
isStep ? 'Step' : 'Hook',
{ specFn: code, specFnArgs: args },
{ beforeFn: beforeFn as Function[], beforeFnArgs: (context: any) => [hookParams?.step, context] },
{ afterFn: afterFn as Function[], afterFnArgs: (context: any) => [hookParams?.step, context] },
{ beforeFn: beforeFn as Function[], beforeFnArgs: (context: unknown) => [hookParams?.step, context] },
{ afterFn: afterFn as Function[], afterFnArgs: (context: unknown) => [hookParams?.step, context] },
cid,
retryTest, hookName, timeout)
}
@ -572,9 +573,9 @@ export const adapterFactory: { init?: Function } = {}
* tested by smoke tests
*/
/* istanbul ignore next */
adapterFactory.init = async function (...args: any[]) {
adapterFactory.init = async function (...args: unknown[]) {
// @ts-ignore just passing through args
const adapter = new _CucumberAdapter(...(args as any))
const adapter = new _CucumberAdapter(...(args as unknown))
const instance = await adapter.init()
return instance
}

View file

@ -1,4 +1,4 @@
import type { ITestCaseHookParameter } from '@cucumber/cucumber'
import type { ITestCaseHookParameter, World } from '@cucumber/cucumber'
import type { Pickle, PickleStep, TestStep, Feature } from '@cucumber/messages'
import type { Frameworks } from '@wdio/types'
@ -100,7 +100,7 @@ export interface CucumberOptions {
/**
* Parameters to be passed to your World
*/
worldParameters?: any;
worldParameters?: unknown;
/**
* Timeout in milliseconds for step definitions.
* @default 60000
@ -189,7 +189,7 @@ export interface HookFunctionExtension {
* @param world world object containing information on pickle and test step
* @param context Cucumber World object
*/
beforeScenario?(world: ITestCaseHookParameter, context: Object): void;
beforeScenario?(world: ITestCaseHookParameter, context: World): void;
/**
*
@ -198,7 +198,7 @@ export interface HookFunctionExtension {
* @param scenario scenario data
* @param context Cucumber World object
*/
beforeStep?(step: PickleStep, scenario: Pickle, context: Object): void;
beforeStep?(step: PickleStep, scenario: Pickle, context: World): void;
/**
*
@ -211,7 +211,7 @@ export interface HookFunctionExtension {
* @param result.duration duration of scenario in milliseconds
* @param context Cucumber World object
*/
afterStep?(step: PickleStep, scenario: Pickle, result: Frameworks.PickleResult, context: Object): void;
afterStep?(step: PickleStep, scenario: Pickle, result: Frameworks.PickleResult, context: World): void;
/**
*
@ -223,7 +223,7 @@ export interface HookFunctionExtension {
* @param result.duration duration of scenario in milliseconds
* @param context Cucumber World object
*/
afterScenario?(world: ITestCaseHookParameter, result: Frameworks.PickleResult, context: Object): void;
afterScenario?(world: ITestCaseHookParameter, result: Frameworks.PickleResult, context: World): void;
/**
*
@ -241,6 +241,16 @@ export interface StepDefinitionOptions {
export interface TestHookDefinitionConfig {
code: Function;
line: number;
options: any;
options: unknown;
uri: string;
}
export interface Payload {
cid?: string
uid?: string
specs?: string[]
error?: Error
parent?: unknown
title?: string
fullTitle?: string
}

View file

@ -10,10 +10,11 @@ import type {
TestStep,
Feature,
Pickle,
TestStepResultStatus
TestStepResultStatus,
FeatureChild
} from '@cucumber/messages'
import type { Capabilities } from '@wdio/types'
import type { ReporterStep, TestHookDefinitionConfig } from './types.js'
import type { ReporterStep, TestHookDefinitionConfig, Payload } from './types.js'
import { CUCUMBER_HOOK_DEFINITION_TYPES } from './constants.js'
const log = logger('@wdio/cucumber-framework:utils')
@ -46,7 +47,7 @@ export function createStepArgument ({ argument }: PickleStep) {
* format message
* @param {object} message { type: string, payload: object }
*/
export function formatMessage ({ payload = {} }: any) {
export function formatMessage ({ payload = {} }: { payload: Payload }): Payload {
const content = { ...payload }
/**
@ -157,7 +158,7 @@ export function addKeywordToStep(steps: ReporterStep[], feature: Feature){
const rules = feature.children.filter((child)=> Object.keys(child)[0]=== 'rule')
let featureChildren = feature.children.filter((child)=> Object.keys(child)[0]!== 'rule')
const rulesChildrens:any = rules.map((child)=>child.rule?.children).flat()
const rulesChildrens = rules.map((child) => child.rule?.children as FeatureChild).flat()
featureChildren = featureChildren.concat(rulesChildrens)
featureChildren.find((child) =>
@ -207,7 +208,7 @@ export function generateSkipTagsFromCapabilities(capabilities: Capabilities.Reso
acc[splitItem.substring(0, pos)] = (0, eval)(
splitItem.substring(pos + 1)
)
} catch (err: any) {
} catch {
log.error(`Couldn't use tag "${splitItem}" for filtering because it is malformed`)
}
}
@ -219,7 +220,7 @@ export function generateSkipTagsFromCapabilities(capabilities: Capabilities.Reso
if (matched) {
const isSkip = [parse(matched[1] ?? '')]
.find((filter: WebdriverIO.Capabilities) => Object.keys(filter)
.every((key: keyof WebdriverIO.Capabilities) => match((capabilities as any)[key], filter[key] as RegExp)))
.every((key: keyof WebdriverIO.Capabilities) => match((capabilities as Record<string, string>)[key], filter[key] as RegExp)))
if (isSkip) {
generatedTags.push(`(not ${tag.replace(/(\(|\))/g, '\\$1')})`)
}
@ -233,7 +234,7 @@ export function generateSkipTagsFromCapabilities(capabilities: Capabilities.Reso
* Retrives scenario description if available.
*/
export function getScenarioDescription(feature: Feature, scenarioId: string){
const children = feature.children?.find((child) => child?.scenario?.id === scenarioId)!
const children = feature.children?.find((child) => child?.scenario?.id === scenarioId)
return children?.scenario?.description || ''
}
@ -247,10 +248,10 @@ export function setUserHookNames (options: typeof supportCodeLibraryBuilder) {
options[hookName].forEach((testRunHookDefinition: TestHookDefinitionConfig) => {
const hookFn = testRunHookDefinition.code
if (!hookFn.name.startsWith('wdioHook')) {
const userHookAsyncFn = async function (this: World, ...args: any) {
const userHookAsyncFn = async function (this: World, ...args: unknown[]) {
return hookFn.apply(this, args)
}
const userHookFn = function (this: World, ...args: any) {
const userHookFn = function (this: World, ...args: unknown[]) {
return hookFn.apply(this, args)
}
testRunHookDefinition.code = (isFunctionAsync(hookFn)) ? userHookAsyncFn : userHookFn

View file

@ -136,7 +136,7 @@ exports[`CucumberFormatter > emits messages for certain cucumber events > should
"suite:end",
{
"cid": "0-1",
"duration": NaN,
"duration": 0,
"file": "/some/path/to/features/my-feature.feature",
"specs": [
"/foobar.js",
@ -509,7 +509,7 @@ exports[`CucumberFormatter > emits messages for certain cucumber events when exe
"suite:end",
{
"cid": "0-1",
"duration": NaN,
"duration": 0,
"file": "/some/path/to/features/my-feature.feature",
"specs": [
"/foobar.js",

Some files were not shown because too many files have changed in this diff Show more