codeceptjs 4.0.1-beta.2 → 4.0.1-beta.20
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/bin/codecept.js +2 -2
- package/lib/actor.js +12 -8
- package/lib/command/definitions.js +8 -3
- package/lib/container.js +65 -17
- package/lib/helper/GraphQL.js +6 -4
- package/lib/helper/JSONResponse.js +3 -4
- package/lib/helper/Playwright.js +97 -113
- package/lib/helper/REST.js +13 -8
- package/lib/helper/extras/PlaywrightLocator.js +13 -34
- package/lib/listener/config.js +11 -3
- package/lib/locator.js +69 -29
- package/lib/mocha/factory.js +2 -27
- package/lib/step/meta.js +18 -1
- package/lib/utils/loaderCheck.js +13 -3
- package/lib/utils/typescript.js +22 -2
- package/lib/workers.js +36 -41
- package/package.json +16 -16
- package/typings/promiseBasedTypes.d.ts +3974 -5520
- package/typings/types.d.ts +4146 -5821
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
package/lib/helper/Playwright.js
CHANGED
|
@@ -30,7 +30,7 @@ import ElementNotFound from './errors/ElementNotFound.js'
|
|
|
30
30
|
import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
|
|
31
31
|
import Popup from './extras/Popup.js'
|
|
32
32
|
import Console from './extras/Console.js'
|
|
33
|
-
import { findReact, findVue
|
|
33
|
+
import { findReact, findVue } from './extras/PlaywrightLocator.js'
|
|
34
34
|
import WebElement from '../element/WebElement.js'
|
|
35
35
|
|
|
36
36
|
let playwright
|
|
@@ -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,8 @@ class Playwright extends Helper {
|
|
|
1399
1388
|
// Ignore errors if browser is already closed
|
|
1400
1389
|
}
|
|
1401
1390
|
}
|
|
1402
|
-
|
|
1391
|
+
|
|
1392
|
+
// Close browserContext if recordHar is enabled
|
|
1403
1393
|
if (this.options.recordHar && this.browserContext) {
|
|
1404
1394
|
try {
|
|
1405
1395
|
await this.browserContext.close()
|
|
@@ -1408,22 +1398,17 @@ class Playwright extends Helper {
|
|
|
1408
1398
|
}
|
|
1409
1399
|
}
|
|
1410
1400
|
this.browserContext = null
|
|
1411
|
-
|
|
1401
|
+
|
|
1402
|
+
// Initiate browser close without waiting for it to complete
|
|
1403
|
+
// The browser process will be cleaned up when the Node process exits
|
|
1412
1404
|
if (this.browser) {
|
|
1413
1405
|
try {
|
|
1414
|
-
//
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
setTimeout(() => reject(new Error('Browser close timeout')), 5000)
|
|
1419
|
-
)
|
|
1420
|
-
])
|
|
1406
|
+
// Fire and forget - don't wait for close to complete
|
|
1407
|
+
this.browser.close().catch(() => {
|
|
1408
|
+
// Silently ignore any errors during async close
|
|
1409
|
+
})
|
|
1421
1410
|
} catch (e) {
|
|
1422
|
-
// Ignore
|
|
1423
|
-
if (!e.message?.includes('Browser close timeout')) {
|
|
1424
|
-
// Non-timeout error, can be ignored as well
|
|
1425
|
-
}
|
|
1426
|
-
// Force cleanup even on error
|
|
1411
|
+
// Ignore any synchronous errors
|
|
1427
1412
|
}
|
|
1428
1413
|
}
|
|
1429
1414
|
this.browser = null
|
|
@@ -1539,7 +1524,7 @@ class Playwright extends Helper {
|
|
|
1539
1524
|
acceptDownloads: true,
|
|
1540
1525
|
...this.options.emulate,
|
|
1541
1526
|
}
|
|
1542
|
-
|
|
1527
|
+
|
|
1543
1528
|
try {
|
|
1544
1529
|
this.browserContext = await this.browser.newContext(contextOptions)
|
|
1545
1530
|
} catch (err) {
|
|
@@ -2504,7 +2489,19 @@ class Playwright extends Helper {
|
|
|
2504
2489
|
* {{> selectOption }}
|
|
2505
2490
|
*/
|
|
2506
2491
|
async selectOption(select, option) {
|
|
2507
|
-
const
|
|
2492
|
+
const selectLocator = Locator.from(select, 'css')
|
|
2493
|
+
let els = null
|
|
2494
|
+
|
|
2495
|
+
if (selectLocator.isFuzzy()) {
|
|
2496
|
+
els = await findByRole(this.page, { role: 'listbox', name: selectLocator.value })
|
|
2497
|
+
if (!els || els.length === 0) {
|
|
2498
|
+
els = await findByRole(this.page, { role: 'combobox', name: selectLocator.value })
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
if (!els || els.length === 0) {
|
|
2503
|
+
els = await findFields.call(this, select)
|
|
2504
|
+
}
|
|
2508
2505
|
assertElementExists(els, select, 'Selectable field')
|
|
2509
2506
|
const el = els[0]
|
|
2510
2507
|
|
|
@@ -2805,17 +2802,6 @@ class Playwright extends Helper {
|
|
|
2805
2802
|
*
|
|
2806
2803
|
*/
|
|
2807
2804
|
async grabTextFrom(locator) {
|
|
2808
|
-
// Handle role locators with text/exact options
|
|
2809
|
-
if (isRoleLocatorObject(locator)) {
|
|
2810
|
-
const elements = await handleRoleLocator(this.page, locator)
|
|
2811
|
-
if (elements && elements.length > 0) {
|
|
2812
|
-
const text = await elements[0].textContent()
|
|
2813
|
-
assertElementExists(text, JSON.stringify(locator))
|
|
2814
|
-
this.debugSection('Text', text)
|
|
2815
|
-
return text
|
|
2816
|
-
}
|
|
2817
|
-
}
|
|
2818
|
-
|
|
2819
2805
|
const locatorObj = new Locator(locator, 'css')
|
|
2820
2806
|
|
|
2821
2807
|
if (locatorObj.isCustom()) {
|
|
@@ -2828,21 +2814,32 @@ class Playwright extends Helper {
|
|
|
2828
2814
|
assertElementExists(text, locatorObj.toString())
|
|
2829
2815
|
this.debugSection('Text', text)
|
|
2830
2816
|
return text
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
if (locatorObj.isRole()) {
|
|
2820
|
+
// Handle role locators with text/exact options
|
|
2821
|
+
const roleElements = await findByRole(this.page, locator)
|
|
2822
|
+
if (roleElements && roleElements.length > 0) {
|
|
2823
|
+
const text = await roleElements[0].textContent()
|
|
2824
|
+
assertElementExists(text, JSON.stringify(locator))
|
|
2836
2825
|
this.debugSection('Text', text)
|
|
2837
2826
|
return text
|
|
2838
|
-
} catch (error) {
|
|
2839
|
-
// Convert Playwright timeout errors to ElementNotFound for consistency
|
|
2840
|
-
if (error.message && error.message.includes('Timeout')) {
|
|
2841
|
-
throw new ElementNotFound(locator, 'text')
|
|
2842
|
-
}
|
|
2843
|
-
throw error
|
|
2844
2827
|
}
|
|
2845
2828
|
}
|
|
2829
|
+
|
|
2830
|
+
locator = this._contextLocator(locator)
|
|
2831
|
+
try {
|
|
2832
|
+
const text = await this.page.textContent(locator)
|
|
2833
|
+
assertElementExists(text, locator)
|
|
2834
|
+
this.debugSection('Text', text)
|
|
2835
|
+
return text
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
// Convert Playwright timeout errors to ElementNotFound for consistency
|
|
2838
|
+
if (error.message && error.message.includes('Timeout')) {
|
|
2839
|
+
throw new ElementNotFound(locator, 'text')
|
|
2840
|
+
}
|
|
2841
|
+
throw error
|
|
2842
|
+
}
|
|
2846
2843
|
}
|
|
2847
2844
|
|
|
2848
2845
|
/**
|
|
@@ -3183,14 +3180,14 @@ class Playwright extends Helper {
|
|
|
3183
3180
|
this.debugSection('Response', await response.text())
|
|
3184
3181
|
|
|
3185
3182
|
// hook to allow JSON response handle this
|
|
3186
|
-
if (this.
|
|
3183
|
+
if (this.options.onResponse) {
|
|
3187
3184
|
const axiosResponse = {
|
|
3188
3185
|
data: await response.json(),
|
|
3189
3186
|
status: response.status(),
|
|
3190
3187
|
statusText: response.statusText(),
|
|
3191
3188
|
headers: response.headers(),
|
|
3192
3189
|
}
|
|
3193
|
-
this.
|
|
3190
|
+
this.options.onResponse(axiosResponse)
|
|
3194
3191
|
}
|
|
3195
3192
|
|
|
3196
3193
|
return response
|
|
@@ -4321,50 +4318,26 @@ function buildLocatorString(locator) {
|
|
|
4321
4318
|
if (locator.isXPath()) {
|
|
4322
4319
|
return `xpath=${locator.value}`
|
|
4323
4320
|
}
|
|
4324
|
-
|
|
4321
|
+
return locator.simplify()
|
|
4325
4322
|
}
|
|
4326
4323
|
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
return
|
|
4332
|
-
}
|
|
4333
|
-
|
|
4334
|
-
/**
|
|
4335
|
-
* Handles role locator objects by converting them to Playwright's getByRole() API
|
|
4336
|
-
* Returns elements array if role locator, null otherwise
|
|
4337
|
-
*/
|
|
4338
|
-
async function handleRoleLocator(context, locator) {
|
|
4339
|
-
if (!isRoleLocatorObject(locator)) return null
|
|
4340
|
-
|
|
4341
|
-
const options = {}
|
|
4342
|
-
if (locator.text) options.name = locator.text
|
|
4343
|
-
if (locator.exact !== undefined) options.exact = locator.exact
|
|
4344
|
-
|
|
4345
|
-
return context.getByRole(locator.role, Object.keys(options).length > 0 ? options : undefined).all()
|
|
4324
|
+
async function findByRole(context, locator) {
|
|
4325
|
+
const matchedLocator = Locator.from(locator)
|
|
4326
|
+
if (!matchedLocator.isRole()) return null
|
|
4327
|
+
const roleOptions = matchedLocator.getRoleOptions()
|
|
4328
|
+
return context.getByRole(roleOptions.role, roleOptions.options).all()
|
|
4346
4329
|
}
|
|
4347
4330
|
|
|
4348
4331
|
async function findElements(matcher, locator) {
|
|
4349
|
-
|
|
4350
|
-
const
|
|
4351
|
-
const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
|
|
4352
|
-
const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
|
|
4353
|
-
|
|
4354
|
-
if (isReactLocator) return findReact(matcher, locator)
|
|
4355
|
-
if (isVueLocator) return findVue(matcher, locator)
|
|
4356
|
-
if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4357
|
-
|
|
4358
|
-
// Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
4359
|
-
const roleElements = await handleRoleLocator(matcher, locator)
|
|
4332
|
+
const matchedLocator = Locator.from(locator)
|
|
4333
|
+
const roleElements = await findByRole(matcher, matchedLocator)
|
|
4360
4334
|
if (roleElements) return roleElements
|
|
4361
4335
|
|
|
4362
|
-
|
|
4336
|
+
const isReactLocator = matchedLocator.type === 'react'
|
|
4337
|
+
const isVueLocator = matchedLocator.type === 'vue'
|
|
4363
4338
|
|
|
4364
|
-
|
|
4365
|
-
if (
|
|
4366
|
-
return findCustomElements.call(this, matcher, locator)
|
|
4367
|
-
}
|
|
4339
|
+
if (isReactLocator) return findReact(matcher, matchedLocator)
|
|
4340
|
+
if (isVueLocator) return findVue(matcher, matchedLocator)
|
|
4368
4341
|
|
|
4369
4342
|
// Check if we have a custom context locator and need to search within it
|
|
4370
4343
|
if (this.contextLocator) {
|
|
@@ -4377,12 +4350,12 @@ async function findElements(matcher, locator) {
|
|
|
4377
4350
|
}
|
|
4378
4351
|
|
|
4379
4352
|
// Search within the first context element
|
|
4380
|
-
const locatorString = buildLocatorString(
|
|
4353
|
+
const locatorString = buildLocatorString(matchedLocator)
|
|
4381
4354
|
return contextElements[0].locator(locatorString).all()
|
|
4382
4355
|
}
|
|
4383
4356
|
}
|
|
4384
4357
|
|
|
4385
|
-
const locatorString = buildLocatorString(
|
|
4358
|
+
const locatorString = buildLocatorString(matchedLocator)
|
|
4386
4359
|
|
|
4387
4360
|
return matcher.locator(locatorString).all()
|
|
4388
4361
|
}
|
|
@@ -4391,7 +4364,7 @@ async function findCustomElements(matcher, locator) {
|
|
|
4391
4364
|
// Always prioritize this.customLocatorStrategies which is set in constructor from config
|
|
4392
4365
|
// and persists in every worker thread instance
|
|
4393
4366
|
let strategyFunction = null
|
|
4394
|
-
|
|
4367
|
+
|
|
4395
4368
|
if (this.customLocatorStrategies && this.customLocatorStrategies[locator.type]) {
|
|
4396
4369
|
strategyFunction = this.customLocatorStrategies[locator.type]
|
|
4397
4370
|
} else if (globalCustomLocatorStrategies.has(locator.type)) {
|
|
@@ -4475,13 +4448,17 @@ async function findCustomElements(matcher, locator) {
|
|
|
4475
4448
|
}
|
|
4476
4449
|
|
|
4477
4450
|
async function findElement(matcher, locator) {
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
if (
|
|
4451
|
+
const matchedLocator = Locator.from(locator)
|
|
4452
|
+
const roleElements = await findByRole(matcher, matchedLocator)
|
|
4453
|
+
if (roleElements && roleElements.length > 0) return roleElements[0]
|
|
4454
|
+
|
|
4455
|
+
const isReactLocator = matchedLocator.type === 'react'
|
|
4456
|
+
const isVueLocator = matchedLocator.type === 'vue'
|
|
4481
4457
|
|
|
4482
|
-
|
|
4458
|
+
if (isReactLocator) return findReact(matcher, matchedLocator)
|
|
4459
|
+
if (isVueLocator) return findVue(matcher, matchedLocator)
|
|
4483
4460
|
|
|
4484
|
-
return matcher.locator(buildLocatorString(
|
|
4461
|
+
return matcher.locator(buildLocatorString(matchedLocator)).first()
|
|
4485
4462
|
}
|
|
4486
4463
|
|
|
4487
4464
|
async function getVisibleElements(elements) {
|
|
@@ -4533,8 +4510,14 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
4533
4510
|
}
|
|
4534
4511
|
|
|
4535
4512
|
async function findClickable(matcher, locator) {
|
|
4513
|
+
// Convert to Locator first to handle JSON strings properly
|
|
4536
4514
|
const matchedLocator = new Locator(locator)
|
|
4537
4515
|
|
|
4516
|
+
// Handle role locators from Locator
|
|
4517
|
+
if (matchedLocator.isRole()) {
|
|
4518
|
+
return findByRole(matcher, matchedLocator)
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4538
4521
|
if (!matchedLocator.isFuzzy()) return findElements.call(this, matcher, matchedLocator)
|
|
4539
4522
|
|
|
4540
4523
|
let els
|
|
@@ -4607,14 +4590,17 @@ async function findCheckable(locator, context) {
|
|
|
4607
4590
|
}
|
|
4608
4591
|
|
|
4609
4592
|
// Handle role locators with text/exact options
|
|
4610
|
-
const roleElements = await
|
|
4593
|
+
const roleElements = await findByRole(contextEl, locator)
|
|
4611
4594
|
if (roleElements) return roleElements
|
|
4612
4595
|
|
|
4613
|
-
const matchedLocator =
|
|
4596
|
+
const matchedLocator = Locator.from(locator)
|
|
4614
4597
|
if (!matchedLocator.isFuzzy()) {
|
|
4615
4598
|
return findElements.call(this, contextEl, matchedLocator)
|
|
4616
4599
|
}
|
|
4617
4600
|
|
|
4601
|
+
const checkboxByRole = await findByRole(contextEl, { role: 'checkbox', name: matchedLocator.value })
|
|
4602
|
+
if (checkboxByRole) return checkboxByRole
|
|
4603
|
+
|
|
4618
4604
|
const literal = xpathLocator.literal(matchedLocator.value)
|
|
4619
4605
|
let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
|
|
4620
4606
|
if (els.length) {
|
|
@@ -4636,17 +4622,15 @@ async function proceedIsChecked(assertType, option) {
|
|
|
4636
4622
|
}
|
|
4637
4623
|
|
|
4638
4624
|
async function findFields(locator) {
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
const roleElements = await handleRoleLocator(page, locator)
|
|
4643
|
-
if (roleElements) return roleElements
|
|
4644
|
-
}
|
|
4625
|
+
const page = await this.page
|
|
4626
|
+
const roleElements = await findByRole(page, locator)
|
|
4627
|
+
if (roleElements) return roleElements
|
|
4645
4628
|
|
|
4646
4629
|
const matchedLocator = new Locator(locator)
|
|
4647
4630
|
if (!matchedLocator.isFuzzy()) {
|
|
4648
4631
|
return this._locate(matchedLocator)
|
|
4649
4632
|
}
|
|
4633
|
+
|
|
4650
4634
|
const literal = xpathLocator.literal(locator)
|
|
4651
4635
|
|
|
4652
4636
|
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
|
|
@@ -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')) {
|
package/lib/helper/REST.js
CHANGED
|
@@ -94,7 +94,9 @@ const config = {}
|
|
|
94
94
|
class REST extends Helper {
|
|
95
95
|
constructor(config) {
|
|
96
96
|
super(config)
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
// Set defaults first
|
|
99
|
+
const defaults = {
|
|
98
100
|
timeout: 10000,
|
|
99
101
|
defaultHeaders: {},
|
|
100
102
|
endpoint: '',
|
|
@@ -103,15 +105,16 @@ class REST extends Helper {
|
|
|
103
105
|
onResponse: null,
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
// Merge config with defaults
|
|
109
|
+
this._setConfig(config)
|
|
110
|
+
this.options = { ...defaults, ...this.options }
|
|
111
|
+
|
|
106
112
|
if (this.options.maxContentLength) {
|
|
107
113
|
const maxContentLength = this.options.maxUploadFileSize * 1024 * 1024
|
|
108
114
|
this.options.maxContentLength = maxContentLength
|
|
109
115
|
this.options.maxBodyLength = maxContentLength
|
|
110
116
|
}
|
|
111
117
|
|
|
112
|
-
// override defaults with config
|
|
113
|
-
this._setConfig(config)
|
|
114
|
-
|
|
115
118
|
this.headers = { ...this.options.defaultHeaders }
|
|
116
119
|
|
|
117
120
|
// Create an agent with SSL certificate
|
|
@@ -215,8 +218,9 @@ class REST extends Helper {
|
|
|
215
218
|
}
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
const onRequest = this.options.onRequest || this.config.onRequest
|
|
222
|
+
if (onRequest) {
|
|
223
|
+
await onRequest(request)
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
try {
|
|
@@ -245,8 +249,9 @@ class REST extends Helper {
|
|
|
245
249
|
}
|
|
246
250
|
response = err.response
|
|
247
251
|
}
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
const onResponse = this.options.onResponse || this.config.onResponse
|
|
253
|
+
if (onResponse) {
|
|
254
|
+
await onResponse(response)
|
|
250
255
|
}
|
|
251
256
|
try {
|
|
252
257
|
this.options.prettyPrintJson ? this.debugSection('Response', beautify(JSON.stringify(response.data))) : this.debugSection('Response', JSON.stringify(response.data))
|
|
@@ -11,22 +11,20 @@ function buildLocatorString(locator) {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async function findElements(matcher, locator) {
|
|
14
|
-
const matchedLocator =
|
|
14
|
+
const matchedLocator = Locator.from(locator, 'css')
|
|
15
15
|
|
|
16
16
|
if (matchedLocator.type === 'react') return findReact(matcher, matchedLocator)
|
|
17
17
|
if (matchedLocator.type === 'vue') return findVue(matcher, matchedLocator)
|
|
18
|
-
if (matchedLocator.type === 'pw') return findByPlaywrightLocator(matcher, matchedLocator)
|
|
19
18
|
if (matchedLocator.isRole()) return findByRole(matcher, matchedLocator)
|
|
20
19
|
|
|
21
20
|
return matcher.locator(buildLocatorString(matchedLocator)).all()
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
async function findElement(matcher, locator) {
|
|
25
|
-
const matchedLocator =
|
|
24
|
+
const matchedLocator = Locator.from(locator, 'css')
|
|
26
25
|
|
|
27
26
|
if (matchedLocator.type === 'react') return findReact(matcher, matchedLocator)
|
|
28
27
|
if (matchedLocator.type === 'vue') return findVue(matcher, matchedLocator)
|
|
29
|
-
if (matchedLocator.type === 'pw') return findByPlaywrightLocator(matcher, matchedLocator, { first: true })
|
|
30
28
|
if (matchedLocator.isRole()) return findByRole(matcher, matchedLocator, { first: true })
|
|
31
29
|
|
|
32
30
|
return matcher.locator(buildLocatorString(matchedLocator)).first()
|
|
@@ -46,49 +44,30 @@ async function getVisibleElements(elements) {
|
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
async function findReact(matcher, locator) {
|
|
49
|
-
const
|
|
50
|
-
let locatorString = `_react=${
|
|
47
|
+
const props = locator.locator?.props
|
|
48
|
+
let locatorString = `_react=${locator.value}`
|
|
51
49
|
|
|
52
|
-
if (
|
|
53
|
-
locatorString += propBuilder(
|
|
50
|
+
if (props) {
|
|
51
|
+
locatorString += propBuilder(props)
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
return matcher.locator(locatorString).all()
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
async function findVue(matcher, locator) {
|
|
60
|
-
const
|
|
61
|
-
let locatorString = `_vue=${
|
|
58
|
+
const props = locator.locator?.props
|
|
59
|
+
let locatorString = `_vue=${locator.value}`
|
|
62
60
|
|
|
63
|
-
if (
|
|
64
|
-
locatorString += propBuilder(
|
|
61
|
+
if (props) {
|
|
62
|
+
locatorString += propBuilder(props)
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
return matcher.locator(locatorString).all()
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
async function findByPlaywrightLocator(matcher, locator, { first = false } = {}) {
|
|
71
|
-
const details = locator.locator ?? { pw: locator.value }
|
|
72
|
-
const locatorValue = details.pw
|
|
73
|
-
|
|
74
|
-
const handle = matcher.locator(locatorValue)
|
|
75
|
-
return first ? handle.first() : handle.all()
|
|
76
|
-
}
|
|
77
|
-
|
|
78
68
|
async function findByRole(matcher, locator, { first = false } = {}) {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const options = { ...rest }
|
|
82
|
-
|
|
83
|
-
if (includeHidden !== undefined) options.includeHidden = includeHidden
|
|
84
|
-
|
|
85
|
-
const accessibleName = name ?? text
|
|
86
|
-
if (accessibleName !== undefined) {
|
|
87
|
-
options.name = accessibleName
|
|
88
|
-
if (exact === true) options.exact = true
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const roleLocator = matcher.getByRole(role, options)
|
|
69
|
+
const roleOptions = locator.getRoleOptions()
|
|
70
|
+
const roleLocator = matcher.getByRole(roleOptions.role, roleOptions.options)
|
|
92
71
|
return first ? roleLocator.first() : roleLocator.all()
|
|
93
72
|
}
|
|
94
73
|
|
|
@@ -107,4 +86,4 @@ function propBuilder(props) {
|
|
|
107
86
|
return _props
|
|
108
87
|
}
|
|
109
88
|
|
|
110
|
-
export { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue,
|
|
89
|
+
export { buildLocatorString, findElements, findElement, getVisibleElements, findReact, findVue, findByRole }
|
package/lib/listener/config.js
CHANGED
|
@@ -12,15 +12,23 @@ export default function () {
|
|
|
12
12
|
return
|
|
13
13
|
}
|
|
14
14
|
global.__codeceptConfigListenerInitialized = true
|
|
15
|
-
|
|
16
|
-
const helpers = global.container.helpers()
|
|
17
15
|
|
|
18
16
|
enableDynamicConfigFor('suite')
|
|
19
17
|
enableDynamicConfigFor('test')
|
|
20
18
|
|
|
21
19
|
function enableDynamicConfigFor(type) {
|
|
22
20
|
event.dispatcher.on(event[type].before, (context = {}) => {
|
|
21
|
+
// Get helpers dynamically at runtime, not at initialization time
|
|
22
|
+
// This ensures we get the actual helper instances, not placeholders
|
|
23
|
+
const helpers = global.container.helpers()
|
|
24
|
+
|
|
23
25
|
function updateHelperConfig(helper, config) {
|
|
26
|
+
// Guard against undefined or invalid helpers
|
|
27
|
+
if (!helper || !helper.constructor) {
|
|
28
|
+
output.debug(`[${ucfirst(type)} Config] Helper not found or not properly initialized`)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
const oldConfig = deepClone(helper.options)
|
|
25
33
|
try {
|
|
26
34
|
helper._setConfig(deepMerge(deepClone(oldConfig), config))
|
|
@@ -41,7 +49,7 @@ export default function () {
|
|
|
41
49
|
for (let name in context.config) {
|
|
42
50
|
const config = context.config[name]
|
|
43
51
|
if (name === '0') {
|
|
44
|
-
// first helper
|
|
52
|
+
// first helper - get dynamically
|
|
45
53
|
name = Object.keys(helpers)[0]
|
|
46
54
|
}
|
|
47
55
|
const helper = helpers[name]
|