Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/webapi/dontSeeElement.mustache
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
Opposite to `seeElement`. Checks that element is not visible (or in DOM)

The second parameter is a context (CSS or XPath locator) to narrow the search.

```js
I.dontSeeElement('.modal'); // modal is not shown
I.dontSeeElement('.modal', '#container');
```

@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|Strict locator.
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
@returns {void} automatically synchronized promise through #recorder
5 changes: 5 additions & 0 deletions docs/webapi/fillField.mustache
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Fills a text field or textarea, after clearing its value, with the given string.
Field is located by name, label, CSS, or XPath.

The third parameter is a context (CSS or XPath locator) to narrow the search.

```js
// by label
I.fillField('Email', 'hello@world.com');
Expand All @@ -10,7 +12,10 @@ I.fillField('password', secret('123456'));
I.fillField('form#login input[name=username]', 'John');
// or by strict locator
I.fillField({css: 'form#login input[name=username]'}, 'John');
// within a context
I.fillField('Name', 'John', '#section2');
```
@param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
@param {CodeceptJS.StringOrSecret} value text value to fill.
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
@returns {void} automatically synchronized promise through #recorder
4 changes: 4 additions & 0 deletions docs/webapi/seeElement.mustache
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
Checks that a given Element is visible
Element is located by CSS or XPath.

The second parameter is a context (CSS or XPath locator) to narrow the search.

```js
I.seeElement('#modal');
I.seeElement('#modal', '#container');
```
@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
@returns {void} automatically synchronized promise through #recorder
5 changes: 5 additions & 0 deletions docs/webapi/selectOption.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ Selects an option in a drop-down select.
Field is searched by label | name | CSS | XPath.
Option is selected by visible text or by value.

The third parameter is a context (CSS or XPath locator) to narrow the search.

```js
I.selectOption('Choose Plan', 'Monthly'); // select by label
I.selectOption('subscription', 'Monthly'); // match option by text
I.selectOption('subscription', '0'); // or by value
I.selectOption('//form/select[@name=account]','Premium');
I.selectOption('form select[name=account]', 'Premium');
I.selectOption({css: 'form select[name=account]'}, 'Premium');
// within a context
I.selectOption('age', '21-60', '#section2');
```

