import path from 'node:path'
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
import logger from '@wdio/logger'
import AccessibilityHandler from '../src/accessibility-handler.js'
import type { BrowserstackConfig, BrowserstackOptions } from '../src/types.js'
import type { Options } from '@wdio/types'
import * as utils from '../src/util.js'
import type { Capabilities } from '@wdio/types'
import * as bstackLogger from '../src/bstackLogger.js'
const log = logger('test')
let accessibilityHandler: AccessibilityHandler
let browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
let options: BrowserstackConfig & BrowserstackOptions
let config : Options.Testrunner
let caps: Capabilities.RemoteCapability
let accessibilityOpts: { [key: string]: any; }
vi.mock('fetch')
vi.mock('@wdio/logger', () => import(path.join(process.cwd(), '__mocks__', '@wdio/logger')))
vi.useFakeTimers().setSystemTime(new Date('2020-01-01'))
vi.mock('uuid', () => ({ v4: () => '123456789' }))
const bstackLoggerSpy = vi.spyOn(bstackLogger.BStackLogger, 'logToFile')
bstackLoggerSpy.mockImplementation(() => {})
beforeEach(() => {
vi.mocked(log.info).mockClear()
vi.mocked(fetch).mockClear()
vi.mocked(fetch).mockReturnValue(Promise.resolve(Response.json({ automation_session: {
browser_url: 'https://www.browserstack.com/automate/builds/1/sessions/2'
} })))
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: async () => { 'done' },
getUrl: () => { return 'https://www.google.com/'},
on: vi.fn(),
} as unknown as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser
caps = {
browserName: 'chrome',
'bstack:options': {
os: 'OS X',
osVersion: 'Catalina',
accessibility: true
} } as Capabilities.RemoteCapability
options = {
accessibility: true
}
config = {}
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'framework', true)
})
it('should initialize correctly', () => {
accessibilityOpts = {
wcagVersion: 'wcag2a',
includeIssueType: {
bestPractice: true,
needsReview: true
}
}
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'framework', true, false, accessibilityOpts)
expect(accessibilityHandler['_platformA11yMeta']).toEqual({ browser_name: 'chrome', browser_version: 'latest', os_name: 'OS X', os_version: 'Catalina' })
expect(accessibilityHandler['_accessibility']).toEqual(true)
expect(accessibilityHandler['_caps']).toEqual(caps)
expect(accessibilityHandler['_framework']).toEqual('framework')
})
describe('before', () => {
const isBrowserstackSessionSpy = vi.spyOn(utils, 'isBrowserstackSession')
const getA11yResultsSummarySpy = vi.spyOn(utils, 'getA11yResultsSummary')
const shouldAddServiceVersionSpy = vi.spyOn(utils, 'shouldAddServiceVersion')
const getA11yResultsSpy = vi.spyOn(utils, 'getA11yResults')
const isAccessibilityAutomationSessionSpy = vi.spyOn(utils, 'isAccessibilityAutomationSession')
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'framework', true, false, accessibilityOpts)
getA11yResultsSpy.mockClear()
isBrowserstackSessionSpy.mockClear()
getA11yResultsSummarySpy.mockClear()
isAccessibilityAutomationSessionSpy.mockClear()
})
it('calls isBrowserstackSession', async () => {
isBrowserstackSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123')
expect(isBrowserstackSessionSpy).toBeCalledTimes(0)
})
it('isBrowserstackSession returns true', async () => {
isBrowserstackSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123')
expect(isBrowserstackSessionSpy).toBeCalledTimes(0)
})
it('calls isAccessibilityAutomationSession', async () => {
isBrowserstackSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123')
expect(isAccessibilityAutomationSessionSpy).toBeCalledTimes(2)
})
it('calls validateCapsWithA11y', async () => {
const _getCapabilityValueSpy = vi.spyOn(accessibilityHandler, '_getCapabilityValue').mockReturnValue(true)
const validateCapsWithA11ySpy = vi.spyOn(utils, 'validateCapsWithA11y')
shouldAddServiceVersionSpy.mockReturnValue(true)
isBrowserstackSessionSpy.mockReturnValue(true)
isAccessibilityAutomationSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123')
expect(_getCapabilityValueSpy).toBeCalledTimes(3)
expect(validateCapsWithA11ySpy).toBeCalledTimes(1)
})
it('calls validateCapsWithNonBstackA11y', async () => {
const validateCapsWithNonBstackA11ySpy = vi.spyOn(utils, 'validateCapsWithNonBstackA11y')
shouldAddServiceVersionSpy.mockReturnValue(false)
isAccessibilityAutomationSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123')
expect(validateCapsWithNonBstackA11ySpy).toBeCalledTimes(1)
})
it('calls getA11yResultsSummary', async () => {
isBrowserstackSessionSpy.mockReturnValue(true)
isAccessibilityAutomationSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123');
(browser as WebdriverIO.Browser).getAccessibilityResultsSummary()
expect(getA11yResultsSummarySpy).toBeCalledTimes(1)
})
it('calls getA11yResults', async () => {
isBrowserstackSessionSpy.mockReturnValue(true)
isAccessibilityAutomationSessionSpy.mockReturnValue(true)
await accessibilityHandler.before('session123');
(browser as WebdriverIO.Browser).getAccessibilityResults()
expect(getA11yResultsSpy).toBeCalledTimes(1)
})
})
describe('beforeScenario', () => {
let executeAsyncSpy: any
let executeSpy: any
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'framework', true, false, accessibilityOpts)
executeAsyncSpy = vi.spyOn((browser as WebdriverIO.Browser), 'executeAsync')
executeSpy = vi.spyOn((browser as WebdriverIO.Browser), 'execute')
vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
vi.spyOn(utils, 'getUniqueIdentifierForCucumber').mockReturnValue('test title')
})
it('should execute test started if page opened and can scan the page', async () => {
const logInfoMock = vi.spyOn(log, 'info')
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(true)
await accessibilityHandler.beforeScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(logInfoMock.mock.calls[0][0])
.toContain('Automate test case execution has started.')
})
it('should not execute test started if url is invalid', async () => {
browser.getUrl = async () => {
return ':data;'
}
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(false)
await accessibilityHandler.beforeScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(executeSpy).toBeCalledTimes(0)
expect(executeAsyncSpy).toBeCalledTimes(0)
})
it('should not execute test started if page opened but cannot start scan', async () => {
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(false)
await accessibilityHandler.beforeScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(executeSpy).toBeCalledTimes(0)
})
it('should not execute test started if shouldRunTestHooks is false', async () => {
accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return false })
await accessibilityHandler.beforeScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(executeSpy).toBeCalledTimes(0)
})
it('should throw error in before scenario if exception occurs', async () => {
const logErrorMock = vi.spyOn(bstackLogger.BStackLogger, 'error')
vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
vi.spyOn(utils, 'getUniqueIdentifierForCucumber').mockReturnValue('test-id')
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockImplementation(() => {
throw new Error('Test Error')
})
accessibilityHandler['_accessibility'] = true
accessibilityHandler['_sessionId'] = 'session123'
await accessibilityHandler.beforeScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(logErrorMock).toHaveBeenCalled()
expect(logErrorMock.mock.calls[0][0])
.toContain('Exception in starting accessibility automation scan for this test case')
})
})
describe('afterScenario', () => {
let executeAsyncSpy: any
let accessibilityHandler: AccessibilityHandler
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, false, 'framework', true, accessibilityOpts)
executeAsyncSpy = vi.spyOn((browser as WebdriverIO.Browser), 'executeAsync')
vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
vi.spyOn(utils, 'getUniqueIdentifierForCucumber').mockReturnValue('test title')
accessibilityHandler['_testMetadata']['test title'] = {
accessibilityScanStarted: true,
scanTestForAccessibility: true
}
accessibilityHandler['sendTestStopEvent'] = vi.fn().mockImplementation(() => { return [] })
})
it('should execute test end if scanTestForAccessibility is true', async () => {
const logInfoMock = vi.spyOn(log, 'info')
await accessibilityHandler.afterScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(accessibilityHandler['sendTestStopEvent']).toBeCalledTimes(1)
expect(logInfoMock.mock.calls[1][0])
.toContain('Accessibility testing for this test case has ended.')
})
it('should return if shouldRunTestHooks is false', async () => {
accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return false })
await accessibilityHandler.afterScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(executeAsyncSpy).toBeCalledTimes(0)
})
it('should return if accessibilityScanStarted is false', async () => {
accessibilityHandler['_testMetadata']['test title'] = {
accessibilityScanStarted: false,
scanTestForAccessibility: true
}
await accessibilityHandler.afterScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(executeAsyncSpy).toBeCalledTimes(0)
})
it('should throw error in after scenario if exception occurs', async () => {
const logErrorMock = vi.spyOn(log, 'error')
accessibilityHandler['sendTestStopEvent'] = vi.fn().mockImplementation(() => { throw new Error() })
await accessibilityHandler.afterScenario({
pickle: {
name: 'pickle-name',
tags: []
},
gherkinDocument: {
uri: '',
feature: {
name: 'feature-name',
description: ''
}
}
} as any)
expect(logErrorMock.mock.calls[0][0])
.toContain('Accessibility results could not be processed for the test case')
})
})
describe('beforeTest', () => {
let executeAsyncSpy: any
let executeSpy: any
describe('mocha', () => {
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'mocha', true, false, accessibilityOpts)
vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
vi.spyOn(utils, 'getUniqueIdentifier').mockReturnValue('test title')
executeAsyncSpy = vi.spyOn((browser as WebdriverIO.Browser), 'executeAsync')
executeSpy = vi.spyOn((browser as WebdriverIO.Browser), 'execute')
})
it('should execute test started if page opened and can scan the page', async () => {
const logInfoMock = vi.spyOn(log, 'info')
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(true)
accessibilityHandler['sendTestStartEvent'] = vi.fn().mockImplementation(() => { return [] })
await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(logInfoMock.mock.calls[0][0])
.toContain('Automate test case execution has started.')
vi.fn().mockRestore()
})
it('should not execute test started if url is invalid', async () => {
browser.getUrl = async () => {
return ':data;'
}
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(false)
await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(executeSpy).toBeCalledTimes(0)
expect(executeAsyncSpy).toBeCalledTimes(0)
})
it('should not execute test started if page opened but cannot start scan', async () => {
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(false)
await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(executeSpy).toBeCalledTimes(0)
})
it('should not execute test started if shouldRunTestHooks is false', async () => {
accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return false })
await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(executeSpy).toBeCalledTimes(0)
})
it('should throw error in before test if exception occurs', async () => {
const logErrorMock = vi.spyOn(log, 'error')
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(true)
accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { throw new Error() })
await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(logErrorMock.mock.calls[0][0])
.toContain('Exception in starting accessibility automation scan for this test case Error')
})
})
describe('jasmine', () => {
let isBrowserstackSession: any
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'jasmine', true, false, accessibilityOpts)
isBrowserstackSession = vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
})
it('should execute test started in case of jasmine', async () => {
vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(true)
await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(isBrowserstackSession).toBeCalledTimes(0)
})
})
})
describe('afterTest', () => {
let executeAsyncSpy: any
let accessibilityHandler: AccessibilityHandler
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'mocha', true, false, accessibilityOpts)
executeAsyncSpy = vi.spyOn((browser as WebdriverIO.Browser), 'executeAsync')
vi.spyOn(utils, 'isBrowserstackSession').mockReturnValue(true)
vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true)
vi.spyOn(utils, 'getUniqueIdentifier').mockReturnValue('test title')
accessibilityHandler['_testMetadata']['test title'] = {
accessibilityScanStarted: true,
scanTestForAccessibility: true
}
})
it('should execute test end if scanTestForAccessibility is true', async () => {
const logInfoMock = vi.spyOn(log, 'info')
await accessibilityHandler.afterTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(logInfoMock.mock.calls[1][0])
.toContain('Accessibility testing for this test case has ended.')
})
it('should not return if accessibilityScanStarted is false', async () => {
accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return false })
await accessibilityHandler.afterTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(executeAsyncSpy).toBeCalledTimes(0)
})
it('should not return if shouldRunTestHooks is false', async () => {
accessibilityHandler['_testMetadata']['test title'] = {
accessibilityScanStarted: false,
scanTestForAccessibility: true
}
await accessibilityHandler.afterTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(executeAsyncSpy).toBeCalledTimes(0)
})
it('should throw error in after test if exception occurs', async () => {
const logErrorMock = vi.spyOn(log, 'error')
accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return true })
accessibilityHandler['sendTestStopEvent'] = vi.fn().mockImplementation(() => { throw new Error() })
await accessibilityHandler.afterTest('suite title', { parent: 'parent', title: 'test' } as any)
expect(logErrorMock.mock.calls[0][0])
.toContain('Accessibility results could not be processed for the test case test. Error :')
})
})
describe('getIdentifier', () => {
let getUniqueIdentifierSpy: any
let getUniqueIdentifierForCucumberSpy: any
beforeEach(() => {
accessibilityHandler = new AccessibilityHandler(browser, caps, options, false, config, 'framework', true, false, accessibilityOpts)
getUniqueIdentifierSpy = vi.spyOn(utils, 'getUniqueIdentifier')
getUniqueIdentifierForCucumberSpy = vi.spyOn(utils, 'getUniqueIdentifierForCucumber')
})
it('non cucumber', () => {
accessibilityHandler['getIdentifier']({ parent: 'parent', title: 'title' } as any)
expect(getUniqueIdentifierSpy).toBeCalledTimes(1)
})
it('cucumber', () => {
accessibilityHandler['getIdentifier']({ pickle: { uri: 'uri', astNodeIds: ['9', '8'] } } as any)
expect(getUniqueIdentifierForCucumberSpy).toBeCalledTimes(1)
})
afterEach(() => {
getUniqueIdentifierSpy.mockReset()
getUniqueIdentifierForCucumberSpy.mockReset()
})
})