codeceptjs 4.0.0-rc.1 → 4.0.0-rc.11
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/README.md +39 -27
- package/bin/mcp-server.js +637 -0
- package/docs/webapi/appendField.mustache +5 -0
- package/docs/webapi/attachFile.mustache +12 -0
- package/docs/webapi/checkOption.mustache +1 -1
- package/docs/webapi/clearField.mustache +5 -0
- package/docs/webapi/dontSeeCurrentPathEquals.mustache +10 -0
- package/docs/webapi/dontSeeElement.mustache +4 -0
- package/docs/webapi/dontSeeInField.mustache +5 -0
- package/docs/webapi/fillField.mustache +5 -0
- package/docs/webapi/moveCursorTo.mustache +5 -1
- package/docs/webapi/seeCurrentPathEquals.mustache +10 -0
- package/docs/webapi/seeElement.mustache +4 -0
- package/docs/webapi/seeInField.mustache +5 -0
- package/docs/webapi/selectOption.mustache +5 -0
- package/docs/webapi/uncheckOption.mustache +1 -1
- package/lib/codecept.js +20 -17
- package/lib/command/init.js +0 -3
- package/lib/command/run-workers.js +1 -0
- package/lib/container.js +19 -4
- package/lib/element/WebElement.js +81 -2
- package/lib/els.js +12 -6
- package/lib/helper/Appium.js +8 -8
- package/lib/helper/Playwright.js +224 -138
- package/lib/helper/Puppeteer.js +211 -69
- package/lib/helper/WebDriver.js +183 -64
- package/lib/helper/errors/MultipleElementsFound.js +27 -110
- package/lib/helper/errors/NonFocusedType.js +8 -0
- package/lib/helper/extras/elementSelection.js +58 -0
- package/lib/helper/extras/focusCheck.js +43 -0
- package/lib/helper/scripts/dropFile.js +11 -0
- package/lib/html.js +14 -1
- package/lib/listener/globalRetry.js +32 -6
- package/lib/mocha/cli.js +10 -0
- package/lib/plugin/aiTrace.js +464 -0
- package/lib/plugin/retryFailedStep.js +28 -19
- package/lib/plugin/stepByStepReport.js +5 -1
- package/lib/step/config.js +15 -2
- package/lib/step/record.js +1 -1
- package/lib/utils.js +48 -0
- package/lib/workers.js +49 -7
- package/package.json +5 -3
- package/typings/index.d.ts +19 -0
- package/lib/listener/enhancedGlobalRetry.js +0 -110
- package/lib/plugin/enhancedRetryFailedStep.js +0 -99
- package/lib/plugin/htmlReporter.js +0 -3648
- package/lib/retryCoordinator.js +0 -207
- package/typings/promiseBasedTypes.d.ts +0 -9469
- package/typings/types.d.ts +0 -11402
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Checks that current URL path does NOT match the expected path.
|
|
2
|
+
Query strings and URL fragments are ignored.
|
|
3
|
+
|
|
4
|
+
```js
|
|
5
|
+
I.dontSeeCurrentPathEquals('/form'); // fails for '/form', '/form?user=1', '/form#section'
|
|
6
|
+
I.dontSeeCurrentPathEquals('/'); // fails for '/', '/?user=ok', '/#top'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
@param {string} path value to check.
|
|
10
|
+
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Opposite to `seeElement`. Checks that element is not visible (or in DOM)
|
|
2
2
|
|
|
3
|
+
The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
4
|
+
|
|
3
5
|
```js
|
|
4
6
|
I.dontSeeElement('.modal'); // modal is not shown
|
|
7
|
+
I.dontSeeElement('.modal', '#container');
|
|
5
8
|
```
|
|
6
9
|
|
|
7
10
|
@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|Strict locator.
|
|
11
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
8
12
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
Checks that value of input field or textarea doesn't equal to given value
|
|
2
2
|
Opposite to `seeInField`.
|
|
3
3
|
|
|
4
|
+
The third parameter is an optional context (CSS or XPath locator) to narrow the search.
|
|
5
|
+
|
|
4
6
|
```js
|
|
5
7
|
I.dontSeeInField('email', 'user@user.com'); // field by name
|
|
6
8
|
I.dontSeeInField({ css: 'form input.email' }, 'user@user.com'); // field by CSS
|
|
9
|
+
// within a context
|
|
10
|
+
I.dontSeeInField('Name', 'old_value', '.form-container');
|
|
7
11
|
```
|
|
8
12
|
|
|
9
13
|
@param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
10
14
|
@param {CodeceptJS.StringOrSecret} value value to check.
|
|
15
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
11
16
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
Fills a text field or textarea, after clearing its value, with the given string.
|
|
2
2
|
Field is located by name, label, CSS, or XPath.
|
|
3
3
|
|
|
4
|
+
The third parameter is an optional context (CSS or XPath locator) to narrow the search.
|
|
5
|
+
|
|
4
6
|
```js
|
|
5
7
|
// by label
|
|
6
8
|
I.fillField('Email', 'hello@world.com');
|
|
@@ -10,7 +12,10 @@ I.fillField('password', secret('123456'));
|
|
|
10
12
|
I.fillField('form#login input[name=username]', 'John');
|
|
11
13
|
// or by strict locator
|
|
12
14
|
I.fillField({css: 'form#login input[name=username]'}, 'John');
|
|
15
|
+
// within a context
|
|
16
|
+
I.fillField('Name', 'John', '#section2');
|
|
13
17
|
```
|
|
14
18
|
@param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
15
19
|
@param {CodeceptJS.StringOrSecret} value text value to fill.
|
|
20
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
16
21
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
Moves cursor to element matched by locator.
|
|
2
2
|
Extra shift can be set with offsetX and offsetY options.
|
|
3
3
|
|
|
4
|
+
An optional `context` (as a second parameter) can be specified to narrow the search to an element within a parent.
|
|
5
|
+
When the second argument is a non-number (string or locator object), it is treated as context.
|
|
6
|
+
|
|
4
7
|
```js
|
|
5
8
|
I.moveCursorTo('.tooltip');
|
|
6
9
|
I.moveCursorTo('#submit', 5,5);
|
|
10
|
+
I.moveCursorTo('#submit', '.container');
|
|
7
11
|
```
|
|
8
12
|
|
|
9
13
|
@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
|
|
10
|
-
@param {number} [offsetX=0] (optional, `0` by default) X-axis offset.
|
|
14
|
+
@param {number|CodeceptJS.LocatorOrString} [offsetX=0] (optional, `0` by default) X-axis offset or context locator.
|
|
11
15
|
@param {number} [offsetY=0] (optional, `0` by default) Y-axis offset.
|
|
12
16
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Checks that current URL path matches the expected path.
|
|
2
|
+
Query strings and URL fragments are ignored.
|
|
3
|
+
|
|
4
|
+
```js
|
|
5
|
+
I.seeCurrentPathEquals('/info'); // passes for '/info', '/info?user=1', '/info#section'
|
|
6
|
+
I.seeCurrentPathEquals('/'); // passes for '/', '/?user=ok', '/#top'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
@param {string} path value to check.
|
|
10
|
+
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
Checks that a given Element is visible
|
|
2
2
|
Element is located by CSS or XPath.
|
|
3
3
|
|
|
4
|
+
The second parameter is a context (CSS or XPath locator) to narrow the search.
|
|
5
|
+
|
|
4
6
|
```js
|
|
5
7
|
I.seeElement('#modal');
|
|
8
|
+
I.seeElement('#modal', '#container');
|
|
6
9
|
```
|
|
7
10
|
@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
|
|
11
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
8
12
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
Checks that the given input field or textarea equals to given value.
|
|
2
2
|
For fuzzy locators, fields are matched by label text, the "name" attribute, CSS, and XPath.
|
|
3
3
|
|
|
4
|
+
The third parameter is an optional context (CSS or XPath locator) to narrow the search.
|
|
5
|
+
|
|
4
6
|
```js
|
|
5
7
|
I.seeInField('Username', 'davert');
|
|
6
8
|
I.seeInField({css: 'form textarea'},'Type your comment here');
|
|
7
9
|
I.seeInField('form input[type=hidden]','hidden_value');
|
|
8
10
|
I.seeInField('#searchform input','Search');
|
|
11
|
+
// within a context
|
|
12
|
+
I.seeInField('Name', 'John', '.form-container');
|
|
9
13
|
```
|
|
10
14
|
@param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
|
|
11
15
|
@param {CodeceptJS.StringOrSecret} value value to check.
|
|
16
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
12
17
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -2,6 +2,8 @@ Selects an option in a drop-down select.
|
|
|
2
2
|
Field is searched by label | name | CSS | XPath.
|
|
3
3
|
Option is selected by visible text or by value.
|
|
4
4
|
|
|
5
|
+
The third parameter is an optional context (CSS or XPath locator) to narrow the search.
|
|
6
|
+
|
|
5
7
|
```js
|
|
6
8
|
I.selectOption('Choose Plan', 'Monthly'); // select by label
|
|
7
9
|
I.selectOption('subscription', 'Monthly'); // match option by text
|
|
@@ -9,6 +11,8 @@ I.selectOption('subscription', '0'); // or by value
|
|
|
9
11
|
I.selectOption('//form/select[@name=account]','Premium');
|
|
10
12
|
I.selectOption('form select[name=account]', 'Premium');
|
|
11
13
|
I.selectOption({css: 'form select[name=account]'}, 'Premium');
|
|
14
|
+
// within a context
|
|
15
|
+
I.selectOption('age', '21-60', '#section2');
|
|
12
16
|
```
|
|
13
17
|
|
|
14
18
|
Provide an array for the second argument to select multiple options.
|
|
@@ -18,4 +22,5 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']);
|
|
|
18
22
|
```
|
|
19
23
|
@param {LocatorOrString} select field located by label|name|CSS|XPath|strict locator.
|
|
20
24
|
@param {string|Array<*>} option visible text or value of option.
|
|
25
|
+
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
|
|
21
26
|
@returns {void} automatically synchronized promise through #recorder
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Unselects a checkbox or radio button.
|
|
2
2
|
Element is located by label or name or CSS or XPath.
|
|
3
3
|
|
|
4
|
-
The second parameter is
|
|
4
|
+
The second parameter is an optional context (CSS or XPath locator) to narrow the search.
|
|
5
5
|
|
|
6
6
|
```js
|
|
7
7
|
I.uncheckOption('#agree');
|
package/lib/codecept.js
CHANGED
|
@@ -120,23 +120,26 @@ class Codecept {
|
|
|
120
120
|
* Executes hooks.
|
|
121
121
|
*/
|
|
122
122
|
async runHooks() {
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
123
|
+
// For workers parent process we only need plugins/hooks.
|
|
124
|
+
// Core listeners are executed inside worker threads.
|
|
125
|
+
if (!this.opts?.skipDefaultListeners) {
|
|
126
|
+
const listenerModules = [
|
|
127
|
+
'./listener/store.js',
|
|
128
|
+
'./listener/steps.js',
|
|
129
|
+
'./listener/config.js',
|
|
130
|
+
'./listener/result.js',
|
|
131
|
+
'./listener/helpers.js',
|
|
132
|
+
'./listener/globalTimeout.js',
|
|
133
|
+
'./listener/globalRetry.js',
|
|
134
|
+
'./listener/retryEnhancer.js',
|
|
135
|
+
'./listener/exit.js',
|
|
136
|
+
'./listener/emptyRun.js',
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
for (const modulePath of listenerModules) {
|
|
140
|
+
const module = await import(modulePath)
|
|
141
|
+
runHook(module.default || module)
|
|
142
|
+
}
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// custom hooks (previous iteration of plugins)
|
package/lib/command/init.js
CHANGED
|
@@ -41,6 +41,7 @@ export default async function (workerCount, selectedRuns, options) {
|
|
|
41
41
|
output.print(`CodeceptJS v${Codecept.version()} ${output.standWithUkraine()}`)
|
|
42
42
|
output.print(`Running tests in ${output.styles.bold(numberOfWorkers)} workers...`)
|
|
43
43
|
store.hasWorkers = true
|
|
44
|
+
process.env.RUNS_WITH_WORKERS = 'true'
|
|
44
45
|
|
|
45
46
|
const workers = new Workers(numberOfWorkers, config)
|
|
46
47
|
workers.overrideConfig(overrideConfigs)
|
package/lib/container.js
CHANGED
|
@@ -657,13 +657,28 @@ async function createPlugins(config, options = {}) {
|
|
|
657
657
|
const enabledPluginsByOptions = (options.plugins || '').split(',')
|
|
658
658
|
for (const pluginName in config) {
|
|
659
659
|
if (!config[pluginName]) config[pluginName] = {}
|
|
660
|
-
|
|
660
|
+
const pluginConfig = config[pluginName]
|
|
661
|
+
if (!pluginConfig.enabled && enabledPluginsByOptions.indexOf(pluginName) < 0) {
|
|
661
662
|
continue // plugin is disabled
|
|
662
663
|
}
|
|
664
|
+
|
|
665
|
+
// Generic workers gate:
|
|
666
|
+
// - runInWorker / runInWorkers controls plugin execution inside worker threads.
|
|
667
|
+
// - runInParent / runInMain can disable plugin in workers parent process.
|
|
668
|
+
const runInWorker = pluginConfig.runInWorker ?? pluginConfig.runInWorkers ?? (pluginName === 'testomatio' ? false : true)
|
|
669
|
+
const runInParent = pluginConfig.runInParent ?? pluginConfig.runInMain ?? true
|
|
670
|
+
|
|
671
|
+
if (options.child && !runInWorker) {
|
|
672
|
+
continue
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (!options.child && process.env.RUNS_WITH_WORKERS === 'true' && !runInParent) {
|
|
676
|
+
continue
|
|
677
|
+
}
|
|
663
678
|
let module
|
|
664
679
|
try {
|
|
665
|
-
if (
|
|
666
|
-
module =
|
|
680
|
+
if (pluginConfig.require) {
|
|
681
|
+
module = pluginConfig.require
|
|
667
682
|
if (module.startsWith('.')) {
|
|
668
683
|
// local
|
|
669
684
|
module = path.resolve(global.codecept_dir, module) // custom plugin
|
|
@@ -673,7 +688,7 @@ async function createPlugins(config, options = {}) {
|
|
|
673
688
|
}
|
|
674
689
|
|
|
675
690
|
// Use async loading for all plugins (ESM and CJS)
|
|
676
|
-
plugins[pluginName] = await loadPluginAsync(module,
|
|
691
|
+
plugins[pluginName] = await loadPluginAsync(module, pluginConfig)
|
|
677
692
|
debug(`plugin ${pluginName} loaded via async import`)
|
|
678
693
|
} catch (err) {
|
|
679
694
|
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from 'assert'
|
|
2
|
+
import { simplifyHtmlElement } from '../html.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Unified WebElement class that wraps native element instances from different helpers
|
|
@@ -81,6 +82,10 @@ class WebElement {
|
|
|
81
82
|
async getProperty(name) {
|
|
82
83
|
switch (this.helperType) {
|
|
83
84
|
case 'playwright':
|
|
85
|
+
// For Locator objects, use inputValue() for the 'value' property
|
|
86
|
+
if (name === 'value' && this.element.inputValue) {
|
|
87
|
+
return this.element.inputValue()
|
|
88
|
+
}
|
|
84
89
|
return this.element.evaluate((el, propName) => el[propName], name)
|
|
85
90
|
case 'webdriver':
|
|
86
91
|
return this.element.getProperty(name)
|
|
@@ -236,10 +241,15 @@ class WebElement {
|
|
|
236
241
|
async type(text, options = {}) {
|
|
237
242
|
switch (this.helperType) {
|
|
238
243
|
case 'playwright':
|
|
244
|
+
// Playwright Locator objects use fill() instead of type()
|
|
245
|
+
if (this.element.fill) {
|
|
246
|
+
return this.element.fill(text, options)
|
|
247
|
+
}
|
|
239
248
|
return this.element.type(text, options)
|
|
240
249
|
case 'webdriver':
|
|
241
250
|
return this.element.setValue(text)
|
|
242
251
|
case 'puppeteer':
|
|
252
|
+
await this.element.evaluate(el => { el.value = '' })
|
|
243
253
|
return this.element.type(text, options)
|
|
244
254
|
default:
|
|
245
255
|
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
@@ -256,7 +266,18 @@ class WebElement {
|
|
|
256
266
|
|
|
257
267
|
switch (this.helperType) {
|
|
258
268
|
case 'playwright':
|
|
259
|
-
|
|
269
|
+
// Playwright Locator objects use locator() method
|
|
270
|
+
if (this.element.locator) {
|
|
271
|
+
const childLocator = this.element.locator(this._normalizeLocator(locator))
|
|
272
|
+
// Get the element handle from the locator
|
|
273
|
+
try {
|
|
274
|
+
childElement = await childLocator.elementHandle()
|
|
275
|
+
} catch (e) {
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
childElement = await this.element.$(this._normalizeLocator(locator))
|
|
280
|
+
}
|
|
260
281
|
break
|
|
261
282
|
case 'webdriver':
|
|
262
283
|
try {
|
|
@@ -285,7 +306,14 @@ class WebElement {
|
|
|
285
306
|
|
|
286
307
|
switch (this.helperType) {
|
|
287
308
|
case 'playwright':
|
|
288
|
-
|
|
309
|
+
// Playwright Locator objects use locator() method
|
|
310
|
+
if (this.element.locator) {
|
|
311
|
+
const childLocator = this.element.locator(this._normalizeLocator(locator))
|
|
312
|
+
// Get all element handles from the locator
|
|
313
|
+
childElements = await childLocator.elementHandles()
|
|
314
|
+
} else {
|
|
315
|
+
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
316
|
+
}
|
|
289
317
|
break
|
|
290
318
|
case 'webdriver':
|
|
291
319
|
childElements = await this.element.$$(this._normalizeLocator(locator))
|
|
@@ -306,6 +334,57 @@ class WebElement {
|
|
|
306
334
|
* @returns {string} Normalized CSS selector
|
|
307
335
|
* @private
|
|
308
336
|
*/
|
|
337
|
+
async toAbsoluteXPath() {
|
|
338
|
+
const xpathFn = (el) => {
|
|
339
|
+
const parts = []
|
|
340
|
+
let current = el
|
|
341
|
+
while (current && current.nodeType === Node.ELEMENT_NODE) {
|
|
342
|
+
let index = 0
|
|
343
|
+
let sibling = current.previousSibling
|
|
344
|
+
while (sibling) {
|
|
345
|
+
if (sibling.nodeType === Node.ELEMENT_NODE && sibling.tagName === current.tagName) {
|
|
346
|
+
index++
|
|
347
|
+
}
|
|
348
|
+
sibling = sibling.previousSibling
|
|
349
|
+
}
|
|
350
|
+
const tagName = current.tagName.toLowerCase()
|
|
351
|
+
const pathIndex = index > 0 ? `[${index + 1}]` : ''
|
|
352
|
+
parts.unshift(`${tagName}${pathIndex}`)
|
|
353
|
+
current = current.parentElement
|
|
354
|
+
}
|
|
355
|
+
return '//' + parts.join('/')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
switch (this.helperType) {
|
|
359
|
+
case 'playwright':
|
|
360
|
+
return this.element.evaluate(xpathFn)
|
|
361
|
+
case 'puppeteer':
|
|
362
|
+
return this.element.evaluate(xpathFn)
|
|
363
|
+
case 'webdriver':
|
|
364
|
+
return this.helper.browser.execute(xpathFn, this.element)
|
|
365
|
+
default:
|
|
366
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async toOuterHTML() {
|
|
371
|
+
switch (this.helperType) {
|
|
372
|
+
case 'playwright':
|
|
373
|
+
return this.element.evaluate(el => el.outerHTML)
|
|
374
|
+
case 'puppeteer':
|
|
375
|
+
return this.element.evaluate(el => el.outerHTML)
|
|
376
|
+
case 'webdriver':
|
|
377
|
+
return this.helper.browser.execute(el => el.outerHTML, this.element)
|
|
378
|
+
default:
|
|
379
|
+
throw new Error(`Unsupported helper type: ${this.helperType}`)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async toSimplifiedHTML(maxLength = 300) {
|
|
384
|
+
const outerHTML = await this.toOuterHTML()
|
|
385
|
+
return simplifyHtmlElement(outerHTML, maxLength)
|
|
386
|
+
}
|
|
387
|
+
|
|
309
388
|
_normalizeLocator(locator) {
|
|
310
389
|
if (typeof locator === 'string') {
|
|
311
390
|
return locator
|
package/lib/els.js
CHANGED
|
@@ -6,10 +6,11 @@ import recordStep from './step/record.js'
|
|
|
6
6
|
import FuncStep from './step/func.js'
|
|
7
7
|
import { truth } from './assert/truth.js'
|
|
8
8
|
import { isAsyncFunction, humanizeFunction } from './utils.js'
|
|
9
|
+
import WebElement from './element/WebElement.js'
|
|
9
10
|
|
|
10
11
|
function element(purpose, locator, fn) {
|
|
11
12
|
let stepConfig
|
|
12
|
-
if (arguments[arguments.length - 1]
|
|
13
|
+
if (StepConfig.isStepConfig(arguments[arguments.length - 1])) {
|
|
13
14
|
stepConfig = arguments[arguments.length - 1]
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -28,7 +29,8 @@ function element(purpose, locator, fn) {
|
|
|
28
29
|
const els = await step.helper._locate(locator)
|
|
29
30
|
output.debug(`Found ${els.length} elements, using first element`)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
const wrapped = new WebElement(els[0], step.helper)
|
|
33
|
+
return fn(wrapped)
|
|
32
34
|
},
|
|
33
35
|
stepConfig,
|
|
34
36
|
)
|
|
@@ -52,7 +54,8 @@ function eachElement(purpose, locator, fn) {
|
|
|
52
54
|
let i = 0
|
|
53
55
|
for (const el of els) {
|
|
54
56
|
try {
|
|
55
|
-
|
|
57
|
+
const wrapped = new WebElement(el, step.helper)
|
|
58
|
+
await fn(wrapped, i)
|
|
56
59
|
} catch (err) {
|
|
57
60
|
output.error(`eachElement: failed operation on element #${i} ${el}`)
|
|
58
61
|
errs.push(err)
|
|
@@ -74,7 +77,8 @@ function expectElement(locator, fn) {
|
|
|
74
77
|
const els = await step.helper._locate(locator)
|
|
75
78
|
output.debug(`Found ${els.length} elements, first will be used for assertion`)
|
|
76
79
|
|
|
77
|
-
const
|
|
80
|
+
const wrapped = new WebElement(els[0], step.helper)
|
|
81
|
+
const result = await fn(wrapped)
|
|
78
82
|
const assertion = truth(`element (${locator})`, fn.toString())
|
|
79
83
|
assertion.assert(result)
|
|
80
84
|
})
|
|
@@ -92,7 +96,8 @@ function expectAnyElement(locator, fn) {
|
|
|
92
96
|
|
|
93
97
|
let found = false
|
|
94
98
|
for (const el of els) {
|
|
95
|
-
const
|
|
99
|
+
const wrapped = new WebElement(el, step.helper)
|
|
100
|
+
const result = await fn(wrapped)
|
|
96
101
|
if (result) {
|
|
97
102
|
found = true
|
|
98
103
|
break
|
|
@@ -113,7 +118,8 @@ function expectAllElements(locator, fn) {
|
|
|
113
118
|
let i = 1
|
|
114
119
|
for (const el of els) {
|
|
115
120
|
output.debug(`checking element #${i}: ${el}`)
|
|
116
|
-
const
|
|
121
|
+
const wrapped = new WebElement(el, step.helper)
|
|
122
|
+
const result = await fn(wrapped)
|
|
117
123
|
const assertion = truth(`element #${i} of (${locator})`, humanizeFunction(fn))
|
|
118
124
|
assertion.assert(result)
|
|
119
125
|
i++
|
package/lib/helper/Appium.js
CHANGED
|
@@ -1543,8 +1543,8 @@ class Appium extends Webdriver {
|
|
|
1543
1543
|
/**
|
|
1544
1544
|
* {{> dontSeeElement }}
|
|
1545
1545
|
*/
|
|
1546
|
-
async dontSeeElement(locator) {
|
|
1547
|
-
if (this.isWeb) return super.dontSeeElement(locator)
|
|
1546
|
+
async dontSeeElement(locator, context = null) {
|
|
1547
|
+
if (this.isWeb) return super.dontSeeElement(locator, context)
|
|
1548
1548
|
|
|
1549
1549
|
// For mobile native apps, use safe isDisplayed wrapper
|
|
1550
1550
|
const parsedLocator = parseLocator.call(this, locator)
|
|
@@ -1589,9 +1589,9 @@ class Appium extends Webdriver {
|
|
|
1589
1589
|
* {{> fillField }}
|
|
1590
1590
|
*
|
|
1591
1591
|
*/
|
|
1592
|
-
async fillField(field, value) {
|
|
1592
|
+
async fillField(field, value, context = null) {
|
|
1593
1593
|
value = value.toString()
|
|
1594
|
-
if (this.isWeb) return super.fillField(field, value)
|
|
1594
|
+
if (this.isWeb) return super.fillField(field, value, context)
|
|
1595
1595
|
return super.fillField(parseLocator.call(this, field), value)
|
|
1596
1596
|
}
|
|
1597
1597
|
|
|
@@ -1706,8 +1706,8 @@ class Appium extends Webdriver {
|
|
|
1706
1706
|
* {{> seeElement }}
|
|
1707
1707
|
*
|
|
1708
1708
|
*/
|
|
1709
|
-
async seeElement(locator) {
|
|
1710
|
-
if (this.isWeb) return super.seeElement(locator)
|
|
1709
|
+
async seeElement(locator, context = null) {
|
|
1710
|
+
if (this.isWeb) return super.seeElement(locator, context)
|
|
1711
1711
|
|
|
1712
1712
|
// For mobile native apps, use safe isDisplayed wrapper
|
|
1713
1713
|
const parsedLocator = parseLocator.call(this, locator)
|
|
@@ -1754,8 +1754,8 @@ class Appium extends Webdriver {
|
|
|
1754
1754
|
*
|
|
1755
1755
|
* Supported only for web testing
|
|
1756
1756
|
*/
|
|
1757
|
-
async selectOption(select, option) {
|
|
1758
|
-
if (this.isWeb) return super.selectOption(select, option)
|
|
1757
|
+
async selectOption(select, option, context = null) {
|
|
1758
|
+
if (this.isWeb) return super.selectOption(select, option, context)
|
|
1759
1759
|
throw new Error("Should be used only in Web context. In native context use 'click' method instead")
|
|
1760
1760
|
}
|
|
1761
1761
|
|