From dc0ace2b70c526bf8bda988ba43bfec1af799470 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 1 Mar 2024 06:04:08 +0530 Subject: [PATCH 01/11] A11y WDIO stability init --- .../src/@types/bstack-service-types.d.ts | 6 +- .../src/accessibility-handler.ts | 94 +++++++++++++------ .../src/scripts/accessibility-scripts.ts | 69 ++++++++++++++ .../src/scripts/test-event-scripts.ts | 68 -------------- .../wdio-browserstack-service/src/service.ts | 32 +++---- .../wdio-browserstack-service/src/util.ts | 56 +++++++---- 6 files changed, 194 insertions(+), 131 deletions(-) create mode 100644 packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts delete mode 100644 packages/wdio-browserstack-service/src/scripts/test-event-scripts.ts diff --git a/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts b/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts index e139b420130..73560d96b61 100644 --- a/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts +++ b/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts @@ -1,11 +1,13 @@ declare namespace WebdriverIO { interface Browser { getAccessibilityResultsSummary: () => Promise<{ [key: string]: any; }>, - getAccessibilityResults: () => Promise> + getAccessibilityResults: () => Promise>, + performScan: () => Promise } interface MultiRemoteBrowser { getAccessibilityResultsSummary: () => Promise<{ [key: string]: any; }>, - getAccessibilityResults: () => Promise> + getAccessibilityResults: () => Promise>, + performScan: () => Promise } } diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index a20ee51f966..df3c1bdccad 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -1,3 +1,5 @@ +import util from 'node:util' + import type { Capabilities, Frameworks } from '@wdio/types' import type { ITestCaseHookParameter } from './cucumber-types.js' @@ -5,6 +7,7 @@ import type { ITestCaseHookParameter } from './cucumber-types.js' import { getA11yResultsSummary, getA11yResults, + performA11yScan, getUniqueIdentifier, getUniqueIdentifierForCucumber, isAccessibilityAutomationSession, @@ -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 { @@ -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, @@ -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)) { @@ -97,6 +104,33 @@ class _AccessibilityHandler { (this._browser as WebdriverIO.Browser).getAccessibilityResults = async () => { return await getA11yResults((this._browser as WebdriverIO.Browser), isBrowserstackSession(this._browser), this._accessibility) } + + (this._browser as WebdriverIO.Browser).performScan = async () => { + return await performA11yScan((this._browser as WebdriverIO.Browser), isBrowserstackSession(this._browser), this._accessibility) + } + + if (this._accessibility) { + if (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') + } + }) + } + } } async beforeTest (suiteTitle: string | undefined, test: Frameworks.Test) { @@ -111,17 +145,16 @@ class _AccessibilityHandler { const testIdentifier = this.getIdentifier(test) const isPageOpened = await this.checkIfPageOpened(this._browser, testIdentifier, shouldScanTest) + if (this._sessionId) { + /* For case with multiple tests under onw browser, before hook of 2nd test should change this map value */ + AccessibilityHandler._a11yScanSessionMap[this._sessionId] = shouldScanTest + } + if (!isPageOpened) { return } 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) - } this._testMetadata[testIdentifier].accessibilityScanStarted = shouldScanTest if (shouldScanTest) { @@ -133,7 +166,6 @@ class _AccessibilityHandler { } async afterTest (suiteTitle: string | undefined, test: Frameworks.Test) { - BStackLogger.debug('Accessibility after test hook. Before sending test stop event') if ( this._framework !== 'mocha' || !this.shouldRunTestHooks(this._browser, this._accessibility) @@ -187,20 +219,19 @@ class _AccessibilityHandler { const gherkinDocument = world.gherkinDocument const featureData = gherkinDocument.feature const uniqueId = getUniqueIdentifierForCucumber(world) - const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true) + const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions) const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario) + if (this._sessionId) { + /* For case with multiple tests under onw browser, before hook of 2nd test should change this map value */ + AccessibilityHandler._a11yScanSessionMap[this._sessionId] = shouldScanScenario + } + if (!isPageOpened) { return } 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) - } this._testMetadata[uniqueId].accessibilityScanStarted = shouldScanScenario if (shouldScanScenario) { @@ -212,7 +243,6 @@ class _AccessibilityHandler { } async afterScenario (world: ITestCaseHookParameter) { - BStackLogger.debug('Accessibility after scenario hook. Before sending test stop event') if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { return } @@ -258,16 +288,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) { @@ -301,6 +326,20 @@ 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 @@ -308,4 +347,3 @@ const AccessibilityHandler: typeof _AccessibilityHandler = o11yClassErrorHandler type AccessibilityHandler = _AccessibilityHandler export default AccessibilityHandler - diff --git a/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts b/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts new file mode 100644 index 00000000000..48b5a26aab8 --- /dev/null +++ b/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts @@ -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 | 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 + } + + private 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() diff --git a/packages/wdio-browserstack-service/src/scripts/test-event-scripts.ts b/packages/wdio-browserstack-service/src/scripts/test-event-scripts.ts deleted file mode 100644 index 3baee1fb609..00000000000 --- a/packages/wdio-browserstack-service/src/scripts/test-event-scripts.ts +++ /dev/null @@ -1,68 +0,0 @@ -export function testStartEvent() { - const callback = arguments[arguments.length - 1] - const fn = () => { - window.addEventListener('A11Y_TAP_STARTED', fn2) - const e = new CustomEvent('A11Y_FORCE_START') - window.dispatchEvent(e) - } - const fn2 = () => { - window.removeEventListener('A11Y_TAP_STARTED', fn) - callback() - } - fn() -} - -export function testForceStop() { - const e = new CustomEvent('A11Y_FORCE_STOP') - window.dispatchEvent(e) -} - -export function testStop(this: any) { - const callback = arguments[arguments.length - 1] - - this.res = null - if (arguments[0].saveResults) { - window.addEventListener('A11Y_TAP_TRANSPORTER', (event: any) => { - (window as any).tapTransporterData = event.detail - this.res = (window as any).tapTransporterData - callback(this.res) - }) - } - const e = new CustomEvent('A11Y_TEST_END', { detail: arguments[0] }) - window.dispatchEvent(e) - if (arguments[0].saveResults !== true ) { - callback() - } -} - -export function accessibilityResults() : Promise> { - return new Promise(function (resolve, reject) { - try { - const event = new CustomEvent('A11Y_TAP_GET_RESULTS') - const fn = function (event: any) { - window.removeEventListener('A11Y_RESULTS_RESPONSE', fn) - resolve(event.detail.data) - } - window.addEventListener('A11Y_RESULTS_RESPONSE', fn) - window.dispatchEvent(event) - } catch { - reject() - } - }) -} - -export function accessibilityResultsSummary() : Promise<{ [key: string]: any; }> { - return new Promise(function (resolve, reject) { - try { - const event = new CustomEvent('A11Y_TAP_GET_RESULTS_SUMMARY') - const fn = function (event: any) { - window.removeEventListener('A11Y_RESULTS_SUMMARY_RESPONSE', fn) - resolve(event.detail.summary) - } - window.addEventListener('A11Y_RESULTS_SUMMARY_RESPONSE', fn) - window.dispatchEvent(event) - } catch { - reject() - } - }) -} diff --git a/packages/wdio-browserstack-service/src/service.ts b/packages/wdio-browserstack-service/src/service.ts index ea99513fd0c..36c5fa93377 100644 --- a/packages/wdio-browserstack-service/src/service.ts +++ b/packages/wdio-browserstack-service/src/service.ts @@ -144,6 +144,22 @@ export default class BrowserstackService implements Services.ServiceInstance { await this._insightsHandler.before() } + if (isBrowserstackSession(this._browser)) { + try { + this._accessibilityHandler = new AccessibilityHandler( + this._browser, + this._caps, + this._isAppAutomate(), + this._config.framework, + this._accessibility, + this._options.accessibilityOptions + ) + await this._accessibilityHandler.before(sessionId) + } catch (err) { + BStackLogger.error(`[Accessibility Test Run] Error in service class before function: ${err}`) + } + } + /** * register command event */ @@ -183,22 +199,6 @@ export default class BrowserstackService implements Services.ServiceInstance { } } - if (this._browser && isBrowserstackSession(this._browser)) { - try { - this._accessibilityHandler = new AccessibilityHandler( - this._browser, - this._caps, - this._isAppAutomate(), - this._config.framework, - this._accessibility, - this._options.accessibilityOptions - ) - await this._accessibilityHandler.before() - } catch (err) { - BStackLogger.error(`[Accessibility Test Run] Error in service class before function: ${err}`) - } - } - return await this._printSessionURL() } diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index cde9ea2ee99..a241903cf22 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -27,10 +27,10 @@ import type { ITestCaseHookParameter } from './cucumber-types.js' import { ACCESSIBILITY_API_URL, BROWSER_DESCRIPTION, DATA_ENDPOINT, DATA_EVENT_ENDPOINT, DATA_SCREENSHOT_ENDPOINT, UPLOAD_LOGS_ADDRESS, UPLOAD_LOGS_ENDPOINT, consoleHolder } from './constants.js' import RequestQueueHandler from './request-handler.js' import CrashReporter from './crash-reporter.js' -import { accessibilityResults, accessibilityResultsSummary } from './scripts/test-event-scripts.js' import { BStackLogger } from './bstackLogger.js' import { FileStream } from './fileStream.js' import BrowserstackLauncherService from './launcher.js' +import AccessibilityScripts from './scripts/accessibility-scripts.js' const pGitconfig = promisify(gitconfig) const __filename = fileURLToPath(import.meta.url) @@ -343,20 +343,11 @@ 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]: any; }) => { 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)) - const excluded = excludeTags?.some((exclude) => tagsList.includes(exclude)) - const included = includeTags?.length === 0 || includeTags?.some((include) => tagsList.includes(include)) - - return !excluded && included - } - const fullTestName = suiteTitle + ' ' + testTitle const excluded = excludeTags?.some((exclude) => fullTestName.includes(exclude)) const included = includeTags?.length === 0 || includeTags?.some((include) => fullTestName.includes(include)) @@ -396,7 +387,10 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce 'source': { frameworkName: 'WebdriverIO-' + config.framework, frameworkVersion: bsConfig.bstackServiceVersion, - sdkVersion: bsConfig.bstackServiceVersion + sdkVersion: bsConfig.bstackServiceVersion, + language: 'ECMAScript', + testFramework: 'webdriverIO', + testFrameworkVersion: bsConfig.bstackServiceVersion }, 'settings': bsConfig.accessibilityOptions || {}, 'versionControl': await getGitMetaData(), @@ -419,7 +413,7 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce try { const response: any = await nodeRequest( - 'POST', 'test_runs', requestOptions, ACCESSIBILITY_API_URL + 'POST', 'v2/test_runs', requestOptions, ACCESSIBILITY_API_URL ) BStackLogger.debug(`[Create Accessibility Test Run] Success response: ${JSON.stringify(response)}`) @@ -430,9 +424,13 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce if (response.data.id) { process.env.BS_A11Y_TEST_RUN_ID = response.data.id } - BStackLogger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.id}`) + if (response.data) { + AccessibilityScripts.update(response.data) + AccessibilityScripts.store() + } + return response.data.scannerVersion } catch (error : any) { if (error.response) { @@ -464,6 +462,27 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce } }) +export const performA11yScan = async (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string, commandName?: string) : Promise => { + if (!isBrowserStackSession) { + BStackLogger.warn('Not a BrowserStack Automate session, cannot perform Accessibility scan.') + return // since we are running only on Automate as of now + } + + if (!isAccessibilityAutomationSession(isAccessibility)) { + BStackLogger.warn('Not an Accessibility Automation session, cannot perform Accessibility scan.') + return + } + + 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 + } catch (err : any) { + BStackLogger.error('Accessibility Scan could not be performed : ' + err) + return + } +} + export const getA11yResults = async (browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise> => { if (!isBrowserStackSession) { BStackLogger.warn('Not a BrowserStack Automate session, cannot retrieve Accessibility results.') @@ -476,7 +495,9 @@ export const getA11yResults = async (browser: WebdriverIO.Browser, isBrowserStac } try { - const results = await (browser as WebdriverIO.Browser).execute(accessibilityResults) + 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) return results } catch { BStackLogger.error('No accessibility results were found.') @@ -495,7 +516,9 @@ export const getA11yResultsSummary = async (browser: WebdriverIO.Browser, isBrow } try { - const summaryResults = await (browser as WebdriverIO.Browser).execute(accessibilityResultsSummary) + 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) return summaryResults } catch { BStackLogger.error('No accessibility summary was found.') @@ -1229,4 +1252,3 @@ export const getPlatformVersion = o11yErrorHandler(function getPlatformVersion(c } return undefined }) - From 5d1e3a8a8894cf747c835758975398dd756c916e Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 1 Mar 2024 06:14:47 +0530 Subject: [PATCH 02/11] reconcile changes --- .../src/accessibility-handler.ts | 4 +++- packages/wdio-browserstack-service/src/util.ts | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index df3c1bdccad..57a42b32f37 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -166,6 +166,7 @@ class _AccessibilityHandler { } async afterTest (suiteTitle: string | undefined, test: Frameworks.Test) { + BStackLogger.debug('Accessibility after test hook. Before sending test stop event') if ( this._framework !== 'mocha' || !this.shouldRunTestHooks(this._browser, this._accessibility) @@ -219,7 +220,7 @@ class _AccessibilityHandler { const gherkinDocument = world.gherkinDocument const featureData = gherkinDocument.feature const uniqueId = getUniqueIdentifierForCucumber(world) - const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions) + const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true) const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario) if (this._sessionId) { @@ -243,6 +244,7 @@ class _AccessibilityHandler { } async afterScenario (world: ITestCaseHookParameter) { + BStackLogger.debug('Accessibility after scenario hook. Before sending test stop event') if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { return } diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index a241903cf22..9e8dde0c08f 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -343,11 +343,20 @@ export const validateCapsWithA11y = (deviceName?: any, platformMeta?: { [key: st return false } -export const shouldScanTestForAccessibility = (suiteTitle: string | undefined, testTitle: string, accessibilityOptions?: { [key: string]: any; }) => { +export const shouldScanTestForAccessibility = (suiteTitle: string | undefined, testTitle: string, accessibilityOptions?: { [key: string]: any; }, world?: { [key: string]: any; }, 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)) + const excluded = excludeTags?.some((exclude) => tagsList.includes(exclude)) + const included = includeTags?.length === 0 || includeTags?.some((include) => tagsList.includes(include)) + + return !excluded && included + } + const fullTestName = suiteTitle + ' ' + testTitle const excluded = excludeTags?.some((exclude) => fullTestName.includes(exclude)) const included = includeTags?.length === 0 || includeTags?.some((include) => fullTestName.includes(include)) From 66ba3d5e326192e5b3ae85cae55dc905531f48c3 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 1 Mar 2024 06:28:42 +0530 Subject: [PATCH 03/11] PR review fixes --- .../src/@types/bstack-service-types.d.ts | 4 ++-- .../wdio-browserstack-service/src/accessibility-handler.ts | 4 ++-- packages/wdio-browserstack-service/src/util.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts b/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts index 73560d96b61..05b92339bfd 100644 --- a/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts +++ b/packages/wdio-browserstack-service/src/@types/bstack-service-types.d.ts @@ -2,12 +2,12 @@ declare namespace WebdriverIO { interface Browser { getAccessibilityResultsSummary: () => Promise<{ [key: string]: any; }>, getAccessibilityResults: () => Promise>, - performScan: () => Promise + performScan: () => Promise<{ [key: string]: any; } | undefined> } interface MultiRemoteBrowser { getAccessibilityResultsSummary: () => Promise<{ [key: string]: any; }>, getAccessibilityResults: () => Promise>, - performScan: () => Promise + performScan: () => Promise<{ [key: string]: any; } | undefined> } } diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index 57a42b32f37..a5b02f41058 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -146,7 +146,7 @@ class _AccessibilityHandler { const isPageOpened = await this.checkIfPageOpened(this._browser, testIdentifier, shouldScanTest) if (this._sessionId) { - /* For case with multiple tests under onw browser, before hook of 2nd test should change this map value */ + /* For case with multiple tests under one browser, before hook of 2nd test should change this map value */ AccessibilityHandler._a11yScanSessionMap[this._sessionId] = shouldScanTest } @@ -224,7 +224,7 @@ class _AccessibilityHandler { const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario) if (this._sessionId) { - /* For case with multiple tests under onw browser, before hook of 2nd test should change this map value */ + /* For case with multiple tests under one browser, before hook of 2nd test should change this map value */ AccessibilityHandler._a11yScanSessionMap[this._sessionId] = shouldScanScenario } diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index 9e8dde0c08f..5d1fd1a8e82 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -471,7 +471,7 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce } }) -export const performA11yScan = async (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string, commandName?: string) : Promise => { +export const performA11yScan = async (browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string, commandName?: string) : Promise<{ [key: string]: any; } | 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 @@ -485,7 +485,7 @@ 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 + return ( results as { [key: string]: any; } | undefined ) } catch (err : any) { BStackLogger.error('Accessibility Scan could not be performed : ' + err) return From aa305df2bac1424fc8e1b5f4d05f90d3160b8f34 Mon Sep 17 00:00:00 2001 From: amaanbs Date: Fri, 1 Mar 2024 08:19:31 +0530 Subject: [PATCH 04/11] add tests --- .../src/accessibility-handler.ts | 112 +++++++++--------- .../src/scripts/accessibility-scripts.ts | 2 +- .../wdio-browserstack-service/src/util.ts | 10 +- .../tests/accessibility-handler.test.ts | 19 ++- .../tests/accessibility-scripts.test.ts | 82 +++++++++++++ .../tests/util.test.ts | 12 +- 6 files changed, 161 insertions(+), 76 deletions(-) create mode 100644 packages/wdio-browserstack-service/tests/accessibility-scripts.test.ts diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index a5b02f41058..bfa0f5391c3 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -110,51 +110,55 @@ class _AccessibilityHandler { } if (this._accessibility) { - if (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') - } - }) + 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) { - if ( - this._framework !== 'mocha' || - !this.shouldRunTestHooks(this._browser, this._accessibility) - ) { - return - } + try { + 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) + 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 (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 - } + if (!isPageOpened) { + return + } - try { this._testMetadata[testIdentifier].accessibilityScanStarted = shouldScanTest if (shouldScanTest) { @@ -212,27 +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) - - 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 - } + 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 + } + + if (!isPageOpened) { + return + } - try { this._testMetadata[uniqueId].accessibilityScanStarted = shouldScanScenario if (shouldScanScenario) { diff --git a/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts b/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts index 48b5a26aab8..79b230c8247 100644 --- a/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts +++ b/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts @@ -24,7 +24,7 @@ class AccessibilityScripts { return AccessibilityScripts.instance } - private readFromExistingFile() { + public readFromExistingFile() { try { if (fs.existsSync(this.commandsPath)) { const data = fs.readFileSync(this.commandsPath, 'utf8') diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index 5d1fd1a8e82..eb1f7765642 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -435,9 +435,13 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce } BStackLogger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.id}`) - if (response.data) { - AccessibilityScripts.update(response.data) - AccessibilityScripts.store() + try { + if (response.data) { + AccessibilityScripts.update(response.data) + AccessibilityScripts.store() + } + } catch { + /* Do nothing */ } return response.data.scannerVersion diff --git a/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts b/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts index d01f01dcfae..b1785c43f07 100644 --- a/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts +++ b/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts @@ -181,8 +181,7 @@ describe('beforeScenario', () => { } } as any) - expect(executeAsyncSpy).toBeCalledTimes(1) - expect(logInfoMock.mock.calls[1][0]) + expect(logInfoMock.mock.calls[0][0]) .toContain('Automate test case execution has started.') }) @@ -228,7 +227,7 @@ describe('beforeScenario', () => { } } as any) - expect(executeSpy).toBeCalledTimes(1) + expect(executeSpy).toBeCalledTimes(0) }) it('should not execute test started if shouldRunTestHooks is false', async () => { @@ -253,8 +252,7 @@ describe('beforeScenario', () => { it('should throw error in before scenario if exception occurs', async () => { const logErrorMock = vi.spyOn(log, 'error') vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(true) - accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return true }) - accessibilityHandler['sendTestStartEvent'] = vi.fn().mockImplementation(() => { throw new Error() }) + accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { throw new Error() }) await accessibilityHandler.beforeScenario({ pickle: { name: 'pickle-name', @@ -270,7 +268,7 @@ describe('beforeScenario', () => { } as any) expect(logErrorMock.mock.calls[0][0]) - .toContain('Exception in starting accessibility automation scan for this test case Error') + .toContain('Exception in starting accessibility automation scan for this test case') }) }) @@ -395,12 +393,10 @@ describe('beforeTest', () => { 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(accessibilityHandler['sendTestStartEvent']).toBeCalledTimes(1) - expect(logInfoMock.mock.calls[1][0]) + expect(logInfoMock.mock.calls[0][0]) .toContain('Automate test case execution has started.') vi.fn().mockRestore() }) @@ -421,7 +417,7 @@ describe('beforeTest', () => { vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(false) await accessibilityHandler.beforeTest('suite title', { parent: 'parent', title: 'test' } as any) - expect(executeSpy).toBeCalledTimes(1) + expect(executeSpy).toBeCalledTimes(0) }) it('should not execute test started if shouldRunTestHooks is false', async () => { @@ -434,8 +430,7 @@ describe('beforeTest', () => { 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(() => { return true }) - accessibilityHandler['sendTestStartEvent'] = vi.fn().mockImplementation(() => { throw new Error() }) + 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]) diff --git a/packages/wdio-browserstack-service/tests/accessibility-scripts.test.ts b/packages/wdio-browserstack-service/tests/accessibility-scripts.test.ts new file mode 100644 index 00000000000..2061bba843f --- /dev/null +++ b/packages/wdio-browserstack-service/tests/accessibility-scripts.test.ts @@ -0,0 +1,82 @@ +import fs from 'node:fs' + +import { describe, expect, it, vi, afterAll, beforeAll } from 'vitest' + +import AccessibilityScripts from '../src/scripts/accessibility-scripts.js' + +vi.mock('node:fs', () => ({ + default: { + readFileSync: vi.fn().mockReturnValue('{"scripts": {"scan": "scan", "getResults": "getResults", "getResultsSummary": "getResultsSummary", "saveResults": "saveResults"}, "commands": [{"command": "command1"}, {"command": "command2"}]}'), + writeFileSync: vi.fn(), + existsSync: vi.fn().mockReturnValue(true), + mkdirSync: vi.fn() + } +})) + +describe('AccessibilityScripts', () => { + let accessibilityScripts: typeof AccessibilityScripts + + beforeAll(() => { + accessibilityScripts = AccessibilityScripts + }) + + afterAll(() => { + accessibilityScripts.store() + }) + + it('should read from existing file if it exists', () => { + // Simulate existing commands.json file + accessibilityScripts.readFromExistingFile() + + expect(accessibilityScripts.performScan).to.equal('scan') + expect(accessibilityScripts.getResults).to.equal('getResults') + expect(accessibilityScripts.getResultsSummary).to.equal('getResultsSummary') + expect(accessibilityScripts.saveTestResults).to.equal('saveResults') + expect(accessibilityScripts.commandsToWrap).to.deep.equal([{ command: 'command1' }, { command: 'command2' }]) + }) + + it('should update data', () => { + const data = { + commands: [{ command: 'command1' }, { command: 'command2' }], + scripts: { + scan: 'scan', + getResults: 'getResults', + getResultsSummary: 'getResultsSummary', + saveResults: 'saveResults', + }, + } as unknown + + accessibilityScripts.update(data as { commands: [any]; scripts: { scan: null; getResults: null; getResultsSummary: null; saveResults: null } }) + + expect(accessibilityScripts.performScan).to.equal('scan') + expect(accessibilityScripts.getResults).to.equal('getResults') + expect(accessibilityScripts.getResultsSummary).to.equal('getResultsSummary') + expect(accessibilityScripts.saveTestResults).to.equal('saveResults') + expect(accessibilityScripts.commandsToWrap).to.deep.equal([{ command: 'command1' }, { command: 'command2' }]) + }) + + it('should store data to file', () => { + // Mock storing data + accessibilityScripts.performScan = 'scan' + accessibilityScripts.getResults = 'getResults' + accessibilityScripts.getResultsSummary = 'getResultsSummary' + accessibilityScripts.saveTestResults = 'saveResults' + accessibilityScripts.commandsToWrap = [{ command: 'command1' }, { command: 'command2' }] + + const writeFileSyncStub = vi.spyOn(fs, 'writeFileSync') + accessibilityScripts.store() + // Check if the correct data is being written to the file + expect(writeFileSyncStub).toHaveBeenCalledWith( + accessibilityScripts.commandsPath, + JSON.stringify({ + commands: accessibilityScripts.commandsToWrap, + scripts: { + scan: accessibilityScripts.performScan, + getResults: accessibilityScripts.getResults, + getResultsSummary: accessibilityScripts.getResultsSummary, + saveResults: accessibilityScripts.saveTestResults, + } + }) + ) + }) +}) diff --git a/packages/wdio-browserstack-service/tests/util.test.ts b/packages/wdio-browserstack-service/tests/util.test.ts index c0ed2670f27..65c82106894 100644 --- a/packages/wdio-browserstack-service/tests/util.test.ts +++ b/packages/wdio-browserstack-service/tests/util.test.ts @@ -1084,7 +1084,7 @@ describe('createAccessibilityTestRun', () => { json: () => Promise.resolve({ data: { accessibilityToken: 'someToken', id: 'id', scannerVersion: '0.0.6.0' } }), } as any) - const result: any = await createAccessibilityTestRun( { framework: 'framework' } as any, { user: 'user', key: 'key' }, {}) + const result: any = await createAccessibilityTestRun( { framework: 'framework' } as any, { user: 'user', key: 'key' }, { bstackServiceVersion: '1.2.3' }) expect(got).toBeCalledTimes(1) expect(result).toEqual('0.0.6.0') }) @@ -1098,7 +1098,7 @@ describe('createAccessibilityTestRun', () => { const result: any = await createAccessibilityTestRun( { framework: 'framework' } as any, { user: 'user', key: 'key' }, {}) expect(got).toBeCalledTimes(1) expect(result).toEqual(null) - expect(logInfoMock.mock.calls[3][0]).contains('xception while creating test run for BrowserStack Accessibility Automation') + expect(logInfoMock.mock.calls[3][0]).contains('Exception while creating test run for BrowserStack Accessibility Automation') }) afterEach(() => { @@ -1164,7 +1164,7 @@ describe('getA11yResults', () => { getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]), browserB: {}, execute: vi.fn(), - executeAsync: async () => { 'done' }, + executeAsync: vi.fn(), on: vi.fn(), } as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser @@ -1182,7 +1182,7 @@ describe('getA11yResults', () => { it('return results object if bstack as well as accessibility session', async () => { vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true) await utils.getA11yResults((browser as WebdriverIO.Browser), true, true) - expect(browser.execute).toBeCalledTimes(1) + expect(browser.executeAsync).toBeCalledTimes(2) }) }) @@ -1210,7 +1210,7 @@ describe('getA11yResultsSummary', () => { getInstance: vi.fn().mockImplementation((browserName: string) => browser[browserName]), browserB: {}, execute: vi.fn(), - executeAsync: async () => { 'done' }, + executeAsync: vi.fn(), on: vi.fn(), } as any as WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser @@ -1228,7 +1228,7 @@ describe('getA11yResultsSummary', () => { it('return results object if bstack as well as accessibility session', async () => { vi.spyOn(utils, 'isAccessibilityAutomationSession').mockReturnValue(true) await utils.getA11yResultsSummary((browser as WebdriverIO.Browser), true, true) - expect(browser.execute).toBeCalledTimes(1) + expect(browser.executeAsync).toBeCalledTimes(2) }) }) From 7baa26777bbc908f536384caa6e4f9881ec57754 Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Mon, 4 Mar 2024 16:12:13 +0530 Subject: [PATCH 05/11] chore: comments --- .../src/accessibility-handler.ts | 82 +++++++++---------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index bfa0f5391c3..5e830b2fdad 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -109,31 +109,32 @@ class _AccessibilityHandler { return await performA11yScan((this._browser as WebdriverIO.Browser), isBrowserstackSession(this._browser), this._accessibility) } - 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 */ + if (!this._accessibility) { + return; + } + 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.shouldPatchExecuteScript(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 */ } } @@ -216,15 +217,15 @@ class _AccessibilityHandler { * Cucumber Only */ async beforeScenario (world: ITestCaseHookParameter) { - try { - if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { - return - } + 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 pickleData = world.pickle + const gherkinDocument = world.gherkinDocument + const featureData = gherkinDocument.feature + const uniqueId = getUniqueIdentifierForCucumber(world) + try { const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true) const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario) @@ -333,18 +334,15 @@ 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) { + private static shouldPatchExecuteScript(script: string | null): Boolean { + if (!script || typeof script !== 'string') { return true } + + return ( + script.toLowerCase().indexOf('browserstack_executor') !== -1 || + script.toLowerCase().indexOf('browserstack_accessibility_automation_script') !== -1 + ) } } From bee5cf37f0cb2245091440123e51f93d1654c50c Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Mon, 4 Mar 2024 16:12:21 +0530 Subject: [PATCH 06/11] chore: comments --- packages/wdio-browserstack-service/src/accessibility-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index 5e830b2fdad..cafc70714ff 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -110,7 +110,7 @@ class _AccessibilityHandler { } if (!this._accessibility) { - return; + return } try { if ('overwriteCommand' in this._browser && Array.isArray(accessibilityScripts.commandsToWrap)) { From 726f8a7917cfadf9d40f5f8afe7f037d0f6e9f89 Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Mon, 4 Mar 2024 16:53:09 +0530 Subject: [PATCH 07/11] fix: tests --- .../src/accessibility-handler.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index cafc70714ff..f27fc6aee4e 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -217,15 +217,15 @@ class _AccessibilityHandler { * Cucumber Only */ async beforeScenario (world: ITestCaseHookParameter) { - if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { - return - } - const pickleData = world.pickle const gherkinDocument = world.gherkinDocument const featureData = gherkinDocument.feature const uniqueId = getUniqueIdentifierForCucumber(world) try { + if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { + return + } + const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true) const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario) From e42e94bd2c3e815cc9e7c417f164c96824d9643b Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Tue, 5 Mar 2024 17:36:37 +0530 Subject: [PATCH 08/11] chore: remove redundant try-catch --- .../src/accessibility-handler.ts | 50 +++++++++---------- .../src/scripts/accessibility-scripts.ts | 3 +- .../wdio-browserstack-service/src/util.ts | 10 ++-- .../tests/accessibility-handler.test.ts | 3 +- .../tests/util.test.ts | 3 ++ 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index f27fc6aee4e..14bb47cd2e4 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -112,30 +112,28 @@ class _AccessibilityHandler { if (!this._accessibility) { return } - 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.shouldPatchExecuteScript(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') + if (!('overwriteCommand' in this._browser && Array.isArray(accessibilityScripts.commandsToWrap))) { + return + } + + 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.shouldPatchExecuteScript(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) { @@ -221,11 +219,11 @@ class _AccessibilityHandler { const gherkinDocument = world.gherkinDocument const featureData = gherkinDocument.feature const uniqueId = getUniqueIdentifierForCucumber(world) - try { - if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { - return - } + if (!this.shouldRunTestHooks(this._browser, this._accessibility)) { + return + } + try { const shouldScanScenario = shouldScanTestForAccessibility(featureData?.name, pickleData.name, this._accessibilityOptions, world, true) const isPageOpened = await this.checkIfPageOpened(this._browser, uniqueId, shouldScanScenario) diff --git a/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts b/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts index 79b230c8247..e3b68a26afc 100644 --- a/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts +++ b/packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts @@ -14,7 +14,8 @@ class AccessibilityScripts { public browserstackFolderPath = path.join(os.homedir(), '.browserstack') public commandsPath = path.join(this.browserstackFolderPath, 'commands.json') - constructor() {} + // don't allow to create instances from it other than through `checkAndGetInstance` + private constructor() {} public static checkAndGetInstance() { if (!AccessibilityScripts.instance) { diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index eb1f7765642..5d1fd1a8e82 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -435,13 +435,9 @@ export const createAccessibilityTestRun = errorHandler(async function createAcce } BStackLogger.debug(`BrowserStack Accessibility Automation Test Run ID: ${response.data.id}`) - try { - if (response.data) { - AccessibilityScripts.update(response.data) - AccessibilityScripts.store() - } - } catch { - /* Do nothing */ + if (response.data) { + AccessibilityScripts.update(response.data) + AccessibilityScripts.store() } return response.data.scannerVersion diff --git a/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts b/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts index b1785c43f07..bbb1e991287 100644 --- a/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts +++ b/packages/wdio-browserstack-service/tests/accessibility-handler.test.ts @@ -252,7 +252,8 @@ describe('beforeScenario', () => { it('should throw error in before scenario if exception occurs', async () => { const logErrorMock = vi.spyOn(log, 'error') vi.spyOn(utils, 'shouldScanTestForAccessibility').mockReturnValue(true) - accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { throw new Error() }) + accessibilityHandler['shouldRunTestHooks'] = vi.fn().mockImplementation(() => { return true }) + accessibilityHandler['checkIfPageOpened'] = vi.fn().mockImplementation(() => { throw new Error() }) await accessibilityHandler.beforeScenario({ pickle: { name: 'pickle-name', diff --git a/packages/wdio-browserstack-service/tests/util.test.ts b/packages/wdio-browserstack-service/tests/util.test.ts index 65c82106894..a31fe29afdc 100644 --- a/packages/wdio-browserstack-service/tests/util.test.ts +++ b/packages/wdio-browserstack-service/tests/util.test.ts @@ -54,6 +54,9 @@ vi.mock('fs', () => ({ 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() } })) From 1bcc3c4a158c3a3ddecc7c12fbd7779061c1db72 Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Tue, 5 Mar 2024 23:00:32 +0530 Subject: [PATCH 09/11] chore: comments --- .../src/accessibility-handler.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index 14bb47cd2e4..00ea8fd2a8c 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -117,23 +117,13 @@ class _AccessibilityHandler { } 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.shouldPatchExecuteScript(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') - } - }) + + accessibilityScripts.commandsToWrap + .filter((command) => command.name && command.class) + .forEach(function (command) { + const browser = that._browser as WebdriverIO.Browser + browser.overwriteCommand(command.name, async (origFunction: Function, ...args: any[]) => { return that.commandWrapper(that, command, origFunction, ...args) }, command.class === 'Element') + }) } async beforeTest (suiteTitle: string | undefined, test: Frameworks.Test) { @@ -293,6 +283,20 @@ class _AccessibilityHandler { * private methods */ + private async commandWrapper (that: this, command: any, origFunction: Function, ...args: any[]) { + if ( + that._sessionId && AccessibilityHandler._a11yScanSessionMap[that._sessionId] && + ( + !command.name.includes('execute') || + !AccessibilityHandler.shouldPatchExecuteScript(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) + } + private async sendTestStopEvent(browser: WebdriverIO.Browser, dataForExtension: any) { BStackLogger.debug('Performing scan before saving results') await performA11yScan(browser, true, true) From 0b35dceb297d179feee1fe1a5bb24bda22ddf872 Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Wed, 6 Mar 2024 13:57:23 +0530 Subject: [PATCH 10/11] chore: refactor --- .../src/accessibility-handler.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index 00ea8fd2a8c..906c542395b 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -116,13 +116,11 @@ class _AccessibilityHandler { return } - const that = this - accessibilityScripts.commandsToWrap .filter((command) => command.name && command.class) - .forEach(function (command) { - const browser = that._browser as WebdriverIO.Browser - browser.overwriteCommand(command.name, async (origFunction: Function, ...args: any[]) => { return that.commandWrapper(that, command, origFunction, ...args) }, command.class === 'Element') + .forEach((command) => { + const browser = this._browser as WebdriverIO.Browser + browser.overwriteCommand(command.name, this.commandWrapper.bind(this, command), command.class === 'Element') }) } @@ -283,16 +281,16 @@ class _AccessibilityHandler { * private methods */ - private async commandWrapper (that: this, command: any, origFunction: Function, ...args: any[]) { + private async commandWrapper (command: any, origFunction: Function, ...args: any[]) { if ( - that._sessionId && AccessibilityHandler._a11yScanSessionMap[that._sessionId] && + this._sessionId && AccessibilityHandler._a11yScanSessionMap[this._sessionId] && ( !command.name.includes('execute') || !AccessibilityHandler.shouldPatchExecuteScript(args.length ? args[0] : null) ) ) { BStackLogger.debug(`Performing scan for ${command.class} ${command.name}`) - await performA11yScan(that._browser, true, true, command.name) + await performA11yScan(this._browser, true, true, command.name) } return origFunction(...args) } From 1e66f8552ebb79b79718205eb3242b68bdcfd2ef Mon Sep 17 00:00:00 2001 From: 07souravkunda Date: Thu, 7 Mar 2024 21:51:59 +0530 Subject: [PATCH 11/11] fix: lint --- packages/wdio-browserstack-service/src/util.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index 3c44ef91330..ab5b4178c20 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -39,7 +39,6 @@ import { import CrashReporter from './crash-reporter.js' import { BStackLogger } from './bstackLogger.js' import { FileStream } from './fileStream.js' -import BrowserstackLauncherService from './launcher.js' import AccessibilityScripts from './scripts/accessibility-scripts.js' import UsageStats from './testOps/usageStats.js' import TestOpsConfig from './testOps/testOpsConfig.js' @@ -1237,7 +1236,6 @@ export const getPlatformVersion = o11yErrorHandler(function getPlatformVersion(c return undefined }) - export const isObjectEmpty = (objectName: unknown) => { return ( objectName &&