Skip to content

Commit 3c634f7

Browse files
authored
fix: relative XPath chaining in BiDi mode (#14912)
Fixes #14675
1 parent 15222c1 commit 3c634f7

1 file changed

Lines changed: 33 additions & 13 deletions

File tree

packages/webdriverio/src/utils/index.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export const getElementFromResponse = (res?: ElementReference) => {
113113
return null
114114
}
115115

116-
function sanitizeCSS (value?: string) {
116+
function sanitizeCSS(value?: string) {
117117
/* istanbul ignore next */
118118
if (!value) {
119119
return value
@@ -128,7 +128,7 @@ function sanitizeCSS (value?: string) {
128128
* @param {string} cssProperty name of css property to parse
129129
* @return {object} parsed css property
130130
*/
131-
export function parseCSS (cssPropertyValue: string, cssProperty?: string) {
131+
export function parseCSS(cssPropertyValue: string, cssProperty?: string) {
132132
const parsedValue: ParsedCSSValue = {
133133
property: cssProperty,
134134
value: cssPropertyValue.toLowerCase().trim(),
@@ -185,7 +185,7 @@ export function parseCSS (cssPropertyValue: string, cssProperty?: string) {
185185
* @param {string} value text
186186
* @return {Array} set of characters or unicode symbols
187187
*/
188-
export function checkUnicode (value: string) {
188+
export function checkUnicode(value: string) {
189189
/**
190190
* "Ctrl" key is specially handled based on OS in action class
191191
*/
@@ -203,7 +203,7 @@ export function checkUnicode (value: string) {
203203
return [UNICODE_CHARACTERS[value as keyof typeof UNICODE_CHARACTERS]]
204204
}
205205

206-
function fetchElementByJSFunction (
206+
function fetchElementByJSFunction(
207207
selector: ElementFunction,
208208
scope: WebdriverIO.Browser | WebdriverIO.Element,
209209
referenceId?: string
@@ -225,15 +225,15 @@ function fetchElementByJSFunction (
225225
return getBrowserObject(scope).executeScript(`return (${script}).apply(null, arguments)`, args)
226226
}
227227

228-
export function isElement (o: Selector){
228+
export function isElement(o: Selector) {
229229
return (
230230
typeof HTMLElement === 'object'
231231
? o instanceof HTMLElement
232-
: o && typeof o === 'object' && o !== null && (o as HTMLElement).nodeType === 1 && typeof (o as HTMLElement).nodeName==='string'
232+
: o && typeof o === 'object' && o !== null && (o as HTMLElement).nodeType === 1 && typeof (o as HTMLElement).nodeName === 'string'
233233
)
234234
}
235235

236-
export function isStaleElementError (err: Error) {
236+
export function isStaleElementError(err: Error) {
237237
return (
238238
// Chrome
239239
err.message.includes('stale element reference') ||
@@ -255,7 +255,7 @@ export function isStaleElementError (err: Error) {
255255
* @param shadowRootId shadow root id that was inspected
256256
* @returns a function to handle the result of a shadow root inspection
257257
*/
258-
export function elementPromiseHandler <T extends object>(handle: string, shadowRootManager: ShadowRootManager, shadowRootId?: string) {
258+
export function elementPromiseHandler<T extends object>(handle: string, shadowRootManager: ShadowRootManager, shadowRootId?: string) {
259259
return (el: T | Error) => {
260260
const errorString = 'error' in el && typeof el.error === 'string'
261261
? el.error
@@ -276,7 +276,7 @@ export function elementPromiseHandler <T extends object>(handle: string, shadowR
276276
}
277277
}
278278

279-
export function transformClassicToBidiSelector (using: string, value: string): remote.BrowsingContextCssLocator | remote.BrowsingContextXPathLocator | remote.BrowsingContextInnerTextLocator {
279+
export function transformClassicToBidiSelector(using: string, value: string): remote.BrowsingContextCssLocator | remote.BrowsingContextXPathLocator | remote.BrowsingContextInnerTextLocator {
280280
if (using === 'css selector' || using === 'tag name') {
281281
return { type: 'css', value }
282282
}
@@ -317,6 +317,16 @@ export async function findDeepElement(
317317
(this as WebdriverIO.Element).elementId
318318
)
319319
const { using, value } = findStrategy(selector as string, this.isW3C, this.isMobile)
320+
321+
/**
322+
* if we are using a relative xpath selector and we have a parent element
323+
* we need to fall back to the regular WebDriver Classic command as BiDi
324+
* does not support relative xpath selectors with a start node
325+
*/
326+
if (using === 'xpath' && (value.startsWith('./') || value.startsWith('..')) && (this as WebdriverIO.Element).elementId) {
327+
return this.findElementFromElement((this as WebdriverIO.Element).elementId, using, value)
328+
}
329+
320330
const locator = transformClassicToBidiSelector(using, value)
321331
322332
/**
@@ -383,6 +393,16 @@ export async function findDeepElements(
383393
(this as WebdriverIO.Element).elementId
384394
)
385395
const { using, value } = findStrategy(selector as string, this.isW3C, this.isMobile)
396+
397+
/**
398+
* if we are using a relative xpath selector and we have a parent element
399+
* we need to fall back to the regular WebDriver Classic command as BiDi
400+
* does not support relative xpath selectors with a start node
401+
*/
402+
if (using === 'xpath' && (value.startsWith('./') || value.startsWith('..')) && (this as WebdriverIO.Element).elementId) {
403+
return this.findElementsFromElement((this as WebdriverIO.Element).elementId, using, value)
404+
}
405+
386406
const locator = transformClassicToBidiSelector(using, value)
387407
388408
/**
@@ -598,7 +618,7 @@ export async function findElements(
598618
* Strip element object and return w3c and jsonwp compatible keys
599619
*/
600620
export function verifyArgsAndStripIfElement(args: unknown) {
601-
function verify (arg: unknown) {
621+
function verify(arg: unknown) {
602622
if (arg && typeof arg === 'object' && arg.constructor.name === 'Element') {
603623
const elem = arg as WebdriverIO.Element
604624
if (!elem.elementId) {
@@ -667,7 +687,7 @@ export async function getElementRect(scope: WebdriverIO.Element) {
667687
* @param {Boolean} [retryCheck=false] true if an url was already check and still failed with fix applied
668688
* @return {string} fixed url
669689
*/
670-
export function validateUrl (url: string, origError?: Error): string {
690+
export function validateUrl(url: string, origError?: Error): string {
671691
try {
672692
const urlObject = new URL(url)
673693
return urlObject.href
@@ -683,7 +703,7 @@ export function validateUrl (url: string, origError?: Error): string {
683703
}
684704
}
685705

686-
export async function hasElementId (element: WebdriverIO.Element) {
706+
export async function hasElementId(element: WebdriverIO.Element) {
687707
/*
688708
* This is only necessary as isDisplayed is on the exclusion list for the middleware
689709
*/
@@ -799,7 +819,7 @@ export const containsHeaderObject = (
799819
return true
800820
}
801821

802-
export function createFunctionDeclarationFromString (userScript: Function | string) {
822+
export function createFunctionDeclarationFromString(userScript: Function | string) {
803823
if (typeof userScript === 'string') {
804824
return `(${SCRIPT_PREFIX}function () {\n${userScript.toString()}\n}${SCRIPT_SUFFIX}).apply(this, arguments);`
805825
}

0 commit comments

Comments
 (0)