Skip to content

Commit 83bb3c5

Browse files
DavertMikDavertMikclaude
authored
improved context methods (#5453)
* improved context methods * fix: pass context parameter through Appium helper overrides Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add context parameter to seeElement, dontSeeElement, fillField, selectOption Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: DavertMik <davert@testomat.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 72055f0 commit 83bb3c5

File tree

10 files changed

+222
-80
lines changed

10 files changed

+222
-80
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
Opposite to `seeElement`. Checks that element is not visible (or in DOM)
22

3+
The second parameter is a context (CSS or XPath locator) to narrow the search.
4+
35
```js
46
I.dontSeeElement('.modal'); // modal is not shown
7+
I.dontSeeElement('.modal', '#container');
58
```
69

710
@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.
812
@returns {void} automatically synchronized promise through #recorder

docs/webapi/fillField.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
Fills a text field or textarea, after clearing its value, with the given string.
22
Field is located by name, label, CSS, or XPath.
33

4+
The third parameter is a context (CSS or XPath locator) to narrow the search.
5+
46
```js
57
// by label
68
I.fillField('Email', 'hello@world.com');
@@ -10,7 +12,10 @@ I.fillField('password', secret('123456'));
1012
I.fillField('form#login input[name=username]', 'John');
1113
// or by strict locator
1214
I.fillField({css: 'form#login input[name=username]'}, 'John');
15+
// within a context
16+
I.fillField('Name', 'John', '#section2');
1317
```
1418
@param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1519
@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.
1621
@returns {void} automatically synchronized promise through #recorder

docs/webapi/seeElement.mustache

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
Checks that a given Element is visible
22
Element is located by CSS or XPath.
33

4+
The second parameter is a context (CSS or XPath locator) to narrow the search.
5+
46
```js
57
I.seeElement('#modal');
8+
I.seeElement('#modal', '#container');
69
```
710
@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.
812
@returns {void} automatically synchronized promise through #recorder

docs/webapi/selectOption.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ Selects an option in a drop-down select.
22
Field is searched by label | name | CSS | XPath.
33
Option is selected by visible text or by value.
44

5+
The third parameter is a context (CSS or XPath locator) to narrow the search.
6+
57
```js
68
I.selectOption('Choose Plan', 'Monthly'); // select by label
79
I.selectOption('subscription', 'Monthly'); // match option by text
810
I.selectOption('subscription', '0'); // or by value
911
I.selectOption('//form/select[@name=account]','Premium');
1012
I.selectOption('form select[name=account]', 'Premium');
1113
I.selectOption({css: 'form select[name=account]'}, 'Premium');
14+
// within a context
15+
I.selectOption('age', '21-60', '#section2');
1216
```
1317

1418
Provide an array for the second argument to select multiple options.
@@ -18,4 +22,5 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']);
1822
```
1923
@param {LocatorOrString} select field located by label|name|CSS|XPath|strict locator.
2024
@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.
2126
@returns {void} automatically synchronized promise through #recorder

lib/helper/Appium.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1543,8 +1543,8 @@ class Appium extends Webdriver {
15431543
/**
15441544
* {{> dontSeeElement }}
15451545
*/
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)
15481548

15491549
// For mobile native apps, use safe isDisplayed wrapper
15501550
const parsedLocator = parseLocator.call(this, locator)
@@ -1589,9 +1589,9 @@ class Appium extends Webdriver {
15891589
* {{> fillField }}
15901590
*
15911591
*/
1592-
async fillField(field, value) {
1592+
async fillField(field, value, context = null) {
15931593
value = value.toString()
1594-
if (this.isWeb) return super.fillField(field, value)
1594+
if (this.isWeb) return super.fillField(field, value, context)
15951595
return super.fillField(parseLocator.call(this, field), value)
15961596
}
15971597

@@ -1706,8 +1706,8 @@ class Appium extends Webdriver {
17061706
* {{> seeElement }}
17071707
*
17081708
*/
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)
17111711

17121712
// For mobile native apps, use safe isDisplayed wrapper
17131713
const parsedLocator = parseLocator.call(this, locator)
@@ -1754,8 +1754,8 @@ class Appium extends Webdriver {
17541754
*
17551755
* Supported only for web testing
17561756
*/
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)
17591759
throw new Error("Should be used only in Web context. In native context use 'click' method instead")
17601760
}
17611761

lib/helper/Playwright.js

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1944,8 +1944,15 @@ class Playwright extends Helper {
19441944
* {{> seeElement }}
19451945
*
19461946
*/
1947-
async seeElement(locator) {
1948-
let els = await this._locate(locator)
1947+
async seeElement(locator, context = null) {
1948+
let els
1949+
if (context) {
1950+
const contextEls = await this._locate(context)
1951+
assertElementExists(contextEls, context, 'Context element')
1952+
els = await findElements.call(this, contextEls[0], locator)
1953+
} else {
1954+
els = await this._locate(locator)
1955+
}
19491956
els = await Promise.all(els.map(el => el.isVisible()))
19501957
try {
19511958
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
@@ -1958,8 +1965,15 @@ class Playwright extends Helper {
19581965
* {{> dontSeeElement }}
19591966
*
19601967
*/
1961-
async dontSeeElement(locator) {
1962-
let els = await this._locate(locator)
1968+
async dontSeeElement(locator, context = null) {
1969+
let els
1970+
if (context) {
1971+
const contextEls = await this._locate(context)
1972+
assertElementExists(contextEls, context, 'Context element')
1973+
els = await findElements.call(this, contextEls[0], locator)
1974+
} else {
1975+
els = await this._locate(locator)
1976+
}
19631977
els = await Promise.all(els.map(el => el.isVisible()))
19641978
try {
19651979
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
@@ -2245,8 +2259,8 @@ class Playwright extends Helper {
22452259
* {{> fillField }}
22462260
*
22472261
*/
2248-
async fillField(field, value) {
2249-
const els = await findFields.call(this, field)
2262+
async fillField(field, value, context = null) {
2263+
const els = await findFields.call(this, field, context)
22502264
assertElementExists(els, field, 'Field')
22512265
if (this.options.strict) assertOnlyOneElement(els, field)
22522266
const el = els[0]
@@ -2340,31 +2354,39 @@ class Playwright extends Helper {
23402354
/**
23412355
* {{> selectOption }}
23422356
*/
2343-
async selectOption(select, option) {
2344-
const context = await this.context
2357+
async selectOption(select, option, context = null) {
2358+
const pageContext = await this.context
23452359
const matchedLocator = new Locator(select)
23462360

2361+
let contextEl
2362+
if (context) {
2363+
const contextEls = await this._locate(context)
2364+
assertElementExists(contextEls, context, 'Context element')
2365+
contextEl = contextEls[0]
2366+
}
2367+
23472368
// Strict locator
23482369
if (!matchedLocator.isFuzzy()) {
23492370
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
2350-
const els = await this._locate(matchedLocator)
2371+
const els = contextEl ? await findElements.call(this, contextEl, matchedLocator) : await this._locate(matchedLocator)
23512372
assertElementExists(els, select, 'Selectable element')
2352-
return proceedSelect.call(this, context, els[0], option)
2373+
return proceedSelect.call(this, pageContext, els[0], option)
23532374
}
23542375

23552376
// Fuzzy: try combobox
23562377
this.debugSection('SelectOption', `Fuzzy: "${matchedLocator.value}"`)
2357-
let els = await findByRole(context, { role: 'combobox', name: matchedLocator.value })
2358-
if (els?.length) return proceedSelect.call(this, context, els[0], option)
2378+
const comboboxSearchCtx = contextEl || pageContext
2379+
let els = await findByRole(comboboxSearchCtx, { role: 'combobox', name: matchedLocator.value })
2380+
if (els?.length) return proceedSelect.call(this, pageContext, els[0], option)
23592381

23602382
// Fuzzy: try listbox
2361-
els = await findByRole(context, { role: 'listbox', name: matchedLocator.value })
2362-
if (els?.length) return proceedSelect.call(this, context, els[0], option)
2383+
els = await findByRole(comboboxSearchCtx, { role: 'listbox', name: matchedLocator.value })
2384+
if (els?.length) return proceedSelect.call(this, pageContext, els[0], option)
23632385

23642386
// Fuzzy: try native select
2365-
els = await findFields.call(this, select)
2387+
els = await findFields.call(this, select, context)
23662388
assertElementExists(els, select, 'Selectable element')
2367-
return proceedSelect.call(this, context, els[0], option)
2389+
return proceedSelect.call(this, pageContext, els[0], option)
23682390
}
23692391

23702392
/**
@@ -4355,34 +4377,45 @@ async function proceedIsChecked(assertType, option) {
43554377
return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
43564378
}
43574379

4358-
async function findFields(locator) {
4380+
async function findFields(locator, context = null) {
4381+
let contextEl
4382+
if (context) {
4383+
const contextEls = await this._locate(context)
4384+
assertElementExists(contextEls, context, 'Context element')
4385+
contextEl = contextEls[0]
4386+
}
4387+
4388+
const locateFn = contextEl
4389+
? loc => findElements.call(this, contextEl, loc)
4390+
: loc => this._locate(loc)
4391+
43594392
// Handle role locators with text/exact options
43604393
if (isRoleLocatorObject(locator)) {
4361-
const page = await this.page
4362-
const roleElements = await handleRoleLocator(page, locator)
4394+
const matcher = contextEl || (await this.page)
4395+
const roleElements = await handleRoleLocator(matcher, locator)
43634396
if (roleElements) return roleElements
43644397
}
43654398

43664399
const matchedLocator = new Locator(locator)
43674400
if (!matchedLocator.isFuzzy()) {
4368-
return this._locate(matchedLocator)
4401+
return locateFn(matchedLocator)
43694402
}
43704403
const literal = xpathLocator.literal(locator)
43714404

4372-
let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
4405+
let els = await locateFn({ xpath: Locator.field.labelEquals(literal) })
43734406
if (els.length) {
43744407
return els
43754408
}
43764409

4377-
els = await this._locate({ xpath: Locator.field.labelContains(literal) })
4410+
els = await locateFn({ xpath: Locator.field.labelContains(literal) })
43784411
if (els.length) {
43794412
return els
43804413
}
4381-
els = await this._locate({ xpath: Locator.field.byName(literal) })
4414+
els = await locateFn({ xpath: Locator.field.byName(literal) })
43824415
if (els.length) {
43834416
return els
43844417
}
4385-
return this._locate({ css: locator })
4418+
return locateFn({ css: locator })
43864419
}
43874420

43884421
async function proceedSelect(context, el, option) {

0 commit comments

Comments
 (0)