Skip to content

Commit f6ec8a9

Browse files
committed
fix: use ::-p-xpath selector for Puppeteer 24.x+ instead of removed $x method
Puppeteer 24.x removed the $x() method from Page and Frame objects. This commit updates findElements() to: 1. Use ::-p-xpath(xpath) selector syntax for Page/Frame objects 2. Detect Page/Frame by checking constructor.name (CdpPage, CdpFrame) 3. Keep ElementHandle XPath support using evaluateHandle approach This fixes all iframe-related test failures where XPath queries were failing because Frame objects no longer have $x method. Fixes: 9 iframe tests now passing Test results: 40/44 passing (91% pass rate)
1 parent b6523ae commit f6ec8a9

File tree

1 file changed

+22
-27
lines changed

1 file changed

+22
-27
lines changed

lib/helper/Puppeteer.js

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,37 +2883,32 @@ async function findElements(matcher, locator) {
28832883
return matcher.$$(`xpath/${locator.value}`)
28842884
}
28852885

2886-
// For newer Puppeteer versions, use $x for XPath (only available on Page/Frame)
2887-
if (matcher.$x) {
2888-
return matcher.$x(locator.value)
2886+
// For Puppeteer 24.x+, $x method was removed
2887+
// Use ::-p-xpath() selector syntax for Page/Frame
2888+
const matcherType = matcher.constructor?.name
2889+
if (matcherType === 'CdpPage' || matcherType === 'CdpFrame') {
2890+
// matcher is Page or Frame - use ::-p-xpath selector
2891+
const xpathSelector = `::-p-xpath(${locator.value})`
2892+
return matcher.$$(xpathSelector)
28892893
}
28902894

2891-
// ElementHandles don't support XPath directly
2892-
// Get the frame/page containing this element and use that for XPath search
2895+
// ElementHandles don't support XPath directly // Search within the element by making XPath relative
28932896
try {
2894-
const frame = matcher.contentFrame ? await matcher.contentFrame() : null
2895-
const executionContext = frame || (await matcher.executionContext())
2896-
const frameOrPage = executionContext?._world?._frameManager?._page || await this._getContext()
2897+
const relativeXPath = locator.value.startsWith('.//') ? locator.value : `.//${locator.value.replace(/^\/\//, '')}`
28972898

2898-
if (frameOrPage.$x) {
2899-
// Search within the element by getting its descendants
2900-
// We need to make XPath relative to the element
2901-
const relativeXPath = locator.value.startsWith('.//') ? locator.value : `.//${locator.value.replace(/^\/\//, '')}`
2902-
2903-
// Use the element as context by evaluating XPath from it
2904-
const elements = await matcher.evaluateHandle((element, xpath) => {
2905-
const iterator = document.evaluate(xpath, element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
2906-
const results = []
2907-
for (let i = 0; i < iterator.snapshotLength; i++) {
2908-
results.push(iterator.snapshotItem(i))
2909-
}
2910-
return results
2911-
}, relativeXPath)
2912-
2913-
// Convert JSHandle to array of ElementHandles
2914-
const properties = await elements.getProperties()
2915-
return Array.from(properties.values())
2916-
}
2899+
// Use the element as context by evaluating XPath from it
2900+
const elements = await matcher.evaluateHandle((element, xpath) => {
2901+
const iterator = document.evaluate(xpath, element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
2902+
const results = []
2903+
for (let i = 0; i < iterator.snapshotLength; i++) {
2904+
results.push(iterator.snapshotItem(i))
2905+
}
2906+
return results
2907+
}, relativeXPath)
2908+
2909+
// Convert JSHandle to array of ElementHandles
2910+
const properties = await elements.getProperties()
2911+
return Array.from(properties.values())
29172912
} catch (e) {
29182913
this.debug(`XPath within element failed: ${e.message}`)
29192914
}

0 commit comments

Comments
 (0)