🏡 index : quine-global/webdriverio.git

/// <reference path="../../webdriverio/src/@types/async.d.ts" />
/// <reference path="../src/@types/bstack-service-types.d.ts" />
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', () => {
    // let _getCapabilityValueSpy
    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()
    })
})