codeceptjs 4.0.0-beta.9.esm-aria → 4.0.1-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/actor.js CHANGED
@@ -75,7 +75,8 @@ export default function (obj = {}, container) {
75
75
  if (!container) {
76
76
  container = Container
77
77
  }
78
-
78
+
79
+ // Get existing actor or create a new one
79
80
  const actor = container.actor() || new Actor()
80
81
 
81
82
  // load all helpers once container initialized
@@ -111,14 +112,17 @@ export default function (obj = {}, container) {
111
112
  }
112
113
  })
113
114
 
114
- container.append({
115
- support: {
116
- I: actor,
117
- },
118
- })
115
+ // Update container.support.I to ensure it has the latest actor reference
116
+ if (!container.actor() || container.actor() !== actor) {
117
+ container.append({
118
+ support: {
119
+ I: actor,
120
+ },
121
+ })
122
+ }
119
123
  })
120
- // store.actor = actor;
121
- // add custom steps from actor
124
+
125
+ // add custom steps from actor immediately
122
126
  Object.keys(obj).forEach(key => {
123
127
  const ms = new MetaStep('I', key)
124
128
  ms.setContext(actor)
package/lib/codecept.js CHANGED
@@ -3,8 +3,9 @@ import { globSync } from 'glob'
3
3
  import shuffle from 'lodash.shuffle'
4
4
  import fsPath from 'path'
5
5
  import { resolve } from 'path'
6
- import { fileURLToPath } from 'url'
6
+ import { fileURLToPath, pathToFileURL } from 'url'
7
7
  import { dirname } from 'path'
8
+ import { createRequire } from 'module'
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url)
10
11
  const __dirname = dirname(__filename)
@@ -18,6 +19,7 @@ import ActorFactory from './actor.js'
18
19
  import output from './output.js'
19
20
  import { emptyFolder } from './utils.js'
20
21
  import { initCodeceptGlobals } from './globals.js'
22
+ import { validateTypeScriptSetup } from './utils/loaderCheck.js'
21
23
  import recorder from './recorder.js'
22
24
 
23
25
  import storeListener from './listener/store.js'
@@ -66,6 +68,21 @@ class Codecept {
66
68
  modulePath = `${modulePath}.js`
67
69
  }
68
70
  }
71
+ } else {
72
+ // For npm packages, resolve from the user's directory
73
+ // This ensures packages like tsx are found in user's node_modules
74
+ const userDir = global.codecept_dir || process.cwd()
75
+
76
+ try {
77
+ // Use createRequire to resolve from user's directory
78
+ const userRequire = createRequire(pathToFileURL(resolve(userDir, 'package.json')).href)
79
+ const resolvedPath = userRequire.resolve(requiredModule)
80
+ modulePath = pathToFileURL(resolvedPath).href
81
+ } catch (resolveError) {
82
+ // If resolution fails, try direct import (will check from CodeceptJS node_modules)
83
+ // This is the fallback for globally installed packages
84
+ modulePath = requiredModule
85
+ }
69
86
  }
70
87
  // Use dynamic import for ESM
71
88
  await import(modulePath)
