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:
parent
34e6d647e8
commit
a2ae35332f
305 changed files with 7459 additions and 9255 deletions
|
|
@ -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
|
||||
|
|
@ -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
1
.gitignore
vendored
|
|
@ -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
72
eslint.config.mjs
Normal 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',
|
||||
}
|
||||
}
|
||||
])
|
||||
|
|
@ -44,6 +44,6 @@ const webviewApp = '/path/to/app'
|
|||
])
|
||||
await driver.deleteSession()
|
||||
})().catch(
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
console.error
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
|
||||
exports.config = {
|
||||
// =====================
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
describe('my app', () => {
|
||||
before(() => console.log('inner before hook'))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import WDIOReporter from '@wdio/reporter'
|
||||
|
||||
export default class CustomReporter extends WDIOReporter {
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"globals": {
|
||||
"browser": true,
|
||||
"browserA": true,
|
||||
"browserB": true,
|
||||
"$": true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
const path = require('node:path')
|
||||
import path from 'node:path'
|
||||
|
||||
exports.config = {
|
||||
/**
|
||||
|
|
|
|||
11
package.json
11
package.json
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/// <reference types="vite/client" />
|
||||
import { automationProtocolPath } from 'virtual:wdio'
|
||||
|
||||
|
|
|
|||
|
|
@ -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> }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
|
|
|
|||
|
|
@ -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}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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': {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ describe('funnelInstrumentation', () => {
|
|||
'percy': false,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const finishExpectedEventData = {
|
||||
...expectedEventData,
|
||||
event_type: 'SDKTestSuccessful',
|
||||
|
|
|
|||
|
|
@ -33,9 +33,6 @@ vi.mock('node:fs', () => ({
|
|||
}))
|
||||
|
||||
vi.spyOn(utils, 'uploadLogs').mockImplementation((_user, _key, _uuid) => new Promise((resolve) => {
|
||||
_user
|
||||
_key
|
||||
_uuid
|
||||
resolve('success')
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -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([])
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
class Launcher {
|
||||
#esmLauncher: any
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue