Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
declare namespace WebdriverIO {
interface Browser {
getAccessibilityResultsSummary: () => Promise<{ [key: string]: any; }>,
getAccessibilityResults: () => Promise<Array<{ [key: string]: any; }>>
getAccessibilityResults: () => Promise<Array<{ [key: string]: any; }>>,
performScan: () => Promise<{ [key: string]: any; } | undefined>
}

interface MultiRemoteBrowser {
getAccessibilityResultsSummary: () => Promise<{ [key: string]: any; }>,
getAccessibilityResults: () => Promise<Array<{ [key: string]: any; }>>
getAccessibilityResults: () => Promise<Array<{ [key: string]: any; }>>,
performScan: () => Promise<{ [key: string]: any; } | undefined>
}
}
142 changes: 93 additions & 49 deletions packages/wdio-browserstack-service/src/accessibility-handler.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import util from 'node:util'

import type { Capabilities, Frameworks } from '@wdio/types'

import type { ITestCaseHookParameter } from './cucumber-types.js'

import {
getA11yResultsSummary,
getA11yResults,
performA11yScan,
getUniqueIdentifier,
getUniqueIdentifierForCucumber,
isAccessibilityAutomationSession,
Expand All @@ -14,7 +17,8 @@ import {
validateCapsWithA11y,
isTrue
} from './util.js'
import { testForceStop, testStartEvent, testStop } from './scripts/test-event-scripts.js'
import accessibilityScripts from './scripts/accessibility-scripts.js'

import { BStackLogger } from './bstackLogger.js'

class _AccessibilityHandler {
Expand All @@ -24,6 +28,8 @@ class _AccessibilityHandler {
private _accessibility?: boolean
private _accessibilityOptions?: { [key: string]: any; }
private _testMetadata: { [key: string]: any; } = {}
private static _a11yScanSessionMap: { [key: string]: any; } = {}
private _sessionId: string | null = null

constructor (
private _browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser,
Expand Down Expand Up @@ -80,7 +86,8 @@ class _AccessibilityHandler {
}
}

async before () {
async before (sessionId: string) {
this._sessionId = sessionId
this._accessibility = isTrue(this._getCapabilityValue(this._caps, 'accessibility', 'browserstack.accessibility'))

if (isBrowserstackSession(this._browser) && isAccessibilityAutomationSession(this._accessibility)) {
Expand All @@ -97,31 +104,61 @@ class _AccessibilityHandler {
(this._browser as WebdriverIO.Browser).getAccessibilityResults = async () => {
return await getA11yResults((this._browser as WebdriverIO.Browser), isBrowserstackSession(this._browser), this._accessibility)
}
}

async beforeTest (suiteTitle: string | undefined, test: Frameworks.Test) {
if (
this._framework !== 'mocha' ||
!this.shouldRunTestHooks(this._browser, this._accessibility)
) {
return
(this._browser as WebdriverIO.Browser).performScan = async () => {
return await performA11yScan((this._browser as WebdriverIO.Browser), isBrowserstackSession(this._browser), this._accessibility)
}

const shouldScanTest = shouldScanTestForAccessibility(suiteTitle, test.title, this._accessibilityOptions)
const testIdentifier = this.getIdentifier(test)
const isPageOpened = await this.checkIfPageOpened(this._browser, testIdentifier, shouldScanTest)

if (!isPageOpened) {
return
if (this._accessibility) {
try {
if ('overwriteCommand' in this._browser && Array.isArray(accessibilityScripts.commandsToWrap)) {
const that = this
accessibilityScripts.commandsToWrap.forEach(async function (command) {
if (command.name && command.class) {
await (that._browser as WebdriverIO.Browser).overwriteCommand(command.name, async function (origFunction: Function, ...args: any[]) {
if (
that._sessionId && AccessibilityHandler._a11yScanSessionMap[that._sessionId] &&
(
!command.name.includes('execute') ||
!AccessibilityHandler.isBrowserstackScript(args.length ? args[0] : null)
)
) {
BStackLogger.debug(`Performing scan for ${command.class} ${command.name}`)
await performA11yScan(that._browser, true, true, command.name)
}
return origFunction(...args)
}, command.class === 'Element')
}
})
}
} catch {
/* Do nothing */
}
}
}

async beforeTest (suiteTitle: string | undefined, test: Frameworks.Test) {
try {
if (shouldScanTest) {
BStackLogger.info('Setup for Accessibility testing has started. Automate test case execution will begin momentarily.')
await this.sendTestStartEvent(this._browser as WebdriverIO.Browser)
} else {
await this.sendTestForceStopEvent(this._browser as WebdriverIO.Browser)
if (
this._framework !== 'mocha' ||
!this.shouldRunTestHooks(this._browser, this._accessibility)
) {
return
}

const shouldScanTest = shouldScanTestForAccessibility(suiteTitle, test.title, this._accessibilityOptions)
const testIdentifier = this.getIdentifier(test)
const isPageOpened = await this.checkIfPageOpened(this._browser, testIdentifier, shouldScanTest)

if (this._sessionId) {
/* For case with multiple tests under one browser, before hook of 2nd test should change this map value */
AccessibilityHandler._a11yScanSessionMap[this._sessionId] = shouldScanTest
}

if (!isPageOpened) {
return
}

this._testMetadata[testIdentifier].accessibilityScanStarted = shouldScanTest

if (shouldScanTest) {
Expand Down Expand Up @@ -179,28 +216,27 @@ class _AccessibilityHandler {
* Cucumber Only
*/
async beforeScenario (world: ITestCaseHookParameter) {
if (!this.shouldRunTestHooks(this._browser, this._accessibility)) {
return
}
try {
if (!this.shouldRunTestHooks(this._browser, this._accessibility)) {
return
}

const pickleData = world.pickle
const gherkinDocument = world.gherkinDocument
const featureData = gherkinDocument.feature
const uniqueId = getUniqueIdentifierForCucumber(world)
const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true)
const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario)
const pickleData = world.pickle
const gherkinDocument = world.gherkinDocument
const featureData = gherkinDocument.feature
const uniqueId = getUniqueIdentifierForCucumber(world)
const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true)
const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario)

if (!isPageOpened) {
return
}
if (this._sessionId) {
/* For case with multiple tests under one browser, before hook of 2nd test should change this map value */
AccessibilityHandler._a11yScanSessionMap[this._sessionId] = shouldScanScenario
}

try {
if (shouldScanScenario) {
BStackLogger.info('Setup for Accessibility testing has started. Automate test case execution will begin momentarily.')
await this.sendTestStartEvent(this._browser as WebdriverIO.Browser)
} else {
await this.sendTestForceStopEvent(this._browser as WebdriverIO.Browser)
if (!isPageOpened) {
return
}

this._testMetadata[uniqueId].accessibilityScanStarted = shouldScanScenario

if (shouldScanScenario) {
Expand Down Expand Up @@ -258,16 +294,11 @@ class _AccessibilityHandler {
* private methods
*/

private sendTestStartEvent(browser: WebdriverIO.Browser) {
return (browser as WebdriverIO.Browser).executeAsync(testStartEvent)
}

private sendTestForceStopEvent(browser: WebdriverIO.Browser) {
return (browser as WebdriverIO.Browser).execute(testForceStop)
}

private sendTestStopEvent(browser: WebdriverIO.Browser, dataForExtension: any) {
return (browser as WebdriverIO.Browser).executeAsync(testStop, dataForExtension)
private async sendTestStopEvent(browser: WebdriverIO.Browser, dataForExtension: any) {
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)
BStackLogger.debug(util.format(results as string))
}

private getIdentifier (test: Frameworks.Test | ITestCaseHookParameter) {
Expand Down Expand Up @@ -301,11 +332,24 @@ class _AccessibilityHandler {

return pageOpen
}

private static isBrowserstackScript(script: string | null): Boolean {
if (!script) {
return true
}
try {
return (
script.toLowerCase().indexOf('browserstack_executor') !== -1 ||
script.toLowerCase().indexOf('browserstack_accessibility_automation_script') !== -1
)
} catch (err: any) {
return true
}
}
}

// https://github.com/microsoft/TypeScript/issues/6543
const AccessibilityHandler: typeof _AccessibilityHandler = o11yClassErrorHandler(_AccessibilityHandler)
type AccessibilityHandler = _AccessibilityHandler

export default AccessibilityHandler

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import path from 'node:path'
import fs from 'node:fs'
import os from 'node:os'

class AccessibilityScripts {
private static instance: AccessibilityScripts | null = null

public performScan: string | null = null
public getResults: string | null = null
public getResultsSummary: string | null = null
public saveTestResults: string | null = null
public commandsToWrap: Array<any> | null = null

public browserstackFolderPath = path.join(os.homedir(), '.browserstack')
public commandsPath = path.join(this.browserstackFolderPath, 'commands.json')

constructor() {}

public static checkAndGetInstance() {
if (!AccessibilityScripts.instance) {
AccessibilityScripts.instance = new AccessibilityScripts()
AccessibilityScripts.instance.readFromExistingFile()
}
return AccessibilityScripts.instance
}

public readFromExistingFile() {
try {
if (fs.existsSync(this.commandsPath)) {
const data = fs.readFileSync(this.commandsPath, 'utf8')
if (data) {
this.update(JSON.parse(data))
}
}
} catch (error: any) {
/* Do nothing */
}
}

public update(data: { commands: [any], scripts: { scan: null; getResults: null; getResultsSummary: null; saveResults: null; }; }) {
if (data.scripts) {
this.performScan = data.scripts.scan
this.getResults = data.scripts.getResults
this.getResultsSummary = data.scripts.getResultsSummary
this.saveTestResults = data.scripts.saveResults
}
if (data.commands && data.commands.length) {
this.commandsToWrap = data.commands
}
}

public store() {
if (!fs.existsSync(this.browserstackFolderPath)){
fs.mkdirSync(this.browserstackFolderPath)
}

fs.writeFileSync(this.commandsPath, JSON.stringify({
commands: this.commandsToWrap,
scripts: {
scan: this.performScan,
getResults: this.getResults,
getResultsSummary: this.getResultsSummary,
saveResults: this.saveTestResults,
}
}))
}
}

export default AccessibilityScripts.checkAndGetInstance()

This file was deleted.

Loading