diff --git a/packages/wdio-browserstack-service/src/constants.ts b/packages/wdio-browserstack-service/src/constants.ts index 0ce42ffc34b..d234111b5eb 100644 --- a/packages/wdio-browserstack-service/src/constants.ts +++ b/packages/wdio-browserstack-service/src/constants.ts @@ -76,6 +76,20 @@ export const EDS_URL = 'https://eds.browserstack.com' export const SUPPORTED_BROWSERS_FOR_AI = ['chrome', 'microsoftedge', 'firefox'] +export const SUPPORTED_BROWSERS_FOR_ACCESSIBILITY = ['chrome', 'chromefortesting', 'safari'] + +export const MIN_BROWSER_VERSIONS_A11Y = { + chrome: 95, + chromefortesting: 141, + safari: 18.4 +} as const + +export const MIN_BROWSER_VERSIONS_A11Y_NON_BSTACK = { + chrome: 100, + chromefortesting: 141, + safari: 18.4 +} as const + export const TCG_URL = 'https://tcg.browserstack.com' export const TCG_INFO = { diff --git a/packages/wdio-browserstack-service/src/util.ts b/packages/wdio-browserstack-service/src/util.ts index c2e0c9c78dc..68f9d2d4975 100644 --- a/packages/wdio-browserstack-service/src/util.ts +++ b/packages/wdio-browserstack-service/src/util.ts @@ -41,7 +41,9 @@ import { APP_ALLY_ISSUES_SUMMARY_ENDPOINT, APP_ALLY_ISSUES_ENDPOINT, CLI_DEBUG_LOGS_FILE, - WDIO_NAMING_PREFIX + WDIO_NAMING_PREFIX, + MIN_BROWSER_VERSIONS_A11Y, + MIN_BROWSER_VERSIONS_A11Y_NON_BSTACK } from './constants.js' import CrashReporter from './crash-reporter.js' import { BStackLogger } from './bstackLogger.js' @@ -479,20 +481,44 @@ export const validateCapsWithA11y = (deviceName?: any, platformMeta?: { [key: st return false } - if (platformMeta?.browser_name?.toLowerCase() !== 'chrome') { - BStackLogger.warn('Accessibility Automation will run only on Chrome browsers.') - return false - } + const browserName = platformMeta?.browser_name?.toLowerCase() const browserVersion = platformMeta?.browser_version - if (!isUndefined(browserVersion) && !(browserVersion === 'latest' || parseFloat(browserVersion + '') > 94)) { - BStackLogger.warn('Accessibility Automation will run only on Chrome browser version greater than 94.') + + const validBrowsers = ['chrome', 'chromefortesting', 'safari'] + if (!browserName || !validBrowsers.includes(browserName)) { + BStackLogger.warn(`Accessibility Automation supports Chrome 95+, Chrome for Testing 141+, and Safari 18.4+. Current browser: ${browserName}`) return false } - if (chromeOptions?.args?.includes('--headless')) { - BStackLogger.warn('Accessibility Automation will not run on legacy headless mode. Switch to new headless mode or avoid using headless mode.') - return false + if (browserName === 'chrome' || browserName === 'chromefortesting') { + const minVersion = MIN_BROWSER_VERSIONS_A11Y[browserName as keyof typeof MIN_BROWSER_VERSIONS_A11Y] + if (browserVersion && browserVersion !== 'latest') { + const version = parseInt(browserVersion.toString().split('.')[0] || '0', 10) + if (version < minVersion) { + BStackLogger.warn(`Accessibility Automation requires ${browserName === 'chrome' ? 'Chrome' : 'Chrome for Testing'} version ${minVersion} or higher.`) + return false + } + } + + if (chromeOptions?.args?.includes('--headless')) { + BStackLogger.warn('Accessibility Automation will not run on legacy headless mode. Switch to new headless mode or avoid using headless mode.') + return false + } + } + + // Safari validation + if (browserName === 'safari') { + if (browserVersion && browserVersion !== 'latest') { + const [currentMajor = 0, currentMinor = 0] = browserVersion.toString().split('.').map(Number) + const [requiredMajor = 0, requiredMinor = 0] = MIN_BROWSER_VERSIONS_A11Y.safari.toString().split('.').map(Number) + + if (currentMajor < requiredMajor || (currentMajor === requiredMajor && currentMinor < requiredMinor)) { + BStackLogger.warn(`Accessibility Automation requires Safari version ${MIN_BROWSER_VERSIONS_A11Y.safari} or higher.`) + return false + } + } } + return true } catch (error) { BStackLogger.debug(`Exception in checking capabilities compatibility with Accessibility. Error: ${error}`) @@ -500,18 +526,47 @@ export const validateCapsWithA11y = (deviceName?: any, platformMeta?: { [key: st return false } -export const validateCapsWithNonBstackA11y = (browserName?: string | undefined, browserVersion?: string | undefined) => { +export const validateCapsWithNonBstackA11y = (browserName?: string | undefined, browserVersion?:string | undefined) => { + try { + const browser = browserName?.toLowerCase() - if (browserName?.toLowerCase() !== 'chrome') { - BStackLogger.warn('Accessibility Automation will run only on Chrome browsers.') - return false - } - if (!isUndefined(browserVersion) && !(browserVersion === 'latest' || parseFloat(browserVersion + '') > 100)) { - BStackLogger.warn('Accessibility Automation will run only on Chrome browser version greater than 100.') - return false - } - return true + // Support Chrome, Chrome for Testing (ChromeForTesting), and Safari on non-BrowserStack infrastructure + const validBrowsers = ['chrome', 'chromefortesting', 'safari'] + if (!browser || !validBrowsers.includes(browser)) { + BStackLogger.warn('Accessibility Automation on non-BrowserStack infrastructure supports Chrome 100+, Chrome for Testing 141+, and Safari 18.4+.') + return false + } + + // Chrome/Chrome for Testing validation + if (browser === 'chrome' || browser === 'chromefortesting') { + const minVersion = MIN_BROWSER_VERSIONS_A11Y_NON_BSTACK[browser as keyof typeof MIN_BROWSER_VERSIONS_A11Y_NON_BSTACK] + if (browserVersion && browserVersion !== 'latest') { + const version = parseInt(browserVersion.toString().split('.')[0] || '0', 10) + if (version < minVersion) { + BStackLogger.warn(`Accessibility Automation requires ${browser === 'chrome' ? 'Chrome' : 'Chrome for Testing'} version ${minVersion}+ on non-BrowserStack infrastructure.`) + return false + } + } + } + + // Safari validation + if (browser === 'safari') { + if (browserVersion && browserVersion !== 'latest') { + const [currentMajor = 0, currentMinor = 0] = browserVersion.toString().split('.').map(Number) + const [requiredMajor = 0, requiredMinor = 0] = MIN_BROWSER_VERSIONS_A11Y_NON_BSTACK.safari.toString().split('.').map(Number) + if (currentMajor < requiredMajor || (currentMajor === requiredMajor && currentMinor < requiredMinor)) { + BStackLogger.warn(`Accessibility Automation requires Safari version ${MIN_BROWSER_VERSIONS_A11Y_NON_BSTACK.safari}+ on non-BrowserStack infrastructure.`) + return false + } + } + } + + return true + } catch (error) { + BStackLogger.debug(`Exception in checking capabilities compatibility with Accessibility. Error: ${error}`) + } + return false } export const shouldScanTestForAccessibility = (suiteTitle: string | undefined, testTitle: string, accessibilityOptions?: { [key: string]: string; }, world?: { [key: string]: unknown; }, isCucumber?: boolean) => { diff --git a/packages/wdio-browserstack-service/tests/util.test.ts b/packages/wdio-browserstack-service/tests/util.test.ts index 28edc4acc5a..17d102c4a34 100644 --- a/packages/wdio-browserstack-service/tests/util.test.ts +++ b/packages/wdio-browserstack-service/tests/util.test.ts @@ -1153,17 +1153,17 @@ describe('validateCapsWithA11y', () => { .toContain('Accessibility Automation will run only on Desktop browsers.') }) - it('returns false if browser is not chrome', async () => { + it('returns false if browser is not supported', async () => { const platformMeta = { - 'browser_name': 'safari' + 'browser_name': 'firefox' } expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(false) expect(logInfoMock.mock.calls[0][0]) - .toContain('Accessibility Automation will run only on Chrome browsers.') + .toContain('Accessibility Automation supports Chrome 95+, Chrome for Testing 141+, and Safari 18.4+') }) - it('returns false if browser version is lesser than 94', async () => { + it('returns false if browser version is lesser than 95', async () => { const platformMeta = { 'browser_name': 'chrome', 'browser_version': '90' @@ -1171,7 +1171,7 @@ describe('validateCapsWithA11y', () => { expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(false) expect(logInfoMock.mock.calls[0][0]) - .toContain('Accessibility Automation will run only on Chrome browser version greater than 94.') + .toContain('Accessibility Automation requires Chrome version 95 or higher') }) it('returns false if browser version is lesser than 94', async () => { @@ -1197,6 +1197,50 @@ describe('validateCapsWithA11y', () => { expect(validateCapsWithA11y(undefined, platformMeta, chromeOptions)).toEqual(true) }) + + it('returns true for Safari 18.4+', async () => { + const platformMeta = { + 'browser_name': 'safari', + 'browser_version': '18.4' + } + expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(true) + }) + + it('returns true for Safari latest', async () => { + const platformMeta = { + 'browser_name': 'safari', + 'browser_version': 'latest' + } + expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(true) + }) + + it('returns false for Safari < 18.4', async () => { + const platformMeta = { + 'browser_name': 'safari', + 'browser_version': '16.0' + } + expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(false) + expect(logInfoMock.mock.calls[0][0]) + .toContain('Safari version 18.4 or higher') + }) + + it('returns true for ChromeForTesting 141+', async () => { + const platformMeta = { + 'browser_name': 'ChromeForTesting', + 'browser_version': '141' + } + expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(true) + }) + + it('returns false for ChromeForTesting < 141', async () => { + const platformMeta = { + 'browser_name': 'ChromeForTesting', + 'browser_version': '140' + } + expect(validateCapsWithA11y(undefined, platformMeta)).toEqual(false) + expect(logInfoMock.mock.calls[0][0]) + .toContain('Accessibility Automation requires Chrome for Testing version 141 or higher') + }) }) describe('validateCapsWithNonBstackA11y', () => { @@ -1205,14 +1249,38 @@ describe('validateCapsWithNonBstackA11y', () => { logInfoMock = vi.spyOn(log, 'warn') }) - it('returns false if browser is not chrome', async () => { + it('returns true for safari 18.4+', async () => { + expect(validateCapsWithNonBstackA11y('safari', '18.4')).toEqual(true) + }) + + it('returns true for safari latest', async () => { + expect(validateCapsWithNonBstackA11y('safari', 'latest')).toEqual(true) + }) + + it('returns false for safari < 18.4', async () => { + expect(validateCapsWithNonBstackA11y('safari', '16.0')).toEqual(false) + expect(logInfoMock.mock.calls[0][0]) + .toContain('Safari version 18.4+') + }) + + it('returns true for ChromeForTesting 141+', async () => { + expect(validateCapsWithNonBstackA11y('ChromeForTesting', '141')).toEqual(true) + }) + + it('returns false for ChromeForTesting < 141', async () => { + expect(validateCapsWithNonBstackA11y('ChromeForTesting', '140')).toEqual(false) + expect(logInfoMock.mock.calls[0][0]) + .toContain('Accessibility Automation requires Chrome for Testing version 141+') + }) + + it('returns false if browser is not supported', async () => { - const browserName = 'safari' + const browserName = 'firefox' const browserVersion = 'latest' expect(validateCapsWithNonBstackA11y(browserName, browserVersion)).toEqual(false) expect(logInfoMock.mock.calls[0][0]) - .toContain('Accessibility Automation will run only on Chrome browsers.') + .toContain('Accessibility Automation on non-BrowserStack infrastructure supports Chrome 100+, Chrome for Testing 141+, and Safari 18.4+') }) it('returns false if browser version is lesser than 100', async () => { @@ -1222,7 +1290,7 @@ describe('validateCapsWithNonBstackA11y', () => { expect(validateCapsWithNonBstackA11y(browserName, browserVersion)).toEqual(false) expect(logInfoMock.mock.calls[0][0]) - .toContain('Accessibility Automation will run only on Chrome browser version greater than 100.') + .toContain('Accessibility Automation requires Chrome version 100+') }) it('returns true if validation done', async () => {