Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: timeout for grabNumberOfVisibleElements #4485

Draft
wants to merge 1 commit into
base: 3.x
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions docs/helpers/Appium.md
Original file line number Diff line number Diff line change
Expand Up @@ -1075,11 +1075,13 @@ Resumes test execution, so **should be used inside async function with `await`**

```js
let numOfElements = await I.grabNumberOfVisibleElements('p');
let numOfElementsWithWait = await I.grabNumberOfVisibleElements('p', 2); // timeout applied
```

#### Parameters

- `locator` **([string][5] \| [object][11])** located by CSS|XPath|strict locator.
- `sec` **[number][10]?** (optional, `1` by default) time in seconds to wait

Returns **[Promise][6]<[number][10]>** number of visible elements

Expand Down
2 changes: 2 additions & 0 deletions docs/helpers/Playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -1276,11 +1276,13 @@ Resumes test execution, so **should be used inside async function with `await`**

```js
let numOfElements = await I.grabNumberOfVisibleElements('p');
let numOfElementsWithWait = await I.grabNumberOfVisibleElements('p', 2); // timeout applied
```

#### Parameters

- `locator` **([string][9] | [object][6])** located by CSS|XPath|strict locator.
- `sec` **[number][20]?** (optional, `1` by default) time in seconds to wait

Returns **[Promise][22]<[number][20]>** number of visible elements

Expand Down
3 changes: 2 additions & 1 deletion docs/helpers/Puppeteer.md
Original file line number Diff line number Diff line change
Expand Up @@ -1125,16 +1125,17 @@ Resumes test execution, so **should be used inside async function with `await`**

```js
let numOfElements = await I.grabNumberOfVisibleElements('p');
let numOfElementsWithWait = await I.grabNumberOfVisibleElements('p', 2); // timeout applied
```

#### Parameters

- `locator` **([string][6] | [object][4])** located by CSS|XPath|strict locator.
- `sec` **[number][11]?** (optional, `1` by default) time in seconds to wait

Returns **[Promise][14]<[number][11]>** number of visible elements