Provide an array for the second argument to select multiple options.
Expand All @@ -18,4 +22,5 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']);
```
@param {LocatorOrString} select field located by label|name|CSS|XPath|strict locator.
@param {string|Array<*>} option visible text or value of option.
@param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
@returns {void} automatically synchronized promise through #recorder
16 changes: 8 additions & 8 deletions lib/helper/Appium.js
Original file line number Diff line number Diff line change
Expand Up @@ -1543,8 +1543,8 @@ class Appium extends Webdriver {
/**
* {{> dontSeeElement }}
*/
async dontSeeElement(locator) {
if (this.isWeb) return super.dontSeeElement(locator)
async dontSeeElement(locator, context = null) {
if (this.isWeb) return super.dontSeeElement(locator, context)

// For mobile native apps, use safe isDisplayed wrapper
const parsedLocator = parseLocator.call(this, locator)
Expand Down Expand Up @@ -1589,9 +1589,9 @@ class Appium extends Webdriver {
* {{> fillField }}
*
*/
async fillField(field, value) {
async fillField(field, value, context = null) {
value = value.toString()
if (this.isWeb) return super.fillField(field, value)
if (this.isWeb) return super.fillField(field, value, context)
return super.fillField(parseLocator.call(this, field), value)
}

Expand Down Expand Up @@ -1706,8 +1706,8 @@ class Appium extends Webdriver {
* {{> seeElement }}
*
*/
async seeElement(locator) {
if (this.isWeb) return super.seeElement(locator)
async seeElement(locator, context = null) {
if (this.isWeb) return super.seeElement(locator, context)

// For mobile native apps, use safe isDisplayed wrapper
const parsedLocator = parseLocator.call(this, locator)
Expand Down Expand Up @@ -1754,8 +1754,8 @@ class Appium extends Webdriver {
*
* Supported only for web testing
*/
async selectOption(select, option) {
if (this.isWeb) return super.selectOption(select, option)
async selectOption(select, option, context = null) {
if (this.isWeb) return super.selectOption(select, option, context)
throw new Error("Should be used only in Web context. In native context use 'click' method instead")
}

Expand Down
81 changes: 57 additions & 24 deletions lib/helper/Playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -1944,8 +1944,15 @@ class Playwright extends Helper {
* {{> seeElement }}
*
*/
async seeElement(locator) {
let els = await this._locate(locator)
async seeElement(locator, context = null) {
let els
if (context) {
const contextEls = await this._locate(context)
assertElementExists(contextEls, context, 'Context element')
els = await findElements.call(this, contextEls[0], locator)
} else {
els = await this._locate(locator)
}
els = await Promise.all(els.map(el => el.isVisible()))
try {
return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT'))
Expand All @@ -1958,8 +1965,15 @@ class Playwright extends Helper {
* {{> dontSeeElement }}
*
*/
async dontSeeElement(locator) {
let els = await this._locate(locator)
async dontSeeElement(locator, context = null) {
let els
if (context) {
const contextEls = await this._locate(context)
assertElementExists(contextEls, context, 'Context element')
els = await findElements.call(this, contextEls[0], locator)
} else {
els = await this._locate(locator)
}
els = await Promise.all(els.map(el => el.isVisible()))
try {
return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT'))
Expand Down Expand Up @@ -2245,8 +2259,8 @@ class Playwright extends Helper {
* {{> fillField }}
*
*/
async fillField(field, value) {
const els = await findFields.call(this, field)
async fillField(field, value, context = null) {
const els = await findFields.call(this, field, context)
assertElementExists(els, field, 'Field')
if (this.options.strict) assertOnlyOneElement(els, field)
const el = els[0]
Expand Down Expand Up @@ -2340,31 +2354,39 @@ class Playwright extends Helper {
/**
* {{> selectOption }}
*/
async selectOption(select, option) {
const context = await this.context
async selectOption(select, option, context = null) {
const pageContext = await this.context
const matchedLocator = new Locator(select)

let contextEl
if (context) {
const contextEls = await this._locate(context)
assertElementExists(contextEls, context, 'Context element')
contextEl = contextEls[0]
}

// Strict locator
if (!matchedLocator.isFuzzy()) {
this.debugSection('SelectOption', `Strict: ${JSON.stringify(select)}`)
const els = await this._locate(matchedLocator)
const els = contextEl ? await findElements.call(this, contextEl, matchedLocator) : await this._locate(matchedLocator)
assertElementExists(els, select, 'Selectable element')
return proceedSelect.call(this, context, els[0], option)
return proceedSelect.call(this, pageContext, els[0], option)
}

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

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

// Fuzzy: try native select
els = await findFields.call(this, select)
els = await findFields.call(this, select, context)
assertElementExists(els, select, 'Selectable element')
return proceedSelect.call(this, context, els[0], option)
return proceedSelect.call(this, pageContext, els[0], option)
}

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

async function findFields(locator) {
async function findFields(locator, context = null) {
let contextEl
if (context) {
const contextEls = await this._locate(context)
assertElementExists(contextEls, context, 'Context element')
contextEl = contextEls[0]
}

const locateFn = contextEl
? loc => findElements.call(this, contextEl, loc)
: loc => this._locate(loc)

// Handle role locators with text/exact options
if (isRoleLocatorObject(locator)) {
const page = await this.page
const roleElements = await handleRoleLocator(page, locator)
const matcher = contextEl || (await this.page)
const roleElements = await handleRoleLocator(matcher, locator)
if (roleElements) return roleElements
}

const matchedLocator = new Locator(locator)
if (!matchedLocator.isFuzzy()) {
return this._locate(matchedLocator)
return locateFn(matchedLocator)
}
const literal = xpathLocator.literal(locator)

let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
let els = await locateFn({ xpath: Locator.field.labelEquals(literal) })
if (els.length) {
return els
}

els = await this._locate({ xpath: Locator.field.labelContains(literal) })
els = await locateFn({ xpath: Locator.field.labelContains(literal) })
if (els.length) {
return els
}
els = await this._locate({ xpath: Locator.field.byName(literal) })
els = await locateFn({ xpath: Locator.field.byName(literal) })
if (els.length) {
return els
}
return this._locate({ css: locator })
return locateFn({ css: locator })
}

async function proceedSelect(context, el, option) {
Expand Down
Loading