From 5314e0ab9931e2497d0a9e5be9ac5502e28ed3d0 Mon Sep 17 00:00:00 2001 From: davert Date: Sun, 2 Jul 2023 13:07:38 +0300 Subject: [PATCH 1/4] added highlight to debug mode --- docs/plugins.md | 60 ++++++++++++-------------- lib/container.js | 11 ++--- lib/helper/Playwright.js | 26 +++++------ lib/helper/Puppeteer.js | 27 ++++++------ lib/helper/WebDriver.js | 41 ++++++++---------- lib/helper/scripts/highlightElement.js | 17 +++----- package.json | 5 +-- 7 files changed, 85 insertions(+), 102 deletions(-) diff --git a/docs/plugins.md b/docs/plugins.md index 524f8f2cc..dbdd048f9 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -558,17 +558,15 @@ Returns **([Promise][7]<any> | [undefined][8])** ## fakerTransform -Use the [faker.js][9] package to generate fake data inside examples on your gherkin tests - -![Faker.js][10] +Use the `@faker-js/faker` package to generate fake data inside examples on your gherkin tests #### Usage -To start please install `faker.js` package +To start please install `@faker-js/faker` package - npm install -D faker + npm install -D @faker-js/faker - yarn add -D faker + yarn add -D @faker-js/faker Add this plugin to config file: @@ -598,7 +596,7 @@ Scenario Outline: ... ## pauseOnFail -Automatically launches [interactive pause][11] when a test fails. +Automatically launches [interactive pause][9] when a test fails. Useful for debugging flaky tests on local environment. Add this plugin to config file: @@ -781,14 +779,14 @@ Possible config options: ## selenoid -[Selenoid][12] plugin automatically starts browsers and video recording. +[Selenoid][10] plugin automatically starts browsers and video recording. Works with WebDriver helper. ### Prerequisite This plugin **requires Docker** to be installed. -> If you have issues starting Selenoid with this plugin consider using the official [Configuration Manager][13] tool from Selenoid +> If you have issues starting Selenoid with this plugin consider using the official [Configuration Manager][11] tool from Selenoid ### Usage @@ -817,7 +815,7 @@ plugins: { } ``` -When `autoCreate` is enabled it will pull the [latest Selenoid from DockerHub][14] and start Selenoid automatically. +When `autoCreate` is enabled it will pull the [latest Selenoid from DockerHub][12] and start Selenoid automatically. It will also create `browsers.json` file required by Selenoid. In automatic mode the latest version of browser will be used for tests. It is recommended to specify exact version of each browser inside `browsers.json` file. @@ -829,10 +827,10 @@ In automatic mode the latest version of browser will be used for tests. It is re While this plugin can create containers for you for better control it is recommended to create and launch containers manually. This is especially useful for Continous Integration server as you can configure scaling for Selenoid containers. -> Use [Selenoid Configuration Manager][13] to create and start containers semi-automatically. +> Use [Selenoid Configuration Manager][11] to create and start containers semi-automatically. 1. Create `browsers.json` file in the same directory `codecept.conf.js` is located - [Refer to Selenoid documentation][15] to know more about browsers.json. + [Refer to Selenoid documentation][13] to know more about browsers.json. _Sample browsers.json_ @@ -857,7 +855,7 @@ _Sample browsers.json_ 2. Create Selenoid container -Run the following command to create a container. To know more [refer here][16] +Run the following command to create a container. To know more [refer here][14] ```bash docker create \ @@ -890,7 +888,7 @@ When `allure` plugin is enabled a video is attached to report automatically. | enableVideo | Enable video recording and use `video` folder of output (default: false) | | enableLog | Enable log recording and use `logs` folder of output (default: false) | | deletePassed | Delete video and logs of passed tests (default : true) | -| additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here][17] to know more | +| additionalParams | example: `additionalParams: '--env TEST=test'` [Refer here][15] to know more | ### Parameters @@ -898,7 +896,7 @@ When `allure` plugin is enabled a video is attached to report automatically. ## stepByStepReport -![step-by-step-report][18] +![step-by-step-report][16] Generates step by step report for a test. After each step in a test a screenshot is created. After test executed screenshots are combined into slideshow. @@ -1079,7 +1077,7 @@ This plugin allows to run webdriverio services like: - browserstack - appium -A complete list of all available services can be found on [webdriverio website][19]. +A complete list of all available services can be found on [webdriverio website][17]. #### Setup @@ -1091,7 +1089,7 @@ See examples below: #### Selenium Standalone Service -Install `@wdio/selenium-standalone-service` package, as [described here][20]. +Install `@wdio/selenium-standalone-service` package, as [described here][18]. It is important to make sure it is compatible with current webdriverio version. Enable `wdio` plugin in plugins list and add `selenium-standalone` service: @@ -1110,7 +1108,7 @@ Please note, this service can be used with Protractor helper as well! #### Sauce Service -Install `@wdio/sauce-service` package, as [described here][21]. +Install `@wdio/sauce-service` package, as [described here][19]. It is important to make sure it is compatible with current webdriverio version. Enable `wdio` plugin in plugins list and add `sauce` service: @@ -1156,28 +1154,24 @@ In the same manner additional services from webdriverio can be installed, enable [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined -[9]: https://www.npmjs.com/package/faker - -[10]: https://raw.githubusercontent.com/Marak/faker.js/master/logo.png - -[11]: /basics/#pause +[9]: /basics/#pause -[12]: https://aerokube.com/selenoid/ +[10]: https://aerokube.com/selenoid/ -[13]: https://aerokube.com/cm/latest/ +[11]: https://aerokube.com/cm/latest/ -[14]: https://hub.docker.com/u/selenoid +[12]: https://hub.docker.com/u/selenoid -[15]: https://aerokube.com/selenoid/latest/#_prepare_configuration +[13]: https://aerokube.com/selenoid/latest/#_prepare_configuration -[16]: https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container +[14]: https://aerokube.com/selenoid/latest/#_option_2_start_selenoid_container -[17]: https://docs.docker.com/engine/reference/commandline/create/ +[15]: https://docs.docker.com/engine/reference/commandline/create/ -[18]: https://codecept.io/img/codeceptjs-slideshow.gif +[16]: https://codecept.io/img/codeceptjs-slideshow.gif -[19]: https://webdriver.io +[17]: https://webdriver.io -[20]: https://webdriver.io/docs/selenium-standalone-service.html +[18]: https://webdriver.io/docs/selenium-standalone-service.html -[21]: https://webdriver.io/docs/sauce-service.html +[19]: https://webdriver.io/docs/sauce-service.html diff --git a/lib/container.js b/lib/container.js index 37e8a2b59..9427718e8 100644 --- a/lib/container.js +++ b/lib/container.js @@ -168,12 +168,13 @@ function createHelpers(config) { // @ts-ignore let HelperClass; - // check if the helper is the built-in, use the require() syntax. + // check if the helper is the built-in, use the require() syntax. if (moduleName.startsWith('./helper/')) { - HelperClass = require(moduleName); } - else { - // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName. - HelperClass = require(moduleName).default || require(moduleName); } + HelperClass = require(moduleName); + } else { + // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName. + HelperClass = require(moduleName).default || require(moduleName); + } if (HelperClass._checkRequirements) { const requirements = HelperClass._checkRequirements(); diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 6bddce056..2e3143880 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -4,6 +4,7 @@ const fs = require('fs'); const Helper = require('@codeceptjs/helper'); const { v4: uuidv4 } = require('uuid'); const Locator = require('../locator'); +const store = require('../store'); const recorder = require('../recorder'); const stringIncludes = require('../assert/include').includes; const { urlEquals } = require('../assert/equal'); @@ -44,7 +45,7 @@ const { setRestartStrategy, restartsSession, restartsContext, restartsBrowser, } = require('./extras/PlaywrightRestartOpts'); const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine'); -const { highlightElement, unhighlightElement } = require('./scripts/highlightElement'); +const { highlightElement } = require('./scripts/highlightElement'); const pathSeparator = path.sep; @@ -1533,15 +1534,10 @@ class Playwright extends Helper { await this._evaluateHandeInContext(el => el.innerHTML = '', el); } - if (this.options.highlightElement) { - highlightElement(el, this.page); - } + highlightActiveElement.call(this, el, this.page); await el.type(value.toString(), { delay: this.options.pressKeyDelay }); - if (this.options.highlightElement) { - unhighlightElement(el, this.page); - } return this._waitForAction(); } @@ -1585,6 +1581,7 @@ class Playwright extends Helper { async appendField(field, value) { const els = await findFields.call(this, field); assertElementExists(els, field, 'Field'); + highlightActiveElement.call(this, els[0], this.page); await els[0].press('End'); await els[0].type(value.toString(), { delay: this.options.pressKeyDelay }); return this._waitForAction(); @@ -1630,6 +1627,7 @@ class Playwright extends Helper { if (await el.getProperty('tagName').then(t => t.jsonValue()) !== 'SELECT') { throw new Error('Element is not '); } + highlightActiveElement.call(this, els[0], this.page); if (!Array.isArray(option)) option = [option]; for (const key in option) { @@ -2333,9 +2332,7 @@ async function proceedClick(locator, context = null, options = {}) { assertElementExists(els, locator, 'Clickable element'); } - if (this.options.highlightElement) { - highlightElement(els[0], this.page); - } + highlightActiveElement.call(this, els[0], this.page); await els[0].click(options); const promises = []; @@ -2344,10 +2341,6 @@ async function proceedClick(locator, context = null, options = {}) { } promises.push(this._waitForAction()); - if (this.options.highlightElement) { - unhighlightElement(els[0], this.page); - } - return Promise.all(promises); } @@ -2692,3 +2685,9 @@ function getNormalizedKey(key) { } return normalizedKey; } + +function highlightActiveElement(element, context) { + if (!this.options.enableHighlight && !store.debugMode) return; + + highlightElement(element, context); +} diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index 10ea7ffba..9054f5d65 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -28,7 +28,8 @@ const { const ElementNotFound = require('./errors/ElementNotFound'); const ConnectionRefused = require('./errors/ConnectionRefused'); const Locator = require('../locator'); -const { highlightElement, unhighlightElement } = require('./scripts/highlightElement'); +const { highlightElement } = require('./scripts/highlightElement'); +const store = require('../store'); const SHADOW = 'shadow'; const webRoot = 'body'; @@ -917,10 +918,7 @@ class WebDriver extends Helper { assertElementExists(res, locator, 'Clickable element'); } const elem = usingFirstElement(res); - - if (this.options.highlightElement) { - highlightElement(elem, this.browser); - } + highlightActiveElement.call(this, elem); return this.browser[clickMethod](getElementId(elem)); } @@ -939,6 +937,7 @@ class WebDriver extends Helper { assertElementExists(res, locator, 'Clickable element'); } const elem = usingFirstElement(res); + highlightActiveElement.call(this, elem); return this.executeScript((el) => { if (document.activeElement instanceof HTMLElement) { @@ -966,6 +965,7 @@ class WebDriver extends Helper { } const elem = usingFirstElement(res); + highlightActiveElement.call(this, elem); return elem.doubleClick(); } @@ -1031,13 +1031,7 @@ class WebDriver extends Helper { const res = await findFields.call(this, field); assertElementExists(res, field, 'Field'); const elem = usingFirstElement(res); - if (this.options.highlightElement) { - highlightElement(elem, this.browser); - } - - if (this.options.highlightElement) { - unhighlightElement(elem, this.browser); - } + highlightActiveElement.call(this, elem); return elem.setValue(value.toString()); } @@ -1049,12 +1043,7 @@ class WebDriver extends Helper { const res = await findFields.call(this, field); assertElementExists(res, field, 'Field'); const elem = usingFirstElement(res); - if (this.options.highlightElement) { - highlightElement(elem, this.browser); - } - if (this.options.highlightElement) { - unhighlightElement(elem, this.browser); - } + highlightActiveElement.call(this, elem); return elem.addValue(value.toString()); } @@ -1066,12 +1055,7 @@ class WebDriver extends Helper { const res = await findFields.call(this, field); assertElementExists(res, field, 'Field'); const elem = usingFirstElement(res); - if (this.options.highlightElement) { - highlightElement(elem, this.browser); - } - if (this.options.highlightElement) { - unhighlightElement(elem, this.browser); - } + highlightActiveElement.call(this, elem); return elem.clearValue(getElementId(elem)); } @@ -1082,6 +1066,7 @@ class WebDriver extends Helper { const res = await findFields.call(this, select); assertElementExists(res, select, 'Selectable field'); const elem = usingFirstElement(res); + highlightActiveElement.call(this, elem); if (!Array.isArray(option)) { option = [option]; @@ -1148,6 +1133,7 @@ class WebDriver extends Helper { assertElementExists(res, field, 'Checkable'); const elem = usingFirstElement(res); const elementId = getElementId(elem); + highlightActiveElement.call(this, elem); const isSelected = await this.browser.isElementSelected(elementId); if (isSelected) return Promise.resolve(true); @@ -1167,6 +1153,7 @@ class WebDriver extends Helper { assertElementExists(res, field, 'Checkable'); const elem = usingFirstElement(res); const elementId = getElementId(elem); + highlightActiveElement.call(this, elem); const isSelected = await this.browser.isElementSelected(elementId); if (!isSelected) return Promise.resolve(true); @@ -2900,6 +2887,12 @@ function isModifierKey(key) { return unicodeModifierKeys.includes(key); } +function highlightActiveElement(element) { + if (!this.options.enableHighlight && !store.debugMode) return; + + highlightElement(element, this.browser); +} + function prepareLocateFn(context) { if (!context) return this._locate.bind(this); return (l) => { diff --git a/lib/helper/scripts/highlightElement.js b/lib/helper/scripts/highlightElement.js index c9aa4346c..87bcd9638 100644 --- a/lib/helper/scripts/highlightElement.js +++ b/lib/helper/scripts/highlightElement.js @@ -1,15 +1,12 @@ module.exports.highlightElement = (element, context) => { try { - context.evaluate(el => el.style.border = '2px solid red', element); + context.evaluate(el => { + const style = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)'; + const prevStyle = el.style.boxShadow; + el.style.boxShadow = style; + setTimeout(() => el.style.boxShadow = prevStyle, 2000); + }, element); } catch (e) { - context.execute(el => el.style.border = '2px solid red', element); - } -}; - -module.exports.unhighlightElement = (element, context) => { - try { - context.evaluate(el => el.style.border = '', element); - } catch (e) { - context.execute(el => el.style.border = '', element); + // ignore highlight } }; diff --git a/package.json b/package.json index e694b55c6..553a08733 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js", "test:unit:webbapi:testCafe": "mocha test/helper/TestCafe_test.js", "def": "./runok.js def", - "dev:graphql": "nodemon test/data/graphql/index.js", + "dev:graphql": "node ./test/data/graphql/index.js", "publish:site": "./runok.js publish:site", "update-contributor-faces": "contributor-faces .", "dtslint": "dtslint typings --localTs './node_modules/typescript/lib'", @@ -133,8 +133,7 @@ "jsdoc-typeof-plugin": "^1.0.0", "json-server": "^0.10.1", "nightmare": "^3.0.2", - "nodemon": "^2.0.22", - "playwright": "^1.32.3", + "playwright": "^1.35.1", "puppeteer": "^10.4.0", "qrcode-terminal": "^0.12.0", "rosie": "^2.1.0", From 160d584ccdb49b59b983375d5bfd9626a7c8672f Mon Sep 17 00:00:00 2001 From: davert Date: Sun, 2 Jul 2023 13:08:46 +0300 Subject: [PATCH 2/4] added highlight to debug mode --- test/unit/locator_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/locator_test.js b/test/unit/locator_test.js index a6ceaa11d..9520555e0 100644 --- a/test/unit/locator_test.js +++ b/test/unit/locator_test.js @@ -1,5 +1,5 @@ const { expect } = require('chai'); -const { DOMParser } = require('@xmldom/xmldom'); +const { DOMParser } = require('xmldom'); const xpath = require('xpath'); const Locator = require('../../lib/locator'); From ed180ba2231d8084ed30c42f944e0e3e8c59b95e Mon Sep 17 00:00:00 2001 From: davert Date: Sun, 2 Jul 2023 13:27:22 +0300 Subject: [PATCH 3/4] fixed tests --- test/unit/html_test.js | 4 ++-- test/unit/worker_test.js | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/unit/html_test.js b/test/unit/html_test.js index cda2dfa28..5dbaaebe1 100644 --- a/test/unit/html_test.js +++ b/test/unit/html_test.js @@ -121,8 +121,8 @@ describe('HTML module', () => { html = fs.readFileSync(path.join(__dirname, '../data/testomat.html'), 'utf8'); // console.log(html); const result = removeNonInteractiveElements(html, opts); - - console.log(minifyHtml(result)); + result.should.include('