Skip to content

Commit 8f7d143

Browse files
Copilotkobenguyent
andcommitted
Add Custom Strategy Locators support to Playwright helper
Co-authored-by: kobenguyent <[email protected]>
1 parent b30dd01 commit 8f7d143

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

lib/helper/Playwright.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const pathSeparator = path.sep
9595
* @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose).
9696
* @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har).
9797
* @prop {string} [testIdAttribute=data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id).
98+
* @prop {object} [customLocatorStrategies] - custom locator strategies. An object with keys as strategy names and values as JavaScript functions. Example: `{ byRole: (selector, root) => { return root.querySelector(\`[role="\${selector}\"]\`) } }`
9899
*/
99100
const config = {}
100101

@@ -343,6 +344,7 @@ class Playwright extends Helper {
343344
this.recordingWebSocketMessages = false
344345
this.recordedWebSocketMessagesAtLeastOnce = false
345346
this.cdpSession = null
347+
this.customLocatorStrategies = config.customLocatorStrategies
346348

347349
// override defaults with config
348350
this._setConfig(config)
@@ -468,6 +470,9 @@ class Playwright extends Helper {
468470
await playwright.selectors.register('__value', createValueEngine)
469471
await playwright.selectors.register('__disabled', createDisabledEngine)
470472
if (process.env.testIdAttribute) await playwright.selectors.setTestIdAttribute(process.env.testIdAttribute)
473+
474+
// Register custom locator strategies
475+
await this._registerCustomLocatorStrategies()
471476
} catch (e) {
472477
console.warn(e)
473478
}
@@ -861,6 +866,65 @@ class Playwright extends Helper {
861866
return this.browser
862867
}
863868

869+
_lookupCustomLocator(customStrategy) {
870+
if (typeof this.customLocatorStrategies !== 'object') {
871+
return null
872+
}
873+
const strategy = this.customLocatorStrategies[customStrategy]
874+
return typeof strategy === 'function' ? strategy : null
875+
}
876+
877+
_isCustomLocator(locator) {
878+
const locatorObj = new Locator(locator)
879+
if (locatorObj.isCustom()) {
880+
const customLocator = this._lookupCustomLocator(locatorObj.type)
881+
if (customLocator) {
882+
return true
883+
}
884+
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".')
885+
}
886+
return false
887+
}
888+
889+
_isCustomLocatorStrategyDefined() {
890+
return this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length
891+
}
892+
893+
async _registerCustomLocatorStrategies() {
894+
if (this._isCustomLocatorStrategyDefined()) {
895+
for (const [strategyName, strategyFunction] of Object.entries(this.customLocatorStrategies)) {
896+
this.debugSection('Playwright', `registering custom locator strategy: ${strategyName}`)
897+
898+
// Create a selector engine for this custom strategy
899+
const engine = {
900+
query(root, selector) {
901+
try {
902+
const customFn = strategyFunction
903+
const result = customFn(selector, root)
904+
return Array.isArray(result) ? result[0] : result
905+
} catch (error) {
906+
console.warn(`Error in custom locator '${strategyName}':`, error)
907+
return null
908+
}
909+
},
910+
911+
queryAll(root, selector) {
912+
try {
913+
const customFn = strategyFunction
914+
const result = customFn(selector, root)
915+
return Array.isArray(result) ? result : (result ? [result] : [])
916+
} catch (error) {
917+
console.warn(`Error in custom locator '${strategyName}':`, error)
918+
return []
919+
}
920+
}
921+
}
922+
923+
await playwright.selectors.register(strategyName, () => engine)
924+
}
925+
}
926+
}
927+
864928
/**
865929
* Create a new browser context with a page. \
866930
* Usually it should be run from a custom helper after call of `_startBrowser()`
@@ -3431,6 +3495,7 @@ async function findElements(matcher, locator) {
34313495
if (locator.react) return findReact(matcher, locator)
34323496
if (locator.vue) return findVue(matcher, locator)
34333497
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
3498+
34343499
locator = new Locator(locator, 'css')
34353500

34363501
return matcher.locator(buildLocatorString(locator)).all()
@@ -3440,6 +3505,7 @@ async function findElement(matcher, locator) {
34403505
if (locator.react) return findReact(matcher, locator)
34413506
if (locator.vue) return findVue(matcher, locator)
34423507
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
3508+
34433509
locator = new Locator(locator, 'css')
34443510

34453511
return matcher.locator(buildLocatorString(locator)).first()

0 commit comments

Comments
 (0)