@@ -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+
4777const popupStore = new Popup ( )
4878const consoleLogStore = new Console ( )
4979const 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