This action supports [React locators](https://codecept.io/react#locators)


Expand Down
2 changes: 2 additions & 0 deletions docs/helpers/TestCafe.md
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,13 @@ Resumes test execution, so **should be used inside async function with `await`**

```js
let numOfElements = await I.grabNumberOfVisibleElements('p');
let numOfElementsWithWait = await I.grabNumberOfVisibleElements('p', 2); // timeout applied
```

#### Parameters

- `locator` **([string][4] | [object][5])** located by CSS|XPath|strict locator.
- `sec` **[number][11]?** (optional, `1` by default) time in seconds to wait

Returns **[Promise][9]<[number][11]>** number of visible elements

Expand Down
2 changes: 2 additions & 0 deletions docs/helpers/WebDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -1342,11 +1342,13 @@ Resumes test execution, so **should be used inside async function with `await`**

```js
let numOfElements = await I.grabNumberOfVisibleElements('p');
let numOfElementsWithWait = await I.grabNumberOfVisibleElements('p', 2); // timeout applied
```

#### Parameters

- `locator` **([string][18] | [object][17])** located by CSS|XPath|strict locator.
- `sec` **[number][24]?** (optional, `1` by default) time in seconds to wait

Returns **[Promise][27]<[number][24]>** number of visible elements

Expand Down
4 changes: 3 additions & 1 deletion docs/webapi/grabNumberOfVisibleElements.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ Resumes test execution, so **should be used inside async function with `await`**

```js
let numOfElements = await I.grabNumberOfVisibleElements('p');
let numOfElementsWithWait = await I.grabNumberOfVisibleElements('p', 2); // timeout applied
```

@param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
@returns {Promise<number>} number of visible elements
@param {number} [sec] (optional, `1` by default) time in seconds to wait
@returns {Promise<number>} number of visible elements
16 changes: 13 additions & 3 deletions lib/helper/Playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const {
clearString,
requireWithFallback,
normalizeSpacesInString,
promiseWithTimeout,
} = require('../utils')
const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
const ElementNotFound = require('./errors/ElementNotFound')
Expand Down Expand Up @@ -1851,10 +1852,19 @@ class Playwright extends Helper {
* {{> grabNumberOfVisibleElements }}
*
*/
async grabNumberOfVisibleElements(locator) {
async grabNumberOfVisibleElements(locator, sec) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
let els = await this._locate(locator)
els = await Promise.all(els.map((el) => el.isVisible()))
return els.filter((v) => v).length

const visibilityChecks = els.map((el) => promiseWithTimeout(el.isVisible(), waitTimeout))

try {
els = await Promise.all(visibilityChecks)
return els.filter((v) => v).length
} catch (error) {
console.error(error)
return 0
}
}

/**
Expand Down
16 changes: 10 additions & 6 deletions lib/helper/Puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const {
isModifierKey,
requireWithFallback,
normalizeSpacesInString,
promiseWithTimeout,
} = require('../utils')
const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
const ElementNotFound = require('./errors/ElementNotFound')
Expand Down Expand Up @@ -1514,17 +1515,20 @@ class Puppeteer extends Helper {
* {{> grabNumberOfVisibleElements }}
* {{ react }}
*/
async grabNumberOfVisibleElements(locator) {
async grabNumberOfVisibleElements(locator, sec) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
let els = await this._locate(locator)
els = (await Promise.all(els.map((el) => el.boundingBox() && el))).filter((v) => v)
els = (await Promise.all(els.map((el) => promiseWithTimeout(el.boundingBox() && el, waitTimeout)))).filter((v) => v)
// Puppeteer visibility was ignored? | Remove when Puppeteer is fixed
els = await Promise.all(
els.map(
async (el) =>
(await el.evaluate(
els.map((el) =>
promiseWithTimeout(
el.evaluate(
(node) =>
window.getComputedStyle(node).visibility !== 'hidden' && window.getComputedStyle(node).display !== 'none',
)) && el,
) && el,
waitTimeout,
),
),
)

Expand Down
10 changes: 7 additions & 3 deletions lib/helper/TestCafe.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const stringIncludes = require('../assert/include').includes
const { urlEquals } = require('../assert/equal')
const { empty } = require('../assert/empty')
const { truth } = require('../assert/truth')
const { xpathLocator, normalizeSpacesInString } = require('../utils')
const { xpathLocator, normalizeSpacesInString, promiseWithTimeout } = require('../utils')
const Locator = require('../locator')

/**
Expand Down Expand Up @@ -696,8 +696,12 @@ class TestCafe extends Helper {
/**
* {{> grabNumberOfVisibleElements }}
*/
async grabNumberOfVisibleElements(locator) {
const count = (await findElements.call(this, this.context, locator)).filterVisible().count
async grabNumberOfVisibleElements(locator, sec) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout

const elements = await promiseWithTimeout(findElements.call(this, this.context, locator), waitTimeout)
const visibleElements = await elements.filterVisible()
const count = visibleElements.count
return count
}

Expand Down
9 changes: 7 additions & 2 deletions lib/helper/WebDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const {
screenshotOutputFolder,
getNormalizedKeyAttributeValue,
modifierKeys,
promiseWithTimeout,
} = require('../utils')
const { isColorProperty, convertColorToRGBA } = require('../colorUtils')
const ElementNotFound = require('./errors/ElementNotFound')
Expand Down Expand Up @@ -1701,12 +1702,16 @@ class WebDriver extends Helper {
/**
* {{> grabNumberOfVisibleElements }}
*/
async grabNumberOfVisibleElements(locator) {
async grabNumberOfVisibleElements(locator, sec) {
const waitTimeout = sec || this.options.waitForTimeoutInSeconds
const res = await this._locate(locator)

let selected = await forEachAsync(res, async (el) => el.isDisplayed())
let selected = await forEachAsync(res, async (el) => promiseWithTimeout(el.isDisplayed(), waitTimeout))

if (!Array.isArray(selected)) selected = [selected]

selected = selected.filter((val) => val === true)

return selected.length
}

Expand Down
8 changes: 8 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,11 @@ module.exports.printObjectProperties = (obj) => {
module.exports.normalizeSpacesInString = (string) => {
return string.replace(/\s+/g, ' ');
};

module.exports.promiseWithTimeout = (promise, timeout = 1000) => {
return Promise.race([
promise,
new Promise((_, reject) => { setTimeout(() => reject(new Error(`Set timeout: ${timeout / 1000} sec(s). Timeout exceeded`)), timeout) },
),
]);
};
2 changes: 1 addition & 1 deletion runok.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Our community prepared some valuable recipes for setting up CI systems with Code
// generate documentation for helpers
const files = fs.readdirSync('lib/helper').filter((f) => path.extname(f) === '.js')

const ignoreList = ['Polly', 'MockRequest'] // WebDriverIO won't be documented and should be removed
const ignoreList = ['Polly', 'MockRequest', 'Nightmare', 'Protractor'] // WebDriverIO won't be documented and should be removed

const partials = fs.readdirSync('docs/webapi').filter((f) => path.extname(f) === '.mustache')
const placeholders = partials.map((file) => `{{> ${path.basename(file, '.mustache')} }}`)
Expand Down
Loading