@@ -39,6 +39,11 @@ let defaultSelectorEnginesInitialized = false
3939let registeredCustomLocatorStrategies = new Set ( )
4040let globalCustomLocatorStrategies = new Map ( )
4141
42+ // Use global object to track selector registration across workers
43+ if ( typeof global . __playwrightSelectorsRegistered === 'undefined' ) {
44+ global . __playwrightSelectorsRegistered = false
45+ }
46+
4247const popupStore = new Popup ( )
4348const consoleLogStore = new Console ( )
4449const availableBrowsers = [ 'chromium' , 'webkit' , 'firefox' , 'electron' ]
@@ -507,22 +512,25 @@ class Playwright extends Helper {
507512 // register an internal selector engine for reading value property of elements in a selector
508513 try {
509514 // Always wrap in try-catch since selectors might be registered globally across workers
510- try {
511- await playwright . selectors . register ( '__value' , createValueEngine )
512- defaultSelectorEnginesInitialized = true
513- } catch ( e ) {
514- if ( ! e . message . includes ( 'already registered' ) ) {
515- throw e
515+ // Check global flag to avoid re-registration in worker processes
516+ if ( ! global . __playwrightSelectorsRegistered ) {
517+ try {
518+ await playwright . selectors . register ( '__value' , createValueEngine )
519+ await playwright . selectors . register ( '__disabled' , createDisabledEngine )
520+ global . __playwrightSelectorsRegistered = true
521+ defaultSelectorEnginesInitialized = true
522+ } catch ( e ) {
523+ if ( ! e . message . includes ( 'already registered' ) ) {
524+ throw e
525+ }
526+ // Selector already registered globally by another worker
527+ global . __playwrightSelectorsRegistered = true
528+ defaultSelectorEnginesInitialized = true
516529 }
517- // Selector already registered globally, mark as initialized
530+ } else {
531+ // Selectors already registered in a worker, skip
518532 defaultSelectorEnginesInitialized = true
519- }
520- try {
521- await playwright . selectors . register ( '__disabled' , createDisabledEngine )
522- } catch ( e ) {
523- if ( ! e . message . includes ( 'already registered' ) ) {
524- throw e
525- }
533+ this . debugSection ( 'Init' , 'Default selector engines already registered globally, skipping' )
526534 }
527535 if ( process . env . testIdAttribute ) {
528536 try {
@@ -684,16 +692,16 @@ class Playwright extends Helper {
684692 this . debugSection ( 'New Session' , JSON . stringify ( this . contextOptions ) )
685693 try {
686694 this . browserContext = await this . browser . newContext ( this . contextOptions ) // Adding the HTTPSError ignore in the context so that we can ignore those errors
687- } catch ( e ) {
688- // In worker processes, selectors might already be registered globally
689- // causing newContext to fail. Try again or use the error
690- if ( e . message && e . message . includes ( 'already registered' ) ) {
691- // Selectors already registered, try creating context anyway
692- // This happens in worker processes where Playwright state is shared
693- this . debugSection ( 'Session' , 'Selectors already registered, retrying context creation' )
694- this . browserContext = await this . browser . newContext ( this . contextOptions )
695+ } catch ( err ) {
696+ // In worker mode with Playwright 1.x, there's a known issue where newContext() fails
697+ // with "selector engine already registered" when selectors are registered globally
698+ // across worker threads. This is safe to retry without ANY custom options.
699+ if ( err . message && err . message . includes ( ' already registered' ) ) {
700+ this . debugSection ( 'Worker Mode' , 'Selector conflict detected, retrying context creation with no options' )
701+ // Create context with NO options to avoid selector conflicts
702+ this . browserContext = await this . browser . newContext ( )
695703 } else {
696- throw e
704+ throw err
697705 }
698706 }
699707 }
@@ -1507,7 +1515,21 @@ class Playwright extends Helper {
15071515 acceptDownloads : true ,
15081516 ...this . options . emulate ,
15091517 }
1510- this . browserContext = await this . browser . newContext ( contextOptions )
1518+
1519+ try {
1520+ this . browserContext = await this . browser . newContext ( contextOptions )
1521+ } catch ( err ) {
1522+ // In worker mode with Playwright 1.x, there's a known issue where newContext() fails
1523+ // with "selector engine already registered" when selectors are registered globally
1524+ // across worker threads. This is safe to retry without ANY custom options.
1525+ if ( err . message && err . message . includes ( 'already registered' ) ) {
1526+ this . debugSection ( 'Worker Mode' , 'Selector conflict in amOnPage, retrying with empty options' )
1527+ // Create context with NO options to avoid selector conflicts
1528+ this . browserContext = await this . browser . newContext ( )
1529+ } else {
1530+ throw err
1531+ }
1532+ }
15111533 }
15121534
15131535 let pages
@@ -4301,7 +4323,15 @@ async function findElements(matcher, locator) {
43014323}
43024324
43034325async function findCustomElements ( matcher , locator ) {
4304- const customLocatorStrategies = this . customLocatorStrategies || globalCustomLocatorStrategies
4326+ // Prefer instance-level custom locators over global registry
4327+ // In worker mode, globalCustomLocatorStrategies might be empty due to thread isolation
4328+ let customLocatorStrategies = this . customLocatorStrategies
4329+
4330+ // Fallback to global registry if instance doesn't have custom locators
4331+ if ( ! customLocatorStrategies || ( ! customLocatorStrategies . get && ! customLocatorStrategies [ locator . type ] ) ) {
4332+ customLocatorStrategies = globalCustomLocatorStrategies
4333+ }
4334+
43054335 const strategyFunction = customLocatorStrategies . get ? customLocatorStrategies . get ( locator . type ) : customLocatorStrategies [ locator . type ]
43064336
43074337 if ( ! strategyFunction ) {
0 commit comments