codeceptjs 4.0.1-beta.23 → 4.0.1-beta.26
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 +1 -2
- package/lib/command/{shell.js → interactive.js} +3 -31
- package/lib/config.js +3 -2
- package/lib/container.js +17 -3
- package/lib/helper/Playwright.js +176 -232
- package/lib/helper/Puppeteer.js +33 -111
- package/lib/helper/WebDriver.js +22 -103
- package/lib/helper/extras/PlaywrightLocator.js +34 -13
- package/lib/helper/extras/PlaywrightReactVueLocator.js +52 -0
- package/lib/locator.js +31 -88
- package/lib/mocha/test.js +4 -2
- package/lib/output.js +2 -2
- package/lib/utils/typescript.js +61 -34
- package/package.json +8 -8
- package/typings/index.d.ts +1 -1
- package/typings/promiseBasedTypes.d.ts +5475 -3929
- package/typings/types.d.ts +5767 -4092
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 } from './extras/
|
|
33
|
+
import { findReact, findVue, findByPlaywrightLocator } from './extras/PlaywrightReactVueLocator.js'
|
|
34
34
|
import WebElement from '../element/WebElement.js'
|
|
35
35
|
|
|
36
36
|
let playwright
|
|
@@ -44,6 +44,36 @@ if (typeof global.__playwrightSelectorsRegistered === 'undefined') {
|
|
|
44
44
|
global.__playwrightSelectorsRegistered = false
|
|
45
45
|
}
|
|
46
46
|
|
|
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
|
+
|
|
47
77
|
const popupStore = new Popup()
|
|
48
78
|
const consoleLogStore = new Console()
|
|
49
79
|
const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron']
|
|
@@ -358,23 +388,13 @@ class Playwright extends Helper {
|
|
|
358
388
|
|
|
359
389
|
// Filter out invalid customLocatorStrategies (empty arrays, objects without functions)
|
|
360
390
|
// This can happen in worker threads where config is serialized/deserialized
|
|
361
|
-
|
|
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)
|
|
372
392
|
this._customLocatorsRegistered = false
|
|
373
393
|
|
|
374
394
|
// Add custom locator strategies to global registry for early registration
|
|
375
395
|
if (this.customLocatorStrategies) {
|
|
376
|
-
for (const [
|
|
377
|
-
globalCustomLocatorStrategies.set(
|
|
396
|
+
for (const [name, func] of Object.entries(this.customLocatorStrategies)) {
|
|
397
|
+
globalCustomLocatorStrategies.set(name, func)
|
|
378
398
|
}
|
|
379
399
|
}
|
|
380
400
|
|
|
@@ -565,54 +585,23 @@ class Playwright extends Helper {
|
|
|
565
585
|
}
|
|
566
586
|
|
|
567
587
|
// Register all custom locator strategies from the global registry
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
+
}
|
|
602
593
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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}`)
|
|
612
603
|
}
|
|
613
604
|
}
|
|
614
|
-
} catch (e) {
|
|
615
|
-
console.warn(e)
|
|
616
605
|
}
|
|
617
606
|
}
|
|
618
607
|
|
|
@@ -1277,28 +1266,31 @@ class Playwright extends Helper {
|
|
|
1277
1266
|
return this.browser
|
|
1278
1267
|
}
|
|
1279
1268
|
|
|
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
|
+
|
|
1280
1279
|
_lookupCustomLocator(customStrategy) {
|
|
1281
|
-
if (
|
|
1282
|
-
return null
|
|
1283
|
-
}
|
|
1280
|
+
if (!this._hasCustomLocatorStrategies()) return null
|
|
1284
1281
|
const strategy = this.customLocatorStrategies[customStrategy]
|
|
1285
1282
|
return typeof strategy === 'function' ? strategy : null
|
|
1286
1283
|
}
|
|
1287
1284
|
|
|
1288
1285
|
_isCustomLocator(locator) {
|
|
1289
1286
|
const locatorObj = new Locator(locator)
|
|
1290
|
-
if (locatorObj.isCustom())
|
|
1291
|
-
|
|
1292
|
-
|
|
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".')
|
|
1298
1290
|
}
|
|
1299
1291
|
|
|
1300
1292
|
_isCustomLocatorStrategyDefined() {
|
|
1301
|
-
return
|
|
1293
|
+
return this._hasCustomLocatorStrategies()
|
|
1302
1294
|
}
|
|
1303
1295
|
|
|
1304
1296
|
/**
|
|
@@ -1321,49 +1313,16 @@ class Playwright extends Helper {
|
|
|
1321
1313
|
}
|
|
1322
1314
|
|
|
1323
1315
|
async _registerCustomLocatorStrategies() {
|
|
1324
|
-
if (!this.
|
|
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
|
|
1358
1317
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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}`)
|
|
1367
1326
|
}
|
|
1368
1327
|
}
|
|
1369
1328
|
}
|
|
@@ -2489,30 +2448,23 @@ class Playwright extends Helper {
|
|
|
2489
2448
|
* {{> selectOption }}
|
|
2490
2449
|
*/
|
|
2491
2450
|
async selectOption(select, option) {
|
|
2492
|
-
const
|
|
2493
|
-
|
|
2451
|
+
const els = await findFields.call(this, select)
|
|
2452
|
+
assertElementExists(els, select, 'Selectable field')
|
|
2453
|
+
const el = els[0]
|
|
2494
2454
|
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
|
|
2498
|
-
const els = await this._locate(matchedLocator)
|
|
2499
|
-
assertElementExists(els, select, 'Selectable element')
|
|
2500
|
-
return proceedSelect.call(this, context, els[0], option)
|
|
2501
|
-
}
|
|
2455
|
+
await highlightActiveElement.call(this, el)
|
|
2456
|
+
let optionToSelect = ''
|
|
2502
2457
|
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2458
|
+
try {
|
|
2459
|
+
optionToSelect = (await el.locator('option', { hasText: option }).textContent()).trim()
|
|
2460
|
+
} catch (e) {
|
|
2461
|
+
optionToSelect = option
|
|
2462
|
+
}
|
|
2507
2463
|
|
|
2508
|
-
|
|
2509
|
-
els = await findByRole(context, { role: 'listbox', name: matchedLocator.value })
|
|
2510
|
-
if (els?.length) return proceedSelect.call(this, context, els[0], option)
|
|
2464
|
+
if (!Array.isArray(option)) option = [optionToSelect]
|
|
2511
2465
|
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
assertElementExists(els, select, 'Selectable element')
|
|
2515
|
-
return proceedSelect.call(this, context, els[0], option)
|
|
2466
|
+
await el.selectOption(option)
|
|
2467
|
+
return this._waitForAction()
|
|
2516
2468
|
}
|
|
2517
2469
|
|
|
2518
2470
|
/**
|
|
@@ -2797,6 +2749,17 @@ class Playwright extends Helper {
|
|
|
2797
2749
|
*
|
|
2798
2750
|
*/
|
|
2799
2751
|
async grabTextFrom(locator) {
|
|
2752
|
+
// Handle role locators with text/exact options
|
|
2753
|
+
if (isRoleLocatorObject(locator)) {
|
|
2754
|
+
const elements = await handleRoleLocator(this.page, locator)
|
|
2755
|
+
if (elements && elements.length > 0) {
|
|
2756
|
+
const text = await elements[0].textContent()
|
|
2757
|
+
assertElementExists(text, JSON.stringify(locator))
|
|
2758
|
+
this.debugSection('Text', text)
|
|
2759
|
+
return text
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
|
|
2800
2763
|
const locatorObj = new Locator(locator, 'css')
|
|
2801
2764
|
|
|
2802
2765
|
if (locatorObj.isCustom()) {
|
|
@@ -2809,32 +2772,21 @@ class Playwright extends Helper {
|
|
|
2809
2772
|
assertElementExists(text, locatorObj.toString())
|
|
2810
2773
|
this.debugSection('Text', text)
|
|
2811
2774
|
return text
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
if (roleElements && roleElements.length > 0) {
|
|
2818
|
-
const text = await roleElements[0].textContent()
|
|
2819
|
-
assertElementExists(text, JSON.stringify(locator))
|
|
2775
|
+
} else {
|
|
2776
|
+
locator = this._contextLocator(locator)
|
|
2777
|
+
try {
|
|
2778
|
+
const text = await this.page.textContent(locator)
|
|
2779
|
+
assertElementExists(text, locator)
|
|
2820
2780
|
this.debugSection('Text', text)
|
|
2821
2781
|
return text
|
|
2782
|
+
} catch (error) {
|
|
2783
|
+
// Convert Playwright timeout errors to ElementNotFound for consistency
|
|
2784
|
+
if (error.message && error.message.includes('Timeout')) {
|
|
2785
|
+
throw new ElementNotFound(locator, 'text')
|
|
2786
|
+
}
|
|
2787
|
+
throw error
|
|
2822
2788
|
}
|
|
2823
2789
|
}
|
|
2824
|
-
|
|
2825
|
-
locator = this._contextLocator(locator)
|
|
2826
|
-
try {
|
|
2827
|
-
const text = await this.page.textContent(locator)
|
|
2828
|
-
assertElementExists(text, locator)
|
|
2829
|
-
this.debugSection('Text', text)
|
|
2830
|
-
return text
|
|
2831
|
-
} catch (error) {
|
|
2832
|
-
// Convert Playwright timeout errors to ElementNotFound for consistency
|
|
2833
|
-
if (error.message && error.message.includes('Timeout')) {
|
|
2834
|
-
throw new ElementNotFound(locator, 'text')
|
|
2835
|
-
}
|
|
2836
|
-
throw error
|
|
2837
|
-
}
|
|
2838
2790
|
}
|
|
2839
2791
|
|
|
2840
2792
|
/**
|
|
@@ -4313,26 +4265,50 @@ function buildLocatorString(locator) {
|
|
|
4313
4265
|
if (locator.isXPath()) {
|
|
4314
4266
|
return `xpath=${locator.value}`
|
|
4315
4267
|
}
|
|
4316
|
-
|
|
4268
|
+
return locator.simplify()
|
|
4317
4269
|
}
|
|
4318
4270
|
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
return
|
|
4271
|
+
/**
|
|
4272
|
+
* Checks if a locator is a role locator object (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
4273
|
+
*/
|
|
4274
|
+
function isRoleLocatorObject(locator) {
|
|
4275
|
+
return locator && typeof locator === 'object' && locator.role && !locator.type
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
/**
|
|
4279
|
+
* Handles role locator objects by converting them to Playwright's getByRole() API
|
|
4280
|
+
* Returns elements array if role locator, null otherwise
|
|
4281
|
+
*/
|
|
4282
|
+
async function handleRoleLocator(context, locator) {
|
|
4283
|
+
if (!isRoleLocatorObject(locator)) return null
|
|
4284
|
+
|
|
4285
|
+
const options = {}
|
|
4286
|
+
if (locator.text) options.name = locator.text
|
|
4287
|
+
if (locator.exact !== undefined) options.exact = locator.exact
|
|
4288
|
+
|
|
4289
|
+
return context.getByRole(locator.role, Object.keys(options).length > 0 ? options : undefined).all()
|
|
4324
4290
|
}
|
|
4325
4291
|
|
|
4326
4292
|
async function findElements(matcher, locator) {
|
|
4327
|
-
|
|
4328
|
-
const
|
|
4293
|
+
// Check if locator is a Locator object with react/vue type, or a raw object with react/vue property
|
|
4294
|
+
const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
|
|
4295
|
+
const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
|
|
4296
|
+
const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
|
|
4297
|
+
|
|
4298
|
+
if (isReactLocator) return findReact(matcher, locator)
|
|
4299
|
+
if (isVueLocator) return findVue(matcher, locator)
|
|
4300
|
+
if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4301
|
+
|
|
4302
|
+
// Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
|
|
4303
|
+
const roleElements = await handleRoleLocator(matcher, locator)
|
|
4329
4304
|
if (roleElements) return roleElements
|
|
4330
4305
|
|
|
4331
|
-
|
|
4332
|
-
const isVueLocator = matchedLocator.type === 'vue'
|
|
4306
|
+
locator = new Locator(locator, 'css')
|
|
4333
4307
|
|
|
4334
|
-
|
|
4335
|
-
if (
|
|
4308
|
+
// Handle custom locators directly instead of relying on Playwright selector engines
|
|
4309
|
+
if (locator.isCustom()) {
|
|
4310
|
+
return findCustomElements.call(this, matcher, locator)
|
|
4311
|
+
}
|
|
4336
4312
|
|
|
4337
4313
|
// Check if we have a custom context locator and need to search within it
|
|
4338
4314
|
if (this.contextLocator) {
|
|
@@ -4345,12 +4321,12 @@ async function findElements(matcher, locator) {
|
|
|
4345
4321
|
}
|
|
4346
4322
|
|
|
4347
4323
|
// Search within the first context element
|
|
4348
|
-
const locatorString = buildLocatorString(
|
|
4324
|
+
const locatorString = buildLocatorString(locator)
|
|
4349
4325
|
return contextElements[0].locator(locatorString).all()
|
|
4350
4326
|
}
|
|
4351
4327
|
}
|
|
4352
4328
|
|
|
4353
|
-
const locatorString = buildLocatorString(
|
|
4329
|
+
const locatorString = buildLocatorString(locator)
|
|
4354
4330
|
|
|
4355
4331
|
return matcher.locator(locatorString).all()
|
|
4356
4332
|
}
|
|
@@ -4443,17 +4419,13 @@ async function findCustomElements(matcher, locator) {
|
|
|
4443
4419
|
}
|
|
4444
4420
|
|
|
4445
4421
|
async function findElement(matcher, locator) {
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
if (
|
|
4449
|
-
|
|
4450
|
-
const isReactLocator = matchedLocator.type === 'react'
|
|
4451
|
-
const isVueLocator = matchedLocator.type === 'vue'
|
|
4422
|
+
if (locator.react) return findReact(matcher, locator)
|
|
4423
|
+
if (locator.vue) return findVue(matcher, locator)
|
|
4424
|
+
if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
|
|
4452
4425
|
|
|
4453
|
-
|
|
4454
|
-
if (isVueLocator) return findVue(matcher, matchedLocator)
|
|
4426
|
+
locator = new Locator(locator, 'css')
|
|
4455
4427
|
|
|
4456
|
-
return matcher.locator(buildLocatorString(
|
|
4428
|
+
return matcher.locator(buildLocatorString(locator)).first()
|
|
4457
4429
|
}
|
|
4458
4430
|
|
|
4459
4431
|
async function getVisibleElements(elements) {
|
|
@@ -4505,14 +4477,26 @@ async function proceedClick(locator, context = null, options = {}) {
|
|
|
4505
4477
|
}
|
|
4506
4478
|
|
|
4507
4479
|
async function findClickable(matcher, locator) {
|
|
4508
|
-
|
|
4509
|
-
if (locator.vue) return findVue(matcher, locator)
|
|
4480
|
+
const matchedLocator = new Locator(locator)
|
|
4510
4481
|
|
|
4511
|
-
|
|
4512
|
-
if (!locator.isFuzzy()) return findElements.call(this, matcher, locator)
|
|
4482
|
+
if (!matchedLocator.isFuzzy()) return findElements.call(this, matcher, matchedLocator)
|
|
4513
4483
|
|
|
4514
4484
|
let els
|
|
4515
|
-
const literal = xpathLocator.literal(
|
|
4485
|
+
const literal = xpathLocator.literal(matchedLocator.value)
|
|
4486
|
+
|
|
4487
|
+
try {
|
|
4488
|
+
els = await matcher.getByRole('button', { name: matchedLocator.value }).all()
|
|
4489
|
+
if (els.length) return els
|
|
4490
|
+
} catch (err) {
|
|
4491
|
+
// getByRole not supported or failed
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
try {
|
|
4495
|
+
els = await matcher.getByRole('link', { name: matchedLocator.value }).all()
|
|
4496
|
+
if (els.length) return els
|
|
4497
|
+
} catch (err) {
|
|
4498
|
+
// getByRole not supported or failed
|
|
4499
|
+
}
|
|
4516
4500
|
|
|
4517
4501
|
els = await findElements.call(this, matcher, Locator.clickable.narrow(literal))
|
|
4518
4502
|
if (els.length) return els
|
|
@@ -4527,7 +4511,7 @@ async function findClickable(matcher, locator) {
|
|
|
4527
4511
|
// Do nothing
|
|
4528
4512
|
}
|
|
4529
4513
|
|
|
4530
|
-
return findElements.call(this, matcher,
|
|
4514
|
+
return findElements.call(this, matcher, matchedLocator.value) // by css or xpath
|
|
4531
4515
|
}
|
|
4532
4516
|
|
|
4533
4517
|
async function proceedSee(assertType, text, context, strict = false) {
|
|
@@ -4567,17 +4551,14 @@ async function findCheckable(locator, context) {
|
|
|
4567
4551
|
}
|
|
4568
4552
|
|
|
4569
4553
|
// Handle role locators with text/exact options
|
|
4570
|
-
const roleElements = await
|
|
4554
|
+
const roleElements = await handleRoleLocator(contextEl, locator)
|
|
4571
4555
|
if (roleElements) return roleElements
|
|
4572
4556
|
|
|
4573
|
-
const matchedLocator = Locator
|
|
4557
|
+
const matchedLocator = new Locator(locator)
|
|
4574
4558
|
if (!matchedLocator.isFuzzy()) {
|
|
4575
4559
|
return findElements.call(this, contextEl, matchedLocator)
|
|
4576
4560
|
}
|
|
4577
4561
|
|
|
4578
|
-
const checkboxByRole = await findByRole(contextEl, { role: 'checkbox', name: matchedLocator.value })
|
|
4579
|
-
if (checkboxByRole) return checkboxByRole
|
|
4580
|
-
|
|
4581
4562
|
const literal = xpathLocator.literal(matchedLocator.value)
|
|
4582
4563
|
let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal))
|
|
4583
4564
|
if (els.length) {
|
|
@@ -4599,15 +4580,17 @@ async function proceedIsChecked(assertType, option) {
|
|
|
4599
4580
|
}
|
|
4600
4581
|
|
|
4601
4582
|
async function findFields(locator) {
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4583
|
+
// Handle role locators with text/exact options
|
|
4584
|
+
if (isRoleLocatorObject(locator)) {
|
|
4585
|
+
const page = await this.page
|
|
4586
|
+
const roleElements = await handleRoleLocator(page, locator)
|
|
4587
|
+
if (roleElements) return roleElements
|
|
4588
|
+
}
|
|
4605
4589
|
|
|
4606
4590
|
const matchedLocator = new Locator(locator)
|
|
4607
4591
|
if (!matchedLocator.isFuzzy()) {
|
|
4608
4592
|
return this._locate(matchedLocator)
|
|
4609
4593
|
}
|
|
4610
|
-
|
|
4611
4594
|
const literal = xpathLocator.literal(locator)
|
|
4612
4595
|
|
|
4613
4596
|
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
|
|
@@ -4626,45 +4609,6 @@ async function findFields(locator) {
|
|
|
4626
4609
|
return this._locate({ css: locator })
|
|
4627
4610
|
}
|
|
4628
4611
|
|
|
4629
|
-
async function proceedSelect(context, el, option) {
|
|
4630
|
-
const role = await el.getAttribute('role')
|
|
4631
|
-
const options = Array.isArray(option) ? option : [option]
|
|
4632
|
-
|
|
4633
|
-
if (role === 'combobox') {
|
|
4634
|
-
this.debugSection('SelectOption', 'Expanding combobox')
|
|
4635
|
-
await highlightActiveElement.call(this, el)
|
|
4636
|
-
const [ariaOwns, ariaControls] = await Promise.all([el.getAttribute('aria-owns'), el.getAttribute('aria-controls')])
|
|
4637
|
-
await el.click()
|
|
4638
|
-
await this._waitForAction()
|
|
4639
|
-
|
|
4640
|
-
const listboxId = ariaOwns || ariaControls
|
|
4641
|
-
let listbox = listboxId ? context.locator(`#${listboxId}`).first() : null
|
|
4642
|
-
if (!listbox || !(await listbox.count())) listbox = context.getByRole('listbox').first()
|
|
4643
|
-
|
|
4644
|
-
for (const opt of options) {
|
|
4645
|
-
const optEl = listbox.getByRole('option', { name: opt }).first()
|
|
4646
|
-
this.debugSection('SelectOption', `Clicking: "${opt}"`)
|
|
4647
|
-
await highlightActiveElement.call(this, optEl)
|
|
4648
|
-
await optEl.click()
|
|
4649
|
-
}
|
|
4650
|
-
return this._waitForAction()
|
|
4651
|
-
}
|
|
4652
|
-
|
|
4653
|
-
if (role === 'listbox') {
|
|
4654
|
-
for (const opt of options) {
|
|
4655
|
-
const optEl = el.getByRole('option', { name: opt }).first()
|
|
4656
|
-
this.debugSection('SelectOption', `Clicking: "${opt}"`)
|
|
4657
|
-
await highlightActiveElement.call(this, optEl)
|
|
4658
|
-
await optEl.click()
|
|
4659
|
-
}
|
|
4660
|
-
return this._waitForAction()
|
|
4661
|
-
}
|
|
4662
|
-
|
|
4663
|
-
await highlightActiveElement.call(this, el)
|
|
4664
|
-
await el.selectOption(option)
|
|
4665
|
-
return this._waitForAction()
|
|
4666
|
-
}
|
|
4667
|
-
|
|
4668
4612
|
async function proceedSeeInField(assertType, field, value) {
|
|
4669
4613
|
const els = await findFields.call(this, field)
|
|
4670
4614
|
assertElementExists(els, field, 'Field')
|