diff --git a/docs/helpers/Appium.md b/docs/helpers/Appium.md index ab0a870a2..e19d55106 100644 --- a/docs/helpers/Appium.md +++ b/docs/helpers/Appium.md @@ -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 diff --git a/docs/helpers/Playwright.md b/docs/helpers/Playwright.md index 75b41c06f..a369698a3 100644 --- a/docs/helpers/Playwright.md +++ b/docs/helpers/Playwright.md @@ -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 diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md index 55fba6b8c..b6c2f42c8 100644 --- a/docs/helpers/Puppeteer.md +++ b/docs/helpers/Puppeteer.md @@ -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) diff --git a/docs/helpers/TestCafe.md b/docs/helpers/TestCafe.md index dcac6f98b..c5571326b 100644 --- a/docs/helpers/TestCafe.md +++ b/docs/helpers/TestCafe.md @@ -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 diff --git a/docs/helpers/WebDriver.md b/docs/helpers/WebDriver.md index 7417312a5..7028c2c18 100644 --- a/docs/helpers/WebDriver.md +++ b/docs/helpers/WebDriver.md @@ -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 diff --git a/docs/webapi/grabNumberOfVisibleElements.mustache b/docs/webapi/grabNumberOfVisibleElements.mustache index 9a19ed641..b3fc6cda8 100644 --- a/docs/webapi/grabNumberOfVisibleElements.mustache +++ b/docs/webapi/grabNumberOfVisibleElements.mustache @@ -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 of visible elements \ No newline at end of file +@param {number} [sec] (optional, `1` by default) time in seconds to wait +@returns {Promise} number of visible elements diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 8e68fc74d..d5e1db39e 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -24,6 +24,7 @@ const { clearString, requireWithFallback, normalizeSpacesInString, + promiseWithTimeout, } = require('../utils') const { isColorProperty, convertColorToRGBA } = require('../colorUtils') const ElementNotFound = require('./errors/ElementNotFound') @@ -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 + } } /** diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 0c219144e..d5a60461a 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -28,6 +28,7 @@ const { isModifierKey, requireWithFallback, normalizeSpacesInString, + promiseWithTimeout, } = require('../utils') const { isColorProperty, convertColorToRGBA } = require('../colorUtils') const ElementNotFound = require('./errors/ElementNotFound') @@ -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, + ), ), ) diff --git a/lib/helper/TestCafe.js b/lib/helper/TestCafe.js index 86f27bc5b..ebeb6c6b1 100644 --- a/lib/helper/TestCafe.js +++ b/lib/helper/TestCafe.js @@ -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') /** @@ -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 } diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index 9bd253405..10291bd90 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -19,6 +19,7 @@ const { screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys, + promiseWithTimeout, } = require('../utils') const { isColorProperty, convertColorToRGBA } = require('../colorUtils') const ElementNotFound = require('./errors/ElementNotFound') @@ -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 } diff --git a/lib/utils.js b/lib/utils.js index fc73a5afb..95f408168 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -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) }, + ), + ]); +}; diff --git a/runok.js b/runok.js index 6e9f09989..6dbc37a00 100755 --- a/runok.js +++ b/runok.js @@ -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')} }}`)