Skip to content

Commit 40b6900

Browse files
committed
improve customLocatorStrategies playwright helper
1 parent 9a5d008 commit 40b6900

File tree

1 file changed

+71
-112
lines changed

1 file changed

+71
-112
lines changed

lib/helper/Playwright.js

Lines changed: 71 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,36 @@ if (typeof global.__playwrightSelectorsRegistered === 'undefined') {
4444
global.__playwrightSelectorsRegistered = false
4545
}
4646

47+
/**
48+
* Creates a Playwright selector engine factory for a custom locator strategy.
49+
* @param {string} name - Strategy name for error messages
50+
* @param {Function} func - The locator function (selector, root) => Element|Element[]
51+
* @returns {Function} Selector engine factory
52+
*/
53+
function createCustomSelectorEngine(name, func) {
54+
return () => ({
55+
create: () => null,
56+
query(root, selector) {
57+
if (!root) return null
58+
try {
59+
const result = func(selector, root)
60+
return Array.isArray(result) ? result[0] : result
61+
} catch (e) {
62+
return null
63+
}
64+
},
65+
queryAll(root, selector) {
66+
if (!root) return []
67+
try {
68+
const result = func(selector, root)
69+
return Array.isArray(result) ? result : result ? [result] : []
70+
} catch (e) {
71+
return []
72+
}
73+
},
74+
})
75+
}
76+
4777
const popupStore = new Popup()
4878
const consoleLogStore = new Console()
4979
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron']
@@ -358,23 +388,13 @@ class Playwright extends Helper {
358388

359389
// Filter out invalid customLocatorStrategies (empty arrays, objects without functions)
360390
// This can happen in worker threads where config is serialized/deserialized
361-
let validCustomLocators = null
362-
if (typeof config.customLocatorStrategies === 'object' && config.customLocatorStrategies !== null) {
363-
// Check if it's an empty array or object with no function properties
364-
const entries = Object.entries(config.customLocatorStrategies)
365-
const hasFunctions = entries.some(([_, value]) => typeof value === 'function')
366-
if (hasFunctions) {
367-
validCustomLocators = config.customLocatorStrategies
368-
}
369-
}
370-
371-
this.customLocatorStrategies = validCustomLocators
391+
this.customLocatorStrategies = this._parseCustomLocatorStrategies(config.customLocatorStrategies)
372392
this._customLocatorsRegistered = false
373393

374394
// Add custom locator strategies to global registry for early registration
375395
if (this.customLocatorStrategies) {
376-
for (const [strategyName, strategyFunction] of Object.entries(this.customLocatorStrategies)) {
377-
globalCustomLocatorStrategies.set(strategyName, strategyFunction)
396+
for (const [name, func] of Object.entries(this.customLocatorStrategies)) {
397+
globalCustomLocatorStrategies.set(name, func)
378398
}
379399
}
380400

@@ -565,54 +585,23 @@ class Playwright extends Helper {
565585
}
566586

567587
// Register all custom locator strategies from the global registry
568-
for (const [strategyName, strategyFunction] of globalCustomLocatorStrategies.entries()) {
569-
if (!registeredCustomLocatorStrategies.has(strategyName)) {
570-
try {
571-
// Create a selector engine factory function exactly like createValueEngine pattern
572-
// Capture variables in closure to avoid reference issues
573-
const createCustomEngine = ((name, func) => {
574-
return () => {
575-
return {
576-
create() {
577-
return null
578-
},
579-
query(root, selector) {
580-
try {
581-
if (!root) return null
582-
const result = func(selector, root)
583-
return Array.isArray(result) ? result[0] : result
584-
} catch (error) {
585-
console.warn(`Error in custom locator "${name}":`, error)
586-
return null
587-
}
588-
},
589-
queryAll(root, selector) {
590-
try {
591-
if (!root) return []
592-
const result = func(selector, root)
593-
return Array.isArray(result) ? result : result ? [result] : []
594-
} catch (error) {
595-
console.warn(`Error in custom locator "${name}":`, error)
596-
return []
597-
}
598-
},
599-
}
600-
}
601-
})(strategyName, strategyFunction)
588+
await this._registerGlobalCustomLocators()
589+
} catch (e) {
590+
console.warn(e)
591+
}
592+
}
602593

603-
await playwright.selectors.register(strategyName, createCustomEngine)
604-
registeredCustomLocatorStrategies.add(strategyName)
605-
} catch (error) {
606-
if (!error.message.includes('already registered')) {
607-
console.warn(`Failed to register custom locator strategy '${strategyName}':`, error)
608-
} else {
609-
console.log(`Custom locator strategy '${strategyName}' already registered`)
610-
}
611-
}
594+
async _registerGlobalCustomLocators() {
595+
for (const [name, func] of globalCustomLocatorStrategies.entries()) {
596+
if (registeredCustomLocatorStrategies.has(name)) continue
597+
try {
598+
await playwright.selectors.register(name, createCustomSelectorEngine(name, func))
599+
registeredCustomLocatorStrategies.add(name)
600+
} catch (e) {
601+
if (!e.message.includes('already registered')) {
602+
this.debugSection('Custom Locator', `Failed to register '${name}': ${e.message}`)
612603
}
613604
}
614-
} catch (e) {
615-
console.warn(e)
616605
}
617606
}
618607

@@ -1277,28 +1266,31 @@ class Playwright extends Helper {
12771266
return this.browser
12781267
}
12791268

1269+
_hasCustomLocatorStrategies() {
1270+
return !!(this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length > 0)
1271+
}
1272+
1273+
_parseCustomLocatorStrategies(strategies) {
1274+
if (typeof strategies !== 'object' || strategies === null) return null
1275+
const hasValidFunctions = Object.values(strategies).some(v => typeof v === 'function')
1276+
return hasValidFunctions ? strategies : null
1277+
}
1278+
12801279
_lookupCustomLocator(customStrategy) {
1281-
if (typeof this.customLocatorStrategies !== 'object' || this.customLocatorStrategies === null) {
1282-
return null
1283-
}
1280+
if (!this._hasCustomLocatorStrategies()) return null
12841281
const strategy = this.customLocatorStrategies[customStrategy]
12851282
return typeof strategy === 'function' ? strategy : null
12861283
}
12871284

12881285
_isCustomLocator(locator) {
12891286
const locatorObj = new Locator(locator)
1290-
if (locatorObj.isCustom()) {
1291-
const customLocator = this._lookupCustomLocator(locatorObj.type)
1292-
if (customLocator) {
1293-
return true
1294-
}
1295-
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".')
1296-
}
1297-
return false
1287+
if (!locatorObj.isCustom()) return false
1288+
if (this._lookupCustomLocator(locatorObj.type)) return true
1289+
throw new Error('Please define "customLocatorStrategies" as an Object and the Locator Strategy as a "function".')
12981290
}
12991291

13001292
_isCustomLocatorStrategyDefined() {
1301-
return !!(this.customLocatorStrategies && Object.keys(this.customLocatorStrategies).length > 0)
1293+
return this._hasCustomLocatorStrategies()
13021294
}
13031295

13041296
/**
@@ -1321,49 +1313,16 @@ class Playwright extends Helper {
13211313
}
13221314

13231315
async _registerCustomLocatorStrategies() {
1324-
if (!this.customLocatorStrategies) return
1325-
1326-
for (const [strategyName, strategyFunction] of Object.entries(this.customLocatorStrategies)) {
1327-
if (!registeredCustomLocatorStrategies.has(strategyName)) {
1328-
try {
1329-
const createCustomEngine = ((name, func) => {
1330-
return () => {
1331-
return {
1332-
create(root, target) {
1333-
return null
1334-
},
1335-
query(root, selector) {
1336-
try {
1337-
if (!root) return null
1338-
const result = func(selector, root)
1339-
return Array.isArray(result) ? result[0] : result
1340-
} catch (error) {
1341-
console.warn(`Error in custom locator "${name}":`, error)
1342-
return null
1343-
}
1344-
},
1345-
queryAll(root, selector) {
1346-
try {
1347-
if (!root) return []
1348-
const result = func(selector, root)
1349-
return Array.isArray(result) ? result : result ? [result] : []
1350-
} catch (error) {
1351-
console.warn(`Error in custom locator "${name}":`, error)
1352-
return []
1353-
}
1354-
},
1355-
}
1356-
}
1357-
})(strategyName, strategyFunction)
1316+
if (!this._hasCustomLocatorStrategies()) return
13581317

1359-
await playwright.selectors.register(strategyName, createCustomEngine)
1360-
registeredCustomLocatorStrategies.add(strategyName)
1361-
} catch (error) {
1362-
if (!error.message.includes('already registered')) {
1363-
console.warn(`Failed to register custom locator strategy '${strategyName}':`, error)
1364-
} else {
1365-
console.log(`Custom locator strategy '${strategyName}' already registered`)
1366-
}
1318+
for (const [name, func] of Object.entries(this.customLocatorStrategies)) {
1319+
if (registeredCustomLocatorStrategies.has(name)) continue
1320+
try {
1321+
await playwright.selectors.register(name, createCustomSelectorEngine(name, func))
1322+
registeredCustomLocatorStrategies.add(name)
1323+
} catch (e) {
1324+
if (!e.message.includes('already registered')) {
1325+
this.debugSection('Custom Locator', `Failed to register '${name}': ${e.message}`)
13671326
}
13681327
}
13691328
}

0 commit comments

Comments
 (0)