@@ -246,6 +263,13 @@ class Codecept {
246
263
  async run(test) {
247
264
  await container.started()
248
265
 
266
+ // Check TypeScript loader configuration before running tests
267
+ const tsValidation = validateTypeScriptSetup(this.testFiles, this.requiringModules || [])
268
+ if (tsValidation.hasError) {
269
+ output.error(tsValidation.message)
270
+ process.exit(1)
271
+ }
272
+
249
273
  // Ensure translations are loaded for Gherkin features
250
274
  try {
251
275
  const { loadTranslations } = await import('./mocha/gherkin.js')
@@ -161,7 +161,7 @@ export default async function (initPath) {
161
161
  isTypeScript = true
162
162
  extension = isTypeScript === true ? 'ts' : 'js'
163
163
  packages.push('typescript')
164
- packages.push('ts-node')
164
+ packages.push('tsx') // Add tsx for TypeScript support
165
165
  packages.push('@types/node')
166
166
  }
167
167
 
@@ -172,6 +172,7 @@ export default async function (initPath) {
172
172
  config.tests = result.tests
173
173
  if (isTypeScript) {
174
174
  config.tests = `${config.tests.replace(/\.js$/, `.${extension}`)}`
175
+ config.require = ['tsx/cjs'] // Add tsx/cjs loader for TypeScript tests
175
176
  }
176
177
 
177
178
  // create a directory tests if it is included in tests path
package/lib/config.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { createRequire } from 'module'
4
4
  import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
5
+ import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
5
6
 
6
7
  const defaultConfig = {
7
8
  output: './_output',
@@ -156,31 +157,15 @@ async function loadConfigFile(configFile) {
156
157
  // For .ts files, try to compile and load as JavaScript
157
158
  if (extensionName === '.ts') {
158
159
  try {
159
- // Try to load ts-node and compile the file
160
- const { transpile } = require('typescript')
161
- const tsContent = fs.readFileSync(configFile, 'utf8')
162
-
163
- // Transpile TypeScript to JavaScript with ES module output
164
- const jsContent = transpile(tsContent, {
165
- module: 99, // ModuleKind.ESNext
166
- target: 99, // ScriptTarget.ESNext
167
- esModuleInterop: true,
168
- allowSyntheticDefaultImports: true,
169
- })
170
-
171
- // Create a temporary JS file with .mjs extension to force ES module treatment
172
- const tempJsFile = configFile.replace('.ts', '.temp.mjs')
173
- fs.writeFileSync(tempJsFile, jsContent)
160
+ // Use the TypeScript transpilation utility
161
+ const typescript = require('typescript')
162
+ const { tempFile, allTempFiles } = await transpileTypeScript(configFile, typescript)
174
163
 
175
164
  try {
176
- configModule = await import(tempJsFile)
177
- // Clean up temp file
178
- fs.unlinkSync(tempJsFile)
165
+ configModule = await import(tempFile)
166
+ cleanupTempFiles(allTempFiles)
179
167
  } catch (err) {
180
- // Clean up temp file even on error
181
- if (fs.existsSync(tempJsFile)) {
182
- fs.unlinkSync(tempJsFile)
183
- }
168
+ cleanupTempFiles(allTempFiles)
184
169
  throw err
185
170
  }
186
171
  } catch (tsError) {
package/lib/container.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { globSync } from 'glob'
2
2
  import path from 'path'
3
+ import fs from 'fs'
3
4
  import debugModule from 'debug'
4
5
  const debug = debugModule('codeceptjs:container')
5
6
  import { MetaStep } from './step.js'
6
7
  import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8
+ import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
7
9
  import Translation from './translation.js'
8
10
  import MochaFactory from './mocha/factory.js'
9
11
  import recorder from './recorder.js'
@@ -20,6 +22,7 @@ let container = {
20
22
  helpers: {},
21
23
  support: {},
22
24
  proxySupport: {},
25
+ proxySupportConfig: {}, // Track config used to create proxySupport
23
26
  plugins: {},
24
27
  actor: null,
25
28
  /**
@@ -30,7 +33,7 @@ let container = {
30
33
  translation: {},
31
34
  /** @type {Result | null} */
32
35
  result: null,
33
- sharedKeys: new Set() // Track keys shared via share() function
36
+ sharedKeys: new Set(), // Track keys shared via share() function
34
37
  }
35
38
 
36
39
  /**
@@ -65,14 +68,15 @@ class Container {
65
68
  container.support = {}
66
69
  container.helpers = await createHelpers(config.helpers || {})
67
70
  container.translation = await loadTranslation(config.translation || null, config.vocabularies || [])
68
- container.proxySupport = createSupportObjects(config.include || {})
71
+ container.proxySupportConfig = config.include || {}
72
+ container.proxySupport = createSupportObjects(container.proxySupportConfig)
69
73
  container.plugins = await createPlugins(config.plugins || {}, opts)
70
74
  container.result = new Result()
71
75
 
72
76
  // Preload includes (so proxies can expose real objects synchronously)
73
77
  const includes = config.include || {}
74
78
 
75
- // Ensure I is available for DI modules at import time
79
+ // Check if custom I is provided
76
80
  if (Object.prototype.hasOwnProperty.call(includes, 'I')) {
77
81
  try {
78
82
  const mod = includes.I
@@ -87,7 +91,7 @@ class Container {
87
91
  throw new Error(`Could not include object I: ${e.message}`)
88
92
  }
89
93
  } else {
90
- // Create default actor if not provided via includes
94
+ // Create default actor - this sets up the callback in asyncHelperPromise
91
95
  createActor()
92
96
  }
93
97
 
@@ -108,6 +112,9 @@ class Container {
108
112
  }
109
113
  }
110
114
 
115
+ // Wait for all async helpers to finish loading and populate the actor
116
+ await asyncHelperPromise
117
+
111
118
  if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
112
119
  if (config.gherkin) await loadGherkinStepsAsync(config.gherkin.steps || [])
113
120
  if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
@@ -202,8 +209,10 @@ class Container {
202
209
 
203
210
  // If new support objects are added, update the proxy support
204
211
  if (newContainer.support) {
205
- const newProxySupport = createSupportObjects(newContainer.support)
206
- container.proxySupport = { ...container.proxySupport, ...newProxySupport }
212
+ // Merge the new support config with existing config
213
+ container.proxySupportConfig = { ...container.proxySupportConfig, ...newContainer.support }
214
+ // Recreate the proxy with merged config
215
+ container.proxySupport = createSupportObjects(container.proxySupportConfig)
207
216
  }
208
217
 
209
218
  debug('appended', JSON.stringify(newContainer).slice(0, 300))
@@ -219,6 +228,7 @@ class Container {
219
228
  static async clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
220
229
  container.helpers = newHelpers
221
230
  container.translation = await loadTranslation()
231
+ container.proxySupportConfig = newSupport
222
232
  container.proxySupport = createSupportObjects(newSupport)
223
233
  container.plugins = newPlugins
224
234
  container.sharedKeys = new Set() // Clear shared keys
@@ -248,10 +258,10 @@ class Container {
248
258
  // Instead of using append which replaces the entire container,
249
259
  // directly update the support object to maintain proxy references
250
260
  Object.assign(container.support, data)
251
-
261
+
252
262
  // Track which keys were explicitly shared
253
263
  Object.keys(data).forEach(key => container.sharedKeys.add(key))
254
-
264
+
255
265
  if (!options.local) {
256
266
  WorkerStorage.share(data)
257
267
  }
@@ -290,7 +300,7 @@ async function createHelpers(config) {
290
300
  if (!HelperClass) {
291
301
  const helperResult = requireHelperFromModule(helperName, config)
292
302
  if (helperResult instanceof Promise) {
293
- // Handle async ESM loading
303
+ // Handle async ESM loading - create placeholder
294
304
  helpers[helperName] = {}
295
305
  asyncHelperPromise = asyncHelperPromise
296
306
  .then(() => helperResult)
@@ -309,8 +319,7 @@ async function createHelpers(config) {
309
319
 
310
320
  checkHelperRequirements(ResolvedHelperClass)
311
321
  helpers[helperName] = new ResolvedHelperClass(config[helperName])
312
- if (helpers[helperName]._init) await helpers[helperName]._init()
313
- debug(`helper ${helperName} async initialized`)
322
+ debug(`helper ${helperName} async loaded`)
314
323
  })
315
324
  continue
316
325
  } else {
@@ -330,9 +339,8 @@ async function createHelpers(config) {
330
339
  throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
331
340
  }
332
341
 
333
- debug(`helper ${helperName} async initialized`)
334
-
335
342
  helpers[helperName] = new ResolvedHelperClass(config[helperName])
343
+ debug(`helper ${helperName} async CJS loaded`)
336
344
  })
337
345
 
338
346
  continue
@@ -347,9 +355,18 @@ async function createHelpers(config) {
347
355
  }
348
356
  }
349
357
 
350
- for (const name in helpers) {
351
- if (helpers[name]._init) await helpers[name]._init()
352
- }
358
+ // Don't await here - let Container.create() handle the await
359
+ // This allows actor callbacks to be registered before resolution
360
+ asyncHelperPromise = asyncHelperPromise.then(async () => {
361
+ // Call _init on all helpers after they're all loaded
362
+ for (const name in helpers) {
363
+ if (helpers[name]._init) {
364
+ await helpers[name]._init()
365
+ debug(`helper ${name} _init() called`)
366
+ }
367
+ }
368
+ })
369
+
353
370
  return helpers
354
371
  }
355
372
 
@@ -381,20 +398,52 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
381
398
  throw err
382
399
  }
383
400
  } else {
401
+ // Handle TypeScript files
402
+ let importPath = moduleName
403
+ let tempJsFile = null
404
+ const ext = path.extname(moduleName)
405
+
406
+ if (ext === '.ts') {
407
+ try {
408
+ // Use the TypeScript transpilation utility
409
+ const typescript = await import('typescript')
410
+ const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
411
+
412
+ debug(`Transpiled TypeScript helper: ${importPath} -> ${tempFile}`)
413
+
414
+ importPath = tempFile
415
+ tempJsFile = allTempFiles
416
+ } catch (tsError) {
417
+ throw new Error(`Failed to load TypeScript helper ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
418
+ }
419
+ }
420
+
384
421
  // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
385
422
  try {
386
423
  // Try dynamic import for both CommonJS and ESM modules
387
- const mod = await import(moduleName)
424
+ const mod = await import(importPath)
388
425
  if (!mod && !mod.default) {
389
426
  throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
390
427
  }
391
428
  HelperClass = mod.default || mod
429
+
430
+ // Clean up temp files if created
431
+ if (tempJsFile) {
432
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
433
+ cleanupTempFiles(filesToClean)
434
+ }
392
435
  } catch (err) {
436
+ // Clean up temp files before rethrowing
437
+ if (tempJsFile) {
438
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
439
+ cleanupTempFiles(filesToClean)
440
+ }
441
+
393
442
  if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
394
443
  // This is an ESM module, use dynamic import
395
444
  try {
396
445
  const pathModule = await import('path')
397
- const absolutePath = pathModule.default.resolve(moduleName)
446
+ const absolutePath = pathModule.default.resolve(importPath)
398
447
  const mod = await import(absolutePath)
399
448
  HelperClass = mod.default || mod
400
449
  debug(`helper ${helperName} loaded via ESM import`)
@@ -523,10 +572,17 @@ function createSupportObjects(config) {
523
572
  return [...new Set([...keys, ...container.sharedKeys])]
524
573
  },
525
574
  getOwnPropertyDescriptor(target, prop) {
575
+ // For destructuring to work, we need to return the actual value from the getter
576
+ let value
577
+ if (container.sharedKeys.has(prop) && prop in container.support) {
578
+ value = container.support[prop]
579
+ } else {
580
+ value = lazyLoad(prop)
581
+ }
526
582
  return {
527
583
  enumerable: true,
528
584
  configurable: true,
529
- value: target[prop],
585
+ value: value,
530
586
  }
531
587
  },
532
588
  get(target, key) {
@@ -674,12 +730,50 @@ async function loadSupportObject(modulePath, supportObjectName) {
674
730
  try {
675
731
  // Use dynamic import for both ESM and CJS modules
676
732
  let importPath = modulePath
677
- // Append .js if no extension provided (ESM resolution requires it)
733
+ let tempJsFile = null
734
+
678
735
  if (typeof importPath === 'string') {
679
736
  const ext = path.extname(importPath)
680
- if (!ext) importPath = `${importPath}.js`
737
+
738
+ // Handle TypeScript files
739
+ if (ext === '.ts') {
740
+ try {
741
+ // Use the TypeScript transpilation utility
742
+ const typescript = await import('typescript')
743
+ const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
744
+
745
+ debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
746
+
747
+ // Attach cleanup handler
748
+ importPath = tempFile
749
+ // Store temp files list in a way that cleanup can access them
750
+ tempJsFile = allTempFiles
751
+ } catch (tsError) {
752
+ throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
753
+ }
754
+ } else if (!ext) {
755
+ // Append .js if no extension provided (ESM resolution requires it)
756
+ importPath = `${importPath}.js`
757
+ }
758
+ }
759
+
760
+ let obj
761
+ try {
762
+ obj = await import(importPath)
763
+ } catch (importError) {
764
+ // Clean up temp files if created before rethrowing
765
+ if (tempJsFile) {
766
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
767
+ cleanupTempFiles(filesToClean)
768
+ }
769
+ throw importError
770
+ } finally {
771
+ // Clean up temp files if created
772
+ if (tempJsFile) {
773
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
774
+ cleanupTempFiles(filesToClean)
775
+ }
681
776
  }
682
- const obj = await import(importPath)
683
777
 
684
778
  // Handle ESM module wrapper
685
779
  let actualObj = obj
@@ -45,6 +45,8 @@ class GraphQL extends Helper {
45
45
  timeout: 10000,
46
46
  defaultHeaders: {},
47
47
  endpoint: '',
48
+ onRequest: null,
49
+ onResponse: null,
48
50
  }
49
51
  this.options = Object.assign(this.options, config)
50
52
  this.headers = { ...this.options.defaultHeaders }
@@ -87,8 +89,8 @@ class GraphQL extends Helper {
87
89
 
88
90
  request.headers = { ...this.headers, ...request.headers }
89
91
 
90
- if (this.config.onRequest) {
91
- await this.config.onRequest(request)
92
+ if (this.options.onRequest) {
93
+ await this.options.onRequest(request)
92
94
  }
93
95
 
94
96
  this.debugSection('Request', JSON.stringify(request))
@@ -102,8 +104,8 @@ class GraphQL extends Helper {
102
104
  response = err.response
103
105
  }
104
106
 
105
- if (this.config.onResponse) {
106
- await this.config.onResponse(response)
107
+ if (this.options.onResponse) {
108
+ await this.options.onResponse(response)
107
109
  }
108
110
 
109
111
  this.debugSection('Response', JSON.stringify(response.data))
@@ -72,8 +72,8 @@ class JSONResponse extends Helper {
72
72
  if (!this.helpers[this.options.requestHelper]) {
73
73
  throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`)
74
74
  }
75
- const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse
76
- this.helpers[this.options.requestHelper].config.onResponse = response => {
75
+ const origOnResponse = this.helpers[this.options.requestHelper].options.onResponse
76
+ this.helpers[this.options.requestHelper].options.onResponse = response => {
77
77
  this.response = response
78
78
  if (typeof origOnResponse === 'function') origOnResponse(response)
79
79
  }
@@ -83,7 +83,6 @@ class JSONResponse extends Helper {
83
83
  this.response = null
84
84
  }
85
85
 
86
-
87
86
  /**
88
87
  * Checks that response code is equal to the provided one
89
88
  *
@@ -372,4 +371,4 @@ class JSONResponse extends Helper {
372
371
  }
373
372
  }
374
373
 
375
- export { JSONResponse as default }
374
+ export { JSONResponse, JSONResponse as default }
@@ -355,7 +355,7 @@ class Playwright extends Helper {
355
355
  this.recordingWebSocketMessages = false
356
356
  this.recordedWebSocketMessagesAtLeastOnce = false
357
357
  this.cdpSession = null
358
-
358
+
359
359
  // Filter out invalid customLocatorStrategies (empty arrays, objects without functions)
360
360
  // This can happen in worker threads where config is serialized/deserialized
361
361
  let validCustomLocators = null
@@ -367,7 +367,7 @@ class Playwright extends Helper {
367
367
  validCustomLocators = config.customLocatorStrategies
368
368
  }
369
369
  }
370
-
370
+
371
371
  this.customLocatorStrategies = validCustomLocators
372
372
  this._customLocatorsRegistered = false
373
373
 
@@ -416,6 +416,7 @@ class Playwright extends Helper {
416
416
  ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors,
417
417
  highlightElement: false,
418
418
  storageState: undefined,
419
+ onResponse: null,
419
420
  }
420
421
 
421
422
  process.env.testIdAttribute = 'data-testid'
@@ -794,10 +795,7 @@ class Playwright extends Helper {
794
795
  await Promise.allSettled(pages.map(p => p.close().catch(() => {})))
795
796
  }
796
797
  // Use timeout to prevent hanging (10s should be enough for browser cleanup)
797
- await Promise.race([
798
- this._stopBrowser(),
799
- new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout')), 10000)),
800
- ])
798
+ await Promise.race([this._stopBrowser(), new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout')), 10000))])
801
799
  } catch (e) {
802
800
  console.warn('Warning during browser restart in _after:', e.message)
803
801
  // Force cleanup even on timeout
@@ -840,10 +838,7 @@ class Playwright extends Helper {
840
838
  if (this.isRunning) {
841
839
  try {
842
840
  // Add timeout protection to prevent hanging
843
- await Promise.race([
844
- this._stopBrowser(),
845
- new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout in afterSuite')), 10000)),
846
- ])
841
+ await Promise.race([this._stopBrowser(), new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout in afterSuite')), 10000))])
847
842
  } catch (e) {
848
843
  console.warn('Warning during suite cleanup:', e.message)
849
844
  // Track suite cleanup failures
@@ -954,10 +949,7 @@ class Playwright extends Helper {
954
949
  if (this.isRunning) {
955
950
  try {
956
951
  // Add timeout protection to prevent hanging
957
- await Promise.race([
958
- this._stopBrowser(),
959
- new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout in cleanup')), 10000)),
960
- ])
952
+ await Promise.race([this._stopBrowser(), new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout in cleanup')), 10000))])
961
953
  } catch (e) {
962
954
  console.warn('Warning during final cleanup:', e.message)
963
955
  // Force cleanup on timeout
@@ -970,10 +962,7 @@ class Playwright extends Helper {
970
962
  if (this.browser) {
971
963
  try {
972
964
  // Add timeout protection to prevent hanging
973
- await Promise.race([
974
- this._stopBrowser(),
975
- new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout in forced cleanup')), 10000)),
976
- ])
965
+ await Promise.race([this._stopBrowser(), new Promise((_, reject) => setTimeout(() => reject(new Error('Browser stop timeout in forced cleanup')), 10000))])
977
966
  } catch (e) {
978
967
  console.warn('Warning during forced cleanup:', e.message)
979
968
  // Force cleanup on timeout
@@ -1390,7 +1379,7 @@ class Playwright extends Helper {
1390
1379
  this.context = null
1391
1380
  this.frame = null
1392
1381
  popupStore.clear()
1393
-
1382
+
1394
1383
  // Remove all event listeners to prevent hanging
1395
1384
  if (this.browser) {
1396
1385
  try {
@@ -1399,7 +1388,7 @@ class Playwright extends Helper {
1399
1388
  // Ignore errors if browser is already closed
1400
1389
  }
1401
1390
  }
1402
-
1391
+
1403
1392
  if (this.options.recordHar && this.browserContext) {
1404
1393
  try {
1405
1394
  await this.browserContext.close()
@@ -1408,16 +1397,11 @@ class Playwright extends Helper {
1408
1397
  }
1409
1398
  }
1410
1399
  this.browserContext = null
1411
-
1400
+
1412
1401
  if (this.browser) {
1413
1402
  try {
1414
1403
  // Add timeout to prevent browser.close() from hanging indefinitely
1415
- await Promise.race([
1416
- this.browser.close(),
1417
- new Promise((_, reject) =>
1418
- setTimeout(() => reject(new Error('Browser close timeout')), 5000)
1419
- )
1420
- ])
1404
+ await Promise.race([this.browser.close(), new Promise((_, reject) => setTimeout(() => reject(new Error('Browser close timeout')), 5000))])
1421
1405
  } catch (e) {
1422
1406
  // Ignore errors if browser is already closed or timeout
1423
1407
  if (!e.message?.includes('Browser close timeout')) {
@@ -1539,7 +1523,7 @@ class Playwright extends Helper {
1539
1523
  acceptDownloads: true,
1540
1524
  ...this.options.emulate,
1541
1525
  }
1542
-
1526
+
1543
1527
  try {
1544
1528
  this.browserContext = await this.browser.newContext(contextOptions)
1545
1529
  } catch (err) {
@@ -3183,14 +3167,14 @@ class Playwright extends Helper {
3183
3167
  this.debugSection('Response', await response.text())
3184
3168
 
3185
3169
  // hook to allow JSON response handle this
3186
- if (this.config.onResponse) {
3170
+ if (this.options.onResponse) {
3187
3171
  const axiosResponse = {
3188
3172
  data: await response.json(),
3189
3173
  status: response.status(),
3190
3174
  statusText: response.statusText(),
3191
3175
  headers: response.headers(),
3192
3176
  }
3193
- this.config.onResponse(axiosResponse)
3177
+ this.options.onResponse(axiosResponse)
3194
3178
  }
3195
3179
 
3196
3180
  return response
@@ -4337,11 +4321,11 @@ function isRoleLocatorObject(locator) {
4337
4321
  */
4338
4322
  async function handleRoleLocator(context, locator) {
4339
4323
  if (!isRoleLocatorObject(locator)) return null
4340
-
4324
+
4341
4325
  const options = {}
4342
4326
  if (locator.text) options.name = locator.text
4343
4327
  if (locator.exact !== undefined) options.exact = locator.exact
4344
-
4328
+
4345
4329
  return context.getByRole(locator.role, Object.keys(options).length > 0 ? options : undefined).all()
4346
4330
  }
4347
4331
 
@@ -4350,7 +4334,7 @@ async function findElements(matcher, locator) {
4350
4334
  const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
4351
4335
  const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
4352
4336
  const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
4353
-
4337
+
4354
4338
  if (isReactLocator) return findReact(matcher, locator)
4355
4339
  if (isVueLocator) return findVue(matcher, locator)
4356
4340
  if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
@@ -4391,7 +4375,7 @@ async function findCustomElements(matcher, locator) {
4391
4375
  // Always prioritize this.customLocatorStrategies which is set in constructor from config
4392
4376
  // and persists in every worker thread instance
4393
4377
  let strategyFunction = null
4394
-
4378
+
4395
4379
  if (this.customLocatorStrategies && this.customLocatorStrategies[locator.type]) {
4396
4380
  strategyFunction = this.customLocatorStrategies[locator.type]
4397
4381
  } else if (globalCustomLocatorStrategies.has(locator.type)) {
@@ -4967,7 +4951,7 @@ async function refreshContextSession() {
4967
4951
  this.debugSection('Session', 'Skipping storage cleanup - no active page/context')
4968
4952
  return
4969
4953
  }
4970
-
4954
+
4971
4955
  const currentUrl = await this.grabCurrentUrl()
4972
4956
 
4973
4957
  if (currentUrl.startsWith('http')) {