From b1f893cf9797209936bc182e489a7f0336b0f5a5 Mon Sep 17 00:00:00 2001 From: Davert Date: Wed, 19 Feb 2020 00:20:08 +0200 Subject: [PATCH 01/23] updated docs & playwright guide --- docs/changelog.md | 19 +++++++++++++++++++ docs/playwright.md | 3 ++- runio.js | 14 ++++++-------- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 8d6800087..b040ec2cf 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,25 @@ layout: Section # Releases +## 2.5.0 + +* **Experimental: [Playwright](/playwright) helper introduced**. + +> [Playwright](https://github.com/microsoft/playwright/) is an alternative to Puppeteer which works very similarly to it but adds cross-browser support with Firefox and Webkit. Until v1.0 Playwright API is not stable but we introduce it to CodeceptJS so you could try it. + +* **[Puppeteer]** Fixed basic auth support when running in multiple sessions. See [#2178](https://github.com/Codeception/CodeceptJS/issues/2178) by **[ian-bartholomew](https://github.com/ian-bartholomew)** +* **[Puppeteer]** Fixed `waitForText` when there is no `body` element on page (redirect). See [#2181](https://github.com/Codeception/CodeceptJS/issues/2181) by **[Vorobeyko](https://github.com/Vorobeyko)** +* [Selenoid plugin] Fixed overriding current capabilities by adding deepMerge. Fixes [#2183](https://github.com/Codeception/CodeceptJS/issues/2183) by **[koushikmohan1996](https://github.com/koushikmohan1996)** +* Added types for `Scenario.todo` by **[Vorobeyko](https://github.com/Vorobeyko)** +* Added types for Mocha by **[Vorobeyko](https://github.com/Vorobeyko)**. Fixed typing conflicts with Jest +* **[FileSystem]** Added methods by **[nitschSB](https://github.com/nitschSB)** + * `waitForFile` + * `seeFileContentsEqualReferenceFile` +* Added `--colors` option to `run` and `run-multiple` so you force colored output in dockerized environment. See [#2189](https://github.com/Codeception/CodeceptJS/issues/2189) by **[mirao](https://github.com/mirao)** +* **[WebDriver]** Added `type` command to enter value without focusing on a field. See [#2198](https://github.com/Codeception/CodeceptJS/issues/2198) by **[xMutaGenx](https://github.com/xMutaGenx)** +* Fixed `codeceptjs gt` command to respect config pattern for tests. See [#2200](https://github.com/Codeception/CodeceptJS/issues/2200) and [#2204](https://github.com/Codeception/CodeceptJS/issues/2204) by **[matheo](https://github.com/matheo)** + + ## 2.4.3 * Hotfix for interactive pause diff --git a/docs/playwright.md b/docs/playwright.md index 09712b192..1955fcca6 100644 --- a/docs/playwright.md +++ b/docs/playwright.md @@ -28,7 +28,7 @@ It's readable and simple and working using Playwright API! To start you need CodeceptJS with Playwright packages installed ```bash -npm install codeceptjs playwright@^0.10.0 --save +npm install codeceptjs playwright@^0.11.0 --save-dev ``` Or see [alternative installation options](http://codecept.io/installation/) @@ -237,6 +237,7 @@ async setPermissions() { const context = browser.defaultContext() return context.setPermissions('https://html5demos.com', ['geolocation']); } +``` > [▶ Learn more about BrowserContext](https://github.com/microsoft/playwright/blob/v0.10.0/docs/api.md#class-browsercontext) diff --git a/runio.js b/runio.js index 68ba6b8ad..da111d619 100755 --- a/runio.js +++ b/runio.js @@ -305,14 +305,11 @@ title: ${name} await this.wiki(); const dir = 'website'; - await git((fn) => { - if (!fs.existsSync(dir)) { - fn.clone('git@github.com:codecept-js/website.git', dir); - } else { - fn.pull(); - } - }); + if (fs.existsSync(dir)) { + await exec('rm -rf ' + dir); + } + await git((fn) => fn.clone('git@github.com:codecept-js/website.git', dir)); await copy('docs', 'website/docs'); await chdir(dir, async () => { @@ -320,7 +317,8 @@ title: ${name} await git((fn) => { fn.add('-A'); fn.commit('-m "synchronized with docs"'); - fn.push('--no-verify'); + fn.pull(); + fn.push(); }); stopOnFail(true); From 8b64df22d6906eb7cca20b04c0adfd3876de7a7f Mon Sep 17 00:00:00 2001 From: Mykhailo Bodnarchuk Date: Wed, 4 Mar 2020 01:41:45 +0100 Subject: [PATCH 02/23] commentSteps plugin --- lib/plugin/commentStep.js | 116 ++++++++++++++++++ .../configs/commentStep/codecept.conf.js | 16 +++ .../configs/commentStep/customHelper.js | 10 ++ .../sandbox/configs/commentStep/first_test.js | 42 +++++++ test/runner/comment_step_test.js | 47 +++++++ 5 files changed, 231 insertions(+) create mode 100644 lib/plugin/commentStep.js create mode 100644 test/data/sandbox/configs/commentStep/codecept.conf.js create mode 100644 test/data/sandbox/configs/commentStep/customHelper.js create mode 100644 test/data/sandbox/configs/commentStep/first_test.js create mode 100644 test/runner/comment_step_test.js diff --git a/lib/plugin/commentStep.js b/lib/plugin/commentStep.js new file mode 100644 index 000000000..58985ee67 --- /dev/null +++ b/lib/plugin/commentStep.js @@ -0,0 +1,116 @@ +const event = require('../event'); +const recorder = require('../recorder'); +const { MetaStep } = require('../step'); + +let currentCommentStep; + +const defaultGlobalName = '__'; + +/** + * Add descriptive nested steps for your tests: + * + * ```js + * Scenario('project update test', async (I) => { + * __`Prepare project`; + * const projectId = await I.createProject(); + * + * __`Change project`; + * projectPage.update(projectId, { title: 'new title' }); + * + * __`Check project`; + * projectPage.open(projectId); + * I.see('new title', 'h1'); + * }) + * ``` + * Steps prefixed with `__` will be printed as nested steps in `--steps` output: + * + * ``` + * Prepare project + * I create project + * Change project + * projectPage update + * Check project + * projectPage open + * I see "new title", "h1" + * ``` + * + * Also those steps will be exported to allure reports. + * + * This plugin can be used + * + * ### Config + * + * * `enabled` - (default: false) enable a plugin + * * `regusterGlobal` - (default: false) register `__` template literal function globally. You can override function global name by providing a name as a value. + * + * ### Examples + * + * Registering `__` globally: + * + * ```js + * plugins: { + * commentStep: { + * enabled: true, + * registerGlobal: true + * } + * } + * ``` + * + * Registering `Step` globally: + * ```js + * plugins: { + * commentStep: { + * enabled: true, + * registerGlobal: 'Step' + * } + * } + * ``` + * + * Using only local function names: + * ```js + * plugins: { + * commentStep: { + * enabled: true + * } + * } + * ``` + * Then inside a test import a comment function from a plugin: + * + * ```js + * // inside a test + * const Step = codeceptjs.container.plugins('commentStep'); + * ``` + * + */ +module.exports = function (config) { + event.dispatcher.on(event.test.started, (test) => { + currentCommentStep = null; + }); + + event.dispatcher.on(event.step.started, (step) => { + if (currentCommentStep) { + const metaStep = getRootMetaStep(step); + metaStep.metaStep = currentCommentStep; + } + }); + + if (config.registerGlobal) { + if (config.registerGlobal === true) { + config.registerGlobal = defaultGlobalName; + } + global[config.registerGlobal] = setCommentString; + } + + return setCommentString; +}; + +function getRootMetaStep(step) { + if (step.metaStep) return getRootMetaStep(step.metaStep); + return step; +} + +function setCommentString(string) { + recorder.add('set comment metastep', () => { + currentCommentStep = new MetaStep(String.raw(string), ''); + }); +} diff --git a/test/data/sandbox/configs/commentStep/codecept.conf.js b/test/data/sandbox/configs/commentStep/codecept.conf.js new file mode 100644 index 000000000..df9bec826 --- /dev/null +++ b/test/data/sandbox/configs/commentStep/codecept.conf.js @@ -0,0 +1,16 @@ +exports.config = { + tests: './*_test.js', + output: './output', + helpers: { + CustomHelper: { + require: './customHelper.js', + }, + }, + plugins: { + commentStep: { + enabled: true, + registerGlobal: true, + }, + }, + name: 'pageobject-as-class', +}; diff --git a/test/data/sandbox/configs/commentStep/customHelper.js b/test/data/sandbox/configs/commentStep/customHelper.js new file mode 100644 index 000000000..84fb53544 --- /dev/null +++ b/test/data/sandbox/configs/commentStep/customHelper.js @@ -0,0 +1,10 @@ +// const Helper = require('../../lib/helper'); + +class CustomHelper extends Helper { + print(s) { + // this.debug('Print message from CustomHelper'); + // console.log(s); + } +} + +module.exports = CustomHelper; diff --git a/test/data/sandbox/configs/commentStep/first_test.js b/test/data/sandbox/configs/commentStep/first_test.js new file mode 100644 index 000000000..1f3a54670 --- /dev/null +++ b/test/data/sandbox/configs/commentStep/first_test.js @@ -0,0 +1,42 @@ +const given = when = then = global.codeceptjs.container.plugins('commentStep'); +const { I } = inject(); + +Feature('CommentStep'); + +const pageObject = { + metaPrint: (data) => { + I.print('meta value'); + I.print(data); + }, +}; + +Scenario('global var', (I) => { + __`Prepare user base`; + I.print('other thins'); + + __`Update data`; + I.print('do some things'); + + __`Check the result`; + I.print('see everything works'); +}); + +Scenario('local vars', (I) => { + given`Prepare project`; + I.print('other thins'); + + when`Update project`; + I.print('do some things'); + + then`Check project`; + I.print('see everything works'); +}); + +Scenario('from page object', (I) => { + __`Prepare project`; + I.print('other thins'); + pageObject.metaPrint('login user'); + + __`Update project`; + I.print('do some things'); +}); diff --git a/test/runner/comment_step_test.js b/test/runner/comment_step_test.js new file mode 100644 index 000000000..099bb6eca --- /dev/null +++ b/test/runner/comment_step_test.js @@ -0,0 +1,47 @@ +const assert = require('assert'); +const path = require('path'); +const exec = require('child_process').exec; + +const runner = path.join(__dirname, '/../../bin/codecept.js'); +const codecept_dir = path.join(__dirname, '/../data/sandbox/configs/commentStep'); +const codecept_run = `${runner} run`; +const config_run_config = (config, grep) => `${codecept_run} --config ${codecept_dir}/${config} ${grep ? `--grep "${grep}"` : ''}`; + +describe('CodeceptJS commentStep plugin', function () { + this.timeout(3000); + + before(() => { + process.chdir(codecept_dir); + }); + + it('should print nested steps when global var comments used', (done) => { + exec(`${config_run_config('codecept.conf.js', 'global var')} --debug`, (err, stdout) => { + stdout.should.include( + ` Prepare user base + I print "other thins" + Update data + I print "do some things" + Check the result + I print "see everything works"`, + ); + assert(!err); + done(); + }); + }); + + + it('should print nested steps when local var comments used', (done) => { + exec(`${config_run_config('codecept.conf.js', 'local var')} --debug`, (err, stdout) => { + stdout.should.include( + ` Prepare project + I print "other thins" + Update project + I print "do some things" + Check project + I print "see everything works"`, + ); + assert(!err); + done(); + }); + }); +}); From f26852d4a0e2d2463bf827c337551ff5f852b9da Mon Sep 17 00:00:00 2001 From: Davert Date: Sun, 29 Mar 2020 14:52:01 +0300 Subject: [PATCH 03/23] minor tests fixes --- docs/custom-helpers.md | 53 +++++++- docs/playwright.md | 59 +++++++++ docs/webapi/setCookie.mustache | 12 +- lib/helper/MockRequest.js | 3 + lib/helper/Playwright.js | 22 +++- lib/helper/Puppeteer.js | 7 +- lib/helper/WebDriver.js | 155 +++++++++++++++++++++++- lib/plugin/commentStep.js | 37 ++++-- lib/session.js | 2 +- package.json | 2 +- runio.js | 2 +- test/acceptance/codecept.Playwright.js | 2 +- test/acceptance/codecept.WebDriver.js | 4 + test/acceptance/config_test.js | 1 + test/acceptance/session_test.js | 37 ++++-- test/helper/Playwright_test.js | 40 ++++++ test/runner/allure_test.js | 9 +- test/runner/comment_step_test.js | 56 +++++---- test/support/ScreenshotSessionHelper.js | 2 +- 19 files changed, 441 insertions(+), 64 deletions(-) diff --git a/docs/custom-helpers.md b/docs/custom-helpers.md index 430215cd4..00e9dc88b 100644 --- a/docs/custom-helpers.md +++ b/docs/custom-helpers.md @@ -3,6 +3,7 @@ permalink: /helpers title: Custom Helpers --- +# Extending CodeceptJS with Custom Helopers Helpers is a core concept of CodeceptJS. Helper is a wrapper on top of various libraries providing unified interface around them. @@ -168,6 +169,50 @@ class MyHelper extends Helper { module.exports = MyHelper; ``` +## Accessing Elements + +WebDriver, Puppeteer, Playwright, and Protractor drivers provide API for web elements. +However, CodeceptJS do not expose them to tests by design, keeping test to be action focused. +If you need to get access to web elements, it is recommended to implement operations for web elements in a custom helper. + +To get access for elements, connect to a corresponding helper and use `_locate` function to match web elements by CSS or XPath, like you usually do: + +### Acessing Elements in WebDriver + +```js +// inside a custom helper +async clickOnEveryElement(locator) { + const { WebDriver } = this.helpers; + const els = await WebDriver._locate(locator); + + for (let el of els) { + await el.click(); + } +} +``` +In this case an an instance of webdriverio element is used. +To get a [complete API of an element](https://webdriver.io/docs/api/) refer to webdriverio docs. + +### Accessing Elements in Playwright & Puppeteer + +Similar method can be implemented for Playwright & Puppeteer: + +```js +// inside a custom helper +async clickOnEveryElement(locator) { + const { Playwright } = this.helpers; + const els = await Playwright._locate(locator); + + for (let el of els) { + await el.click(); + } +} +``` + +In this case `el` will be an instance of [ElementHandle](https://github.com/microsoft/playwright/blob/v0.12.1/docs/api.md#class-elementhandle) which is similar for Playwright & [Puppeteer](https://pptr.dev/#?product=Puppeteer&version=v2.1.1&show=api-class-elementhandle). + +> ℹ There are more `_locate*` methods in each helper. Take a look on documentation of a helper you use to see which exact method it exposes. + ## Configuration Helpers should be enabled inside `codecept.json` or `codecept.conf.js` files. Command `generate helper` @@ -175,13 +220,13 @@ does that for you, however you can enable them manually by placing helper to `he You can also pass additional config options to your helper from a config - **(please note, this example contains comments, while JSON format doesn't support them)**: ```js -"helpers": { +helpers: { // here goes standard helpers: // WebDriver, Protractor, Nightmare, etc... // and their configuration - "MyHelper": { - "require": "./my_helper.js", // path to module - "defaultHost": "http://mysite.com" // custom config param + MyHelper: { + require: "./my_helper.js", // path to module + defaultHost: "http://mysite.com" // custom config param } } diff --git a/docs/playwright.md b/docs/playwright.md index 335a9e4eb..595c5eb52 100644 --- a/docs/playwright.md +++ b/docs/playwright.md @@ -201,6 +201,65 @@ I.see('0 items left', '.todo-count'); CodeceptJS allows you to implement custom actions like `I.createTodo` or use **PageObjects**. Learn how to improve your tests in [PageObjects](http://codecept.io/pageobjects/) guide. +## Multi Session Testing + +TO launch additional browser context (or incognito window) use `session` command. + +```js +Scenario('I try to open this site as anonymous user', () => { + I.amOnPage('/'); + I.dontSee('Agree to cookies'); + session('anonymous user', () => { + I.amOnPage('/'); + I.see('Agree to cookies'); + }); +}) +``` + +> ℹ Learn more about [multi-session testing](/basics/#multiple-sessions) + +## Device Emulation + +Playwright can emulate browsers of mobile devices. Instead of paying for expensive devices for mobile tests you can adjust Playwright settings so it could emulate mobile browsers on iPhone, Samsung Galaxy, etc. + +Device emulation can be enabled in CodeceptJS globally in a config or per session. + +Playwright contains a [list of predefined devices](https://github.com/Microsoft/playwright/blob/master/src/deviceDescriptors.ts) to emulate, for instance this is how you can enable iPhone 6 emulation for all tests: + +```js +const { devices } = require('playwright'); + +helpers: { + Playwright: { + // regular config goes here + emulate: devices['iPhone 6'] + } +} +``` +To adjust browser settings you can pass [custom options](https://github.com/microsoft/playwright/blob/v0.12.1/docs/api.md#browsernewcontextoptions) + +```js +helpers: { + Playwright: { + // regular config goes here + // put on mobile device + emulate: { isMobile: true, deviceScaleFactor: 2 } + } +} +``` + +To enable device emulation for a specific test, create an additional browser session and pass in config as a second parameter: + +```js +const { devices } = require('playwright'); + +Scenario('website looks nice on iPhone', () => { + session('mobile user', devices['iPhone 6'], () => { + I.amOnPage('/'); + I.see('Hello, iPhone user!') + }) +}); +``` ## Extending diff --git a/docs/webapi/setCookie.mustache b/docs/webapi/setCookie.mustache index 1165a965d..c792d8c1b 100644 --- a/docs/webapi/setCookie.mustache +++ b/docs/webapi/setCookie.mustache @@ -1,7 +1,15 @@ -Sets a cookie. +Sets cookie(s). + +Can be a single cookie object or an array of cookies: ```js I.setCookie({name: 'auth', value: true}); + +// as array +I.setCookie([ + {name: 'auth', value: true}, + {name: 'agree', value: true} +]); ``` -@param {object} cookie a cookie object. \ No newline at end of file +@param {object|array} cookie a cookie object or array of cookie objects. \ No newline at end of file diff --git a/lib/helper/MockRequest.js b/lib/helper/MockRequest.js index 7a5a252a9..6bc32eddc 100644 --- a/lib/helper/MockRequest.js +++ b/lib/helper/MockRequest.js @@ -57,6 +57,9 @@ let PollyJS; */ class MockRequest extends Helper { constructor(config) { + console.log('DEPRECATION NOTICE:'); + console.log('MockRequest helper was moved to a standalone package: https://github.com/codecept-js/mock-request'); + console.log('Disable MockRequest in config & install @codeceptjs/mock-request package\n'); super(config); this._setConfig(config); PollyJS = requireg('@pollyjs/core').Polly; diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 49681640b..fa7839ceb 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -195,6 +195,9 @@ class Playwright extends Helper { _validateConfig(config) { const defaults = { + // options to emulate context + emulate: {}, + browser: 'chromium', waitForAction: 100, waitForTimeout: 1000, @@ -331,11 +334,11 @@ class Playwright extends Helper { _session() { const defaultContext = this.browserContext; return { - start: async (sessionName = '') => { - this.debugSection('Incognito Tab', 'opened'); + start: async (sessionName = '', config) => { + this.debugSection('New Context', config ? JSON.stringify(config) : 'opened'); this.activeSessionName = sessionName; - const bc = await this.browser.newContext(); + const bc = await this.browser.newContext(config); const page = await bc.newPage(); targetCreatedHandler.call(this, page); this._setPage(page); @@ -509,7 +512,7 @@ class Playwright extends Helper { this.browser.on('targetchanged', (target) => { this.debugSection('Url', target.url()); }); - this.browserContext = await this.browser.newContext(); + this.browserContext = await this.browser.newContext(this.options.emulate); const existingPages = await this.browserContext.pages(); @@ -912,9 +915,16 @@ class Playwright extends Helper { * ```js * I.openNewTab(); * ``` + * + * You can pass in [page options](https://github.com/microsoft/playwright/blob/v0.12.1/docs/api.md#browsernewpageoptions) to emulate device on this page + * + * ```js + * // enable mobile + * I.openNewTab({ isMobile: true }); + * ``` */ - async openNewTab() { - await this._setPage(await this.browserContext.newPage()); + async openNewTab(options) { + await this._setPage(await this.browserContext.newPage(options)); return this._waitForAction(); } diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index d838bb43f..b80cb4156 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -300,12 +300,15 @@ class Puppeteer extends Helper { _session() { return { - start: async (name = '', config = {}) => { + start: async (name = '', config) => { this.debugSection('Incognito Tab', 'opened'); this.activeSessionName = name; const bc = await this.browser.createIncognitoBrowserContext(); - await bc.newPage(); + const page = await bc.newPage(); + if (config) { + await page.emulate(config); + } // Create a new page inside context. return bc; diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index f13845d51..524d616a0 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -2,6 +2,7 @@ let webdriverio; const assert = require('assert'); const path = require('path'); +const fs = require('fs'); const requireg = require('requireg'); const Helper = require('../helper'); @@ -31,6 +32,7 @@ const Locator = require('../locator'); const SHADOW = 'shadow'; const webRoot = 'body'; +let version; /** * WebDriver helper which wraps [webdriverio](http://webdriver.io/) library to * manipulate browser using Selenium WebDriver or PhantomJS. @@ -381,10 +383,22 @@ class WebDriver extends Helper { if (webdriverio.VERSION && webdriverio.VERSION.indexOf('4') === 0) { throw new Error(`This helper is compatible with "webdriverio@5". Current version: ${webdriverio.VERSION}. Please upgrade webdriverio to v5+ or use WebDriverIO helper instead`); } + try { + version = JSON.parse(fs.readFileSync(path.join(requireg.resolve('webdriverio'), '/../../', 'package.json')).toString()).version; + } catch (err) { + this.debug('Can\'t detect webdriverio version, assuming webdriverio v6 is used'); + } + + if (isWebDriver5()) { + console.log('DEPRECATION NOTICE:'); + console.log('You are using webdriverio v5. It is recommended to update to webdriverio@6.\nSupport of webdriverio v5 is deprecated and will be removed in CodeceptJS 3.0\n'); + } // set defaults this.root = webRoot; this.isWeb = true; this.isRunning = false; + this.sessionWindows = {}; + this.activeSessionName = ''; this._setConfig(config); @@ -565,7 +579,7 @@ class WebDriver extends Helper { _session() { const defaultSession = this.browser; return { - start: async (opts) => { + start: async (sessionName, opts) => { // opts.disableScreenshots = true; // screenshots cant be saved as session will be already closed opts = this._validateConfig(Object.assign(this.options, opts)); this.debugSection('New Browser', JSON.stringify(opts)); @@ -586,9 +600,15 @@ class WebDriver extends Helper { if (this.context !== this.root) throw new Error('Can\'t start session inside within block'); this.browser = browser; this.$$ = this.browser.$$.bind(this.browser); + this.sessionWindows[this.activeSessionName] = browser; }, - restoreVars: async () => { - this.browser = defaultSession; + restoreVars: async (session) => { + if (!session) { + this.browser = defaultSession; + } else { + this.activeSessionName = session; + this.browser = this.sessionWindows[this.activeSessionName]; + } this.$$ = this.browser.$$.bind(this.browser); }, }; @@ -1456,6 +1476,7 @@ class WebDriver extends Helper { const res = await this._locate(withStrictLocator(locator), true); assertElementExists(res, locator); const elem = usingFirstElement(res); + if (isWebDriver5()) return elem.moveTo(xOffset, yOffset); return elem.moveTo({ xOffset, yOffset }); } @@ -1466,6 +1487,15 @@ class WebDriver extends Helper { async saveScreenshot(fileName, fullPage = false) { const outputFile = screenshotOutputFolder(fileName); + if (this.activeSessionName) { + const browser = this.sessionWindows[this.activeSessionName]; + + if (browser) { + this.debug(`Screenshot of ${this.activeSessionName} session has been saved to ${outputFile}`); + return browser.saveScreenshot(outputFile); + } + } + if (!fullPage) { this.debug(`Screenshot has been saved to ${outputFile}`); return this.browser.saveScreenshot(outputFile); @@ -1884,6 +1914,20 @@ class WebDriver extends Helper { const client = this.browser; const aSec = sec || this.options.waitForTimeout; let currUrl = ''; + if (isWebDriver5()) { + return client + .waitUntil(function () { + return this.getUrl().then((res) => { + currUrl = decodeUrl(res); + return currUrl.indexOf(urlPart) > -1; + }); + }, aSec * 1000).catch((e) => { + if (e.message.indexOf('timeout')) { + throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`); + } + throw e; + }); + } return client .waitUntil(function () { return this.getUrl().then((res) => { @@ -1928,6 +1972,21 @@ class WebDriver extends Helper { async waitForText(text, sec = null, context = null) { const aSec = sec || this.options.waitForTimeout; const _context = context || this.root; + if (isWebDriver5()) { + return this.browser.waitUntil( + async () => { + const res = await this.$$(withStrictLocator.call(this, _context)); + if (!res || res.length === 0) return false; + const selected = await forEachAsync(res, async el => this.browser.getElementText(getElementId(el))); + if (Array.isArray(selected)) { + return selected.filter(part => part.indexOf(text) >= 0).length > 0; + } + return selected.indexOf(text) >= 0; + }, aSec * 1000, + `element (${_context}) is not in DOM or there is no element(${_context}) with text "${text}" after ${aSec} sec`, + ); + } + return this.browser.waitUntil( async () => { const res = await this.$$(withStrictLocator.call(this, _context)); @@ -1950,6 +2009,20 @@ class WebDriver extends Helper { async waitForValue(field, value, sec = null) { const client = this.browser; const aSec = sec || this.options.waitForTimeout; + if (isWebDriver5()) { + return client.waitUntil( + async () => { + const res = await findFields.call(this, field); + if (!res || res.length === 0) return false; + const selected = await forEachAsync(res, async el => el.getValue()); + if (Array.isArray(selected)) { + return selected.filter(part => part.indexOf(value) >= 0).length > 0; + } + return selected.indexOf(value) >= 0; + }, aSec * 1000, + `element (${field}) is not in DOM or there is no element(${field}) with value "${value}" after ${aSec} sec`, + ); + } return client.waitUntil( async () => { const res = await findFields.call(this, field); @@ -1972,6 +2045,17 @@ class WebDriver extends Helper { */ async waitForVisible(locator, sec = null) { const aSec = sec || this.options.waitForTimeout; + if (isWebDriver5()) { + return this.browser.waitUntil(async () => { + const res = await this.$$(withStrictLocator(locator)); + if (!res || res.length === 0) return false; + const selected = await forEachAsync(res, async el => el.isDisplayed()); + if (Array.isArray(selected)) { + return selected.filter(val => val === true).length > 0; + } + return selected; + }, aSec * 1000, `element (${new Locator(locator)}) still not visible after ${aSec} sec`); + } return this.browser.waitUntil(async () => { const res = await this.$$(withStrictLocator(locator)); if (!res || res.length === 0) return false; @@ -1988,6 +2072,16 @@ class WebDriver extends Helper { */ async waitNumberOfVisibleElements(locator, num, sec = null) { const aSec = sec || this.options.waitForTimeout; + if (isWebDriver5()) { + return this.browser.waitUntil(async () => { + const res = await this.$$(withStrictLocator(locator)); + if (!res || res.length === 0) return false; + let selected = await forEachAsync(res, async el => el.isDisplayed()); + + if (!Array.isArray(selected)) selected = [selected]; + return selected.length === num; + }, aSec * 1000, `The number of elements (${new Locator(locator)}) is not ${num} after ${aSec} sec`); + } return this.browser.waitUntil(async () => { const res = await this.$$(withStrictLocator(locator)); if (!res || res.length === 0) return false; @@ -2004,6 +2098,14 @@ class WebDriver extends Helper { */ async waitForInvisible(locator, sec = null) { const aSec = sec || this.options.waitForTimeout; + if (isWebDriver5()) { + return this.browser.waitUntil(async () => { + const res = await this.$$(withStrictLocator(locator)); + if (!res || res.length === 0) return true; + const selected = await forEachAsync(res, async el => el.isDisplayed()); + return !selected.length; + }, aSec * 1000, `element (${new Locator(locator)}) still visible after ${aSec} sec`); + } return this.browser.waitUntil(async () => { const res = await this.$$(withStrictLocator(locator)); if (!res || res.length === 0) return true; @@ -2031,6 +2133,15 @@ class WebDriver extends Helper { */ async waitForDetached(locator, sec = null) { const aSec = sec || this.options.waitForTimeout; + if (isWebDriver5()) { + return this.browser.waitUntil(async () => { + const res = await this.$$(withStrictLocator(locator)); + if (!res || res.length === 0) { + return true; + } + return false; + }, aSec * 1000, `element (${new Locator(locator)}) still on page after ${aSec} sec`); + } return this.browser.waitUntil(async () => { const res = await this.$$(withStrictLocator(locator)); if (!res || res.length === 0) { @@ -2055,6 +2166,9 @@ class WebDriver extends Helper { } const aSec = sec || this.options.waitForTimeout; + if (isWebDriver5()) { + return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), aSec * 1000, ''); + } return this.browser.waitUntil(async () => this.browser.execute(fn, ...args), { timeout: aSec * 1000, timeoutMsg: '' }); } @@ -2065,7 +2179,10 @@ class WebDriver extends Helper { async waitUntil(fn, sec = null, timeoutMsg = null, interval = null) { const aSec = sec || this.options.waitForTimeout; const _interval = typeof interval === 'number' ? interval * 1000 : null; - return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval); + if (isWebDriver5()) { + return this.browser.waitUntil(fn, aSec * 1000, timeoutMsg, _interval); + } + return this.browser.waitUntil(fn, { timeout: aSec * 1000, timeoutMsg, interval: _interval }); } /** @@ -2102,6 +2219,19 @@ class WebDriver extends Helper { const aSec = sec || this.options.waitForTimeout; let target; const current = await this.browser.getWindowHandle(); + + if (isWebDriver5()) { + await this.browser.waitUntil(async () => { + await this.browser.getWindowHandles().then((handles) => { + if (handles.indexOf(current) + num + 1 <= handles.length) { + target = handles[handles.indexOf(current) + num]; + } + }); + return target; + }, aSec * 1000, `There is no ability to switch to next tab with offset ${num}`); + return this.browser.switchToWindow(target); + } + await this.browser.waitUntil(async () => { await this.browser.getWindowHandles().then((handles) => { if (handles.indexOf(current) + num + 1 <= handles.length) { @@ -2128,6 +2258,19 @@ class WebDriver extends Helper { const aSec = sec || this.options.waitForTimeout; const current = await this.browser.getWindowHandle(); let target; + + if (isWebDriver5()) { + await this.browser.waitUntil(async () => { + await this.browser.getWindowHandles().then((handles) => { + if (handles.indexOf(current) - num > -1) { + target = handles[handles.indexOf(current) - num]; + } + }); + return target; + }, aSec * 1000, `There is no ability to switch to previous tab with offset ${num}`); + return this.browser.switchToWindow(target); + } + await this.browser.waitUntil(async () => { await this.browser.getWindowHandles().then((handles) => { if (handles.indexOf(current) - num > -1) { @@ -2708,4 +2851,8 @@ function prepareLocateFn(context) { }; } +function isWebDriver5() { + return version && version.indexOf('5') === 0; +} + module.exports = WebDriver; diff --git a/lib/plugin/commentStep.js b/lib/plugin/commentStep.js index 58985ee67..0077e91d8 100644 --- a/lib/plugin/commentStep.js +++ b/lib/plugin/commentStep.js @@ -11,13 +11,13 @@ const defaultGlobalName = '__'; * * ```js * Scenario('project update test', async (I) => { - * __`Prepare project`; - * const projectId = await I.createProject(); + * __`Given`; + * const projectId = await I.have('project'); * - * __`Change project`; + * __`When`; * projectPage.update(projectId, { title: 'new title' }); * - * __`Check project`; + * __`Then`; * projectPage.open(projectId); * I.see('new title', 'h1'); * }) @@ -25,11 +25,11 @@ const defaultGlobalName = '__'; * Steps prefixed with `__` will be printed as nested steps in `--steps` output: * * ``` - * Prepare project - * I create project - * Change project + * Given + * I have "project" + * When * projectPage update - * Check project + * Then * projectPage open * I see "new title", "h1" * ``` @@ -74,13 +74,30 @@ const defaultGlobalName = '__'; * } * } * ``` - * Then inside a test import a comment function from a plugin: + * Then inside a test import a comment function from a plugin. + * For instance, you can prepare Given/When/Then functions to use them inside tests: * * ```js * // inside a test - * const Step = codeceptjs.container.plugins('commentStep'); + * const step = codeceptjs.container.plugins('commentStep'); + * + * const Given = () => step`Given`; + * const When = () => step`When`; + * const Then = () => step`Then`; * ``` * + * Scenario('project update test', async (I) => { + * Given(); + * const projectId = await I.have('project'); + * + * When(); + * projectPage.update(projectId, { title: 'new title' }); + * + * Then(); + * projectPage.open(projectId); + * I.see('new title', 'h1'); + * }); + * ``` */ module.exports = function (config) { event.dispatcher.on(event.test.started, (test) => { diff --git a/lib/session.js b/lib/session.js index a7229ce3f..d3d7abe08 100644 --- a/lib/session.js +++ b/lib/session.js @@ -65,7 +65,7 @@ function session(sessionName, config, fn) { } recorder.add('save vars', async () => { - savedSessions[sessionName].vars = await savedSessions[sessionName].start(sessionName); + savedSessions[sessionName].vars = await savedSessions[sessionName].start(sessionName, config); }); } diff --git a/package.json b/package.json index 6cd187f77..68cd2adb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeceptjs", - "version": "2.5.0", + "version": "2.6.0", "description": "Supercharged End 2 End Testing Framework for NodeJS", "keywords": [ "acceptance", diff --git a/runio.js b/runio.js index da111d619..d766aa7f1 100755 --- a/runio.js +++ b/runio.js @@ -306,7 +306,7 @@ title: ${name} const dir = 'website'; if (fs.existsSync(dir)) { - await exec('rm -rf ' + dir); + await exec(`rm -rf ${dir}`); } await git((fn) => fn.clone('git@github.com:codecept-js/website.git', dir)); diff --git a/test/acceptance/codecept.Playwright.js b/test/acceptance/codecept.Playwright.js index 2d952ca9b..6b30f04c9 100644 --- a/test/acceptance/codecept.Playwright.js +++ b/test/acceptance/codecept.Playwright.js @@ -8,7 +8,7 @@ module.exports.config = { helpers: { Playwright: { url: TestHelper.siteUrl(), - show: false, + show: true, browser: process.env.BROWSER || 'chromium', }, ScreenshotSessionHelper: { diff --git a/test/acceptance/codecept.WebDriver.js b/test/acceptance/codecept.WebDriver.js index 574ea9b5e..b5ee5480f 100644 --- a/test/acceptance/codecept.WebDriver.js +++ b/test/acceptance/codecept.WebDriver.js @@ -18,6 +18,10 @@ module.exports.config = { // }, }, MockRequest: {}, + ScreenshotSessionHelper: { + require: '../support/ScreenshotSessionHelper.js', + outputPath: './output', + }, }, include: {}, bootstrap: done => setTimeout(done, 5000), // let's wait for selenium diff --git a/test/acceptance/config_test.js b/test/acceptance/config_test.js index ed0547a3c..01ba02ec0 100644 --- a/test/acceptance/config_test.js +++ b/test/acceptance/config_test.js @@ -24,6 +24,7 @@ Scenario('change config 4 @WebDriverIO @Puppeteer @Playwright @Protractor @Night return { url: 'https://codecept.io/', capabilities: { 'moz:title': test.title } }; }); + Scenario('change config 5 @WebDriverIO @Puppeteer @Playwright @Protractor @Nightmare', (I) => { I.amOnPage('/'); I.dontSeeInCurrentUrl('github.com'); diff --git a/test/acceptance/session_test.js b/test/acceptance/session_test.js index 5b8ad9d01..5974f38c8 100644 --- a/test/acceptance/session_test.js +++ b/test/acceptance/session_test.js @@ -1,4 +1,5 @@ const assert = require('assert'); +const path = require('path'); Feature('Session'); @@ -13,9 +14,7 @@ Scenario('simple session @WebDriverIO @Protractor @Puppeteer @Playwright', (I) = I.seeInCurrentUrl('/info'); }); -Scenario('screenshots reflect the current page of current session @Puppeteer @Playwright', async (I) => { - const outputPath = await I.getOutputPath(); - +Scenario('screenshots reflect the current page of current session @Puppeteer @Playwright @WebDriver', async (I) => { I.amOnPage('/'); I.saveScreenshot('session_default_1.png'); @@ -31,10 +30,10 @@ Scenario('screenshots reflect the current page of current session @Puppeteer @Pl }); const [default1Digest, default2Digest, john1Digest, john2Digest] = await I.getMD5Digests([ - `${outputPath}/session_default_1.png`, - `${outputPath}/session_default_2.png`, - `${outputPath}/session_john_1.png`, - `${outputPath}/session_john_2.png`, + `${output_dir}/session_default_1.png`, + `${output_dir}/session_default_2.png`, + `${output_dir}/session_john_1.png`, + `${output_dir}/session_john_2.png`, ]); // Assert that screenshots of same page in same session are equal @@ -131,6 +130,30 @@ Scenario('should work with within @WebDriverIO @Protractor @Puppeteer @Playwrigh }); }); +Scenario('change page emulation @Playwright', async (I) => { + const assert = require('assert'); + I.amOnPage('/'); + session('mobile user', { + viewport: { width: 300, height: 500 }, + }, async () => { + I.amOnPage('/'); + const width = await I.executeScript('window.innerWidth'); + assert.equal(width, 300); + }); +}); + + +Scenario('emulate iPhone @Playwright', async (I) => { + const { devices } = require('playwright'); + const assert = require('assert'); + I.amOnPage('/'); + session('mobile user', devices['iPhone 6'], async () => { + I.amOnPage('/'); + const width = await I.executeScript('window.innerWidth'); + assert.equal(width, 981); + }); +}); + xScenario('should use different base URL @Protractor @Puppeteer @Playwright', (I) => { // nah, that's broken I.amOnPage('/'); I.see('Welcome to test app'); diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index e61c6a5bb..40fdb283a 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -997,3 +997,43 @@ describe('Playwright - BasicAuth', () => { }); }); }); + +describe('Playwright - Emulation', () => { + before(() => { + const { devices } = require('playwright'); + global.codecept_dir = path.join(__dirname, '/../data'); + + I = new Playwright({ + url: 'http://localhost:8000', + browser: 'chromium', + windowSize: '500x700', + emulate: devices['iPhone 6'], + show: false, + restart: true, + waitForTimeout: 5000, + waitForAction: 500, + chrome: { + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }, + }); + I._init(); + return I._beforeSuite(); + }); + + beforeEach(() => { + return I._before().then(() => { + page = I.page; + browser = I.browser; + }); + }); + + afterEach(() => { + return I._after(); + }); + + it('should open page as iPhone ', async () => { + await I.amOnPage('/'); + const width = await I.executeScript('window.innerWidth'); + assert.equal(width, 980); + }); +}); diff --git a/test/runner/allure_test.js b/test/runner/allure_test.js index 929ba8136..5ea9f0069 100644 --- a/test/runner/allure_test.js +++ b/test/runner/allure_test.js @@ -2,6 +2,7 @@ const assert = require('assert'); const path = require('path'); const { exec } = require('child_process'); const fs = require('fs'); +const { satisfyNodeVersion } = require('../../lib/command/utils'); const { deleteDir } = require('../../lib/utils'); const runner = path.join(__dirname, '/../../bin/codecept.js'); @@ -58,7 +59,13 @@ describe('CodeceptJS Allure Plugin', () => { }); }); - it('should report BeforeSuite errors when executing via run-workers command', (done) => { + it('should report BeforeSuite errors when executing via run-workers command', function (done) { + try { + satisfyNodeVersion('>=11.7.0'); + } catch (err) { + this.skip(); + } + exec(codecept_workers_config('before_suite_test_failed.conf.js'), (err, stdout) => { stdout.should.include('FAIL | 0 passed, 1 failed'); diff --git a/test/runner/comment_step_test.js b/test/runner/comment_step_test.js index 099bb6eca..05749f728 100644 --- a/test/runner/comment_step_test.js +++ b/test/runner/comment_step_test.js @@ -3,9 +3,14 @@ const path = require('path'); const exec = require('child_process').exec; const runner = path.join(__dirname, '/../../bin/codecept.js'); -const codecept_dir = path.join(__dirname, '/../data/sandbox/configs/commentStep'); +const codecept_dir = path.join( + __dirname, + '/../data/sandbox/configs/commentStep', +); const codecept_run = `${runner} run`; -const config_run_config = (config, grep) => `${codecept_run} --config ${codecept_dir}/${config} ${grep ? `--grep "${grep}"` : ''}`; +const config_run_config = (config, grep) => `${codecept_run} --config ${codecept_dir}/${config} ${ + grep ? `--grep "${grep}"` : '' +}`; describe('CodeceptJS commentStep plugin', function () { this.timeout(3000); @@ -14,34 +19,39 @@ describe('CodeceptJS commentStep plugin', function () { process.chdir(codecept_dir); }); - it('should print nested steps when global var comments used', (done) => { - exec(`${config_run_config('codecept.conf.js', 'global var')} --debug`, (err, stdout) => { - stdout.should.include( - ` Prepare user base + it('should print nested steps when global var comments used', done => { + exec( + `${config_run_config('codecept.conf.js', 'global var')} --debug`, + (err, stdout) => { + stdout.should.include( + ` Prepare user base I print "other thins" - Update data + Update data I print "do some things" - Check the result + Check the result I print "see everything works"`, - ); - assert(!err); - done(); - }); + ); + assert(!err); + done(); + }, + ); }); - - it('should print nested steps when local var comments used', (done) => { - exec(`${config_run_config('codecept.conf.js', 'local var')} --debug`, (err, stdout) => { - stdout.should.include( - ` Prepare project + it('should print nested steps when local var comments used', done => { + exec( + `${config_run_config('codecept.conf.js', 'local var')} --debug`, + (err, stdout) => { + stdout.should.include( + ` Prepare project I print "other thins" - Update project + Update project I print "do some things" - Check project + Check project I print "see everything works"`, - ); - assert(!err); - done(); - }); + ); + assert(!err); + done(); + }, + ); }); }); diff --git a/test/support/ScreenshotSessionHelper.js b/test/support/ScreenshotSessionHelper.js index 49d82d2da..7f4c0809d 100644 --- a/test/support/ScreenshotSessionHelper.js +++ b/test/support/ScreenshotSessionHelper.js @@ -14,7 +14,7 @@ class ScreenshotSessionHelper extends Helper { constructor(config) { super(config); - this.outputPath = config.outputPath; + this.outputPath = output_dir; } getMD5Digests(files = []) { From 482d211eed165c323a670f28b2063748b5975cce Mon Sep 17 00:00:00 2001 From: George Griffiths Date: Sun, 29 Mar 2020 16:58:10 +0100 Subject: [PATCH 04/23] Playwright fixes - Work in progress (#2291) * fix wait for value * fix waitForEnabled * fix add cookies Co-authored-by: George Griffiths --- lib/helper/Playwright.js | 9 ++++--- lib/helper/extras/PlaywrightPropEngine.js | 32 +++++++++++++++++++++-- settings_override.xml | 0 test/helper/Playwright_test.js | 2 +- 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 settings_override.xml diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index fa7839ceb..c65fc3ed8 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -37,7 +37,7 @@ const popupStore = new Popup(); const consoleLogStore = new Console(); const availableBrowsers = ['chromium', 'webkit', 'firefox']; -const { createValueEngine } = require('./extras/PlaywrightPropEngine'); +const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine'); /** * Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside: * @@ -264,6 +264,7 @@ class Playwright extends Helper { // register an internal selector engine for reading value property of elements in a selector try { await playwright.selectors.register('__value', createValueEngine); + await playwright.selectors.register('__disabled', createDisabledEngine); } catch (e) { console.warn(e); } @@ -1336,9 +1337,9 @@ class Playwright extends Helper { */ async setCookie(cookie) { if (Array.isArray(cookie)) { - return this.browserContext.setCookies(...cookie); + return this.browserContext.addCookies(...cookie); } - return this.browserContext.setCookies([cookie]); + return this.browserContext.addCookies([cookie]); } /** @@ -1619,7 +1620,7 @@ class Playwright extends Helper { const context = await this._getContext(); if (!locator.isXPath()) { // playwright combined selectors - waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> :not([disabled])`, { timeout: waitTimeout }); + waiter = context.waitForSelector(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> __disabled=false`, { timeout: waitTimeout }); } else { const enabledFn = function ([locator, $XPath]) { eval($XPath); // eslint-disable-line no-eval diff --git a/lib/helper/extras/PlaywrightPropEngine.js b/lib/helper/extras/PlaywrightPropEngine.js index 28966ef9f..8da862604 100644 --- a/lib/helper/extras/PlaywrightPropEngine.js +++ b/lib/helper/extras/PlaywrightPropEngine.js @@ -11,7 +11,7 @@ module.exports.createValueEngine = () => { if (!root) { return null; } - return `${root.value}` === selector; + return `${root.value}`.includes(selector) ? root : null; }, // Returns all elements matching given selector in the root's subtree. @@ -19,7 +19,35 @@ module.exports.createValueEngine = () => { if (!root) { return null; } - return `${root.value}` === selector; + return `${root.value}`.includes(selector) ? root : null; + }, + }; +}; + +module.exports.createDisabledEngine = () => { + return { + // Creates a selector that matches given target when queried at the root. + // Can return undefined if unable to create one. + create(root, target) { + return null; + }, + + // Returns the first element matching given selector in the root's subtree. + query(root, value) { + const bool = value === 'true'; + if (!root) { + return null; + } + return root.disabled === bool ? root : null; + }, + + // Returns all elements matching given selector in the root's subtree. + queryAll(root, value) { + const bool = value === 'true'; + if (!root) { + return null; + } + return root.disabled === bool ? root : null; }, }; }; diff --git a/settings_override.xml b/settings_override.xml new file mode 100644 index 000000000..e69de29bb diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index 40fdb283a..83d43dba5 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -27,7 +27,7 @@ describe('Playwright', function () { I = new Playwright({ url: siteUrl, windowSize: '500x700', - show: true, + show: false, waitForTimeout: 5000, waitForAction: 500, restart: true, From 3e1953f5aa9119513d7ecfaeefe03599f72909f0 Mon Sep 17 00:00:00 2001 From: Davert Date: Sun, 29 Mar 2020 19:58:52 +0300 Subject: [PATCH 05/23] fixed tests for playwright --- lib/helper/Playwright.js | 80 +++++++++++++++++----------------- lib/helper/Puppeteer.js | 2 + package.json | 2 +- test/helper/Playwright_test.js | 10 ----- test/helper/webapi.js | 1 + 5 files changed, 44 insertions(+), 51 deletions(-) diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index c65fc3ed8..3ec3b5734 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -723,9 +723,9 @@ class Playwright extends Helper { assertElementExists(els, locator, 'Element'); await els[0].scrollIntoViewIfNeeded(); const elementCoordinates = await els[0]._clickablePoint(); - await this.executeScript((x, y) => window.scrollBy(x, y), elementCoordinates.x + offsetX, elementCoordinates.y + offsetY); + await this.executeScript((offsetX, offsetY) => window.scrollBy(offsetX, offsetY), { offsetX: elementCoordinates.x + offsetX, offsetY: elementCoordinates.y + offsetY }); } else { - await this.executeScript((x, y) => window.scrollTo(x, y), offsetX, offsetY); + await this.executeScript(({ offsetX, offsetY }) => window.scrollTo(offsetX, offsetY), { offsetX, offsetY }); } return this._waitForAction(); } @@ -983,12 +983,26 @@ class Playwright extends Helper { } /** - * {{> clickLink }} + * Clicks link and waits for navigation (deprecated) + */ + async clickLink(locator, context = null) { + console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.'); + console.log('Replace I.clickLink with I.click'); + return this.click(locator, context); + } + + /** * + * Force clicks an element without waiting for it to become visible and not animating. + * + * ```js + * I.forceClick('#hiddenButton'); + * I.forceClick('Click me', '#hidden'); + * ``` * */ - async clickLink(locator, context = null) { - return proceedClick.call(this, locator, context, { waitForNavigation: true }); + async forceClick(locator, context = null) { + return proceedClick.call(this, locator, context, { force: true }); } @@ -1381,39 +1395,33 @@ class Playwright extends Helper { } /** - * {{> executeScript }} + * Executes a script on the page: * - * If a function returns a Promise It will wait for it resolution. + * ```js + * I.executeScript(() => window.alert('Hello world')); + * ``` + * + * Additional parameters of the function can be passed as an object argument: + * + * ```js + * I.executeScript(({x, y}) => x + y, {x, y}); + * ``` + * You can pass only one parameter into a function + * but you can pass in array or object. + * + * ```js + * I.executeScript(([x, y]) => x + y, [x, y]); + * ``` + * If a function returns a Promise it will wait for its resolution. */ - async executeScript(fn) { + async executeScript(fn, arg) { let context = this.page; if (this.context && this.context.constructor.name === 'Frame') { context = this.context; // switching to iframe context } - return context.evaluate.apply(context, arguments); + return context.evaluate.apply(context, arg); } - /** - * {{> executeAsyncScript }} - * - * Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise. - */ - async executeAsyncScript(fn) { - const args = Array.from(arguments); - const asyncFn = function () { - const args = Array.from(arguments); - const fn = eval(`(${args.shift()})`); // eslint-disable-line no-eval - return new Promise((done) => { - args.push(done); - fn.apply(null, args); - }); - }; - args[0] = args[0].toString(); - args.unshift(asyncFn); - return this.page.evaluate.apply(this.page, args); - } - - /** * {{> grabTextFrom }} * @@ -1693,16 +1701,8 @@ class Playwright extends Helper { * {{> waitForClickable }} */ async waitForClickable(locator, waitTimeout) { - const els = await this._locate(locator); - assertElementExists(els, locator); - - return this.waitForFunction(isElementClickable, [els[0]], waitTimeout).catch(async (e) => { - if (/failed: timeout/i.test(e.message)) { - throw new Error(`element ${new Locator(locator).toString()} still not clickable after ${waitTimeout || this.options.waitForTimeout / 1000} sec`); - } else { - throw e; - } - }); + console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clikable'); + console.log('Remove usage of this function'); } /** diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index b80cb4156..94ca85b9b 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -1211,6 +1211,8 @@ class Puppeteer extends Helper { /** * {{> attachFile }} + * + * > ⚠ There is an [issue with file upload in Puppeteer 2.1.0 & 2.1.1](https://github.com/puppeteer/puppeteer/issues/5420), downgrade to 2.0.0 if you face it. */ async attachFile(locator, pathToFile) { const file = path.join(global.codecept_dir, pathToFile); diff --git a/package.json b/package.json index 68cd2adb6..117db7e9f 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "nodemon": "^1.19.4", "playwright": "^0.12.1", "protractor": "^5.4.1", - "puppeteer": "^2.1.1", + "puppeteer": "^2.0.0", "qrcode-terminal": "^0.12.0", "rosie": "^1.6.0", "runio.js": "^1.0.20", diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index 83d43dba5..e99201e67 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -84,16 +84,6 @@ describe('Playwright', function () { const url = await page.url(); return url.should.eql(`${siteUrl}/`); }); - - it('should be unauthenticated ', async () => { - let err = true; - try { - await I.amOnPage('/basic_auth'); - } catch (e) { - err = false; - } - if (err) throw new Error('Should fail at auth page'); - }); }); describe('grabDataFromPerformanceTiming', () => { diff --git a/test/helper/webapi.js b/test/helper/webapi.js index 4bcc2f0ca..ed014259b 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -400,6 +400,7 @@ module.exports.tests = function () { it('should execute async script', async function () { if (isHelper('TestCafe')) this.skip(); // TODO Not yet implemented + if (isHelper('Playwright')) this.skip(); // TODO Not yet implemented await I.amOnPage('/'); const val = await I.executeAsyncScript((val, done) => { From 31ad05ff8e7b86386ae9f9acd745781a65903595 Mon Sep 17 00:00:00 2001 From: Davert Date: Sun, 29 Mar 2020 22:21:31 +0300 Subject: [PATCH 06/23] fixed Playwright/Puppeteer tests --- lib/helper/Playwright.js | 2 +- package.json | 2 +- test/helper/Playwright_test.js | 76 ---------------------------------- 3 files changed, 2 insertions(+), 78 deletions(-) diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 3ec3b5734..ba0aedd29 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -1419,7 +1419,7 @@ class Playwright extends Helper { if (this.context && this.context.constructor.name === 'Frame') { context = this.context; // switching to iframe context } - return context.evaluate.apply(context, arg); + return context.evaluate.apply(context, [fn, arg]); } /** diff --git a/package.json b/package.json index 117db7e9f..1497eac41 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "nodemon": "^1.19.4", "playwright": "^0.12.1", "protractor": "^5.4.1", - "puppeteer": "^2.0.0", + "puppeteer": "^1.20.0", "qrcode-terminal": "^0.12.0", "rosie": "^1.6.0", "runio.js": "^1.0.20", diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index e99201e67..c0acfa3e5 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -771,82 +771,6 @@ describe('Playwright', function () { await FS.waitForFile('downloads/avatar.jpg', 5); }); }); - - describe('#waitForClickable', () => { - it('should wait for clickable', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: 'input#text' }); - }); - - it('should wait for clickable by XPath', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ xpath: './/input[@id="text"]' }); - }); - - it('should fail for disabled element', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: '#button' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {css: #button} still not clickable after 0.1 sec'); - }); - }); - - it('should fail for disabled element by XPath', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ xpath: './/button[@id="button"]' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {xpath: .//button[@id="button"]} still not clickable after 0.1 sec'); - }); - }); - - it('should fail for element not in viewport by top', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: '#notInViewportTop' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {css: #notInViewportTop} still not clickable after 0.1 sec'); - }); - }); - - it('should fail for element not in viewport by bottom', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: '#notInViewportBottom' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {css: #notInViewportBottom} still not clickable after 0.1 sec'); - }); - }); - - it('should fail for element not in viewport by left', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: '#notInViewportLeft' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {css: #notInViewportLeft} still not clickable after 0.1 sec'); - }); - }); - - it('should fail for element not in viewport by right', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: '#notInViewportRight' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {css: #notInViewportRight} still not clickable after 0.1 sec'); - }); - }); - - it('should fail for overlapping element', async () => { - await I.amOnPage('/form/wait_for_clickable'); - await I.waitForClickable({ css: '#div2_button' }, 0.1); - await I.waitForClickable({ css: '#div1_button' }, 0.1).then((isClickable) => { - if (isClickable) throw new Error('Element is clickable, but must be unclickable'); - }).catch((e) => { - e.message.should.include('element {css: #div1_button} still not clickable after 0.1 sec'); - }); - }); - }); }); let remoteBrowser; From 54a0d16aabd31a739d80fad05e5415193eecc892 Mon Sep 17 00:00:00 2001 From: Koushik Mohan Date: Sun, 29 Mar 2020 19:22:17 +0000 Subject: [PATCH 07/23] Fix run-workers issue in gherkin (#2293) --- lib/mochaFactory.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/mochaFactory.js b/lib/mochaFactory.js index 92b2234be..064f7992f 100644 --- a/lib/mochaFactory.js +++ b/lib/mochaFactory.js @@ -38,19 +38,21 @@ class MochaFactory { }; mocha.loadFiles = (fn) => { - // load features - mocha.files - .filter(file => file.match(/\.feature$/)) - .map(file => fs.readFileSync(file, 'utf8')) - .forEach(content => mocha.suite.addSuite(gherkinParser(content))); + if (mocha.suite.suites.length === 0) { + // load features + mocha.files + .filter(file => file.match(/\.feature$/)) + .map(file => fs.readFileSync(file, 'utf8')) + .forEach(content => mocha.suite.addSuite(gherkinParser(content))); - // remove feature files - mocha.files = mocha.files.filter(file => !file.match(/\.feature$/)); + // remove feature files + mocha.files = mocha.files.filter(file => !file.match(/\.feature$/)); - Mocha.prototype.loadFiles.call(mocha, fn); + Mocha.prototype.loadFiles.call(mocha, fn); - // add ids for each test - mocha.suite.eachTest(test => test.id = genTestId(test)); + // add ids for each test + mocha.suite.eachTest(test => test.id = genTestId(test)); + } }; // use standard reporter From 027447c949abd60c5aeeac92848c9e6331822cd4 Mon Sep 17 00:00:00 2001 From: Davert Date: Sun, 29 Mar 2020 22:49:20 +0300 Subject: [PATCH 08/23] fixed allure test --- test/runner/allure_test.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/runner/allure_test.js b/test/runner/allure_test.js index 5ea9f0069..501443e30 100644 --- a/test/runner/allure_test.js +++ b/test/runner/allure_test.js @@ -60,14 +60,12 @@ describe('CodeceptJS Allure Plugin', () => { }); it('should report BeforeSuite errors when executing via run-workers command', function (done) { - try { - satisfyNodeVersion('>=11.7.0'); - } catch (err) { + if (parseInt(process.version.match(/\d+/), 10) < 12) { this.skip(); } exec(codecept_workers_config('before_suite_test_failed.conf.js'), (err, stdout) => { - stdout.should.include('FAIL | 0 passed, 1 failed'); + stdout.should.include('FAIL | 0 passed'); const files = fs.readdirSync(path.join(codecept_dir, 'output/failed')); const testResultPath = files[0]; From 7eefb25e4aa01a0be60af68a5a5dd3ffaced839d Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 00:08:54 +0300 Subject: [PATCH 09/23] updated changelog, fixed tests --- CHANGELOG.md | 66 ++++++++++++++++++++++++++++++++++++++++ docs/locators.md | 2 ++ docs/playwright.md | 2 +- docs/shadow.md | 6 ++-- docs/webdriver.md | 6 ++-- lib/helper/Playwright.js | 21 +++++++++++++ lib/helper/Puppeteer.js | 3 ++ 7 files changed, 101 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f12d69ee4..e394bfd75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,69 @@ +## 2.6.0 + +* **[Playwright] Updated to Playwright 0.12** by @Georgegriff. + +Upgrade playwright to ^0.12: + +``` +npm i playwright@^0.12 --save +``` + +[Notable changes](https://github.com/microsoft/playwright/releases/tag/v0.12.0): + * Fixed opening two browsers on start + * `executeScript` - passed function now accepts only one argument. Pass in objects or arrays if you need multtple arguments: +```js +// Old style, does not work anymore: +I.executeScript((x, y) => x + y, x, y); +// New style, passing an object: +I.executeScript(({x, y}) => x + y, {x, y}); +``` + * `click` - automatically waits for element to become clickable (visible, not animated) and waits for navigation. + * `clickLink` - deprecated + * `waitForClickable` - deprecated + * Added support for custom locators. See #2277 + * Introduced [device emulation](/playwright/#device-emulation): + * globally via `emulate` config option + * per session + +**[WebDriver] Updated to webdriverio v6** by @PeterNgTr. + +Read [release notes](https://webdriver.io/blog/2020/03/26/webdriverio-v6-released.html), then +upgrade webdriverio to ^6.0: + +``` +npm i webdriverio@^6.0 --save +``` +*(webdriverio v5 support is deprecated and will be removed in CodeceptJS 3.0)* + +[WebDriver] Introduced [Shadow DOM support](/shadow) by @gkushang + +```js +I.click({ shadow: ['my-app', 'recipe-hello', 'button'] }); +``` + +* **Fixed parallel execution of `run-workers` for Gherkin** scenarios by @koushikmohan1996 +* [MockRequest] Updated and **moved to [standalone package](https://github.com/codecept-js/mock-request)**: + * full support for record/replay mode for Puppeteer + * added `mockServer` method to use flexible PollyJS API to define mocks + * fixed stale browser screen in record mode. +* [Playwright] Added support on for `screenshotOnFail` plugin by @amonkc +* Gherkin improvement: setting different tags per examples. See #2208 by @acuper +* [TestCafe] Updated `click` to take first visible element. Fixes #2226 by @theTainted +* [Puppeteer][WebDriver] Updated `waitForClickable` method to check for element overlapping. See #2261 by @PiQx +* [REST] Rrespect Content-Type header. See #2262 by @pmarshall-legacy +* [commentStep Plugin introduced](/plugins#commentstep). Allows to annotate logical parts of a test: + +```js +__`Given`; +I.amOnPage('/profile') + +__`When`; +I.click('Logout'); + +__`Then`; +I.see('You are logged out'); +``` + ## 2.5.0 * **Experimental: [Playwright](/playwright) helper introduced**. diff --git a/docs/locators.md b/docs/locators.md index 923c62931..e53f8eeaa 100644 --- a/docs/locators.md +++ b/docs/locators.md @@ -12,6 +12,8 @@ CodeceptJS provides flexible strategies for locating elements: * [Locator Builder](#locator-builder) * [ID locators](#id-locators): by CSS id or by accessibility id * [Custom Locator Strategies](#custom-locators): by data attributes or whatever you prefer. +* [Shadow DOM](/shadow): to access shadow dom elements +* [React](/react): to access React elements by component names and props Most methods in CodeceptJS use locators which can be either a string or an object. diff --git a/docs/playwright.md b/docs/playwright.md index 595c5eb52..0de3b4d08 100644 --- a/docs/playwright.md +++ b/docs/playwright.md @@ -232,7 +232,7 @@ const { devices } = require('playwright'); helpers: { Playwright: { // regular config goes here - emulate: devices['iPhone 6'] + emulate: devices['iPhone 6'], } } ``` diff --git a/docs/shadow.md b/docs/shadow.md index 9955a3ccd..ad4aa0560 100644 --- a/docs/shadow.md +++ b/docs/shadow.md @@ -5,6 +5,8 @@ title: Locating Shadow Dom Elements # Locating Shadow Dom Elements +> ℹ Shadow DOM locators is supported only in WebDriver helper + Shadow DOM is one of the key browser features that make up web components. Web components are a really great way to build reusable elements, and are able to scale all the way up to complete web applications. Style encapsulation, the feature that gives shadow DOM it's power, has been a bit of a pain when it comes to E2E or UI testing. Things just got a little easier though, as CodeceptJS introduced built-in support for shadow DOM via locators of type `shadow`. Let's dig into what they're all about. Generated HTML code may often look like this (ref: [Salesforce's Lighting Web Components](https://github.com/salesforce/lwc)): @@ -31,11 +33,11 @@ This uses custom elements, `my-app`, `recipe-hello`, `recipe-hello-binding` and For Web Components or [Salesforce's Lighting Web Components](https://github.com/salesforce/lwc) with Shadow DOM's, a special `shadow` locator is available. It allows to select an element by its shadow dom sequences and sequences are defined as an Array of `elements`. Elements defined in the array of `elements` must be in the ordered the shadow elements appear in the DOM. ```js -{ shadow: ['my-app', 'recipe-hello', 'button'] } +{ shadow: ['my-app', 'recipe-hello', 'button'] } { shadow: ['my-app', 'recipe-hello-binding', 'ui-input', 'input.input'] } ``` -In WebDriver, you can use shadow locators in any method where locator is required. +In WebDriver, you can use shadow locators in any method where locator is required. For example, to fill value in `input` field or to click the `Click Me!` button, in above HTML code: diff --git a/docs/webdriver.md b/docs/webdriver.md index 519b632cd..5487d490e 100644 --- a/docs/webdriver.md +++ b/docs/webdriver.md @@ -204,7 +204,7 @@ Scenario('login test', (I) => { > ▶ Actions like `amOnPage`, `click`, `fillField` are not limited to WebDriver only. They work similarly for all available helpers. [Go to Basics guide to learn them](/basics#writing-tests). -An empty test case can be created with `codeceptjs gt` command. +An empty test case can be created with `npx codeceptjs gt` command. ``` npx codeceptjs gt @@ -223,7 +223,7 @@ Scenario('open my website', (I) => { This is just enough to run a test, open a browser, and think what to do next to write a test case. -When you execute such test with `codeceptjs run` command you may see the browser is started +When you execute such test with `npx codeceptjs run` command you may see the browser is started ``` npx codeceptjs run --steps @@ -264,6 +264,8 @@ Scenario('create todo item', (I) => { > [▶ Working example of CodeceptJS WebDriver tests](https://github.com/DavertMik/codeceptjs-webdriver-example) for TodoMVC application. +WebDriver helper supports standard [CSS/XPath and text locators](/locators) as well as non-trivial [React locators](/react) and [Shadow DOM](/shadow). + ## Waiting Web applications do not always respond instantly. That's why WebDriver protocol has methods to wait for changes on a page. CodeceptJS provides such commands prefixed with `wait*` so you could explicitly define what effects we wait for. diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index ba0aedd29..6a48da92f 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -32,6 +32,7 @@ const Console = require('./extras/Console'); let playwright; let perfTiming; +let defaultSelectorEnginesInitialized = false; const popupStore = new Popup(); const consoleLogStore = new Console(); @@ -62,6 +63,7 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright * * `show`: (optional, default: false) - show browser window. * * `restart`: (optional, default: true) - restart browser between tests. * * `disableScreenshots`: (optional, default: false) - don't save screenshot on failure. + * * `emulate`: (optional, default: {}) launch browser in device emulation mode. * * `fullPageScreenshots` (optional, default: false) - make full page screenshots on failure. * * `uniqueScreenshotNames`: (optional, default: false) - option to prevent screenshot override if you have scenarios with the same name in different suites. * * `keepBrowserState`: (optional, default: false) - keep browser state between tests when `restart` is set to false. @@ -156,6 +158,23 @@ const { createValueEngine, createDisabledEngine } = require('./extras/Playwright * } * ``` * + * #### Example #6: Lunach tests emulating iPhone 6 + * + * + * + * ```js + * const { devices } = require('playwright'); + * + * { + * helpers: { + * Playwright: { + * url: "http://localhost", + * emulate: devices['iPhone 6'], + * } + * } + * } + * ``` + * * Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored. * * ## Access From Helpers @@ -262,6 +281,8 @@ class Playwright extends Helper { async _init() { // register an internal selector engine for reading value property of elements in a selector + if (defaultSelectorEnginesInitialized) return; + defaultSelectorEnginesInitialized = true; try { await playwright.selectors.register('__value', createValueEngine); await playwright.selectors.register('__disabled', createDisabledEngine); diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 94ca85b9b..f823d47f9 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -222,6 +222,9 @@ class Puppeteer extends Helper { { name: 'show', message: 'Show browser window', default: true, type: 'confirm', }, + { + name: 'windowSize', message: 'Browser viewport size', default: '1200x900', + }, ]; } From 3b041e5615776158731aafcc63d55e9154a93777 Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 01:31:42 +0300 Subject: [PATCH 10/23] fixed tests --- CHANGELOG.md | 2 + docs/plugins.md | 103 +++++++++++++++++++++++++++++++ lib/command/run-workers.js | 2 + lib/helper/Puppeteer.js | 3 - test/runner/comment_step_test.js | 22 ++----- test/runner/run_workers_test.js | 1 + 6 files changed, 114 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e394bfd75..a153897ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,8 @@ I.click({ shadow: ['my-app', 'recipe-hello', 'button'] }); * [TestCafe] Updated `click` to take first visible element. Fixes #2226 by @theTainted * [Puppeteer][WebDriver] Updated `waitForClickable` method to check for element overlapping. See #2261 by @PiQx * [REST] Rrespect Content-Type header. See #2262 by @pmarshall-legacy +* [allure plugin] Fixes BeforeSuite failures in allure reports. See #2248 by @Georgegriff +* [WebDriver][Puppeteer][Playwright] A screenshot of for an active session is saved in multi-session mode. See #2253 by @ChexWarrior * [commentStep Plugin introduced](/plugins#commentstep). Allows to annotate logical parts of a test: ```js diff --git a/docs/plugins.md b/docs/plugins.md index 8d708480d..b065af547 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -305,6 +305,109 @@ Scenario('login', async (I, login) => { - `config` +## commentStep + +Add descriptive nested steps for your tests: + +```js +Scenario('project update test', async (I) => { + __`Given`; + const projectId = await I.have('project'); + + __`When`; + projectPage.update(projectId, { title: 'new title' }); + + __`Then`; + projectPage.open(projectId); + I.see('new title', 'h1'); +}) +``` + +Steps prefixed with `__` will be printed as nested steps in `--steps` output: + + Given + I have "project" + When + projectPage update + Then + projectPage open + I see "new title", "h1" + +Also those steps will be exported to allure reports. + +This plugin can be used + +### Config + +- `enabled` - (default: false) enable a plugin +- `regusterGlobal` - (default: false) register `__` template literal function globally. You can override function global name by providing a name as a value. + +### Examples + +Registering `__` globally: + +```js +plugins: { + commentStep: { + enabled: true, + registerGlobal: true + } +} +``` + +Registering `Step` globally: + +```js +plugins: { + commentStep: { + enabled: true, + registerGlobal: 'Step' + } +} +``` + +Using only local function names: + +```js +plugins: { + commentStep: { + enabled: true + } +} +``` + +Then inside a test import a comment function from a plugin. +For instance, you can prepare Given/When/Then functions to use them inside tests: + +```js +// inside a test +const step = codeceptjs.container.plugins('commentStep'); + +const Given = () => step`Given`; +const When = () => step`When`; +const Then = () => step`Then`; +``` + +Scenario('project update test', async (I) => { + Given(); + const projectId = await I.have('project'); + + When(); + projectPage.update(projectId, { title: 'new title' }); + + Then(); + projectPage.open(projectId); + I.see('new title', 'h1'); +}); + +``` + +``` + +### Parameters + +- `config` + ## customLocator Creates a [custom locator][3] by using special attributes in HTML. diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js index cb8361134..fb832bb6d 100644 --- a/lib/command/run-workers.js +++ b/lib/command/run-workers.js @@ -232,6 +232,7 @@ function simplifyObject(object) { const updateFinishedTests = (test, maxReruns) => { const { id } = test; if (finishedTests[id]) { + const stats = { passes: 0, failures: -1, tests: 0 }; if (finishedTests[id].runs > maxReruns) { const stats = { passes: 0, @@ -241,6 +242,7 @@ const updateFinishedTests = (test, maxReruns) => { appendStats(stats); } else { finishedTests[id].runs++; + appendStats(stats); } } else { finishedTests[id] = test; diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index f823d47f9..0c48b7f43 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -309,9 +309,6 @@ class Puppeteer extends Helper { const bc = await this.browser.createIncognitoBrowserContext(); const page = await bc.newPage(); - if (config) { - await page.emulate(config); - } // Create a new page inside context. return bc; diff --git a/test/runner/comment_step_test.js b/test/runner/comment_step_test.js index 05749f728..17a9d41e2 100644 --- a/test/runner/comment_step_test.js +++ b/test/runner/comment_step_test.js @@ -23,14 +23,9 @@ describe('CodeceptJS commentStep plugin', function () { exec( `${config_run_config('codecept.conf.js', 'global var')} --debug`, (err, stdout) => { - stdout.should.include( - ` Prepare user base - I print "other thins" - Update data - I print "do some things" - Check the result - I print "see everything works"`, - ); + stdout.should.include(' Prepare user base \n I print "other thins"'); + stdout.should.include(' Update data \n I print "do some things"'); + stdout.should.include(' Check the result \n I print "see everything works"'); assert(!err); done(); }, @@ -41,14 +36,9 @@ describe('CodeceptJS commentStep plugin', function () { exec( `${config_run_config('codecept.conf.js', 'local var')} --debug`, (err, stdout) => { - stdout.should.include( - ` Prepare project - I print "other thins" - Update project - I print "do some things" - Check project - I print "see everything works"`, - ); + stdout.should.include(' Prepare project \n I print "other thins"'); + stdout.should.include(' Update project \n I print "do some things"'); + stdout.should.include(' Check project \n I print "see everything works"'); assert(!err); done(); }, diff --git a/test/runner/run_workers_test.js b/test/runner/run_workers_test.js index 4479a417e..c5761f036 100644 --- a/test/runner/run_workers_test.js +++ b/test/runner/run_workers_test.js @@ -105,6 +105,7 @@ describe('CodeceptJS Workers Runner', function () { it('should retry test', function (done) { if (!semver.satisfies(process.version, '>=11.7.0')) this.skip('not for node version'); exec(`${codecept_run} 2 --grep "retry"`, (err, stdout, stderr) => { + console.log(stdout); stdout.should.include('CodeceptJS'); // feature stdout.should.include('OK | 1 passed'); done(); From 3c129231695ac1b490606cf8129bd98243c5a479 Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 01:42:21 +0300 Subject: [PATCH 11/23] fixed playwright setup, fixed worker tests --- .github/workflows/playwright.yml | 6 ++++++ lib/command/run-workers.js | 11 ++--------- test/runner/run_workers_test.js | 1 - 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 99a4eeeb9..c04cf2bb3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -8,6 +8,11 @@ on: branches: - master +env: + CI: true + # Force terminal colors. @see https://www.npmjs.com/package/colors + FORCE_COLOR: 1 + jobs: build: @@ -29,6 +34,7 @@ jobs: sudo apt-get install libgbm-dev libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libgles2 libevent-2.1-6 libnotify4 libxslt1.1 sudo apt-get install xvfb sudo apt-get install php + - run: xvfb-run --auto-servernum -- bash -c "npm run ccoverage 2>./chromium-linux-testrun.log" - name: npm install run: | npm install diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js index fb832bb6d..3f91f3b2d 100644 --- a/lib/command/run-workers.js +++ b/lib/command/run-workers.js @@ -233,17 +233,10 @@ const updateFinishedTests = (test, maxReruns) => { const { id } = test; if (finishedTests[id]) { const stats = { passes: 0, failures: -1, tests: 0 }; - if (finishedTests[id].runs > maxReruns) { - const stats = { - passes: 0, - failures: -1, - tests: 0, - }; - appendStats(stats); - } else { + if (finishedTests[id].runs <= maxReruns) { finishedTests[id].runs++; - appendStats(stats); } + appendStats(stats); } else { finishedTests[id] = test; finishedTests[id].runs = 1; diff --git a/test/runner/run_workers_test.js b/test/runner/run_workers_test.js index c5761f036..4479a417e 100644 --- a/test/runner/run_workers_test.js +++ b/test/runner/run_workers_test.js @@ -105,7 +105,6 @@ describe('CodeceptJS Workers Runner', function () { it('should retry test', function (done) { if (!semver.satisfies(process.version, '>=11.7.0')) this.skip('not for node version'); exec(`${codecept_run} 2 --grep "retry"`, (err, stdout, stderr) => { - console.log(stdout); stdout.should.include('CodeceptJS'); // feature stdout.should.include('OK | 1 passed'); done(); From edaf4ef64f5324559a7fd4b37606cf5b4ce9ca60 Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 23:06:11 +0300 Subject: [PATCH 12/23] fixed WebDriver test --- lib/helper/Playwright.js | 5 ++--- lib/helper/Puppeteer.js | 10 ++++------ lib/helper/WebDriver.js | 8 +++----- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 6a48da92f..d3683df9d 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -382,12 +382,11 @@ class Playwright extends Helper { if (!session) { this.activeSessionName = ''; - const existingPages = await this.browserContext.pages(); - await this._setPage(existingPages[0]); } else { this.activeSessionName = session; - await this._setPage(this.sessionPages[this.activeSessionName]); } + const existingPages = await this.browserContext.pages(); + await this._setPage(existingPages[0]); return this._waitForAction(); }, diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 0c48b7f43..51a7c6689 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -248,6 +248,7 @@ class Puppeteer extends Helper { async _before() { + this.sessionPages = {}; recorder.retry({ retries: 5, when: err => err.message.indexOf('context') > -1, // ignore context errors @@ -326,15 +327,12 @@ class Puppeteer extends Helper { if (!session) { this.activeSessionName = ''; - - // Find original page on default context - const defaultCtx = this.browser.defaultBrowserContext(); - const existingPages = defaultCtx.targets().filter(t => t.type() === 'page'); - await this._setPage(await existingPages[0].page()); } else { this.activeSessionName = session; - await this._setPage(this.sessionPages[this.activeSessionName]); } + const defaultCtx = this.browser.defaultBrowserContext(); + const existingPages = defaultCtx.targets().filter(t => t.type() === 'page'); + await this._setPage(await existingPages[0].page()); return this._waitForAction(); }, diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index 524d616a0..a8e6b6733 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -584,7 +584,7 @@ class WebDriver extends Helper { opts = this._validateConfig(Object.assign(this.options, opts)); this.debugSection('New Browser', JSON.stringify(opts)); const browser = await webdriverio.remote(opts); - + this.activeSessionName = sessionName; if (opts.timeouts && this.isWeb) { await this._defineBrowserTimeout(browser, opts.timeouts); } @@ -604,11 +604,9 @@ class WebDriver extends Helper { }, restoreVars: async (session) => { if (!session) { - this.browser = defaultSession; - } else { - this.activeSessionName = session; - this.browser = this.sessionWindows[this.activeSessionName]; + this.activeSessionName = ''; } + this.browser = defaultSession; this.$$ = this.browser.$$.bind(this.browser); }, }; From 4a568ff92699334285096e1707f7608eaff690f8 Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 23:07:18 +0300 Subject: [PATCH 13/23] removed xvfb from playwright setup --- .github/workflows/playwright.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c04cf2bb3..0fd3df9f8 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -34,7 +34,6 @@ jobs: sudo apt-get install libgbm-dev libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libgles2 libevent-2.1-6 libnotify4 libxslt1.1 sudo apt-get install xvfb sudo apt-get install php - - run: xvfb-run --auto-servernum -- bash -c "npm run ccoverage 2>./chromium-linux-testrun.log" - name: npm install run: | npm install From 35d4a922d79f095a89974e1978ba68cdc8b40e8d Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 23:20:54 +0300 Subject: [PATCH 14/23] added xvfb run to playwright setup --- .github/workflows/playwright.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 0fd3df9f8..844653cba 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -40,8 +40,8 @@ jobs: - name: start a server run: "php -S 127.0.0.1:8000 -t test/data/app &" - name: run chromium tests - run: " ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug" + run: 'xvfb-run --auto-servernum -- bash -c "node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug"' - name: run firefox tests - run: "BROWSER=firefox ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug" + run: 'xvfb-run --auto-servernum -- bash -c "BROWSER=firefox node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug"' - name: run webkit tests - run: "BROWSER=webkit ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug" + run: 'xvfb-run --auto-servernum -- bash -c "BROWSER=webkit node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug"' From 1bd3963172844550c403880d641636b0003b39f6 Mon Sep 17 00:00:00 2001 From: Davert Date: Mon, 30 Mar 2020 23:35:27 +0300 Subject: [PATCH 15/23] added one lib to playwright setup --- .github/workflows/playwright.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 844653cba..ebec574b9 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -31,8 +31,7 @@ jobs: - name: install required packages run: | sudo apt-get update - sudo apt-get install libgbm-dev libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libgles2 libevent-2.1-6 libnotify4 libxslt1.1 - sudo apt-get install xvfb + sudo apt-get install libgbm1 libgbm-dev libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libgles2 libevent-2.1-6 libnotify4 libxslt1.1 sudo apt-get install php - name: npm install run: | @@ -40,8 +39,8 @@ jobs: - name: start a server run: "php -S 127.0.0.1:8000 -t test/data/app &" - name: run chromium tests - run: 'xvfb-run --auto-servernum -- bash -c "node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug"' + run: "./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug" - name: run firefox tests - run: 'xvfb-run --auto-servernum -- bash -c "BROWSER=firefox node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug"' + run: "BROWSER=firefox node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug" - name: run webkit tests - run: 'xvfb-run --auto-servernum -- bash -c "BROWSER=webkit node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug"' + run: "BROWSER=webkit node ./bin/codecept.js run -c test/acceptance/codecept.Playwright.js --grep @Playwright --debug" From fd0a2d1c4108c08dee1daebbe4c9aaddf2f811f9 Mon Sep 17 00:00:00 2001 From: Sharode <60081532+Sharode@users.noreply.github.com> Date: Mon, 30 Mar 2020 14:10:08 -0400 Subject: [PATCH 16/23] fixed typo in basic within section readme (#2294) --- docs/basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/basics.md b/docs/basics.md index d3cb25dca..9e02721fe 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -622,7 +622,7 @@ within('.js-signup-form', () => { I.see('There were problems creating your account.'); ``` -> ⚠ `within` can cause problems when used incorrectly. If you see a weired behavior of a test try to refactor it to not use `within`. It is recommended to keep within for simplest cases when possible. +> ⚠ `within` can cause problems when used incorrectly. If you see a weird behavior of a test try to refactor it to not use `within`. It is recommended to keep within for simplest cases when possible. `within` can also work with IFrames. A special `frame` locator is required to locate the iframe and get into its context. From 3d02a58b740436283f1cd97f1d18bc68c5acc984 Mon Sep 17 00:00:00 2001 From: Paul Vincent Beigang Date: Fri, 27 Mar 2020 22:24:58 +0100 Subject: [PATCH 17/23] Store --profile in process.env.profile instead of process.profile to make it available in workers. --- docs/configuration.md | 4 ++-- examples/codecept.conf.example.js | 4 ++-- lib/command/interactive.js | 2 +- lib/command/run-multiple.js | 2 +- lib/command/run-rerun.js | 2 +- lib/command/run-workers.js | 2 +- lib/command/run.js | 2 +- test/data/sandbox/config.js | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f8edcaa58..17940cafe 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -118,7 +118,7 @@ exports.config = { ## Profile -Using values from `process.profile` you can change the config dynamically. +Using `process.env.profile` you can change the config dynamically. It provides value of `--profile` option passed to runner. Use its value to change config value on the fly. @@ -134,7 +134,7 @@ exports.config = { WebDriver: { url: 'http://localhost:3000', // load value from `profile` - browser: process.profile || 'firefox' + browser: process.env.profile || 'firefox' } } diff --git a/examples/codecept.conf.example.js b/examples/codecept.conf.example.js index 8f5d63510..da0bc889d 100644 --- a/examples/codecept.conf.example.js +++ b/examples/codecept.conf.example.js @@ -1,6 +1,6 @@ console.log('Use JS config file'); -console.log(process.profile); +console.log(process.env.profile); exports.config = { tests: './*_test.js', @@ -9,7 +9,7 @@ exports.config = { helpers: { WebDriverIO: { url: 'http://localhost', - browser: process.profile || 'firefox', + browser: process.env.profile || 'firefox', restart: true, }, }, diff --git a/lib/command/interactive.js b/lib/command/interactive.js index f49542487..53fbdb3c6 100644 --- a/lib/command/interactive.js +++ b/lib/command/interactive.js @@ -6,7 +6,7 @@ const event = require('../event'); const output = require('../output'); module.exports = function (path, options) { - process.profile = options.profile; + process.env.profile = options.profile; const testsPath = getTestRoot(path); const config = getConfig(testsPath); diff --git a/lib/command/run-multiple.js b/lib/command/run-multiple.js index 8bf77b4e2..9265ae045 100644 --- a/lib/command/run-multiple.js +++ b/lib/command/run-multiple.js @@ -29,7 +29,7 @@ let processesDone; module.exports = function (selectedRuns, options) { // registering options globally to use in config - process.profile = options.profile; + process.env.profile = options.profile; const configFile = options.config; let codecept; diff --git a/lib/command/run-rerun.js b/lib/command/run-rerun.js index eee1f2b65..2faa0a7d2 100644 --- a/lib/command/run-rerun.js +++ b/lib/command/run-rerun.js @@ -7,7 +7,7 @@ const Codecept = require('../rerun'); module.exports = function (test, options) { // registering options globally to use in config - process.profile = options.profile; + process.env.profile = options.profile; const configFile = options.config; let codecept; diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js index 3f91f3b2d..1a1a471ef 100644 --- a/lib/command/run-workers.js +++ b/lib/command/run-workers.js @@ -32,7 +32,7 @@ module.exports = function (workers, options) { 'Required minimum Node version of 11.7.0 to work with "run-workers"', ); - process.profile = options.profile; + process.env.profile = options.profile; const { Worker } = require('worker_threads'); diff --git a/lib/command/run.js b/lib/command/run.js index f9a65ee2f..d03248566 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -10,7 +10,7 @@ const Codecept = require('../codecept'); module.exports = function (test, options) { // registering options globally to use in config - process.profile = options.profile; + process.env.profile = options.profile; const configFile = options.config; let codecept; diff --git a/test/data/sandbox/config.js b/test/data/sandbox/config.js index 460dea48c..e11369349 100644 --- a/test/data/sandbox/config.js +++ b/test/data/sandbox/config.js @@ -1,4 +1,4 @@ -const profile = process.profile; +const profile = process.env.profile; exports.config = { tests: './*_test.js', From efbe0a3de65f71b367b2cc93e99b6d5e8802a346 Mon Sep 17 00:00:00 2001 From: Paul Vincent Beigang Date: Tue, 31 Mar 2020 12:26:33 +0200 Subject: [PATCH 18/23] Backward compatibility for using in process.profile. --- lib/command/interactive.js | 2 ++ lib/command/run-rerun.js | 3 ++- lib/command/run.js | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/command/interactive.js b/lib/command/interactive.js index 53fbdb3c6..a19245c63 100644 --- a/lib/command/interactive.js +++ b/lib/command/interactive.js @@ -6,6 +6,8 @@ const event = require('../event'); const output = require('../output'); module.exports = function (path, options) { + // Backward compatibility for --profile + process.profile = options.profile; process.env.profile = options.profile; const testsPath = getTestRoot(path); diff --git a/lib/command/run-rerun.js b/lib/command/run-rerun.js index 2faa0a7d2..46e56c743 100644 --- a/lib/command/run-rerun.js +++ b/lib/command/run-rerun.js @@ -7,7 +7,8 @@ const Codecept = require('../rerun'); module.exports = function (test, options) { // registering options globally to use in config - process.env.profile = options.profile; + // Backward compatibility for --profile + process.profile = options.profile; const configFile = options.config; let codecept; diff --git a/lib/command/run.js b/lib/command/run.js index d03248566..33e56be97 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -10,6 +10,8 @@ const Codecept = require('../codecept'); module.exports = function (test, options) { // registering options globally to use in config + // Backward compatibility for --profile + process.profile = options.profile; process.env.profile = options.profile; const configFile = options.config; let codecept; From d56ad007d5af342942cd159b27794e0f413fbfb3 Mon Sep 17 00:00:00 2001 From: Paul Vincent Beigang Date: Tue, 31 Mar 2020 13:43:58 +0200 Subject: [PATCH 19/23] Add fallback in test config. --- test/data/sandbox/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/data/sandbox/config.js b/test/data/sandbox/config.js index e11369349..7244343c9 100644 --- a/test/data/sandbox/config.js +++ b/test/data/sandbox/config.js @@ -1,4 +1,4 @@ -const profile = process.env.profile; +const profile = process.env.profile || process.profile; exports.config = { tests: './*_test.js', From d082c6c43a0e9505f694dd1c9fb868684d3f9226 Mon Sep 17 00:00:00 2001 From: Paul Vincent Beigang Date: Tue, 31 Mar 2020 19:04:51 +0200 Subject: [PATCH 20/23] Store --profile in process.env.profile also for run-rerun command. --- lib/command/run-rerun.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/command/run-rerun.js b/lib/command/run-rerun.js index 46e56c743..3b5e28df0 100644 --- a/lib/command/run-rerun.js +++ b/lib/command/run-rerun.js @@ -9,6 +9,7 @@ module.exports = function (test, options) { // registering options globally to use in config // Backward compatibility for --profile process.profile = options.profile; + process.env.profile = options.profile; const configFile = options.config; let codecept; From 59bbd9caab36fdf94b89f302a1cc70bbe814372e Mon Sep 17 00:00:00 2001 From: Davert Date: Tue, 31 Mar 2020 21:21:24 +0300 Subject: [PATCH 21/23] fixed tests --- test/acceptance/codecept.Playwright.js | 2 +- test/acceptance/session_test.js | 3 ++- test/helper/Playwright_test.js | 13 ------------- test/helper/webapi.js | 2 +- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/test/acceptance/codecept.Playwright.js b/test/acceptance/codecept.Playwright.js index 6b30f04c9..2d952ca9b 100644 --- a/test/acceptance/codecept.Playwright.js +++ b/test/acceptance/codecept.Playwright.js @@ -8,7 +8,7 @@ module.exports.config = { helpers: { Playwright: { url: TestHelper.siteUrl(), - show: true, + show: false, browser: process.env.BROWSER || 'chromium', }, ScreenshotSessionHelper: { diff --git a/test/acceptance/session_test.js b/test/acceptance/session_test.js index 5974f38c8..294ed82e1 100644 --- a/test/acceptance/session_test.js +++ b/test/acceptance/session_test.js @@ -145,12 +145,13 @@ Scenario('change page emulation @Playwright', async (I) => { Scenario('emulate iPhone @Playwright', async (I) => { const { devices } = require('playwright'); + if (process.env.BROWSER === 'firefox') return; const assert = require('assert'); I.amOnPage('/'); session('mobile user', devices['iPhone 6'], async () => { I.amOnPage('/'); const width = await I.executeScript('window.innerWidth'); - assert.equal(width, 981); + assert.ok(width > 950 && width < 1000); }); }); diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index c0acfa3e5..8e99cb00f 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -54,19 +54,6 @@ describe('Playwright', function () { return I._after(); }); - xdescribe('Session', () => { - it('should not fail for localStorage.clear() on about:blank', async () => { - I.options.restart = false; - return I.page.goto('about:blank') - .then(() => I._after()) - .then(() => { I.options.restart = true; }) - .catch((e) => { - I.options.restart = true; - throw new Error(e); - }); - }); - }); - describe('open page : #amOnPage', () => { it('should open main page of configured site', async () => { await I.amOnPage('/'); diff --git a/test/helper/webapi.js b/test/helper/webapi.js index ed014259b..b21647898 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -400,7 +400,7 @@ module.exports.tests = function () { it('should execute async script', async function () { if (isHelper('TestCafe')) this.skip(); // TODO Not yet implemented - if (isHelper('Playwright')) this.skip(); // TODO Not yet implemented + if (isHelper('Playwright')) return; // It won't be implemented await I.amOnPage('/'); const val = await I.executeAsyncScript((val, done) => { From c185a267a0df0181971db9827bfc8ee73630c062 Mon Sep 17 00:00:00 2001 From: Davert Date: Tue, 31 Mar 2020 22:30:46 +0300 Subject: [PATCH 22/23] updated changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a153897ea..92ac8aa0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,9 +50,18 @@ I.click({ shadow: ['my-app', 'recipe-hello', 'button'] }); * Gherkin improvement: setting different tags per examples. See #2208 by @acuper * [TestCafe] Updated `click` to take first visible element. Fixes #2226 by @theTainted * [Puppeteer][WebDriver] Updated `waitForClickable` method to check for element overlapping. See #2261 by @PiQx +* [Puppeteer] Dropped `puppeteer-firefox` support, as Puppeteer supports Firefox natively. * [REST] Rrespect Content-Type header. See #2262 by @pmarshall-legacy * [allure plugin] Fixes BeforeSuite failures in allure reports. See #2248 by @Georgegriff * [WebDriver][Puppeteer][Playwright] A screenshot of for an active session is saved in multi-session mode. See #2253 by @ChexWarrior +* Fixed `--profile` option by @pablopaul. Profile value to be passed into `run-multiple` and `run-workers`: + +``` +npx codecept run-workers 2 --profile firefox +``` + +Value is available at `process.env.profile` (previously `process.profile`). See #2302. Fixes #1968 #1315 + * [commentStep Plugin introduced](/plugins#commentstep). Allows to annotate logical parts of a test: ```js From 92010410b24aa25f3149a2ef9368d6ac1eb4511f Mon Sep 17 00:00:00 2001 From: Davert Date: Tue, 31 Mar 2020 22:36:14 +0300 Subject: [PATCH 23/23] updated documentation --- docs/helpers/Appium.md | 26 ++++ docs/helpers/Nightmare.md | 12 +- docs/helpers/Playwright.md | 186 +++++++++++++------------ docs/helpers/Protractor.md | 12 +- docs/helpers/Puppeteer.md | 160 +++++++++++---------- docs/helpers/TestCafe.md | 12 +- docs/helpers/WebDriver.md | 276 +++++++++++++++++++++---------------- 7 files changed, 394 insertions(+), 290 deletions(-) diff --git a/docs/helpers/Appium.md b/docs/helpers/Appium.md index 965a9cec6..82e78c615 100644 --- a/docs/helpers/Appium.md +++ b/docs/helpers/Appium.md @@ -1107,6 +1107,30 @@ I.waitForText('Thank you, form has been submitted', 5, '#modal'); - `sec` **[number][8]** (optional, `1` by default) time in seconds to wait (optional, default `1`) - `context` **([string][4] \| [object][6])?** (optional) element located by CSS|XPath|strict locator. (optional, default `null`) +### \_isShadowLocator + +Check if locator is type of "Shadow" + +#### Parameters + +- `locator` **[object][6]** + +### \_locateShadow + +Locate Element within the Shadow Dom + +#### Parameters + +- `locator` **[object][6]** + +### \_smartWait + +Smart Wait to locate an element + +#### Parameters + +- `locator` **[object][6]** + ### \_locate Get elements by different locator types, including strict locator. @@ -1477,6 +1501,8 @@ I.moveCursorTo('#submit', 5,5); #### Parameters - `locator` **([string][4] \| [object][6])** located by CSS|XPath|strict locator. +- `xOffset` +- `yOffset` - `offsetX` **[number][8]** (optional, `0` by default) X-axis offset. (optional, default `0`) - `offsetY` **[number][8]** (optional, `0` by default) Y-axis offset. (optional, default `0`) diff --git a/docs/helpers/Nightmare.md b/docs/helpers/Nightmare.md index 7829cda42..dcc74f483 100644 --- a/docs/helpers/Nightmare.md +++ b/docs/helpers/Nightmare.md @@ -876,15 +876,23 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']); ### setCookie -Sets a cookie. +Sets cookie(s). + +Can be a single cookie object or an array of cookies: ```js I.setCookie({name: 'auth', value: true}); + +// as array +I.setCookie([ + {name: 'auth', value: true}, + {name: 'agree', value: true} +]); ``` #### Parameters -- `cookie` **[object][4]** a cookie object.Wrapper for `.cookies.set(cookie)`. +- `cookie` **([object][4] | [array][11])** a cookie object or array of cookie objects.Wrapper for `.cookies.set(cookie)`. [See more][14] ### triggerMouseEvent diff --git a/docs/helpers/Playwright.md b/docs/helpers/Playwright.md index cdf451da0..500337d54 100644 --- a/docs/helpers/Playwright.md +++ b/docs/helpers/Playwright.md @@ -32,6 +32,7 @@ This helper should be configured in codecept.json or codecept.conf.js - `show`: - show browser window. - `restart`: - restart browser between tests. - `disableScreenshots`: - don't save screenshot on failure. +- `emulate`: launch browser in device emulation mode. - `fullPageScreenshots` - make full page screenshots on failure. - `uniqueScreenshotNames`: - option to prevent screenshot override if you have scenarios with the same name in different suites. - `keepBrowserState`: - keep browser state between tests when `restart` is set to false. @@ -126,6 +127,21 @@ This helper should be configured in codecept.json or codecept.conf.js } ``` +#### Example #6: Lunach tests emulating iPhone 6 + +```js +const { devices } = require('playwright'); + +{ + helpers: { + Playwright: { + url: "http://localhost", + emulate: devices['iPhone 6'], + } + } +} +``` + Note: When connecting to remote browser `show` and specific `chrome` options (e.g. `headless` or `devtools`) are ignored. ## Access From Helpers @@ -376,16 +392,12 @@ I.click({css: 'nav a.login'}); ### clickLink -Performs a click on a link and waits for navigation before moving on. - -```js -I.clickLink('Logout', '#nav'); -``` +Clicks link and waits for navigation (deprecated) #### Parameters -- `locator` **([string][7] | [object][5])** clickable link or button located by text, or any element located by CSS|XPath|strict locator -- `context` **([string][7]? | [object][5])** (optional, `null` by default) element to search in CSS|XPath|Strict locator +- `locator` +- `context` ### closeCurrentTab @@ -575,68 +587,33 @@ I.dragSlider('#slider', -70); - `locator` **([string][7] | [object][5])** located by label|name|CSS|XPath|strict locator. - `offsetX` **[number][8]** position to drag. -### executeAsyncScript - -Executes async script on page. -Provided function should execute a passed callback (as first argument) to signal it is finished. +### executeScript -Example: In Vue.js to make components completely rendered we are waiting for [nextTick][9]. +Executes a script on the page: ```js -I.executeAsyncScript(function(done) { - Vue.nextTick(done); // waiting for next tick -}); +I.executeScript(() => window.alert('Hello world')); ``` -By passing value to `done()` function you can return values. -Additional arguments can be passed as well, while `done` function is always last parameter in arguments list. +Additional parameters of the function can be passed as an object argument: ```js -let val = await I.executeAsyncScript(function(url, done) { - // in browser context - $.ajax(url, { success: (data) => done(data); } -}, 'http://ajax.callback.url/'); +I.executeScript(({x, y}) => x + y, {x, y}); ``` -#### Parameters - -- `fn` **([string][7] | [function][10])** function to be executed in browser context. -- `args` **...any** to be passed to function. - -Returns **[Promise][11]<any>** Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise. - -### executeScript - -Executes sync script on a page. -Pass arguments to function as additional parameters. -Will return execution result to a test. -In this case you should use async function and await to receive results. - -Example with jQuery DatePicker: +You can pass only one parameter into a function +but you can pass in array or object. ```js -// change date of jQuery DatePicker -I.executeScript(function() { - // now we are inside browser context - $('date').datetimepicker('setDate', new Date()); -}); +I.executeScript(([x, y]) => x + y, [x, y]); ``` -Can return values. Don't forget to use `await` to get them. - -```js -let date = await I.executeScript(function(el) { - // only basic types can be returned - return $(el).datetimepicker('getDate').toString(); -}, '#date'); // passing jquery selector -``` +If a function returns a Promise it will wait for its resolution. #### Parameters -- `fn` **([string][7] | [function][10])** function to be executed in browser context. -- `args` **...any** to be passed to function. - -Returns **[Promise][11]<any>** If a function returns a Promise It will wait for it resolution. +- `fn` +- `arg` ### fillField @@ -659,6 +636,20 @@ I.fillField({css: 'form#login input[name=username]'}, 'John'); - `field` **([string][7] | [object][5])** located by label|name|CSS|XPath|strict locator. - `value` **[string][7]** text value to fill. +### forceClick + +Force clicks an element without waiting for it to become visible and not animating. + +```js +I.forceClick('#hiddenButton'); +I.forceClick('Click me', '#hidden'); +``` + +#### Parameters + +- `locator` +- `context` + ### grabAttributeFrom Retrieves an attribute from an element located by CSS or XPath and returns it to test. @@ -674,7 +665,7 @@ let hint = await I.grabAttributeFrom('#tooltip', 'title'); - `locator` **([string][7] | [object][5])** element located by CSS|XPath|strict locator. - `attr` **[string][7]** attribute name. -Returns **[Promise][11]<[string][7]>** attribute value +Returns **[Promise][9]<[string][7]>** attribute value ### grabBrowserLogs @@ -685,7 +676,7 @@ let logs = await I.grabBrowserLogs(); console.log(JSON.stringify(logs)) ``` -Returns **[Promise][11]<[Array][12]<any>>** +Returns **[Promise][9]<[Array][10]<any>>** ### grabCookie @@ -702,7 +693,7 @@ assert(cookie.value, '123456'); - `name` **[string][7]?** cookie name. -Returns **[Promise][11]<[string][7]>** attribute valueReturns cookie in JSON format. If name not passed returns all cookies for this domain. +Returns **[Promise][9]<[string][7]>** attribute valueReturns cookie in JSON format. If name not passed returns all cookies for this domain. ### grabCssPropertyFrom @@ -718,7 +709,7 @@ const value = await I.grabCssPropertyFrom('h3', 'font-weight'); - `locator` **([string][7] | [object][5])** element located by CSS|XPath|strict locator. - `cssProperty` **[string][7]** CSS property name. -Returns **[Promise][11]<[string][7]>** CSS value +Returns **[Promise][9]<[string][7]>** CSS value ### grabCurrentUrl @@ -730,7 +721,7 @@ let url = await I.grabCurrentUrl(); console.log(`Current URL is [${url}]`); ``` -Returns **[Promise][11]<[string][7]>** current URL +Returns **[Promise][9]<[string][7]>** current URL ### grabDataFromPerformanceTiming @@ -798,7 +789,7 @@ let postHTML = await I.grabHTMLFrom('#post'); - `locator` - `element` **([string][7] | [object][5])** located by CSS|XPath|strict locator. -Returns **[Promise][11]<[string][7]>** HTML code for an element +Returns **[Promise][9]<[string][7]>** HTML code for an element ### grabNumberOfOpenTabs @@ -808,7 +799,7 @@ Grab number of open tabs. let tabs = await I.grabNumberOfOpenTabs(); ``` -Returns **[Promise][11]<[number][8]>** number of open tabs +Returns **[Promise][9]<[number][8]>** number of open tabs ### grabNumberOfVisibleElements @@ -822,7 +813,7 @@ let numOfElements = await I.grabNumberOfVisibleElements('p'); - `locator` **([string][7] | [object][5])** located by CSS|XPath|strict locator. -Returns **[Promise][11]<[number][8]>** number of visible elements +Returns **[Promise][9]<[number][8]>** number of visible elements ### grabPageScrollPosition @@ -833,7 +824,7 @@ Resumes test execution, so **should be used inside an async function with `await let { x, y } = await I.grabPageScrollPosition(); ``` -Returns **[Promise][11]<[Object][5]<[string][7], any>>** scroll position +Returns **[Promise][9]<[Object][5]<[string][7], any>>** scroll position ### grabPopupText @@ -843,7 +834,7 @@ Grab the text within the popup. If no popup is visible then it will return null await I.grabPopupText(); ``` -Returns **[Promise][11]<([string][7] | null)>** +Returns **[Promise][9]<([string][7] | null)>** ### grabSource @@ -854,7 +845,7 @@ Resumes test execution, so should be used inside an async function. let pageSource = await I.grabSource(); ``` -Returns **[Promise][11]<[string][7]>** source code +Returns **[Promise][9]<[string][7]>** source code ### grabTextFrom @@ -871,7 +862,7 @@ If multiple elements found returns an array of texts. - `locator` **([string][7] | [object][5])** element located by CSS|XPath|strict locator. -Returns **[Promise][11]<([string][7] | [Array][12]<[string][7]>)>** attribute value +Returns **[Promise][9]<([string][7] | [Array][10]<[string][7]>)>** attribute value ### grabTitle @@ -882,7 +873,7 @@ Resumes test execution, so **should be used inside async with `await`** operator let title = await I.grabTitle(); ``` -Returns **[Promise][11]<[string][7]>** title +Returns **[Promise][9]<[string][7]>** title ### grabValueFrom @@ -897,7 +888,7 @@ let email = await I.grabValueFrom('input[name=email]'); - `locator` **([string][7] | [object][5])** field located by label|name|CSS|XPath|strict locator. -Returns **[Promise][11]<[string][7]>** attribute value +Returns **[Promise][9]<[string][7]>** attribute value ### haveRequestHeaders @@ -937,11 +928,22 @@ Open new tab and switch to it I.openNewTab(); ``` +You can pass in [page options][11] to emulate device on this page + +```js +// enable mobile +I.openNewTab({ isMobile: true }); +``` + +#### Parameters + +- `options` + ### pressKey Presses a key in the browser (on a focused element). -_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][13]. +_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][12]. ```js I.pressKey('Backspace'); @@ -1000,13 +1002,13 @@ Some of the supported key names are: #### Parameters -- `key` **([string][7] | [Array][12]<[string][7]>)** key or array of keys to press._Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/Playwright#1313][14]). +- `key` **([string][7] | [Array][10]<[string][7]>)** key or array of keys to press._Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/Playwright#1313][13]). ### pressKeyDown Presses a key in the browser and leaves it in a down state. -To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][15]). +To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][14]). ```js I.pressKeyDown('Control'); @@ -1022,7 +1024,7 @@ I.pressKeyUp('Control'); Releases a key in the browser which was previously set to a down state. -To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][15]). +To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][14]). ```js I.pressKeyDown('Control'); @@ -1090,7 +1092,7 @@ I.saveScreenshot('debug.png', true) //resizes to available scrollHeight and scro #### Parameters - `fileName` **[string][7]** file name to save. -- `fullPage` **[boolean][16]** (optional, `false` by default) flag to enable fullscreen screenshot mode. +- `fullPage` **[boolean][15]** (optional, `false` by default) flag to enable fullscreen screenshot mode. ### scrollPageToBottom @@ -1376,19 +1378,27 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']); #### Parameters - `select` **([string][7] | [object][5])** field located by label|name|CSS|XPath|strict locator. -- `option` **([string][7] | [Array][12]<any>)** visible text or value of option. +- `option` **([string][7] | [Array][10]<any>)** visible text or value of option. ### setCookie -Sets a cookie. +Sets cookie(s). + +Can be a single cookie object or an array of cookies: ```js I.setCookie({name: 'auth', value: true}); + +// as array +I.setCookie([ + {name: 'auth', value: true}, + {name: 'agree', value: true} +]); ``` #### Parameters -- `cookie` **[object][5]** a cookie object. +- `cookie` **([object][5] | [array][10])** a cookie object or array of cookie objects. ### switchTo @@ -1531,8 +1541,8 @@ I.waitForFunction((count) => window.requests == count, [3], 5) // pass args and #### Parameters -- `fn` **([string][7] | [function][10])** to be executed in browser context. -- `argsOrSec` **([Array][12]<any> | [number][8])?** (optional, `1` by default) arguments for function or seconds. +- `fn` **([string][7] | [function][16])** to be executed in browser context. +- `argsOrSec` **([Array][10]<any> | [number][8])?** (optional, `1` by default) arguments for function or seconds. - `sec` **[number][8]?** (optional, `1` by default) time in seconds to wait ### waitForInvisible @@ -1570,7 +1580,7 @@ I.waitForRequest(request => request.url() === 'http://example.com' && request.me #### Parameters -- `urlOrPredicate` **([string][7] | [function][10])** +- `urlOrPredicate` **([string][7] | [function][16])** - `sec` **[number][8]?** seconds to wait ### waitForResponse @@ -1584,7 +1594,7 @@ I.waitForResponse(request => request.url() === 'http://example.com' && request.m #### Parameters -- `urlOrPredicate` **([string][7] | [function][10])** +- `urlOrPredicate` **([string][7] | [function][16])** - `sec` **[number][8]?** number of seconds to wait ### waitForText @@ -1684,7 +1694,7 @@ I.waitUntil(() => window.requests == 0, 5); #### Parameters -- `fn` **([function][10] | [string][7])** function which is executed in browser context. +- `fn` **([function][16] | [string][7])** function which is executed in browser context. - `sec` **[number][8]** (optional, `1` by default) time in seconds to wait - `timeoutMsg` **[string][7]** message to show in case of timeout fail. - `interval` **[number][8]?** @@ -1719,20 +1729,20 @@ I.waitUrlEquals('http://127.0.0.1:8000/info'); [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number -[9]: https://vuejs.org/v2/api/#Vue-nextTick +[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise -[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise +[11]: https://github.com/microsoft/playwright/blob/v0.12.1/docs/api.md#browsernewpageoptions -[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[12]: #fillfield -[13]: #fillfield +[13]: https://github.com/GoogleChrome/Playwright/issues/1313 -[14]: https://github.com/GoogleChrome/Playwright/issues/1313 +[14]: #click -[15]: #click +[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[16]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function [17]: https://codecept.io/react diff --git a/docs/helpers/Protractor.md b/docs/helpers/Protractor.md index fe0bb3544..3e0ec6440 100644 --- a/docs/helpers/Protractor.md +++ b/docs/helpers/Protractor.md @@ -1207,15 +1207,23 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']); ### setCookie -Sets a cookie. +Sets cookie(s). + +Can be a single cookie object or an array of cookies: ```js I.setCookie({name: 'auth', value: true}); + +// as array +I.setCookie([ + {name: 'auth', value: true}, + {name: 'agree', value: true} +]); ``` #### Parameters -- `cookie` **[object][10]** a cookie object. +- `cookie` **([object][10] | [array][14])** a cookie object or array of cookie objects. ### switchTo diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md index c0d60459c..9a3092abc 100644 --- a/docs/helpers/Puppeteer.md +++ b/docs/helpers/Puppeteer.md @@ -287,7 +287,7 @@ I.attachFile('form input[name=avatar]', 'data/avatar.jpg'); #### Parameters - `locator` **([string][8] | [object][6])** field located by label|name|CSS|XPath|strict locator. -- `pathToFile` **[string][8]** local file path relative to codecept.json config file. +- `pathToFile` **[string][8]** local file path relative to codecept.json config file.> ⚠ There is an [issue with file upload in Puppeteer 2.1.0 & 2.1.1][9], downgrade to 2.0.0 if you face it. ### cancelPopup @@ -601,7 +601,7 @@ I.dragSlider('#slider', -70); #### Parameters - `locator` **([string][8] | [object][6])** located by label|name|CSS|XPath|strict locator. -- `offsetX` **[number][9]** position to drag. +- `offsetX` **[number][10]** position to drag. @@ -613,7 +613,7 @@ This action supports [React locators](https://codecept.io/react#locators) Executes async script on page. Provided function should execute a passed callback (as first argument) to signal it is finished. -Example: In Vue.js to make components completely rendered we are waiting for [nextTick][10]. +Example: In Vue.js to make components completely rendered we are waiting for [nextTick][11]. ```js I.executeAsyncScript(function(done) { @@ -633,10 +633,10 @@ let val = await I.executeAsyncScript(function(url, done) { #### Parameters -- `fn` **([string][8] | [function][11])** function to be executed in browser context. +- `fn` **([string][8] | [function][12])** function to be executed in browser context. - `args` **...any** to be passed to function. -Returns **[Promise][12]<any>** Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise. +Returns **[Promise][13]<any>** Asynchronous scripts can also be executed with `executeScript` if a function returns a Promise. ### executeScript @@ -666,10 +666,10 @@ let date = await I.executeScript(function(el) { #### Parameters -- `fn` **([string][8] | [function][11])** function to be executed in browser context. +- `fn` **([string][8] | [function][12])** function to be executed in browser context. - `args` **...any** to be passed to function. -Returns **[Promise][12]<any>** If a function returns a Promise It will wait for it resolution. +Returns **[Promise][13]<any>** If a function returns a Promise It will wait for it resolution. ### fillField @@ -711,7 +711,7 @@ let hint = await I.grabAttributeFrom('#tooltip', 'title'); - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. - `attr` **[string][8]** attribute name. -Returns **[Promise][12]<[string][8]>** attribute value +Returns **[Promise][13]<[string][8]>** attribute value @@ -727,7 +727,7 @@ let logs = await I.grabBrowserLogs(); console.log(JSON.stringify(logs)) ``` -Returns **[Promise][12]<[Array][13]<any>>** +Returns **[Promise][13]<[Array][14]<any>>** ### grabCookie @@ -744,7 +744,7 @@ assert(cookie.value, '123456'); - `name` **[string][8]?** cookie name. -Returns **[Promise][12]<[string][8]>** attribute valueReturns cookie in JSON format. If name not passed returns all cookies for this domain. +Returns **[Promise][13]<[string][8]>** attribute valueReturns cookie in JSON format. If name not passed returns all cookies for this domain. ### grabCssPropertyFrom @@ -760,7 +760,7 @@ const value = await I.grabCssPropertyFrom('h3', 'font-weight'); - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. - `cssProperty` **[string][8]** CSS property name. -Returns **[Promise][12]<[string][8]>** CSS value +Returns **[Promise][13]<[string][8]>** CSS value @@ -777,7 +777,7 @@ let url = await I.grabCurrentUrl(); console.log(`Current URL is [${url}]`); ``` -Returns **[Promise][12]<[string][8]>** current URL +Returns **[Promise][13]<[string][8]>** current URL ### grabDataFromPerformanceTiming @@ -845,7 +845,7 @@ let postHTML = await I.grabHTMLFrom('#post'); - `locator` - `element` **([string][8] | [object][6])** located by CSS|XPath|strict locator. -Returns **[Promise][12]<[string][8]>** HTML code for an element +Returns **[Promise][13]<[string][8]>** HTML code for an element ### grabNumberOfOpenTabs @@ -855,7 +855,7 @@ Grab number of open tabs. let tabs = await I.grabNumberOfOpenTabs(); ``` -Returns **[Promise][12]<[number][9]>** number of open tabs +Returns **[Promise][13]<[number][10]>** number of open tabs ### grabNumberOfVisibleElements @@ -869,7 +869,7 @@ let numOfElements = await I.grabNumberOfVisibleElements('p'); - `locator` **([string][8] | [object][6])** located by CSS|XPath|strict locator. -Returns **[Promise][12]<[number][9]>** number of visible elements +Returns **[Promise][13]<[number][10]>** number of visible elements @@ -885,7 +885,7 @@ Resumes test execution, so **should be used inside an async function with `await let { x, y } = await I.grabPageScrollPosition(); ``` -Returns **[Promise][12]<[Object][6]<[string][8], any>>** scroll position +Returns **[Promise][13]<[Object][6]<[string][8], any>>** scroll position ### grabPopupText @@ -895,7 +895,7 @@ Grab the text within the popup. If no popup is visible then it will return null await I.grabPopupText(); ``` -Returns **[Promise][12]<([string][8] | null)>** +Returns **[Promise][13]<([string][8] | null)>** ### grabSource @@ -906,7 +906,7 @@ Resumes test execution, so should be used inside an async function. let pageSource = await I.grabSource(); ``` -Returns **[Promise][12]<[string][8]>** source code +Returns **[Promise][13]<[string][8]>** source code ### grabTextFrom @@ -923,7 +923,7 @@ If multiple elements found returns an array of texts. - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -Returns **[Promise][12]<([string][8] | [Array][13]<[string][8]>)>** attribute value +Returns **[Promise][13]<([string][8] | [Array][14]<[string][8]>)>** attribute value @@ -939,7 +939,7 @@ Resumes test execution, so **should be used inside async with `await`** operator let title = await I.grabTitle(); ``` -Returns **[Promise][12]<[string][8]>** title +Returns **[Promise][13]<[string][8]>** title ### grabValueFrom @@ -954,12 +954,12 @@ let email = await I.grabValueFrom('input[name=email]'); - `locator` **([string][8] | [object][6])** field located by label|name|CSS|XPath|strict locator. -Returns **[Promise][12]<[string][8]>** attribute value +Returns **[Promise][13]<[string][8]>** attribute value ### handleDownloads Sets a directory to where save files. Allows to test file downloads. -Should be used with [FileSystem helper][14] to check that file were downloaded correctly. +Should be used with [FileSystem helper][15] to check that file were downloaded correctly. By default files are saved to `output/downloads`. This directory is cleaned on every `handleDownloads` call, to ensure no old files are kept. @@ -1002,8 +1002,8 @@ I.moveCursorTo('#submit', 5,5); #### Parameters - `locator` **([string][8] | [object][6])** located by CSS|XPath|strict locator. -- `offsetX` **[number][9]** (optional, `0` by default) X-axis offset. -- `offsetY` **[number][9]** (optional, `0` by default) Y-axis offset. +- `offsetX` **[number][10]** (optional, `0` by default) X-axis offset. +- `offsetY` **[number][10]** (optional, `0` by default) Y-axis offset. This action supports [React locators](https://codecept.io/react#locators) @@ -1021,7 +1021,7 @@ I.openNewTab(); Presses a key in the browser (on a focused element). -_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][15]. +_Hint:_ For populating text field or textarea, it is recommended to use [`fillField`][16]. ```js I.pressKey('Backspace'); @@ -1080,13 +1080,13 @@ Some of the supported key names are: #### Parameters -- `key` **([string][8] | [Array][13]<[string][8]>)** key or array of keys to press._Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313][16]). +- `key` **([string][8] | [Array][14]<[string][8]>)** key or array of keys to press._Note:_ Shortcuts like `'Meta'` + `'A'` do not work on macOS ([GoogleChrome/puppeteer#1313][17]). ### pressKeyDown Presses a key in the browser and leaves it in a down state. -To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][17]). +To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][18]). ```js I.pressKeyDown('Control'); @@ -1102,7 +1102,7 @@ I.pressKeyUp('Control'); Releases a key in the browser which was previously set to a down state. -To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][17]). +To make combinations with modifier key and user operation (e.g. `'Control'` + [`click`][18]). ```js I.pressKeyDown('Control'); @@ -1129,8 +1129,8 @@ First parameter can be set to `maximize`. #### Parameters -- `width` **[number][9]** width in pixels or `maximize`. -- `height` **[number][9]** height in pixels.Unlike other drivers Puppeteer changes the size of a viewport, not the window! +- `width` **[number][10]** width in pixels or `maximize`. +- `height` **[number][10]** height in pixels.Unlike other drivers Puppeteer changes the size of a viewport, not the window! Puppeteer does not control the window of a browser so it can't adjust its real size. It also can't maximize a window. @@ -1170,7 +1170,7 @@ I.saveScreenshot('debug.png', true) //resizes to available scrollHeight and scro #### Parameters - `fileName` **[string][8]** file name to save. -- `fullPage` **[boolean][18]** (optional, `false` by default) flag to enable fullscreen screenshot mode. +- `fullPage` **[boolean][19]** (optional, `false` by default) flag to enable fullscreen screenshot mode. ### scrollPageToBottom @@ -1201,8 +1201,8 @@ I.scrollTo('#submit', 5, 5); #### Parameters - `locator` **([string][8] | [object][6])** located by CSS|XPath|strict locator. -- `offsetX` **[number][9]** (optional, `0` by default) X-axis offset. -- `offsetY` **[number][9]** (optional, `0` by default) Y-axis offset. +- `offsetX` **[number][10]** (optional, `0` by default) X-axis offset. +- `offsetY` **[number][10]** (optional, `0` by default) Y-axis offset. ### see @@ -1410,7 +1410,7 @@ I.seeNumberOfElements('#submitBtn', 1); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `num` **[number][9]** number of elements. +- `num` **[number][10]** number of elements. This action supports [React locators](https://codecept.io/react#locators) @@ -1428,7 +1428,7 @@ I.seeNumberOfVisibleElements('.buttons', 3); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `num` **[number][9]** number of elements. +- `num` **[number][10]** number of elements. This action supports [React locators](https://codecept.io/react#locators) @@ -1483,19 +1483,27 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']); #### Parameters - `select` **([string][8] | [object][6])** field located by label|name|CSS|XPath|strict locator. -- `option` **([string][8] | [Array][13]<any>)** visible text or value of option. +- `option` **([string][8] | [Array][14]<any>)** visible text or value of option. ### setCookie -Sets a cookie. +Sets cookie(s). + +Can be a single cookie object or an array of cookies: ```js I.setCookie({name: 'auth', value: true}); + +// as array +I.setCookie([ + {name: 'auth', value: true}, + {name: 'agree', value: true} +]); ``` #### Parameters -- `cookie` **[object][6]** a cookie object. +- `cookie` **([object][6] | [array][14])** a cookie object or array of cookie objects. ### switchTo @@ -1521,7 +1529,7 @@ I.switchToNextTab(2); #### Parameters -- `num` **[number][9]** +- `num` **[number][10]** ### switchToPreviousTab @@ -1534,7 +1542,7 @@ I.switchToPreviousTab(2); #### Parameters -- `num` **[number][9]** +- `num` **[number][10]** ### uncheckOption @@ -1564,7 +1572,7 @@ I.wait(2); // wait 2 secs #### Parameters -- `sec` **[number][9]** number of second to wait. +- `sec` **[number][10]** number of second to wait. ### waitForClickable @@ -1580,7 +1588,7 @@ I.waitForClickable('.btn.continue', 5); // wait for 5 secs - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. - `waitTimeout` -- `sec` **[number][9]?** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]?** (optional, `1` by default) time in seconds to wait ### waitForDetached @@ -1594,7 +1602,7 @@ I.waitForDetached('#popup'); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait ### waitForElement @@ -1609,7 +1617,7 @@ I.waitForElement('.btn.continue', 5); // wait for 5 secs #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `sec` **[number][9]?** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]?** (optional, `1` by default) time in seconds to wait @@ -1624,7 +1632,7 @@ Element can be located by CSS or XPath. #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `sec` **[number][9]** (optional) time in seconds to wait, 1 by default. +- `sec` **[number][10]** (optional) time in seconds to wait, 1 by default. ### waitForFunction @@ -1643,9 +1651,9 @@ I.waitForFunction((count) => window.requests == count, [3], 5) // pass args and #### Parameters -- `fn` **([string][8] | [function][11])** to be executed in browser context. -- `argsOrSec` **([Array][13]<any> | [number][9])?** (optional, `1` by default) arguments for function or seconds. -- `sec` **[number][9]?** (optional, `1` by default) time in seconds to wait +- `fn` **([string][8] | [function][12])** to be executed in browser context. +- `argsOrSec` **([Array][14]<any> | [number][10])?** (optional, `1` by default) arguments for function or seconds. +- `sec` **[number][10]?** (optional, `1` by default) time in seconds to wait ### waitForInvisible @@ -1659,7 +1667,7 @@ I.waitForInvisible('#popup'); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait ### waitForNavigation @@ -1682,8 +1690,8 @@ I.waitForRequest(request => request.url() === 'http://example.com' && request.me #### Parameters -- `urlOrPredicate` **([string][8] | [function][11])** -- `sec` **[number][9]?** seconds to wait +- `urlOrPredicate` **([string][8] | [function][12])** +- `sec` **[number][10]?** seconds to wait ### waitForResponse @@ -1696,8 +1704,8 @@ I.waitForResponse(request => request.url() === 'http://example.com' && request.m #### Parameters -- `urlOrPredicate` **([string][8] | [function][11])** -- `sec` **[number][9]?** number of seconds to wait +- `urlOrPredicate` **([string][8] | [function][12])** +- `sec` **[number][10]?** number of seconds to wait ### waitForText @@ -1713,7 +1721,7 @@ I.waitForText('Thank you, form has been submitted', 5, '#modal'); #### Parameters - `text` **[string][8]** to wait for. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait - `context` **([string][8] | [object][6])?** (optional) element located by CSS|XPath|strict locator. ### waitForValue @@ -1728,7 +1736,7 @@ I.waitForValue('//input', "GoodValue"); - `field` **([string][8] | [object][6])** input field. - `value` **[string][8]** expected value. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait ### waitForVisible @@ -1742,7 +1750,7 @@ I.waitForVisible('#popup'); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to waitThis method accepts [React selectors][19]. +- `sec` **[number][10]** (optional, `1` by default) time in seconds to waitThis method accepts [React selectors][20]. ### waitInUrl @@ -1755,7 +1763,7 @@ I.waitInUrl('/info', 2); #### Parameters - `urlPart` **[string][8]** value to check. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait ### waitNumberOfVisibleElements @@ -1768,8 +1776,8 @@ I.waitNumberOfVisibleElements('a', 3); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `num` **[number][9]** number of elements. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `num` **[number][10]** number of elements. +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait @@ -1788,7 +1796,7 @@ I.waitToHide('#popup'); #### Parameters - `locator` **([string][8] | [object][6])** element located by CSS|XPath|strict locator. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait ### waitUntil @@ -1801,10 +1809,10 @@ I.waitUntil(() => window.requests == 0, 5); #### Parameters -- `fn` **([function][11] | [string][8])** function which is executed in browser context. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `fn` **([function][12] | [string][8])** function which is executed in browser context. +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait - `timeoutMsg` **[string][8]** message to show in case of timeout fail. -- `interval` **[number][9]?** +- `interval` **[number][10]?** ### waitUrlEquals @@ -1818,7 +1826,7 @@ I.waitUrlEquals('http://127.0.0.1:8000/info'); #### Parameters - `urlPart` **[string][8]** value to check. -- `sec` **[number][9]** (optional, `1` by default) time in seconds to wait +- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait [1]: https://github.com/GoogleChrome/puppeteer @@ -1836,24 +1844,26 @@ I.waitUrlEquals('http://127.0.0.1:8000/info'); [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number +[9]: https://github.com/puppeteer/puppeteer/issues/5420 + +[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number -[10]: https://vuejs.org/v2/api/#Vue-nextTick +[11]: https://vuejs.org/v2/api/#Vue-nextTick -[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function -[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise +[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise -[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[14]: https://codecept.io/helpers/FileSystem +[15]: https://codecept.io/helpers/FileSystem -[15]: #fillfield +[16]: #fillfield -[16]: https://github.com/GoogleChrome/puppeteer/issues/1313 +[17]: https://github.com/GoogleChrome/puppeteer/issues/1313 -[17]: #click +[18]: #click -[18]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[19]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[19]: https://codecept.io/react +[20]: https://codecept.io/react diff --git a/docs/helpers/TestCafe.md b/docs/helpers/TestCafe.md index 89e788675..686fcddbd 100644 --- a/docs/helpers/TestCafe.md +++ b/docs/helpers/TestCafe.md @@ -852,15 +852,23 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']); ### setCookie -Sets a cookie. +Sets cookie(s). + +Can be a single cookie object or an array of cookies: ```js I.setCookie({name: 'auth', value: true}); + +// as array +I.setCookie([ + {name: 'auth', value: true}, + {name: 'agree', value: true} +]); ``` #### Parameters -- `cookie` **[object][5]** a cookie object. +- `cookie` **([object][5] | [array][9])** a cookie object or array of cookie objects. ### switchTo diff --git a/docs/helpers/WebDriver.md b/docs/helpers/WebDriver.md index 23a36e7ec..60f0f3c93 100644 --- a/docs/helpers/WebDriver.md +++ b/docs/helpers/WebDriver.md @@ -352,6 +352,14 @@ const browser = WebDriver.browser - `config` +### _isShadowLocator + +Check if locator is type of "Shadow" + +#### Parameters + +- `locator` **[object][18]** + ### _locate Get elements by different locator types, including strict locator. @@ -363,7 +371,7 @@ this.helpers['WebDriver']._locate({name: 'password'}).then //... #### Parameters -- `locator` **([string][18] | [object][19])** element located by CSS|XPath|strict locator. +- `locator` **([string][19] | [object][18])** element located by CSS|XPath|strict locator. - `smartWait` ### _locateCheckable @@ -376,7 +384,7 @@ this.helpers['WebDriver']._locateCheckable('I agree with terms and conditions'). #### Parameters -- `locator` **([string][18] | [object][19])** element located by CSS|XPath|strict locator. +- `locator` **([string][19] | [object][18])** element located by CSS|XPath|strict locator. ### _locateClickable @@ -388,7 +396,7 @@ this.helpers['WebDriver']._locateClickable('Next page').then // ... #### Parameters -- `locator` **([string][18] | [object][19])** element located by CSS|XPath|strict locator. +- `locator` **([string][19] | [object][18])** element located by CSS|XPath|strict locator. ### _locateFields @@ -400,7 +408,23 @@ this.helpers['WebDriver']._locateFields('Your email').then // ... #### Parameters -- `locator` **([string][18] | [object][19])** element located by CSS|XPath|strict locator. +- `locator` **([string][19] | [object][18])** element located by CSS|XPath|strict locator. + +### _locateShadow + +Locate Element within the Shadow Dom + +#### Parameters + +- `locator` **[object][18]** + +### _smartWait + +Smart Wait to locate an element + +#### Parameters + +- `locator` **[object][18]** ### acceptPopup @@ -421,7 +445,7 @@ I.amOnPage('/login'); // opens a login page #### Parameters -- `url` **[string][18]** url path or global url. +- `url` **[string][19]** url path or global url. ### appendField @@ -434,8 +458,8 @@ I.appendField('#myTextField', 'appended'); #### Parameters -- `field` **([string][18] | [object][19])** located by label|name|CSS|XPath|strict locator -- `value` **[string][18]** text value to append. +- `field` **([string][19] | [object][18])** located by label|name|CSS|XPath|strict locator +- `value` **[string][19]** text value to append. @@ -455,8 +479,8 @@ I.attachFile('form input[name=avatar]', 'data/avatar.jpg'); #### Parameters -- `locator` **([string][18] | [object][19])** field located by label|name|CSS|XPath|strict locator. -- `pathToFile` **[string][18]** local file path relative to codecept.json config file. +- `locator` **([string][19] | [object][18])** field located by label|name|CSS|XPath|strict locator. +- `pathToFile` **[string][19]** local file path relative to codecept.json config file. Appium: not tested ### cancelPopup @@ -478,8 +502,8 @@ I.checkOption('agree', '//form'); #### Parameters -- `field` **([string][18] | [object][19])** checkbox located by label | name | CSS | XPath | strict locator. -- `context` **([string][18]? | [object][19])** (optional, `null` by default) element located by CSS | XPath | strict locator. +- `field` **([string][19] | [object][18])** checkbox located by label | name | CSS | XPath | strict locator. +- `context` **([string][19]? | [object][18])** (optional, `null` by default) element located by CSS | XPath | strict locator. Appium: not tested ### clearCookie @@ -494,7 +518,7 @@ I.clearCookie('test'); #### Parameters -- `cookie` **[string][18]?** (optional, `null` by default) cookie name +- `cookie` **[string][19]?** (optional, `null` by default) cookie name ### clearField @@ -509,7 +533,7 @@ I.clearField('#email'); #### Parameters - `field` -- `editable` **([string][18] | [object][19])** field located by label|name|CSS|XPath|strict locator. +- `editable` **([string][19] | [object][18])** field located by label|name|CSS|XPath|strict locator. ### click @@ -537,8 +561,8 @@ I.click({css: 'nav a.login'}); #### Parameters -- `locator` **([string][18] | [object][19])** clickable link or button located by text, or any element located by CSS|XPath|strict locator. -- `context` **([string][18]? | [object][19])** (optional, `null` by default) element to search in CSS|XPath|Strict locator. +- `locator` **([string][19] | [object][18])** clickable link or button located by text, or any element located by CSS|XPath|strict locator. +- `context` **([string][19]? | [object][18])** (optional, `null` by default) element to search in CSS|XPath|Strict locator. This action supports [React locators](https://codecept.io/react#locators) @@ -587,8 +611,8 @@ I.dontSee('Login', '.nav'); // no login inside .nav element #### Parameters -- `text` **[string][18]** which is not present. -- `context` **([string][18] | [object][19])?** (optional) element located by CSS|XPath|strict locator in which to perfrom search. +- `text` **[string][19]** which is not present. +- `context` **([string][19] | [object][18])?** (optional) element located by CSS|XPath|strict locator in which to perfrom search. This action supports [React locators](https://codecept.io/react#locators) @@ -606,7 +630,7 @@ I.dontSeeCheckboxIsChecked('agree'); // located by name #### Parameters -- `field` **([string][18] | [object][19])** located by label|name|CSS|XPath|strict locator.Appium: not tested +- `field` **([string][19] | [object][18])** located by label|name|CSS|XPath|strict locator.Appium: not tested ### dontSeeCookie @@ -618,7 +642,7 @@ I.dontSeeCookie('auth'); // no auth cookie #### Parameters -- `name` **[string][18]** cookie name. +- `name` **[string][19]** cookie name. ### dontSeeCurrentUrlEquals @@ -632,7 +656,7 @@ I.dontSeeCurrentUrlEquals('http://mysite.com/login'); // absolute urls are also #### Parameters -- `url` **[string][18]** value to check. +- `url` **[string][19]** value to check. ### dontSeeElement @@ -644,7 +668,7 @@ I.dontSeeElement('.modal'); // modal is not shown #### Parameters -- `locator` **([string][18] | [object][19])** located by CSS|XPath|Strict locator. +- `locator` **([string][19] | [object][18])** located by CSS|XPath|Strict locator. @@ -661,7 +685,7 @@ I.dontSeeElementInDOM('.nav'); // checks that element is not on page visible or #### Parameters -- `locator` **([string][18] | [object][19])** located by CSS|XPath|Strict locator. +- `locator` **([string][19] | [object][18])** located by CSS|XPath|Strict locator. ### dontSeeInCurrentUrl @@ -669,7 +693,7 @@ Checks that current url does not contain a provided fragment. #### Parameters -- `url` **[string][18]** value to check. +- `url` **[string][19]** value to check. ### dontSeeInField @@ -683,8 +707,8 @@ I.dontSeeInField({ css: 'form input.email' }, 'user@user.com'); // field by CSS #### Parameters -- `field` **([string][18] | [object][19])** located by label|name|CSS|XPath|strict locator. -- `value` **[string][18]** value to check. +- `field` **([string][19] | [object][18])** located by label|name|CSS|XPath|strict locator. +- `value` **[string][19]** value to check. ### dontSeeInSource @@ -697,7 +721,7 @@ I.dontSeeInSource('