diff --git a/packages/wdio-browserstack-service/src/accessibility-handler.ts b/packages/wdio-browserstack-service/src/accessibility-handler.ts index 76ae8329cc0..c8fbe89c91d 100644 --- a/packages/wdio-browserstack-service/src/accessibility-handler.ts +++ b/packages/wdio-browserstack-service/src/accessibility-handler.ts @@ -73,6 +73,7 @@ import { import accessibilityScripts from './scripts/accessibility-scripts.js' import PerformanceTester from './instrumentation/performance/performance-tester.js' import * as PERFORMANCE_SDK_EVENTS from './instrumentation/performance/constants.js' +import { ACCESSIBILITY_WRAP_EXCLUDED_COMMANDS } from './constants.js' import { BStackLogger } from './bstackLogger.js' @@ -254,6 +255,10 @@ class _AccessibilityHandler { accessibilityScripts.commandsToWrap .filter((command) => command.name && command.class) + // Skip synchronous chainable commands (e.g. `action`) — the async + // wrapper would turn their return value into a Promise and break + // chaining. See SDK-6265. + .filter((command) => !ACCESSIBILITY_WRAP_EXCLUDED_COMMANDS.includes(command.name)) .forEach((command) => { const browser = this._browser as WebdriverIO.Browser try { diff --git a/packages/wdio-browserstack-service/src/cli/modules/accessibilityModule.ts b/packages/wdio-browserstack-service/src/cli/modules/accessibilityModule.ts index d71be0fe25c..6ab8b678486 100644 --- a/packages/wdio-browserstack-service/src/cli/modules/accessibilityModule.ts +++ b/packages/wdio-browserstack-service/src/cli/modules/accessibilityModule.ts @@ -13,6 +13,7 @@ import type { Command } from '../../scripts/accessibility-scripts.js' import accessibilityScripts from '../../scripts/accessibility-scripts.js' import { _getParamsForAppAccessibility, formatString, getAppA11yResults, getAppA11yResultsSummary, shouldScanTestForAccessibility, validateCapsWithA11y, validateCapsWithAppA11y, isBrowserstackSession } from '../../util.js' import { AutomationFrameworkConstants } from '../frameworks/constants/automationFrameworkConstants.js' +import { ACCESSIBILITY_WRAP_EXCLUDED_COMMANDS } from '../../constants.js' import util from 'node:util' import type { Accessibility } from '@browserstack/wdio-browserstack-service' import PerformanceTester from '../../instrumentation/performance/performance-tester.js' @@ -138,6 +139,10 @@ export default class AccessibilityModule extends BaseModule { if (this.scriptInstance.commandsToWrap && this.scriptInstance.commandsToWrap.length > 0) { this.scriptInstance.commandsToWrap .filter((command) => command.name && command.class) + // Skip synchronous chainable commands (e.g. `action`) — the async + // wrapper would turn their return value into a Promise and break + // chaining. See SDK-6265. + .filter((command) => !ACCESSIBILITY_WRAP_EXCLUDED_COMMANDS.includes(command.name)) .forEach((command) => { browser.overwriteCommand( // @ts-expect-error fix type diff --git a/packages/wdio-browserstack-service/src/constants.ts b/packages/wdio-browserstack-service/src/constants.ts index 1a97ffba197..9d17a020d2c 100644 --- a/packages/wdio-browserstack-service/src/constants.ts +++ b/packages/wdio-browserstack-service/src/constants.ts @@ -62,6 +62,16 @@ export const PERCY_DOM_CHANGING_COMMANDS_ENDPOINTS = [ '/session/:sessionId/appium/device/shake' ] +/** + * Commands that synchronously return a chainable builder (rather than a Promise) + * and therefore must NOT be wrapped by the accessibility command wrapper. The + * wrapper is async, so wrapping a synchronous command converts its return value + * into a Promise and breaks chaining (e.g. `browser.action('pointer').move(...)` + * fails with "move is not a function" because `action` now resolves to a Promise). + * See SDK-6265. + */ +export const ACCESSIBILITY_WRAP_EXCLUDED_COMMANDS = ['action'] + export const CAPTURE_MODES = ['click', 'auto', 'screenshot', 'manual', 'testcase'] export const LOG_KIND_USAGE_MAP = { 'TEST_LOG': 'log',