Skip to content

Commit fb4a674

Browse files
authored
Merge pull request #1 from Bhargavi-BS/SDK-3039
support for non-bstack ALLY
2 parents 44cdf4b + 6de1d82 commit fb4a674

10 files changed

Lines changed: 310 additions & 73 deletions

packages/wdio-browserstack-service/src/accessibility-handler.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import util from 'node:util'
22

3-
import type { Capabilities, Frameworks } from '@wdio/types'
3+
import type { Capabilities, Frameworks, Options } from '@wdio/types'
4+
5+
import type { BrowserstackConfig, BrowserstackOptions } from './types.js'
46

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

@@ -19,6 +21,8 @@ import {
1921
o11yClassErrorHandler,
2022
shouldScanTestForAccessibility,
2123
validateCapsWithA11y,
24+
shouldAddServiceVersion,
25+
validateCapsWithNonBstackA11y,
2226
isTrue,
2327
validateCapsWithAppA11y,
2428
getAppA11yResults,
@@ -35,6 +39,9 @@ class _AccessibilityHandler {
3539
private _caps: Capabilities.ResolvedTestrunnerCapabilities
3640
private _suiteFile?: string
3741
private _accessibility?: boolean
42+
private _turboscale?: boolean
43+
private _options: BrowserstackConfig & BrowserstackOptions
44+
private _config: Options.Testrunner
3845
private _accessibilityOptions?: { [key: string]: unknown; }
3946
private _testMetadata: { [key: string]: unknown; } = {}
4047
private static _a11yScanSessionMap: { [key: string]: unknown; } = {}
@@ -44,9 +51,12 @@ class _AccessibilityHandler {
4451
constructor (
4552
private _browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser,
4653
_capabilities: Capabilities.ResolvedTestrunnerCapabilities,
54+
_options : BrowserstackConfig & BrowserstackOptions,
4755
private isAppAutomate: boolean,
56+
_config : Options.Testrunner,
4857
private _framework?: string,
4958
_accessibilityAutomation?: boolean | string,
59+
_turboscale?: boolean | string,
5060
_accessibilityOpts?: { [key: string]: unknown; }
5161
) {
5262
const caps = (this._browser as WebdriverIO.Browser).capabilities as WebdriverIO.Capabilities
@@ -64,6 +74,9 @@ class _AccessibilityHandler {
6474
this._caps = _capabilities
6575
this._accessibility = isTrue(_accessibilityAutomation)
6676
this._accessibilityOptions = _accessibilityOpts
77+
this._options = _options
78+
this._config= _config
79+
this._turboscale = isTrue(_turboscale)
6780
}
6881

6982
setSuiteFile(filename: string) {
@@ -103,7 +116,12 @@ class _AccessibilityHandler {
103116
this._sessionId = sessionId
104117
this._accessibility = isTrue(this._getCapabilityValue(this._caps, 'accessibility', 'browserstack.accessibility'))
105118

106-
if (isBrowserstackSession(this._browser)) {
119+
//checks for running ALLY on non-bstack infra
120+
if (isAccessibilityAutomationSession(this._accessibility) && (this._turboscale || !shouldAddServiceVersion(this._config, this._options.testObservability))){
121+
if (validateCapsWithNonBstackA11y(this._platformA11yMeta.browser_name as string, this._platformA11yMeta?.browser_version as string)){
122+
this._accessibility = true
123+
}
124+
} else {
107125
if (isAccessibilityAutomationSession(this._accessibility) && !this.isAppAutomate) {
108126
const deviceName = this._getCapabilityValue(this._caps, 'deviceName', 'device')
109127
const chromeOptions = this._getCapabilityValue(this._caps, 'goog:chromeOptions', '') as Capabilities.ChromeOptions
@@ -356,7 +374,7 @@ class _AccessibilityHandler {
356374
if (!browser) {
357375
return false
358376
}
359-
return isBrowserstackSession(browser) && isAccessibilityAutomationSession(isAccessibility)
377+
return isAccessibilityAutomationSession(isAccessibility)
360378
}
361379

362380
private async checkIfPageOpened(browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, testIdentifier: string, shouldScanTest?: boolean) {

packages/wdio-browserstack-service/src/launcher.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939
ObjectsAreEqual, getBasicAuthHeader,
4040
isValidCapsForHealing,
4141
getBooleanValueFromString,
42+
validateCapsWithNonBstackA11y,
43+
mergeChromeOptions
4244
} from './util.js'
4345
import CrashReporter from './crash-reporter.js'
4446
import { BStackLogger } from './bstackLogger.js'
@@ -50,7 +52,7 @@ import { sendFinish, sendStart } from './instrumentation/funnelInstrumentation.j
5052
import AiHandler from './ai-handler.js'
5153
import PerformanceTester from './instrumentation/performance/performance-tester.js'
5254
import * as PERFORMANCE_SDK_EVENTS from './instrumentation/performance/constants.js'
53-
55+
import accessibilityScripts from './scripts/accessibility-scripts.js'
5456
import { _fetch as fetch } from './fetchWrapper.js'
5557

5658
type BrowserstackLocal = BrowserstackLocalLauncher.Local & {
@@ -295,6 +297,13 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
295297
buildIdentifier: this._buildIdentifier
296298
}, this.browserStackConfig, this._accessibilityAutomation)
297299
}
300+
301+
//added checks for Accessibility running on non-bstack infra
302+
if (isAccessibilityAutomationSession(this._accessibilityAutomation) && (process.env.BROWSERSTACK_TURBOSCALE || !shouldAddServiceVersion(this._config, this._options.testObservability))){
303+
const overrideOptions: Partial<Capabilities.ChromeOptions> = accessibilityScripts.ChromeExtension
304+
this._updateObjectTypeCaps(capabilities, 'goog:chromeOptions', overrideOptions)
305+
}
306+
298307
if (buildStartResponse?.accessibility) {
299308
if (this._accessibilityAutomation === null) {
300309
this.browserStackConfig.accessibility = buildStartResponse.accessibility.success as boolean
@@ -573,7 +582,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
573582
}
574583
}
575584

576-
_updateObjectTypeCaps(capabilities?: Capabilities.TestrunnerCapabilities, capType?: string, value?: { [key: string]: unknown }) {
585+
_updateObjectTypeCaps(capabilities?: Capabilities.TestrunnerCapabilities | WebdriverIO.Capabilities, capType?: string, value?: { [key: string]: unknown }) {
577586
try {
578587
if (Array.isArray(capabilities)) {
579588
capabilities
@@ -588,6 +597,19 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
588597
return c as WebdriverIO.Capabilities
589598
})
590599
.forEach((capability: WebdriverIO.Capabilities) => {
600+
if (validateCapsWithNonBstackA11y(capability.browserName, capability.browserVersion )){
601+
if (capType === 'goog:chromeOptions' && value) {
602+
603+
const chromeOptions = capability['goog:chromeOptions'] as unknown as Capabilities.ChromeOptions
604+
if (chromeOptions){
605+
const finalChromeOptions = mergeChromeOptions(chromeOptions, value)
606+
capability['goog:chromeOptions'] = finalChromeOptions
607+
} else {
608+
capability['goog:chromeOptions'] = value
609+
}
610+
return
611+
}
612+
}
591613
if (!capability['bstack:options']) {
592614
const extensionCaps = Object.keys(capability).filter((cap) => cap.includes(':'))
593615
if (extensionCaps.length) {
@@ -622,6 +644,21 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
622644
})
623645
} else if (typeof capabilities === 'object') {
624646
Object.entries(capabilities as Capabilities.RequestedMultiremoteCapabilities).forEach(([, caps]) => {
647+
if (validateCapsWithNonBstackA11y(
648+
(caps.capabilities as WebdriverIO.Capabilities).browserName,
649+
(caps.capabilities as WebdriverIO.Capabilities).browserVersion
650+
)) {
651+
if (capType === 'goog:chromeOptions' && value) {
652+
const chromeOptions = (caps.capabilities as WebdriverIO.Capabilities)['goog:chromeOptions'] as unknown as Capabilities.ChromeOptions
653+
if (chromeOptions) {
654+
const finalChromeOptions = mergeChromeOptions(chromeOptions, value);
655+
(caps.capabilities as WebdriverIO.Capabilities)['goog:chromeOptions'] = finalChromeOptions
656+
} else {
657+
(caps.capabilities as WebdriverIO.Capabilities)['goog:chromeOptions'] = value
658+
}
659+
return
660+
}
661+
}
625662
if (!(caps.capabilities as WebdriverIO.Capabilities)['bstack:options']) {
626663
const extensionCaps = Object.keys(caps.capabilities).filter((cap) => cap.includes(':'))
627664
if (extensionCaps.length) {

packages/wdio-browserstack-service/src/scripts/accessibility-scripts.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class AccessibilityScripts {
2222
public getResultsSummary: string | null = null
2323
public saveTestResults: string | null = null
2424
public commandsToWrap: Array<Command> | null = null
25+
public ChromeExtension: { [key: string]: unknown } = {}
2526

2627
public browserstackFolderPath = ''
2728
public commandsPath = ''
@@ -77,7 +78,7 @@ class AccessibilityScripts {
7778
}
7879
}
7980

80-
public update(data: { commands: [], scripts: Scripts }) {
81+
public update(data: { commands: [], scripts: Scripts, nonBStackInfraA11yChromeOptions: {} }) {
8182
if (data.scripts) {
8283
this.performScan = data.scripts.scan
8384
this.getResults = data.scripts.getResults
@@ -87,6 +88,10 @@ class AccessibilityScripts {
8788
if (data.commands && data.commands.length) {
8889
this.commandsToWrap = data.commands
8990
}
91+
if (data.nonBStackInfraA11yChromeOptions){
92+
this.ChromeExtension = data.nonBStackInfraA11yChromeOptions
93+
}
94+
9095
}
9196

9297
public store() {
@@ -101,7 +106,8 @@ class AccessibilityScripts {
101106
getResults: this.getResults,
102107
getResultsSummary: this.getResultsSummary,
103108
saveResults: this.saveTestResults,
104-
}
109+
},
110+
nonBStackInfraA11yChromeOptions: this.ChromeExtension,
105111
}))
106112
}
107113
}

packages/wdio-browserstack-service/src/service.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -149,22 +149,23 @@ export default class BrowserstackService implements Services.ServiceInstance {
149149
try {
150150
const sessionId = this._browser.sessionId
151151

152-
if (isBrowserstackSession(this._browser)) {
153-
try {
154-
this._accessibilityHandler = new AccessibilityHandler(
155-
this._browser,
156-
this._caps,
157-
this._isAppAutomate(),
158-
this._config.framework,
159-
this._accessibility,
160-
this._options.accessibilityOptions
161-
)
162-
await this._accessibilityHandler.before(sessionId)
152+
try {
153+
this._accessibilityHandler = new AccessibilityHandler(
154+
this._browser,
155+
this._caps,
156+
this._options,
157+
this._isAppAutomate(),
158+
this._config,
159+
this._config.framework,
160+
this._accessibility,
161+
this._turboScale,
162+
this._options.accessibilityOptions
163+
)
164+
await this._accessibilityHandler.before(sessionId)
163165

164-
Listener.setAccessibilityOptions(this._options.accessibilityOptions)
165-
} catch (err) {
166-
BStackLogger.error(`[Accessibility Test Run] Error in service class before function: ${err}`)
167-
}
166+
Listener.setAccessibilityOptions(this._options.accessibilityOptions)
167+
} catch (err) {
168+
BStackLogger.error(`[Accessibility Test Run] Error in service class before function: ${err}`)
168169
}
169170

170171
if (shouldProcessEventForTesthub('')) {

packages/wdio-browserstack-service/src/util.ts

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import * as PERFORMANCE_SDK_EVENTS from './instrumentation/performance/constants
2121
import { logBuildError, handleErrorForObservability, handleErrorForAccessibility, getProductMapForBuildStartCall } from './testHub/utils.js'
2222
import type BrowserStackConfig from './config.js'
2323
import type { Errors } from './testHub/utils.js'
24-
2524
import type { UserConfig, UploadType, BrowserstackConfig, BrowserstackOptions, LaunchResponse } from './types.js'
2625
import type { ITestCaseHookParameter } from './cucumber-types.js'
2726
import {
@@ -328,9 +327,11 @@ export const processAccessibilityResponse = (response: LaunchResponse) => {
328327

329328
if (response.accessibility.options) {
330329
const { accessibilityToken, pollingTimeout, scannerVersion } = jsonifyAccessibilityArray(response.accessibility.options.capabilities, 'name', 'value')
330+
const result = jsonifyAccessibilityArray(response.accessibility.options.capabilities, 'name', 'value')
331331
const scriptsJson = {
332332
'scripts': jsonifyAccessibilityArray(response.accessibility.options.scripts, 'name', 'command'),
333-
'commands': response.accessibility.options.commandsToWrap.commands
333+
'commands': response.accessibility.options.commandsToWrap.commands,
334+
'nonBStackInfraA11yChromeOptions': result['goog:chromeOptions']
334335
}
335336
if (scannerVersion) {
336337
process.env.BSTACK_A11Y_SCANNER_VERSION = scannerVersion as string
@@ -398,6 +399,11 @@ export const launchTestSession = PerformanceTester.measureWrapper(PERFORMANCE_SD
398399
config: {}
399400
}
400401

402+
if (accessibilityAutomation && (isTurboScale(options) || data.browserstackAutomation === false)){
403+
data.accessibility.settings ??= {}
404+
data.accessibility.settings['includeEncodedExtension'] = true
405+
}
406+
401407
try {
402408
if (Object.keys(CrashReporter.userConfigForReporting).length === 0) {
403409
CrashReporter.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== undefined ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {}
@@ -420,6 +426,7 @@ export const launchTestSession = PerformanceTester.measureWrapper(PERFORMANCE_SD
420426
body: JSON.stringify(data)
421427
})
422428
const jsonResponse: LaunchResponse = await response.json()
429+
delete data?.accessibility?.settings?.includeEncodedExtension
423430
BStackLogger.debug(`[Start_Build] Success response: ${JSON.stringify(jsonResponse)}`)
424431
process.env[TESTOPS_BUILD_COMPLETED_ENV] = 'true'
425432
if (jsonResponse.jwt) {
@@ -486,6 +493,20 @@ export const validateCapsWithA11y = (deviceName?: any, platformMeta?: { [key: st
486493
return false
487494
}
488495

496+
export const validateCapsWithNonBstackA11y = (browserName?: string | undefined, browserVersion?:string | undefined ) => {
497+
498+
if (browserName?.toLowerCase() !== 'chrome') {
499+
BStackLogger.warn('Accessibility Automation will run only on Chrome browsers.')
500+
return false
501+
}
502+
if ( !isUndefined(browserVersion) && !(browserVersion === 'latest' || parseFloat(browserVersion + '') > 100)) {
503+
BStackLogger.warn('Accessibility Automation will run only on Chrome browser version greater than 100.')
504+
return false
505+
}
506+
return true
507+
508+
}
509+
489510
export const shouldScanTestForAccessibility = (suiteTitle: string | undefined, testTitle: string, accessibilityOptions?: { [key: string]: string; }, world?: { [key: string]: unknown; }, isCucumber?: boolean ) => {
490511
try {
491512
const includeTags = Array.isArray(accessibilityOptions?.includeTagsInTestingScope) ? accessibilityOptions?.includeTagsInTestingScope : []
@@ -551,10 +572,6 @@ export const _getParamsForAppAccessibility = ( commandName?: string ): { thTestR
551572

552573
/* eslint-disable @typescript-eslint/no-explicit-any */
553574
export const performA11yScan = async (isAppAutomate: boolean, browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string, commandName?: string) : Promise<{ [key: string]: any; } | undefined> => {
554-
if (!isBrowserStackSession) {
555-
BStackLogger.warn('Not a BrowserStack Automate session, cannot perform Accessibility scan.')
556-
return // since we are running only on Automate as of now
557-
}
558575

559576
if (!isAccessibilityAutomationSession(isAccessibility)) {
560577
BStackLogger.warn('Not an Accessibility Automation session, cannot perform Accessibility scan.')
@@ -580,10 +597,6 @@ export const performA11yScan = async (isAppAutomate: boolean, browser: Webdriver
580597
}
581598

582599
export const getA11yResults = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.GET_RESULTS, async (isAppAutomate: boolean, browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise<Array<{ [key: string]: any; }>> => {
583-
if (!isBrowserStackSession) {
584-
BStackLogger.warn('Not a BrowserStack Automate session, cannot retrieve Accessibility results.')
585-
return [] // since we are running only on Automate as of now
586-
}
587600

588601
if (!isAccessibilityAutomationSession(isAccessibility)) {
589602
BStackLogger.warn('Not an Accessibility Automation session, cannot retrieve Accessibility results.')
@@ -663,9 +676,6 @@ const getAppA11yResultResponse = async (apiUrl: string, isAppAutomate: boolean,
663676
}
664677

665678
export const getA11yResultsSummary = PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.A11Y_EVENTS.GET_RESULTS_SUMMARY, async (isAppAutomate: boolean, browser: WebdriverIO.Browser, isBrowserStackSession?: boolean, isAccessibility?: boolean | string) : Promise<{ [key: string]: any; }> => {
666-
if (!isBrowserStackSession) {
667-
return {} // since we are running only on Automate as of now
668-
}
669679

670680
if (!isAccessibilityAutomationSession(isAccessibility)) {
671681
BStackLogger.warn('Not an Accessibility Automation session, cannot retrieve Accessibility results summary.')
@@ -1640,3 +1650,43 @@ export function getBooleanValueFromString(value: string | undefined): boolean {
16401650
return ['true'].includes(value.trim().toLowerCase())
16411651
}
16421652

1653+
export function mergeDeep(target: Record<string, any>, ...sources: any[]): Record<string, any> {
1654+
if (!sources.length) {return target}
1655+
const source = sources.shift()
1656+
1657+
if (isObject(target) && isObject(source)) {
1658+
for (const key in source) {
1659+
const sourceValue = source[key]
1660+
const targetValue = target[key]
1661+
1662+
if (isObject(sourceValue)) {
1663+
if (!targetValue || !isObject(targetValue)) {
1664+
target[key] = {}
1665+
}
1666+
mergeDeep(target[key], sourceValue)
1667+
} else {
1668+
target[key] = sourceValue
1669+
}
1670+
}
1671+
}
1672+
1673+
return mergeDeep(target, ...sources)
1674+
}
1675+
1676+
export function mergeChromeOptions(base: Capabilities.ChromeOptions, override: Partial<Capabilities.ChromeOptions>): Capabilities.ChromeOptions {
1677+
const merged: Capabilities.ChromeOptions = { ...base }
1678+
1679+
if (override.args) {
1680+
merged.args = [...(base.args || []), ...override.args]
1681+
}
1682+
1683+
if (override.extensions) {
1684+
merged.extensions = [...(base.extensions || []), ...override.extensions]
1685+
}
1686+
1687+
if (override.prefs) {
1688+
merged.prefs = mergeDeep({ ...(base.prefs || {}) }, override.prefs)
1689+
}
1690+
return merged
1691+
}
1692+

0 commit comments

Comments
 (0)