webdriverio/packages/wdio-browserstack-service/tests/util.test.ts
Christian Bromann a2ae35332f
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
2024-12-18 11:55:43 -08:00

1458 lines
54 KiB
TypeScript

import path from 'node:path'
import type { LaunchResponse } from '../src/types.js'
import { describe, expect, it, vi, beforeEach, afterEach, beforeAll, afterAll } from 'vitest'
import gitRepoInfo from 'git-repo-info'
import CrashReporter from '../src/crash-reporter.js'
import logger from '@wdio/logger'
import * as utils from '../src/util.js'
import logPatcher from '../src/logPatcher.js'
import {
getBrowserDescription,
getBrowserCapabilities,
isBrowserstackCapability,
getParentSuiteName,
getCiInfo,
getCloudProvider,
isBrowserstackSession,
getUniqueIdentifier,
getUniqueIdentifierForCucumber,
removeAnsiColors,
getScenarioExamples,
stopBuildUpstream,
launchTestSession,
getGitMetaData,
getLogTag,
getHookType,
isScreenshotCommand,
getObservabilityUser,
getObservabilityKey,
getObservabilityBuild,
getObservabilityProject,
getObservabilityBuildTags,
o11yErrorHandler,
frameworkSupportsHook,
getFailureObject,
validateCapsWithA11y,
shouldScanTestForAccessibility,
isAccessibilityAutomationSession,
isTrue,
uploadLogs,
getObservabilityProduct,
isUndefined,
processTestObservabilityResponse,
processAccessibilityResponse,
processLaunchBuildResponse,
jsonifyAccessibilityArray
} from '../src/util.js'
import * as bstackLogger from '../src/bstackLogger.js'
import { BROWSERSTACK_OBSERVABILITY, TESTOPS_BUILD_COMPLETED_ENV, BROWSERSTACK_TESTHUB_JWT, BROWSERSTACK_ACCESSIBILITY } from '../src/constants.js'
import * as testHubUtils from '../src/testHub/utils.js'
const log = logger('test')
vi.mock('fetch')
vi.mock('git-repo-info')
vi.useFakeTimers().setSystemTime(new Date('2020-01-01'))
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.mock('fs', () => ({
default: {
createReadStream: vi.fn().mockImplementation(() => {return { pipe: vi.fn().mockReturnThis() }}),
createWriteStream: vi.fn().mockReturnValue({ pipe: vi.fn() }),
stat: vi.fn().mockReturnValue(Promise.resolve({ size: 123 })),
existsSync: vi.fn(),
mkdirSync: vi.fn(),
writeFileSync: vi.fn(),
}
}))
vi.mock('./fileStream')
const bstackLoggerSpy = vi.spyOn(bstackLogger.BStackLogger, 'logToFile')
bstackLoggerSpy.mockImplementation(() => {})
function assertMethodCalls(mock: { mock: { calls: any[] } }, expectedMethod: any, expectedCallCount: any) {
const matchingCalls = mock.mock.calls.filter(
([, options]) => options.method === expectedMethod
)
expect(matchingCalls.length).toBe(expectedCallCount)
}
describe('getBrowserCapabilities', () => {
it('should get default browser capabilities', () => {
const browser = {
capabilities: {
browser: 'browser'
}
} as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser))
.toEqual(browser.capabilities as any)
})
it('should get multiremote browser capabilities', () => {
const browser = {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {
capabilities: {
browser: 'browser'
}
}
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {}, 'browserA'))
.toEqual(browser.browserA.capabilities as any)
})
it('should handle null multiremote browser capabilities', () => {
const browser = {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {}
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {}, 'browserB')).toEqual({})
})
it('should merge service capabilities and browser capabilities', () => {
const browser = {
capabilities: {
browser: 'browser',
os: 'OS X',
}
} as unknown as WebdriverIO.Browser
expect(getBrowserCapabilities(browser, { os: 'Windows' }))
.toEqual({ os:'Windows', browser: 'browser' } as any)
})
it('should merge multiremote service capabilities and browser capabilities', () => {
const browser = {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {
capabilities: {
browser: 'browser',
os: 'OS X',
}
}
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {
browserA: { capabilities: { os: 'Windows' } } }, 'browserA'))
.toEqual({ os:'Windows', browser: 'browser' } as any)
})
it('should handle null multiremote browser capabilities', () => {
const browser = {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {}
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, {}, 'browserB'))
.toEqual({})
})
it('should handle null multiremote browser capabilities', () => {
const browser = {
isMultiremote: true,
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserA: {}
} as unknown as WebdriverIO.MultiRemoteBrowser
expect(getBrowserCapabilities(browser, { browserB: {} } as any, 'browserB'))
.toEqual({})
})
})
describe('getBrowserDescription', () => {
const defaultCap = {
'device': 'device',
'os': 'os',
'osVersion': 'osVersion',
'browserName': 'browserName',
'browser': 'browser',
'browserVersion': 'browserVersion',
}
const defaultDesc = 'device os osVersion browserName browser browserVersion'
const legacyCap = {
'os_version': 'os_version',
'browser_version': 'browser_version'
}
it('should get correct description for default capabilities', () => {
expect(getBrowserDescription(defaultCap)).toEqual(defaultDesc)
})
it('should get correct description for legacy capabilities', () => {
expect(getBrowserDescription(legacyCap)).toEqual('os_version browser_version')
})
it('should get correct description for W3C capabilities', () => {
expect(getBrowserDescription({ 'bstack:options': defaultCap })).toEqual(defaultDesc)
})
it('should merge W3C and lecacy capabilities', () => {
expect(getBrowserDescription({ 'bstack:options': defaultCap })).toEqual(defaultDesc)
})
it('should not crash when capabilities is null or undefined', () => {
// @ts-expect-error test invalid params
expect(getBrowserDescription(undefined)).toEqual('')
// @ts-expect-error test invalid params
expect(getBrowserDescription(null)).toEqual('')
})
})
describe('isBrowserstackCapability', () => {
it('should return false if browserstack W3C capabilities is absent or not valid', () => {
expect(isBrowserstackCapability({})).toBe(false)
expect(isBrowserstackCapability()).toBe(false)
// @ts-expect-error test invalid params
expect(isBrowserstackCapability({ 'bstack:options': null })).toBe(false)
})
it('should return false if only key in browserstack W3C capabilities is wdioService', () => {
expect(isBrowserstackCapability({ 'bstack:options': { wdioService: 'version' } })).toBe(false)
})
it('should detect browserstack W3C capabilities', () => {
expect(isBrowserstackCapability({ 'bstack:options': { os: 'some os' } })).toBe(true)
})
})
describe('getParentSuiteName', () => {
it('should return the parent suite name', () => {
expect(getParentSuiteName('foo bar', 'foo')).toBe('foo')
expect(getParentSuiteName('foo', 'foo bar')).toBe('foo')
expect(getParentSuiteName('foo bar', 'foo baz')).toBe('foo')
expect(getParentSuiteName('foo bar', 'foo bar')).toBe('foo bar')
})
it('should return empty string if no common parent', () => {
expect(getParentSuiteName('foo bar', 'baz bar')).toBe('')
})
it('should handle empty values', () => {
expect(getParentSuiteName('', 'foo')).toBe('')
expect(getParentSuiteName('foo', '')).toBe('')
})
})
describe('getCiInfo', () => {
describe('should handle if running on CI', () => {
beforeEach(() => {
process.env.CI = 'true'
})
afterEach(() => {
delete process.env.CI
})
it('should return object if any CI being used - codeship', () => {
process.env.CI_NAME = 'codeship'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.CI_NAME
})
it('should return object if any CI being used - JENKINS', () => {
process.env.JENKINS_URL = 'url'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.JENKINS_URL
})
it('should return object if any CI being used - TRAVIS', () => {
process.env.TRAVIS = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.TRAVIS
})
it('should return object if any CI being used - CIRCLECI', () => {
process.env.CIRCLECI = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.CIRCLECI
})
it('should return object if any CI being used - BITBUCKET', () => {
process.env.BITBUCKET_BRANCH = 'true'
process.env.BITBUCKET_COMMIT = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.BITBUCKET_COMMIT
delete process.env.BITBUCKET_BRANCH
})
it('should return object if any CI being used - DRONE', () => {
process.env.DRONE = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.DRONE
})
it('should return object if any CI being used - SEMAPHORE', () => {
process.env.SEMAPHORE = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.SEMAPHORE
})
it('should return object if any CI being used - GITLAB_CI', () => {
process.env.GITLAB_CI = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.GITLAB_CI
})
it('should return object if any CI being used - BUILDKITE', () => {
process.env.BUILDKITE = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.BUILDKITE
})
it('should return object if any CI being used - TF_BUILD', () => {
process.env.TF_BUILD = 'True'
process.env.TF_BUILD_BUILDNUMBER = '123'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.TF_BUILD
delete process.env.TF_BUILD_BUILDNUMBER
})
it('should return object if any CI being used - Appveyor', () => {
process.env.APPVEYOR = 'True'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.APPVEYOR
})
it('should return object if any CI being used - CodeBuild', () => {
process.env.CODEBUILD_BUILD_ID = '1211'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.CODEBUILD_BUILD_ID
})
it('should return object if any CI being used - Bamboo', () => {
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', () => {
process.env.WERCKER = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.WERCKER
})
it('should return object if any CI being used - GCP', () => {
process.env.GCP_PROJECT = 'True'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.GCP_PROJECT
})
it('should return object if any CI being used - Shippable', () => {
process.env.SHIPPABLE = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.SHIPPABLE
})
it('should return object if any CI being used - Netlify', () => {
process.env.NETLIFY = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.NETLIFY
})
it('should return object if any CI being used - Github Actions', () => {
process.env.GITHUB_ACTIONS = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.GITHUB_ACTIONS
})
it('should return object if any CI being used - Vercel', () => {
process.env.VERCEL = '1'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.VERCEL
})
it('should return object if any CI being used - Teamcity', () => {
process.env.TEAMCITY_VERSION = '3.4'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.TEAMCITY_VERSION
})
it('should return object if any CI being used - Concourse', () => {
process.env.CONCOURSE = 'true'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.CONCOURSE
})
it('should return object if any CI being used - GoCD', () => {
process.env.GO_JOB_NAME = 'job'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.GO_JOB_NAME
})
it('should return object if any CI being used - CodeFresh', () => {
process.env.CF_BUILD_ID = 'True'
expect(getCiInfo()).toBeInstanceOf(Object)
delete process.env.CF_BUILD_ID
})
})
it('should return null if no CI being used', () => {
expect(getCiInfo()).toBeNull()
})
})
describe('getCloudProvider', () => {
it('return unknown_grid if test not on browserstack', () => {
expect(getCloudProvider({})).toEqual('unknown_grid')
})
it('return Browserstack if test being run on browserstack', () => {
expect(getCloudProvider({ options: { hostname: 'hub.browserstack.com' } })).toEqual('browserstack')
})
})
describe('isBrowserstackSession', () => {
it('return false if run locally', () => {
expect(isBrowserstackSession({})).toEqual(false)
})
it('return false if run on any other cloud service', () => {
expect(isBrowserstackSession({ options: { hostname: 'anything-saucelabs.com' } })).toEqual(false)
})
it('return true if run on Browserstack', () => {
expect(isBrowserstackSession({ options: { hostname: 'hub.browserstack.com' } })).toEqual(true)
})
})
describe('getUniqueIdentifier', () => {
it('return unique identifier for mocha tests', () => {
const test = {
parent: 'root-level',
title: 'title',
fullName: '',
ctx: '',
type: '',
fullTitle: '',
pending: false,
file: ''
}
expect(getUniqueIdentifier(test)).toEqual('root-level - title')
})
it('return unique identifier for jasmine tests', () => {
const test = {
description: 'title',
fullName: 'root-level title',
pending: false
}
expect(getUniqueIdentifier(test as any, 'jasmine')).toEqual('root-level title')
})
})
describe('getUniqueIdentifierForCucumber', () => {
it('return unique identifier for cucumber tests', () => {
const test = {
pickle: {
uri: 'uri',
astNodeIds: ['1', '2']
}
}
expect(getUniqueIdentifierForCucumber(test)).toEqual('uri_1,2')
})
})
describe('removeAnsiColors', () => {
it('remove color encoding', () => {
expect(removeAnsiColors('\x1b[F\x1b[31;1mHello, there!\x1b[m\x1b[E')).toEqual('Hello, there!')
})
})
describe('getScenarioExamples', () => {
it('return undefined if no nesting is there', () => {
const test = {
pickle: {
name: 'name',
astNodeIds: ['1']
}
}
expect(getScenarioExamples(test)).toEqual(undefined)
})
it('return undefined if astNodeIds not present', () => {
const test = {
pickle: {
name: 'name'
}
}
expect(getScenarioExamples(test)).toEqual(undefined)
})
it('return examples array', () => {
const test = {
gherkinDocument: {
feature: {
children: [
{
background: {
id: '1',
name: '',
}
},
{
scenario: {
id: '8',
steps: [
{
id: '2',
keyword: 'Given ',
text: 'the title is <title>',
},
{
id: '3',
keyword: 'Then ',
text: 'I expect that element "h1" contains the same text as element ".subtitle"',
}
],
examples: [
{
id: '7',
tags: [],
location: { line: 12, column: 9 },
keyword: 'Examples',
name: '',
description: '',
tableHeader: {
id: '4',
location: { line: 13, column: 13 },
cells: [
{
location: { line: 13, column: 15 },
value: 'title'
}
]
},
tableBody: [
{
id: '5',
location: { line: 14, column: 13 },
cells: [
{
location: { line: 14, column: 15 },
value: 'value1'
}
]
},
{
id: '6',
location: { line: 15, column: 13 },
cells: [
{
location: { line: 15, column: 15 },
value: 'value2'
}
]
}
]
}
]
}
}
]
},
},
pickle: {
name: 'name',
astNodeIds: ['8', '5']
}
}
expect(getScenarioExamples(test)).toEqual(['value1'])
})
it('return array with passed examples if rule present', () => {
const test = {
gherkinDocument: {
feature: {
children: [
{
background: {
id: '1',
name: '',
}
},
{
rule: {
children: [
{
scenario: {
id: '8',
steps: [
{
id: '2',
keyword: 'Given ',
text: 'the title is <title>',
},
{
id: '3',
keyword: 'Then ',
text: 'I expect that element "h1" contains the same text as element ".subtitle"',
}
],
examples: [
{
id: '7',
tags: [],
location: { line: 12, column: 9 },
keyword: 'Examples',
name: '',
description: '',
tableHeader: {
id: '4',
location: { line: 13, column: 13 },
cells: [
{
location: { line: 13, column: 15 },
value: 'title'
}
]
},
tableBody: [
{
id: '5',
location: { line: 14, column: 13 },
cells: [
{
location: { line: 14, column: 15 },
value: 'value1'
}
]
},
{
id: '6',
location: { line: 15, column: 13 },
cells: [
{
location: { line: 15, column: 15 },
value: 'value2'
}
]
}
]
}
]
}
}
]
}
}
]
},
},
pickle: {
name: 'name',
astNodeIds: ['8', '5']
}
}
expect(getScenarioExamples(test)).toEqual(['value1'])
})
it('return undefined if no examples present', () => {
const test = {
gherkinDocument: {
feature: {
children: []
},
},
pickle: {
name: 'name',
astNodeIds: ['8', '5']
}
}
expect(getScenarioExamples(test)).toEqual(undefined)
})
})
describe('stopBuildUpstream', () => {
it('return error if completed but jwt token not present', async () => {
process.env[TESTOPS_BUILD_COMPLETED_ENV] = 'true'
delete process.env[BROWSERSTACK_TESTHUB_JWT]
const result: any = await stopBuildUpstream()
delete process.env[TESTOPS_BUILD_COMPLETED_ENV]
expect(result.status).toEqual('error')
expect(result.message).toEqual('Token/buildID is undefined, build creation might have failed')
})
it('return success if completed', async () => {
process.env[TESTOPS_BUILD_COMPLETED_ENV] = 'true'
process.env[BROWSERSTACK_TESTHUB_JWT] = 'jwt'
vi.mocked(fetch).mockReturnValueOnce(Promise.resolve(Response.json({})))
const result: any = await stopBuildUpstream()
expect(vi.mocked(fetch).mock.calls[0][1]?.method).toEqual('PUT')
expect(result.status).toEqual('success')
})
it('return error if failed', async () => {
process.env[TESTOPS_BUILD_COMPLETED_ENV] = 'true'
process.env[BROWSERSTACK_TESTHUB_JWT] = 'jwt'
vi.mocked(fetch).mockReturnValueOnce(Promise.reject(Response.json({})))
const result: any = await stopBuildUpstream()
expect(vi.mocked(fetch).mock.calls[0][1]?.method).toEqual('PUT')
expect(result.status).toEqual('error')
})
afterEach(() => {
vi.mocked(fetch).mockClear()
})
})
describe('launchTestSession', () => {
vi.mocked(gitRepoInfo).mockReturnValue({} as any)
vi.spyOn(testHubUtils, 'getProductMap').mockReturnValue({} as any)
it('return undefined if completed', async () => {
vi.mocked(fetch).mockReturnValueOnce(Promise.resolve(Response.json({ build_hashed_id: 'build_id', jwt: 'jwt' })))
const result: any = await launchTestSession( { framework: 'framework' } as any, { }, {}, {})
assertMethodCalls(vi.mocked(fetch), 'POST', 1)
expect(result).toEqual(undefined)
})
})
describe('getLogTag', () => {
it('return correct tag', () => {
expect(getLogTag('TestRunStarted')).toEqual('Test_Upload')
expect(getLogTag('TestRunFinished')).toEqual('Test_Upload')
expect(getLogTag('HookRunStarted')).toEqual('Hook_Upload')
expect(getLogTag('HookRunFinished')).toEqual('Hook_Upload')
expect(getLogTag('LogCreated')).toEqual('Log_Upload')
})
})
describe('getGitMetaData', () => {
it('return undefined', async () => {
vi.mocked(gitRepoInfo).mockReturnValue({} as any)
const result: any = await getGitMetaData()
expect(result).toEqual(undefined)
})
it('return non empty object', async () => {
vi.mocked(gitRepoInfo).mockReturnValue({ commonGitDir: '/tmp', worktreeGitDir: '/tmp' } as any)
try {
const result: any = await getGitMetaData()
expect(result).toEqual({})
} catch (e) {
//
}
})
})
describe('getHookType', () => {
it('get hook type as string', () => {
expect(getHookType('"before each" hook for test 1')).toEqual('BEFORE_EACH')
expect(getHookType('"after each" hook for test 1')).toEqual('AFTER_EACH')
expect(getHookType('"before all" hook for test 1')).toEqual('BEFORE_ALL')
expect(getHookType('"after all" hook for test 1')).toEqual('AFTER_ALL')
expect(getHookType('no hook test')).toEqual('unknown')
})
})
describe('isScreenshotCommand', () => {
it('get true if screenshot command', () => {
expect(isScreenshotCommand({ endpoint: 'session/:sessionId/screenshot' })).toEqual(true)
})
it('get false if not a screenshot command', () => {
expect(isScreenshotCommand({ endpoint: 'session/:sessionId/element/text' })).toEqual(false)
})
})
describe('getObservabilityUser', () => {
it('get env var', () => {
process.env.BROWSERSTACK_USERNAME = 'try'
expect(getObservabilityUser({}, {})).toEqual('try')
delete process.env.BROWSERSTACK_USERNAME
})
it('get user passed in testObservabilityOptions', () => {
delete process.env.BROWSERSTACK_USERNAME
expect(getObservabilityUser({ testObservabilityOptions: { user: 'user' } } as any, {})).toEqual('user')
})
it('get user passed at root level', () => {
delete process.env.BROWSERSTACK_USERNAME
expect(getObservabilityUser({ testObservabilityOptions: { } } as any, { user: 'user-root' })).toEqual('user-root')
})
it('get undefined', () => {
delete process.env.BROWSERSTACK_USERNAME
expect(getObservabilityUser({}, {})).toEqual(undefined)
})
})
describe('getObservabilityKey', () => {
it('get env var', () => {
process.env.BROWSERSTACK_ACCESS_KEY = 'try'
expect(getObservabilityKey({}, {})).toEqual('try')
delete process.env.BROWSERSTACK_ACCESS_KEY
})
it('get key passed in testObservabilityOptions', () => {
delete process.env.BROWSERSTACK_ACCESS_KEY
expect(getObservabilityKey({ testObservabilityOptions: { key: 'user-key' } } as any, {})).toEqual('user-key')
})
it('get key passed at root level', () => {
delete process.env.BROWSERSTACK_ACCESS_KEY
expect(getObservabilityKey({ testObservabilityOptions: { } } as any, { key: 'key-root' })).toEqual('key-root')
})
it('get undefined', () => {
delete process.env.BROWSERSTACK_ACCESS_KEY
expect(getObservabilityKey({}, {})).toEqual(undefined)
})
})
describe('getObservabilityBuild', () => {
it('get env var', () => {
process.env.TEST_OBSERVABILITY_BUILD_NAME = 'try'
expect(getObservabilityBuild({})).toEqual('try')
delete process.env.TEST_OBSERVABILITY_BUILD_NAME
})
it('get name passed in testObservabilityOptions', () => {
delete process.env.TEST_OBSERVABILITY_BUILD_NAME
expect(getObservabilityBuild({ testObservabilityOptions: { buildName: 'build' } } as any)).toEqual('build')
})
it('get name passed at root level', () => {
delete process.env.TEST_OBSERVABILITY_BUILD_NAME
expect(getObservabilityBuild({ key: 'key-root', testObservabilityOptions: { } } as any, 'build-name')).toEqual('build-name')
})
it('get default', () => {
delete process.env.TEST_OBSERVABILITY_BUILD_NAME
expect(getObservabilityBuild({})).toEqual(path.basename(path.resolve(process.cwd())))
})
})
describe('getObservabilityProject', () => {
it('get env var', () => {
process.env.TEST_OBSERVABILITY_PROJECT_NAME = 'try'
expect(getObservabilityProject({})).toEqual('try')
delete process.env.TEST_OBSERVABILITY_PROJECT_NAME
})
it('get name passed in testObservabilityOptions', () => {
delete process.env.TEST_OBSERVABILITY_PROJECT_NAME
expect(getObservabilityProject({ testObservabilityOptions: { projectName: 'project' } } as any)).toEqual('project')
})
it('get name passed at root level', () => {
delete process.env.TEST_OBSERVABILITY_PROJECT_NAME
expect(getObservabilityProject({ key: 'key-root', testObservabilityOptions: { } } as any, 'project-name')).toEqual('project-name')
})
it('get undefined', () => {
delete process.env.TEST_OBSERVABILITY_PROJECT_NAME
expect(getObservabilityProject({})).toEqual(undefined)
})
})
describe('getObservabilityBuildTags', () => {
it('get array from env var', () => {
process.env.TEST_OBSERVABILITY_BUILD_TAG = 'try,qa'
expect(getObservabilityBuildTags({})).toEqual(['try', 'qa'])
delete process.env.TEST_OBSERVABILITY_BUILD_TAG
})
it('get tags passed in testObservabilityOptions', () => {
delete process.env.TEST_OBSERVABILITY_BUILD_TAG
expect(getObservabilityBuildTags({ testObservabilityOptions: { buildTag: ['qa', 'test'] } } as any)).toEqual(['qa', 'test'])
})
it('get name passed at root level', () => {
delete process.env.TEST_OBSERVABILITY_BUILD_TAG
expect(getObservabilityBuildTags({ key: 'key-root', testObservabilityOptions: { } } as any, 'qa')).toEqual(['qa'])
})
it('get empty array', () => {
delete process.env.TEST_OBSERVABILITY_BUILD_TAG
expect(getObservabilityBuildTags({})).toEqual([])
})
})
describe('o11yErrorHandler', () => {
let spy: any
beforeEach(() => {
spy = vi.spyOn(CrashReporter, 'uploadCrashReport')
spy.mockImplementation(() => {})
})
afterEach(() => {
spy.mockClear()
spy.mockReset()
})
describe('synchronous function', () => {
const func = (a: number, b: number) => {
if (a === 0 && b === 0) {
throw 'zero error'
}
return a + b
}
it ('should pass the arguments and return value correctly', () => {
const newFunc = o11yErrorHandler(func)
expect(() => {
expect(newFunc(1, 2)).toEqual(3)
}).not.toThrow()
expect(spy).toBeCalledTimes(0)
})
it('should catch error thrown from function', () => {
const newFunc = o11yErrorHandler(func)
expect(() => {
newFunc(0, 0)
}).not.toThrow()
expect(spy).toBeCalledTimes(1)
})
})
describe('asynchronous function', () => {
const func = async (a: number, b: number) => {
const val = await new Promise(resolve => {
if (a === 0 && b === 0) {
throw 'zero error'
}
resolve(a * b)
})
return val
}
it('should return values correctly from async function', async () => {
const newFunc = o11yErrorHandler(func)
const val = await newFunc(1, 2)
expect(val).toEqual(2)
expect(spy).toBeCalledTimes(0)
})
it('should catch error from async function', async () => {
const newFunc = o11yErrorHandler(func)
await newFunc(0, 0)
expect(spy).toBeCalledTimes(1)
})
})
})
describe('validateCapsWithA11y', () => {
let logInfoMock: any
beforeEach(() => {
logInfoMock = vi.spyOn(log, 'warn')
})
it('returns false if deviceName is defined', async () => {
expect(validateCapsWithA11y('Samsung S22')).toEqual(false)
expect(logInfoMock.mock.calls[0][0])
.toContain('Accessibility Automation will run only on Desktop browsers.')
})
it('returns false if browser is not chrome', async () => {
const platformMeta = {
'browser_name': 'safari'
}
expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(false)
expect(logInfoMock.mock.calls[0][0])
.toContain('Accessibility Automation will run only on Chrome browsers.')
})
it('returns false if browser version is lesser than 94', async () => {
const platformMeta = {
'browser_name': 'chrome',
'browser_version': '90'
}
expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(false)
expect(logInfoMock.mock.calls[0][0])
.toContain('Accessibility Automation will run only on Chrome browser version greater than 94.')
})
it('returns false if browser version is lesser than 94', async () => {
const platformMeta = {
'browser_name': 'chrome',
'browser_version': 'latest'
}
const chromeOptions = {
args: ['--headless']
}
expect(validateCapsWithA11y(undefined, platformMeta, chromeOptions)).toEqual(false)
expect(logInfoMock.mock.calls[0][0])
.toContain('Accessibility Automation will not run on legacy headless mode. Switch to new headless mode or avoid using headless mode.')
})
it('returns true if validation done', async () => {
const platformMeta = {
'browser_name': 'chrome',
'browser_version': 'latest'
}
const chromeOptions = {}
expect(validateCapsWithA11y(undefined, platformMeta, chromeOptions)).toEqual(true)
})
})
describe('shouldScanTestForAccessibility', () => {
const cucumberWorldObj = {
pickle: {
tags: [
{
name: 'someTag'
}
]
}
}
it('returns true if full test name contains includeTags', async () => {
expect(shouldScanTestForAccessibility('suite title', 'test title', { includeTagsInTestingScope: 'title' })).toEqual(true)
})
it('returns false if full test name contains excludeTags', async () => {
expect(shouldScanTestForAccessibility('suite title', 'test title', { excludeTagsInTestingScope: 'title' })).toEqual(true)
})
it('returns true if cucumber tags contain includeTags', async () => {
expect(shouldScanTestForAccessibility('suite title', 'test title', { includeTagsInTestingScope: 'someTag' }, cucumberWorldObj, true )).toEqual(true)
})
it('returns false if cucumber tags contain excludeTags', async () => {
expect(shouldScanTestForAccessibility('suite title', 'test title', { excludeTagsInTestingScope: 'someTag' }, cucumberWorldObj, true)).toEqual(true)
})
})
describe('isAccessibilityAutomationSession', () => {
it('returns true if accessibility is true and ally token is present', async () => {
process.env.BSTACK_A11Y_JWT = 'someToken'
expect(isAccessibilityAutomationSession(true)).toEqual(true)
})
it('returns true if accessibility is true and ally token is present', async () => {
process.env.BSTACK_A11Y_JWT = ''
expect(isAccessibilityAutomationSession(true)).toEqual(false)
})
})
describe('getA11yResults', () => {
const browser = {
sessionId: 'session123',
config: {},
capabilities: {
device: '',
os: 'OS X',
os_version: 'Catalina',
browserName: 'chrome'
},
instances: ['browserA', 'browserB'],
isMultiremote: false,
browserA: {
sessionId: 'session456',
capabilities: { 'bstack:options': {
device: '',
os: 'Windows',
osVersion: 10,
browserName: 'chrome'
} }
},
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserB: {},
execute: vi.fn(),
executeAsync: vi.fn(),
on: vi.fn(),
} 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)
expect(result).toEqual([])
})
it('return success object if ally token defined and no error in response data', async () => {
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(false)
const result: any = await utils.getA11yResults((browser as WebdriverIO.Browser), true, false)
expect(result).toEqual([])
})
it('return results object if bstack as well as accessibility session', async () => {
process.env.BSTACK_A11Y_JWT = 'abc'
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
await utils.getA11yResults((browser as WebdriverIO.Browser), true, true)
delete process.env.BSTACK_A11Y_JWT
expect(browser.executeAsync).toBeCalledTimes(2)
})
})
describe('getA11yResultsSummary', () => {
const browser = {
sessionId: 'session123',
config: {},
capabilities: {
device: '',
os: 'OS X',
os_version: 'Catalina',
browserName: 'chrome'
},
instances: ['browserA', 'browserB'],
isMultiremote: false,
browserA: {
sessionId: 'session456',
capabilities: { 'bstack:options': {
device: '',
os: 'Windows',
osVersion: 10,
browserName: 'chrome'
} }
},
getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]),
browserB: {},
execute: vi.fn(),
executeAsync: vi.fn(),
on: vi.fn(),
} 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)
expect(result).toEqual({})
})
it('return success object if ally token defined and no error in response data', async () => {
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(false)
const result: any = await utils.getA11yResultsSummary((browser as WebdriverIO.Browser), true, false)
expect(result).toEqual({})
})
it('return results object if bstack as well as accessibility session', async () => {
process.env.BSTACK_A11Y_JWT = 'abc'
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
await utils.getA11yResultsSummary((browser as WebdriverIO.Browser), true, true)
delete process.env.BSTACK_A11Y_JWT
expect(browser.executeAsync).toBeCalledTimes(2)
})
})
describe('isTrue', () => {
it('returns true if value is `true`', async () => {
expect(isTrue('true')).toEqual(true)
})
it('returns false if value is `false`', async () => {
expect(isTrue('false')).toEqual(false)
})
it('returns false if value is undefined', async () => {
expect(isTrue(undefined)).toEqual(false)
})
})
describe('getBrowserStackUser', function () {
it('should return userName from config if not present as env variable', function () {
expect(utils.getBrowserStackUser({
user: 'config_user_name',
key: 'config_user_key',
capabilities: {}
})).toEqual('config_user_name')
})
it('should return userName if present as env variable', function () {
process.env.BROWSERSTACK_USERNAME = 'user_name'
expect(utils.getBrowserStackUser({
user: 'config_user_name',
key: 'config_user_key',
capabilities: {}
})).toEqual('user_name')
})
})
describe('getBrowserStackKey', function () {
it('should return accessKey from config if not present as env variable', function () {
expect(utils.getBrowserStackKey({
user: 'config_user_name',
key: 'config_user_key',
capabilities: {}
})).toEqual('config_user_key')
})
it('should return accessKey if present as env variable', function () {
process.env.BROWSERSTACK_ACCESS_KEY = 'user_key'
expect(utils.getBrowserStackKey({
user: 'config_user_name',
key: 'config_user_key',
capabilities: {}
})).toEqual('user_key')
})
})
describe('frameworkSupportsHook', function () {
describe('mocha', function () {
it('should return true for beforeHook', function () {
expect(frameworkSupportsHook('before', 'mocha')).toBe(true)
})
})
describe('cucumber', function () {
it('should return true for cucumber', function () {
expect(frameworkSupportsHook('before', 'cucumber')).toBe(true)
})
})
it('should return false for any other framework', function () {
expect(frameworkSupportsHook('before', 'jasmine')).toBe(false)
})
})
describe('uploadLogs', function () {
beforeAll(() => {
vi.mocked(fetch).mockClear()
vi.mocked(fetch).mockReturnValueOnce(Promise.resolve(Response.json({ status: 'success', message: 'Logs uploaded Successfully' })))
})
it('should return if user is undefined', async function () {
await uploadLogs(undefined, 'some_key', 'some_uuid')
expect(fetch).not.toHaveBeenCalled()
})
it('should return if key is undefined', async function () {
await uploadLogs('some_user', undefined, 'some_uuid')
expect(fetch).not.toHaveBeenCalled()
})
it('should upload the logs', async function () {
await uploadLogs('some_user', 'some_key', 'some_uuid')
expect(fetch).toHaveBeenCalled()
})
afterAll(() => {
vi.mocked(fetch).mockClear()
})
})
describe('getFailureObject', function () {
it('should return parsed failure object for string error', function () {
const error = 'some error'
expect(getFailureObject(error)).toEqual({
failure: [{ backtrace: [''] }],
failure_reason: 'some error',
failure_type: 'UnhandledError'
})
})
it('should parse for assertion error', function () {
const error = new Error('AssertionError: 2 is not equal to 4')
expect(getFailureObject(error)).toMatchObject({
failure_reason: 'AssertionError: 2 is not equal to 4',
failure_type: 'AssertionError'
})
})
it ('should get stacktrace for error object', function () {
const error = new Error('some error')
expect(getFailureObject(error)).toMatchObject({
failure: [{ backtrace: [error.stack.toString()] }]
})
})
})
describe('getObservabilityProduct', () => {
it ('should return app automate', function () {
expect(getObservabilityProduct(undefined, true)).toEqual('app-automate')
})
})
describe('isUndefined', () => {
it ('should return true for empty string', function () {
expect(isUndefined('')).toEqual(true)
})
})
describe('processTestObservabilityResponse', () => {
let response: LaunchResponse, handleErrorForObservabilitySpy
beforeAll(() => {
response = {
jwt: 'abc',
build_hashed_id: 'abc',
observability: {
success: true,
options: {},
errors: undefined
},
accessibility: {
success: true,
options: {
status: 'true',
commandsToWrap: {
scriptsToRun: [],
commands: []
},
scripts: [{
name: 'abc',
command: 'abc'
}],
capabilities: [{
name: 'abc',
value: 'abc'
}]
},
errors: undefined
}
}
})
it ('processTestObservabilityResponse should not log an error', function () {
processTestObservabilityResponse(response)
expect(process.env[BROWSERSTACK_OBSERVABILITY]).toEqual('true')
})
it ('processTestObservabilityResponse should log error if observability success is false', function () {
handleErrorForObservabilitySpy = vi.spyOn(testHubUtils, 'handleErrorForObservability').mockReturnValue({} as any)
const res = response
res.observability!.success = false
processTestObservabilityResponse(res)
expect(handleErrorForObservabilitySpy).toBeCalled()
})
it ('processTestObservabilityResponse should log error if observability field not found', function () {
handleErrorForObservabilitySpy = vi.spyOn(testHubUtils, 'handleErrorForObservability').mockReturnValue({} as any)
const res = response
res.observability = undefined
processTestObservabilityResponse(res)
expect(handleErrorForObservabilitySpy).toBeCalled()
})
afterEach(() => {
handleErrorForObservabilitySpy?.mockClear()
})
})
describe('processAccessibilityResponse', () => {
let response: LaunchResponse, handleErrorForAccessibilitySpy
beforeAll(() => {
response = {
jwt: 'abc',
build_hashed_id: 'abc',
observability: {
success: true,
options: {},
errors: undefined
},
accessibility: {
success: true,
options: {
status: 'true',
commandsToWrap: {
scriptsToRun: [],
commands: []
},
scripts: [{
name: 'abc',
command: 'abc'
}],
capabilities: [
{
name: 'accessibilityToken',
value: 'abc'
},
{
name: 'scannerVersion',
value: 'abc'
}
]
},
errors: undefined
}
}
})
it ('processAccessibilityResponse should not log an error', function () {
processAccessibilityResponse(response)
expect(process.env[BROWSERSTACK_ACCESSIBILITY]).toEqual('true')
})
it ('processAccessibilityResponse should log error if accessibility success is false', function () {
handleErrorForAccessibilitySpy = vi.spyOn(testHubUtils, 'handleErrorForAccessibility').mockReturnValue({} as any)
const res = response
res.accessibility!.success = false
processAccessibilityResponse(res)
expect(handleErrorForAccessibilitySpy).toBeCalled()
})
it ('processAccessibilityResponse should log error if accessibility field not found', function () {
handleErrorForAccessibilitySpy = vi.spyOn(testHubUtils, 'handleErrorForAccessibility').mockReturnValue({} as any)
const res = response
res.accessibility = undefined
processAccessibilityResponse(res)
expect(handleErrorForAccessibilitySpy).toBeCalled()
})
afterEach(() => {
handleErrorForAccessibilitySpy?.mockClear()
})
})
describe('processLaunchBuildResponse', () => {
let response: LaunchResponse, observabilitySpy, accessibilitySpy
beforeAll(() => {
response = {
jwt: 'abc',
build_hashed_id: 'abc',
observability: {
success: true,
options: {},
errors: undefined
},
accessibility: {
success: true,
options: {
status: 'true',
commandsToWrap: {
scriptsToRun: [],
commands: []
},
scripts: [{
name: 'abc',
command: 'abc'
}],
capabilities: [{
name: 'accessibilityToken',
value: 'abc'
}]
},
errors: undefined
}
}
})
beforeEach(() => {
observabilitySpy = vi.spyOn(utils, 'processTestObservabilityResponse').mockImplementation(() => {})
accessibilitySpy = vi.spyOn(utils, 'processAccessibilityResponse').mockImplementation(() => {})
})
it ('processTestObservabilityResponse should be called', function () {
processLaunchBuildResponse(response, { testObservability: true, accessibility: true, capabilities: {} })
expect(process.env[BROWSERSTACK_OBSERVABILITY]).toEqual('true')
})
it ('processAccessibilityResponse should be called', function () {
processLaunchBuildResponse(response, { testObservability: true, accessibility: true, capabilities: {} })
expect(process.env[BROWSERSTACK_ACCESSIBILITY]).toEqual('true')
})
afterEach(() => {
observabilitySpy?.mockClear()
accessibilitySpy?.mockClear()
})
})
describe('jsonifyAccessibilityArray', () => {
const array = [{
name: 'accessibilityToken',
value: 'abc'
}]
it('jsonifyAccessibilityArray', () => {
expect(jsonifyAccessibilityArray(array, 'name', 'value')).toEqual({ 'accessibilityToken': 'abc' })
})
})
describe('logPatcher', () => {
const BSTestOpsPatcher = new logPatcher({})
const emitSpy = vi.spyOn(process, 'emit')
it('logPatcher methods should emit data', () => {
BSTestOpsPatcher.info('abc')
BSTestOpsPatcher.error('abc')
BSTestOpsPatcher.warn('abc')
BSTestOpsPatcher.trace('abc')
BSTestOpsPatcher.debug('abc')
BSTestOpsPatcher.log('abc')
expect(emitSpy).toBeCalled()
})
})