diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index b38236895..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,63 +0,0 @@ -module.exports = { - extends: 'airbnb-base', - env: { - node: true, - }, - parserOptions: { - ecmaVersion: 2020, - }, - rules: { - 'func-names': 0, - 'no-use-before-define': 0, - 'no-unused-vars': 0, - 'no-underscore-dangle': 0, - 'no-undef': 0, - 'prefer-destructuring': 0, - 'no-param-reassign': 0, - 'max-len': 0, - camelcase: 0, - 'no-shadow': 0, - 'consistent-return': 0, - 'no-console': 0, - 'global-require': 0, - 'class-methods-use-this': 0, - 'no-plusplus': 0, - 'no-return-assign': 0, - 'prefer-rest-params': 0, - 'no-useless-escape': 0, - 'no-restricted-syntax': 0, - 'no-unused-expressions': 0, - 'guard-for-in': 0, - 'no-multi-assign': 0, - 'require-yield': 0, - 'prefer-spread': 0, - 'import/no-dynamic-require': 0, - 'no-continue': 0, - 'no-mixed-operators': 0, - 'default-case': 0, - 'import/no-extraneous-dependencies': 0, - 'no-cond-assign': 0, - 'import/no-unresolved': 0, - 'no-await-in-loop': 0, - 'arrow-body-style': 0, - 'no-loop-func': 0, - 'arrow-parens': 0, - 'default-param-last': 0, - semi: 0, - 'operator-linebreak': 0, - 'nonblock-statement-body-position': 0, - curly: 0, - 'implicit-arrow-linebreak': 0, - indent: 0, - 'object-curly-newline': 0, - 'semi-style': 0, - 'function-paren-newline': 0, - 'prefer-template': 0, - 'newline-per-chained-call': 0, - 'prefer-arrow-callback': 0, - 'no-bitwise': 0, - 'prefer-const': 0, - 'no-extra-semi': 0, - }, - ignorePatterns: ['test/data/output', 'lib/css2xpath/*'], -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7d43abc66..bbbcf4c35 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -15,8 +15,3 @@ updates: ignore: - dependency-name: "escape-string-regexp" versions: [">=5.0"] - - dependency-name: "apollo-server-express" - versions: [">=3.0"] - - dependency-name: "eslint" - versions: [ ">8.57.0" ] - diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index d08b16c98..ed1cc7135 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -25,7 +25,7 @@ jobs: steps: # Checkout the repository - name: Checkout Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Install Docker Compose - name: Install Docker Compose diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 459c1e979..341fb4b8e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: testomatio/check-tests@master + - uses: testomatio/check-tests@stable if: github.repository == 'codeceptjs/CodeceptJS' && github.event.pull_request.title == '3.x' with: framework: mocha diff --git a/.github/workflows/mockServerHelper.yml b/.github/workflows/mockServerHelper.yml deleted file mode 100644 index c80393e18..000000000 --- a/.github/workflows/mockServerHelper.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Mock Server Tests - -on: - push: - branches: - - 3.x - pull_request: - branches: - - '**' - -env: - CI: true - # Force terminal colors. @see https://www.npmjs.com/package/colors - FORCE_COLOR: 1 - -jobs: - build: - - runs-on: ubuntu-22.04 - - strategy: - matrix: - node-version: [20.x] - - steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - name: npm install - run: npm i --force - - name: run unit tests - run: npm run test:unit:mockServer diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2381874cb..1f8dc9769 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,7 +16,7 @@ env: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/webdriver.devtools.yml b/.github/workflows/webdriver.devtools.yml deleted file mode 100644 index ab27e7f44..000000000 --- a/.github/workflows/webdriver.devtools.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: WebDriver - Devtools Tests - -on: - push: - branches: - - 3.x - pull_request: - branches: - - '**' - -env: - CI: true - # Force terminal colors. @see https://www.npmjs.com/package/colors - FORCE_COLOR: 1 - -jobs: - build: - - runs-on: ubuntu-20.04 - strategy: - matrix: - node-version: [20.x] - - steps: - - uses: actions/checkout@v4 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - uses: shivammathur/setup-php@v2 - with: - php-version: 8.0 - - name: npm install - run: | - npm i --force - env: - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: true - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - - name: start a server - run: "php -S 127.0.0.1:8000 -t test/data/app &" - - name: run unit tests - run: ./node_modules/.bin/mocha test/helper/WebDriver_devtools_test.js --exit - - name: run tests - run: "./bin/codecept.js run -c test/acceptance/codecept.WebDriver.devtools.js --grep @WebDriver --debug" - diff --git a/CHANGELOG.md b/CHANGELOG.md index a6def637d..7c769257c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +## 3.6.8 + +❤️ Thanks all to those who contributed to make this release! ❤️ + +🛩️ *Features* +* feat(cli): mask sensitive data in logs (#4630) - by @kobenguyent +``` +export const config: CodeceptJS.MainConfig = { + tests: '**/*.e2e.test.ts', + retry: 4, + output: './output', + maskSensitiveData: true, + emptyOutputFolder: true, +... + + I login {"username":"helloworld@test.com","password": "****"} + I send post request "https://localhost:8000/login", {"username":"helloworld@test.com","password": "****"} + › [Request] {"baseURL":"https://localhost:8000/login","method":"POST","data":{"username":"helloworld@test.com","password": "****"},"headers":{}} + › [Response] {"access-token": "****"} +``` + +* feat(REST): DELETE request supports payload (#4493) - by @schaudhary111 + +```js +I.sendDeleteRequestWithPayload('/api/users/1', { author: 'john' }); +``` + +🐛 *Bug Fixes* +* fix(playwright): Different behavior of see* and waitFor* when used in within (#4557) - by @kobenguyent +* fix(cli): dry run returns no tests when using a regex grep (#4608) - by @kobenguyent +```bash +> codeceptjs dry-run --steps --grep "(?=.*Checkout process)" +``` +* fix: Replace deprecated faker.name with faker.person (#4581) - by @thomashohn +* fix(wdio): Remove dependency to devtools (#4563) - by @thomashohn +* fix(typings): wrong defineParameterType (#4548) - by @kobenguyent +* fix(typing): `Locator.build` complains the empty locator (#4543) - by @kobenguyent +* fix: add hint to `I.seeEmailAttachment` treats parameter as regular expression (#4629) - by @ngraf +``` +Add hint to "I.seeEmailAttachment" that under the hood parameter is treated as RegExp. +When you don't know it, it can cause a lot of pain, wondering why your test fails with I.seeEmailAttachment('Attachment(1).pdf') although it looks just fine, but actually I.seeEmailAttachment('Attachment\\(1\\).pdf is required to make the test green, in case the attachment is called "Attachment(1).pdf" with special character in it. +``` +* fix(playwright): waitForText fails when text contains double quotes (#4528) - by @DavertMik +* fix(mock-server-helper): move to stand-alone package: https://www.npmjs.com/package/@codeceptjs/mock-server-helper (#4536) - by @kobenguyent +* fix(appium): issue with async on runOnIos and runOnAndroid (#4525) - by @kobenguyent +* fix: push ws messages to array (#4513) - by @kobenguyent + +📖 *Documentation* +* fix(docs): typo in ai.md (#4501) - by @tomaculum + ## 3.6.6 ❤️ Thanks all to those who contributed to make this release! ❤️ diff --git a/README.md b/README.md index 4f5406735..044a7c55a 100644 --- a/README.md +++ b/README.md @@ -312,17 +312,17 @@ Thanks all to those who are and will have contributing to this awesome project! + + - - [//]: contributor-faces diff --git a/docs/ai.md b/docs/ai.md index b3b1ea5ee..9d1776b77 100644 --- a/docs/ai.md +++ b/docs/ai.md @@ -533,7 +533,7 @@ ai: { * `maxLength`: the size of HTML to cut to not reach the token limit. 50K is the current default but you may try to increase it or even set it to null. * `simplify`: should we process HTML before sending to GPT. This will remove all non-interactive elements from HTML. -* `minify`: shold HTML be additionally minified. This removed empty attributes, shortens notations, etc. +* `minify`: should HTML be additionally minified. This removed empty attributes, shortens notations, etc. * `interactiveElements`: explicit list of all elements that are considered interactive. * `textElements`: elements that contain text which can be used for test automation. * `allowedAttrs`: explicit list of attributes that may be used to construct locators. If you use special `data-` attributes to enable locators, add them to the list. diff --git a/docs/api.md b/docs/api.md index a2bb24c78..80bc146e1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -148,6 +148,7 @@ REST helper can send GET/POST/PATCH/etc requests to REST API endpoint: * [`I.sendPutRequest()`](/helpers/REST#sendPutRequest) * [`I.sendPatchRequest()`](/helpers/REST#sendPatchRequest) * [`I.sendDeleteRequest()`](/helpers/REST#sendDeleteRequest) +* [`I.sendDeleteRequestWithPayload()`](/helpers/REST#sendDeleteRequestWithPayload) * ... Authentication headers can be set in [helper's config](https://codecept.io/helpers/REST/#configuration) or per test with headers or special methods like `I.amBearerAuthenticated`. diff --git a/docs/changelog.md b/docs/changelog.md index d028f8a18..bd22e1210 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,68 @@ layout: Section # Releases +## 3.6.6 + +❤️ Thanks all to those who contributed to make this release! ❤️ + +🛩️ *Features* +* feat(locator): add withAttrEndsWith, withAttrStartsWith, withAttrContains ([#4334](https://github.com/codeceptjs/CodeceptJS/issues/4334)) - by **[Maksym-Artemenko](https://github.com/Maksym-Artemenko)** +* feat: soft assert ([#4473](https://github.com/codeceptjs/CodeceptJS/issues/4473)) - by **[kobenguyent](https://github.com/kobenguyent)** + * Soft assert + +Zero-configuration when paired with other helpers like REST, Playwright: + +```js +// inside codecept.conf.js +{ + helpers: { + Playwright: {...}, + SoftExpectHelper: {}, + } +} +``` + +```js +// in scenario +I.softExpectEqual('a', 'b') +I.flushSoftAssertions() // Throws an error if any soft assertions have failed. The error message contains all the accumulated failures. +``` +* feat(cli): print failed hooks ([#4476](https://github.com/codeceptjs/CodeceptJS/issues/4476)) - by **[kobenguyent](https://github.com/kobenguyent)** + * run command + ![Screenshot 2024-09-02 at 15 25 20](https://github.com/user-attachments/assets/625c6b54-03f6-41c6-9d0c-cd699582404a) + + * run workers command +![Screenshot 2024-09-02 at 15 24 53](https://github.com/user-attachments/assets/efff0312-1229-44b6-a94f-c9b9370b9a64) + +🐛 *Bug Fixes* +* fix(AI): minor AI improvements - by **[DavertMik](https://github.com/DavertMik)** +* fix(AI): add missing await in AI.js ([#4486](https://github.com/codeceptjs/CodeceptJS/issues/4486)) - by **[tomaculum](https://github.com/tomaculum)** +* fix(playwright): no async save video page ([#4472](https://github.com/codeceptjs/CodeceptJS/issues/4472)) - by **[kobenguyent](https://github.com/kobenguyent)** +* fix(rest): httpAgent condition ([#4484](https://github.com/codeceptjs/CodeceptJS/issues/4484)) - by **[kobenguyent](https://github.com/kobenguyent)** +* fix: DataCloneError error when `I.executeScript` command is used with `run-workers` ([#4483](https://github.com/codeceptjs/CodeceptJS/issues/4483)) - by **[code4muktesh](https://github.com/code4muktesh)** +* fix: no error thrown from rerun script ([#4494](https://github.com/codeceptjs/CodeceptJS/issues/4494)) - by **[lin-brian-l](https://github.com/lin-brian-l)** + + +```js +// fix the validation of httpAgent config. we could now pass ca, instead of key/cert. +{ + helpers: { + REST: { + endpoint: 'http://site.com/api', + prettyPrintJson: true, + httpAgent: { + ca: fs.readFileSync(__dirname + '/path/to/ca.pem'), + rejectUnauthorized: false, + keepAlive: true + } + } + } +} +``` + +📖 *Documentation* +* doc(AI): minor AI improvements - by **[DavertMik](https://github.com/DavertMik)** + ## 3.6.5 ❤️ Thanks all to those who contributed to make this release! ❤️ diff --git a/docs/community-helpers.md b/docs/community-helpers.md index 2f135e440..7394b79eb 100644 --- a/docs/community-helpers.md +++ b/docs/community-helpers.md @@ -43,6 +43,7 @@ Please **add your own** by editing this page. * [codeceptjs-slack-reporter](https://www.npmjs.com/package/codeceptjs-slack-reporter) Get a Slack notification when one or more scenarios fail. * [codeceptjs-browserlogs-plugin](https://github.com/pavkam/codeceptjs-browserlogs-plugin) Record the browser logs for failed tests. * [codeceptjs-testrail](https://github.com/PeterNgTr/codeceptjs-testrail) - a plugin to integrate with [Testrail](https://www.gurock.com/testrail) +* [codeceptjs-monocart-coverage](https://github.com/cenfun/codeceptjs-monocart-coverage) - a plugin to generate coverage reports, it integrate with [monocart coverage reports](https://github.com/cenfun/monocart-coverage-reports) ## Browser request control * [codeceptjs-resources-check](https://github.com/luarmr/codeceptjs-resources-check) Load a URL with Puppeteer and listen to the requests while the page is loading. Enabling count the number or check the sizes of the requests. diff --git a/docs/data.md b/docs/data.md index 91d8f4a22..6c8bc4288 100644 --- a/docs/data.md +++ b/docs/data.md @@ -54,6 +54,7 @@ I.sendPostRequest() I.sendPutRequest() I.sendPatchRequest() I.sendDeleteRequest() +I.sendDeleteRequestWithPayload() ``` As well as a method for setting headers: `haveRequestHeaders`. @@ -210,7 +211,7 @@ var Factory = require('rosie').Factory; var faker = require('@faker-js/faker'); module.exports = new Factory() - .attr('name', () => faker.name.findName()) + .attr('name', () => faker.person.findName()) .attr('email', () => faker.internet.email()); ``` @@ -270,7 +271,7 @@ module.exports = new Factory((buildObj) => { input: { ...buildObj }, } }) - .attr('name', () => faker.name.findName()) + .attr('name', () => faker.person.findName()) .attr('email', () => faker.internet.email()); ``` diff --git a/docs/email.md b/docs/email.md index 1a1a567c0..db8ee3297 100644 --- a/docs/email.md +++ b/docs/email.md @@ -156,8 +156,8 @@ I.seeEmailIsFrom('@mysite.com'); I.seeInEmailSubject('Awesome Proposal!'); I.seeInEmailBody('To unsubscribe click here'); I.seeNumberOfEmailAttachments(2); -I.seeEmailAttachment('Attachment_1.pdf') -I.seeEmailAttachment('Attachment_2.pdf') +I.seeEmailAttachment('Attachment_1.pdf'); // Regular expression. Escape special characters like '(' or ')' in filename. +I.seeEmailAttachment('Attachment_2.pdf'); ``` > More methods are listed in [helper's API reference](https://github.com/codeceptjs/mailslurp-helper/blob/master/README.md#api) diff --git a/docs/helpers/AI.md b/docs/helpers/AI.md index a3b6082ea..96e0dc607 100644 --- a/docs/helpers/AI.md +++ b/docs/helpers/AI.md @@ -22,11 +22,11 @@ Use it only in development mode. It is recommended to run it only inside pause() This helper should be configured in codecept.conf.{js|ts} -- `chunkSize`: - The maximum number of characters to send to the AI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4. +* `chunkSize`: - The maximum number of characters to send to the AI API at once. We split HTML fragments by 8000 chars to not exceed token limit. Increase this value if you use GPT-4. ### Parameters -- `config` +* `config` ### askForPageObject @@ -50,11 +50,11 @@ Asks for a page object based on the provided page name, locator, and extra promp #### Parameters -- `pageName` **[string][1]** The name of the page to retrieve the object for. -- `extraPrompt` **([string][1] | null)** An optional extra prompt for additional context or information. -- `locator` **([string][1] | null)** An optional locator to find a specific element on the page. +* `pageName` **[string][1]** The name of the page to retrieve the object for. +* `extraPrompt` **([string][1] | null)** An optional extra prompt for additional context or information. +* `locator` **([string][1] | null)** An optional locator to find a specific element on the page. -Returns **[Promise][2]<[Object][3]>** A promise that resolves to the requested page object. +Returns **[Promise][2]<[Object][3]>** A promise that resolves to the requested page object. ### askGptGeneralPrompt @@ -62,9 +62,9 @@ Send a general request to AI and return response. #### Parameters -- `prompt` **[string][1]** +* `prompt` **[string][1]** -Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated response from the GPT model. +Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated response from the GPT model. ### askGptOnPage @@ -76,9 +76,9 @@ I.askGptOnPage('what does this page do?'); #### Parameters -- `prompt` **[string][1]** The question or prompt to ask the GPT model. +* `prompt` **[string][1]** The question or prompt to ask the GPT model. -Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated responses from the GPT model, joined by newlines. +Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated responses from the GPT model, joined by newlines. ### askGptOnPageFragment @@ -90,10 +90,10 @@ I.askGptOnPageFragment('describe features of this screen', '.screen'); #### Parameters -- `prompt` **[string][1]** The question or prompt to ask the GPT-3.5 model. -- `locator` **[string][1]** The locator or selector used to identify the HTML fragment on the page. +* `prompt` **[string][1]** The question or prompt to ask the GPT-3.5 model. +* `locator` **[string][1]** The locator or selector used to identify the HTML fragment on the page. -Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated response from the GPT model. +Returns **[Promise][2]<[string][1]>** A Promise that resolves to the generated response from the GPT model. [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String diff --git a/docs/helpers/ApiDataFactory.md b/docs/helpers/ApiDataFactory.md index 2e25d1636..d76af53e0 100644 --- a/docs/helpers/ApiDataFactory.md +++ b/docs/helpers/ApiDataFactory.md @@ -58,7 +58,7 @@ const { faker } = require('@faker-js/faker'); module.exports = new Factory() // no need to set id, it will be set by REST API - .attr('author', () => faker.name.findName()) + .attr('author', () => faker.person.findName()) .attr('title', () => faker.lorem.sentence()) .attr('body', () => faker.lorem.paragraph()); ``` @@ -71,12 +71,12 @@ Then configure ApiDataHelper to match factories and REST API: ApiDataFactory has following config options: -- `endpoint`: base URL for the API to send requests to. -- `cleanup` (default: true): should inserted records be deleted up after tests -- `factories`: list of defined factories -- `returnId` (default: false): return id instead of a complete response when creating items. -- `headers`: list of headers -- `REST`: configuration for REST requests +* `endpoint`: base URL for the API to send requests to. +* `cleanup` (default: true): should inserted records be deleted up after tests +* `factories`: list of defined factories +* `returnId` (default: false): return id instead of a complete response when creating items. +* `headers`: list of headers +* `REST`: configuration for REST requests See the example: @@ -121,25 +121,26 @@ For instance, to set timeout you should add: By default to create a record ApiDataFactory will use endpoint and plural factory name: -- create: `POST {endpoint}/{resource} data` -- delete: `DELETE {endpoint}/{resource}/id` +* create: `POST {endpoint}/{resource} data` +* delete: `DELETE {endpoint}/{resource}/id` Example (`endpoint`: `http://app.com/api`): -- create: POST request to `http://app.com/api/users` -- delete: DELETE request to `http://app.com/api/users/1` +* create: POST request to `http://app.com/api/users` +* delete: DELETE request to `http://app.com/api/users/1` This behavior can be configured with following options: -- `uri`: set different resource uri. Example: `uri: account` => `http://app.com/api/account`. -- `create`: override create options. Expected format: `{ method: uri }`. Example: `{ "post": "/users/create" }` -- `delete`: override delete options. Expected format: `{ method: uri }`. Example: `{ "post": "/users/delete/{id}" }` +* `uri`: set different resource uri. Example: `uri: account` => `http://app.com/api/account`. +* `create`: override create options. Expected format: `{ method: uri }`. Example: `{ "post": "/users/create" }` +* `delete`: override delete options. Expected format: `{ method: uri }`. Example: `{ "post": "/users/delete/{id}" }` Requests can also be overridden with a function which returns [axois request config][4]. ```js create: (data) => ({ method: 'post', url: '/posts', data }), delete: (id) => ({ method: 'delete', url: '/posts', data: { id } }) + ``` Requests can be updated on the fly by using `onRequest` function. For instance, you can pass in current session from a cookie. @@ -189,7 +190,7 @@ By default `id` property of response is taken. This behavior can be changed by s ### Parameters -- `config` +* `config` ### _requestCreate @@ -198,8 +199,8 @@ Can be replaced from a in custom helper. #### Parameters -- `factory` **any** -- `data` **any** +* `factory` **any** +* `data` **any** ### _requestDelete @@ -208,8 +209,8 @@ Can be replaced from a custom helper. #### Parameters -- `factory` **any** -- `id` **any** +* `factory` **any** +* `id` **any** ### have @@ -227,11 +228,11 @@ I.have('user', { }, { age: 33, height: 55 }) #### Parameters -- `factory` **any** factory to use -- `params` **any?** predefined parameters -- `options` **any?** options for programmatically generate the attributes +* `factory` **any** factory to use +* `params` **any?** predefined parameters +* `options` **any?** options for programmatically generate the attributes -Returns **[Promise][5]<any>** +Returns **[Promise][5]** ### haveMultiple @@ -250,10 +251,10 @@ I.haveMultiple('post', 3, { author: 'davert' }, { publish_date: '01.01.1997' }); #### Parameters -- `factory` **any** -- `times` **any** -- `params` **any?** -- `options` **any?** +* `factory` **any** +* `times` **any** +* `params` **any?** +* `options` **any?** [1]: https://github.com/rosiejs/rosie diff --git a/docs/helpers/Appium.md b/docs/helpers/Appium.md index ab0a870a2..7b9fdab62 100644 --- a/docs/helpers/Appium.md +++ b/docs/helpers/Appium.md @@ -12,8 +12,8 @@ title: Appium **Extends Webdriver** Appium helper extends [Webdriver][1] helper. - It supports all browser methods and also includes special methods for mobile apps testing. - You can use this helper to test Web on desktop and mobile devices and mobile apps. +It supports all browser methods and also includes special methods for mobile apps testing. +You can use this helper to test Web on desktop and mobile devices and mobile apps. ## Appium Installation @@ -32,20 +32,20 @@ Launch the daemon: `appium` This helper should be configured in codecept.conf.ts or codecept.conf.js -- `appiumV2`: set this to true if you want to run tests with AppiumV2. See more how to setup [here][3] -- `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage -- `host`: (default: 'localhost') Appium host -- `port`: (default: '4723') Appium port -- `platform`: (Android or IOS), which mobile OS to use; alias to desiredCapabilities.platformName -- `restart`: restart browser or app between tests (default: true), if set to false cookies will be cleaned but browser window will be kept and for apps nothing will be changed. -- `desiredCapabilities`: \[], Appium capabilities, see below - - `platformName` - Which mobile OS platform to use - - `appPackage` - Java package of the Android app you want to run - - `appActivity` - Activity name for the Android activity you want to launch from your package. - - `deviceName`: The kind of mobile device or emulator to use - - `platformVersion`: Mobile OS version - - `app` - The absolute local path or remote http URL to an .ipa or .apk file, or a .zip containing one of these. Appium will attempt to install this app binary on the appropriate device first. - - `browserName`: Name of mobile web browser to automate. Should be an empty string if automating an app instead. +* `appiumV2`: set this to true if you want to run tests with AppiumV2. See more how to setup [here][3] +* `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage +* `host`: (default: 'localhost') Appium host +* `port`: (default: '4723') Appium port +* `platform`: (Android or IOS), which mobile OS to use; alias to desiredCapabilities.platformName +* `restart`: restart browser or app between tests (default: true), if set to false cookies will be cleaned but browser window will be kept and for apps nothing will be changed. +* `desiredCapabilities`: \[], Appium capabilities, see below + * `platformName` - Which mobile OS platform to use + * `appPackage` - Java package of the Android app you want to run + * `appActivity` - Activity name for the Android activity you want to launch from your package. + * `deviceName`: The kind of mobile device or emulator to use + * `platformVersion`: Mobile OS version + * `app` - The absolute local path or remote http URL to an .ipa or .apk file, or a .zip containing one of these. Appium will attempt to install this app binary on the appropriate device first. + * `browserName`: Name of mobile web browser to automate. Should be an empty string if automating an app instead. Example Android App: @@ -157,7 +157,7 @@ let browser = this.helpers['Appium'].browser ### Parameters -- `config` +* `config` ### runOnIOS @@ -192,8 +192,8 @@ I.runOnAndroid((caps) => { #### Parameters -- `caps` **any** -- `fn` **any** +* `caps` **any** +* `fn` **any** ### runOnAndroid @@ -228,8 +228,8 @@ I.runOnAndroid((caps) => { #### Parameters -- `caps` **any** -- `fn` **any** +* `caps` **any** +* `fn` **any** ### runInWeb @@ -242,10 +242,6 @@ I.runInWeb(() => { }); ``` -#### Parameters - -- `fn` **any** - ### checkIfAppIsInstalled Returns app installation status. @@ -256,9 +252,9 @@ I.checkIfAppIsInstalled("com.example.android.apis"); #### Parameters -- `bundleId` **[string][5]** String ID of bundled app +* `bundleId` **[string][5]** String ID of bundled app -Returns **[Promise][6]<[boolean][7]>** Appium: support only Android +Returns **[Promise][6]<[boolean][7]>** Appium: support only Android ### seeAppIsInstalled @@ -270,9 +266,9 @@ I.seeAppIsInstalled("com.example.android.apis"); #### Parameters -- `bundleId` **[string][5]** String ID of bundled app +* `bundleId` **[string][5]** String ID of bundled app -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### seeAppIsNotInstalled @@ -284,9 +280,9 @@ I.seeAppIsNotInstalled("com.example.android.apis"); #### Parameters -- `bundleId` **[string][5]** String ID of bundled app +* `bundleId` **[string][5]** String ID of bundled app -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### installApp @@ -298,9 +294,9 @@ I.installApp('/path/to/file.apk'); #### Parameters -- `path` **[string][5]** path to apk file +* `path` **[string][5]** path to apk file -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### removeApp @@ -314,8 +310,8 @@ Appium: support only Android #### Parameters -- `appId` **[string][5]** -- `bundleId` **[string][5]?** ID of bundle +* `appId` **[string][5]** +* `bundleId` **[string][5]?** ID of bundle ### resetApp @@ -335,9 +331,9 @@ I.seeCurrentActivityIs(".HomeScreenActivity") #### Parameters -- `currentActivity` **[string][5]** +* `currentActivity` **[string][5]** -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### seeDeviceIsLocked @@ -347,7 +343,7 @@ Check whether the device is locked. I.seeDeviceIsLocked(); ``` -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### seeDeviceIsUnlocked @@ -357,7 +353,7 @@ Check whether the device is not locked. I.seeDeviceIsUnlocked(); ``` -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### seeOrientationIs @@ -370,9 +366,9 @@ I.seeOrientationIs('LANDSCAPE') #### Parameters -- `orientation` **(`"LANDSCAPE"` \| `"PORTRAIT"`)** LANDSCAPE or PORTRAITAppium: support Android and iOS +* `orientation` **(`"LANDSCAPE"` | `"PORTRAIT"`)** LANDSCAPE or PORTRAITAppium: support Android and iOS -Returns **[Promise][6]<void>** +Returns **[Promise][6]\** ### setOrientation @@ -385,7 +381,7 @@ I.setOrientation('LANDSCAPE') #### Parameters -- `orientation` **(`"LANDSCAPE"` \| `"PORTRAIT"`)** LANDSCAPE or PORTRAITAppium: support Android and iOS +* `orientation` **(`"LANDSCAPE"` | `"PORTRAIT"`)** LANDSCAPE or PORTRAITAppium: support Android and iOS ### grabAllContexts @@ -393,7 +389,7 @@ Get list of all available contexts let contexts = await I.grabAllContexts(); -Returns **[Promise][6]<[Array][8]<[string][5]>>** Appium: support Android and iOS +Returns **[Promise][6]<[Array][8]<[string][5]>>** Appium: support Android and iOS ### grabContext @@ -403,7 +399,7 @@ Retrieve current context let context = await I.grabContext(); ``` -Returns **[Promise][6]<([string][5] | null)>** Appium: support Android and iOS +Returns **[Promise][6]<([string][5] | null)>** Appium: support Android and iOS ### grabCurrentActivity @@ -413,7 +409,7 @@ Get current device activity. let activity = await I.grabCurrentActivity(); ``` -Returns **[Promise][6]<[string][5]>** Appium: support only Android +Returns **[Promise][6]<[string][5]>** Appium: support only Android ### grabNetworkConnection @@ -425,7 +421,7 @@ properties to the response object to allow easier assertions. let con = await I.grabNetworkConnection(); ``` -Returns **[Promise][6]<{}>** Appium: support only Android +Returns **[Promise][6]<{}>** Appium: support only Android ### grabOrientation @@ -435,7 +431,7 @@ Get current orientation. let orientation = await I.grabOrientation(); ``` -Returns **[Promise][6]<[string][5]>** Appium: support Android and iOS +Returns **[Promise][6]<[string][5]>** Appium: support Android and iOS ### grabSettings @@ -445,7 +441,7 @@ Get all the currently specified settings. let settings = await I.grabSettings(); ``` -Returns **[Promise][6]<[string][5]>** Appium: support Android and iOS +Returns **[Promise][6]<[string][5]>** Appium: support Android and iOS ### switchToContext @@ -453,7 +449,7 @@ Switch to the specified context. #### Parameters -- `context` **any** the context to switch to +* `context` **any** the context to switch to ### switchToWeb @@ -470,14 +466,14 @@ I.switchToWeb('WEBVIEW_io.selendroid.testapp'); #### Parameters -- `context` **[string][5]?** +* `context` **[string][5]?** -Returns **[Promise][6]<void>** +Returns **[Promise][6]\** ### switchToNative Switches to native context. -By default switches to NATIVE_APP context unless other specified. +By default switches to NATIVE\_APP context unless other specified. ```js I.switchToNative(); @@ -488,9 +484,9 @@ I.switchToNative('SOME_OTHER_CONTEXT'); #### Parameters -- `context` **any?** (optional, default `null`) +* `context` **any?** (optional, default `null`) -Returns **[Promise][6]<void>** +Returns **[Promise][6]\** ### startActivity @@ -504,18 +500,18 @@ Appium: support only Android #### Parameters -- `appPackage` **[string][5]** -- `appActivity` **[string][5]** +* `appPackage` **[string][5]** +* `appActivity` **[string][5]** -Returns **[Promise][6]<void>** +Returns **[Promise][6]\** ### setNetworkConnection Set network connection mode. -- airplane mode -- wifi mode -- data data +* airplane mode +* wifi mode +* data data ```js I.setNetworkConnection(0) // airplane mode off, wifi off, data off @@ -531,9 +527,9 @@ Appium: support only Android #### Parameters -- `value` **[number][10]** The network connection mode bitmask +* `value` **[number][10]** The network connection mode bitmask -Returns **[Promise][6]<[number][10]>** +Returns **[Promise][6]<[number][10]>** ### setSettings @@ -545,7 +541,7 @@ I.setSettings({cyberdelia: 'open'}); #### Parameters -- `settings` **[object][11]** objectAppium: support Android and iOS +* `settings` **[object][11]** objectAppium: support Android and iOS ### hideDeviceKeyboard @@ -564,8 +560,8 @@ Appium: support Android and iOS #### Parameters -- `strategy` **(`"tapOutside"` \| `"pressKey"`)?** Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’) -- `key` **[string][5]?** Optional key +* `strategy` **(`"tapOutside"` | `"pressKey"`)?** Desired strategy to close keyboard (‘tapOutside’ or ‘pressKey’) +* `key` **[string][5]?** Optional key ### sendDeviceKeyEvent @@ -578,9 +574,9 @@ I.sendDeviceKeyEvent(3); #### Parameters -- `keyValue` **[number][10]** Device specific key value +* `keyValue` **[number][10]** Device specific key value -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### openNotifications @@ -590,7 +586,7 @@ Open the notifications panel on the device. I.openNotifications(); ``` -Returns **[Promise][6]<void>** Appium: support only Android +Returns **[Promise][6]\** Appium: support only Android ### makeTouchAction @@ -606,10 +602,10 @@ I.makeTouchAction("~buttonStartWebviewCD", 'tap'); #### Parameters -- `locator` -- `action` +* `locator` +* `action` -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### tap @@ -623,9 +619,9 @@ Shortcut for `makeTouchAction` #### Parameters -- `locator` **any** +* `locator` **any** -Returns **[Promise][6]<void>** +Returns **[Promise][6]\** ### swipe @@ -640,12 +636,12 @@ I.swipe(locator, 800, 1200, 1000); #### Parameters -- `locator` **([string][5] \| [object][11])** -- `xoffset` **[number][10]** -- `yoffset` **[number][10]** -- `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) +* `locator` **([string][5] | [object][11])** +* `xoffset` **[number][10]** +* `yoffset` **[number][10]** +* `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### performSwipe @@ -657,8 +653,8 @@ I.performSwipe({ x: 300, y: 100 }, { x: 200, y: 100 }); #### Parameters -- `from` **[object][11]** -- `to` **[object][11]** Appium: support Android and iOS +* `from` **[object][11]** +* `to` **[object][11]** Appium: support Android and iOS ### swipeDown @@ -673,11 +669,11 @@ I.swipeDown(locator, 1200, 1000); // set offset and speed #### Parameters -- `locator` **([string][5] \| [object][11])** -- `yoffset` **[number][10]?** (optional) (optional, default `1000`) -- `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) +* `locator` **([string][5] | [object][11])** +* `yoffset` **[number][10]?** (optional) (optional, default `1000`) +* `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### swipeLeft @@ -692,11 +688,11 @@ I.swipeLeft(locator, 1200, 1000); // set offset and speed #### Parameters -- `locator` **([string][5] \| [object][11])** -- `xoffset` **[number][10]?** (optional) (optional, default `1000`) -- `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) +* `locator` **([string][5] | [object][11])** +* `xoffset` **[number][10]?** (optional) (optional, default `1000`) +* `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### swipeRight @@ -711,11 +707,11 @@ I.swipeRight(locator, 1200, 1000); // set offset and speed #### Parameters -- `locator` **([string][5] \| [object][11])** -- `xoffset` **[number][10]?** (optional) (optional, default `1000`) -- `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) +* `locator` **([string][5] | [object][11])** +* `xoffset` **[number][10]?** (optional) (optional, default `1000`) +* `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### swipeUp @@ -730,11 +726,11 @@ I.swipeUp(locator, 1200, 1000); // set offset and speed #### Parameters -- `locator` **([string][5] \| [object][11])** -- `yoffset` **[number][10]?** (optional) (optional, default `1000`) -- `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) +* `locator` **([string][5] | [object][11])** +* `yoffset` **[number][10]?** (optional) (optional, default `1000`) +* `speed` **[number][10]** (optional), 1000 by default (optional, default `1000`) -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### swipeTo @@ -752,14 +748,14 @@ I.swipeTo( #### Parameters -- `searchableLocator` **[string][5]** -- `scrollLocator` **[string][5]** -- `direction` **[string][5]** -- `timeout` **[number][10]** -- `offset` **[number][10]** -- `speed` **[number][10]** +* `searchableLocator` **[string][5]** +* `scrollLocator` **[string][5]** +* `direction` **[string][5]** +* `timeout` **[number][10]** +* `offset` **[number][10]** +* `speed` **[number][10]** -Returns **[Promise][6]<void>** Appium: support Android and iOS +Returns **[Promise][6]\** Appium: support Android and iOS ### touchPerform @@ -790,7 +786,7 @@ Appium: support Android and iOS #### Parameters -- `actions` **[Array][8]** Array of touch actions +* `actions` **[Array][8]** Array of touch actions ### pullFile @@ -804,10 +800,10 @@ I.pullFile('/storage/emulated/0/DCIM/logo.png', output_dir); #### Parameters -- `path` **[string][5]** -- `dest` **[string][5]** +* `path` **[string][5]** +* `dest` **[string][5]** -Returns **[Promise][6]<[string][5]>** Appium: support Android and iOS +Returns **[Promise][6]<[string][5]>** Appium: support Android and iOS ### shakeDevice @@ -817,7 +813,7 @@ Perform a shake action on the device. I.shakeDevice(); ``` -Returns **[Promise][6]<void>** Appium: support only iOS +Returns **[Promise][6]\** Appium: support only iOS ### rotate @@ -831,14 +827,14 @@ See corresponding [webdriverio reference][15]. #### Parameters -- `x` -- `y` -- `duration` -- `radius` -- `rotation` -- `touchCount` +* `x` +* `y` +* `duration` +* `radius` +* `rotation` +* `touchCount` -Returns **[Promise][6]<void>** Appium: support only iOS +Returns **[Promise][6]\** Appium: support only iOS ### setImmediateValue @@ -848,10 +844,10 @@ See corresponding [webdriverio reference][16]. #### Parameters -- `id` -- `value` +* `id` +* `value` -Returns **[Promise][6]<void>** Appium: support only iOS +Returns **[Promise][6]\** Appium: support only iOS ### simulateTouchId @@ -865,9 +861,9 @@ I.touchId(false); // simulates invalid fingerprint #### Parameters -- `match` +* `match` -Returns **[Promise][6]<void>** Appium: support only iOS +Returns **[Promise][6]\** Appium: support only iOS TODO: not tested ### closeApp @@ -878,7 +874,7 @@ Close the given application. I.closeApp(); ``` -Returns **[Promise][6]<void>** Appium: support both Android and iOS +Returns **[Promise][6]\** Appium: support both Android and iOS ### appendField @@ -893,8 +889,8 @@ I.appendField('password', secret('123456')); #### Parameters -- `field` **([string][5] \| [object][11])** located by label|name|CSS|XPath|strict locator -- `value` **[string][5]** text value to append. +* `field` **([string][5] | [object][11])** located by label|name|CSS|XPath|strict locator +* `value` **[string][5]** text value to append. Returns **void** automatically synchronized promise through #recorder @@ -913,8 +909,8 @@ I.checkOption('agree', '//form'); #### Parameters -- `field` **([string][5] \| [object][11])** checkbox located by label | name | CSS | XPath | strict locator. -- `context` **([string][5]? | [object][11])** (optional, `null` by default) element located by CSS | XPath | strict locator. (optional, default `null`) +* `field` **([string][5] | [object][11])** checkbox located by label | name | CSS | XPath | strict locator. +* `context` **([string][5]? | [object][11])** (optional, `null` by default) element located by CSS | XPath | strict locator. (optional, default `null`) Returns **void** automatically synchronized promise through #recorder @@ -944,8 +940,8 @@ I.click({css: 'nav a.login'}); #### Parameters -- `locator` **([string][5] \| [object][11])** clickable link or button located by text, or any element located by CSS|XPath|strict locator. -- `context` **([string][5]? | [object][11] | null)** (optional, `null` by default) element to search in CSS|XPath|Strict locator. (optional, default `null`) +* `locator` **([string][5] | [object][11])** clickable link or button located by text, or any element located by CSS|XPath|strict locator. +* `context` **([string][5]? | [object][11] | null)** (optional, `null` by default) element to search in CSS|XPath|Strict locator. (optional, default `null`) Returns **void** automatically synchronized promise through #recorder @@ -961,7 +957,7 @@ I.dontSeeCheckboxIsChecked('agree'); // located by name #### Parameters -- `field` **([string][5] \| [object][11])** located by label|name|CSS|XPath|strict locator. +* `field` **([string][5] | [object][11])** located by label|name|CSS|XPath|strict locator. Returns **void** automatically synchronized promise through #recorder @@ -975,7 +971,7 @@ I.dontSeeElement('.modal'); // modal is not shown #### Parameters -- `locator` **([string][5] \| [object][11])** located by CSS|XPath|Strict locator. +* `locator` **([string][5] | [object][11])** located by CSS|XPath|Strict locator. Returns **void** automatically synchronized promise through #recorder @@ -991,8 +987,8 @@ I.dontSeeInField({ css: 'form input.email' }, 'user@user.com'); // field by CSS #### Parameters -- `field` **([string][5] \| [object][11])** located by label|name|CSS|XPath|strict locator. -- `value` **([string][5] \| [object][11])** value to check. +* `field` **([string][5] | [object][11])** located by label|name|CSS|XPath|strict locator. +* `value` **([string][5] | [object][11])** value to check. Returns **void** automatically synchronized promise through #recorder @@ -1008,8 +1004,8 @@ I.dontSee('Login', '.nav'); // no login inside .nav element #### Parameters -- `text` **[string][5]** which is not present. -- `context` **([string][5] \| [object][11])?** (optional) element located by CSS|XPath|strict locator in which to perfrom search. (optional, default `null`) +* `text` **[string][5]** which is not present. +* `context` **([string][5] | [object][11])?** (optional) element located by CSS|XPath|strict locator in which to perfrom search. (optional, default `null`) Returns **void** automatically synchronized promise through #recorder @@ -1031,8 +1027,8 @@ I.fillField({css: 'form#login input[name=username]'}, 'John'); #### Parameters -- `field` **([string][5] \| [object][11])** located by label|name|CSS|XPath|strict locator. -- `value` **([string][5] \| [object][11])** text value to fill. +* `field` **([string][5] | [object][11])** located by label|name|CSS|XPath|strict locator. +* `value` **([string][5] | [object][11])** text value to fill. Returns **void** automatically synchronized promise through #recorder @@ -1047,9 +1043,9 @@ let pins = await I.grabTextFromAll('#pin li'); #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. -Returns **[Promise][6]<[Array][8]<[string][5]>>** attribute value +Returns **[Promise][6]<[Array][8]<[string][5]>>** attribute value ### grabTextFrom @@ -1064,9 +1060,9 @@ If multiple elements found returns first element. #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. -Returns **[Promise][6]<[string][5]>** attribute value +Returns **[Promise][6]<[string][5]>** attribute value ### grabNumberOfVisibleElements @@ -1079,9 +1075,9 @@ let numOfElements = await I.grabNumberOfVisibleElements('p'); #### Parameters -- `locator` **([string][5] \| [object][11])** located by CSS|XPath|strict locator. +* `locator` **([string][5] | [object][11])** located by CSS|XPath|strict locator. -Returns **[Promise][6]<[number][10]>** number of visible elements +Returns **[Promise][6]<[number][10]>** number of visible elements ### grabAttributeFrom @@ -1097,10 +1093,10 @@ let hint = await I.grabAttributeFrom('#tooltip', 'title'); #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. -- `attr` **[string][5]** attribute name. +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. +* `attr` **[string][5]** attribute name. -Returns **[Promise][6]<[string][5]>** attribute value +Returns **[Promise][6]<[string][5]>** attribute value ### grabAttributeFromAll @@ -1114,10 +1110,10 @@ let hints = await I.grabAttributeFromAll('.tooltip', 'title'); #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. -- `attr` **[string][5]** attribute name. +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. +* `attr` **[string][5]** attribute name. -Returns **[Promise][6]<[Array][8]<[string][5]>>** attribute value +Returns **[Promise][6]<[Array][8]<[string][5]>>** attribute value ### grabValueFromAll @@ -1130,9 +1126,9 @@ let inputs = await I.grabValueFromAll('//form/input'); #### Parameters -- `locator` **([string][5] \| [object][11])** field located by label|name|CSS|XPath|strict locator. +* `locator` **([string][5] | [object][11])** field located by label|name|CSS|XPath|strict locator. -Returns **[Promise][6]<[Array][8]<[string][5]>>** attribute value +Returns **[Promise][6]<[Array][8]<[string][5]>>** attribute value ### grabValueFrom @@ -1146,9 +1142,9 @@ let email = await I.grabValueFrom('input[name=email]'); #### Parameters -- `locator` **([string][5] \| [object][11])** field located by label|name|CSS|XPath|strict locator. +* `locator` **([string][5] | [object][11])** field located by label|name|CSS|XPath|strict locator. -Returns **[Promise][6]<[string][5]>** attribute value +Returns **[Promise][6]<[string][5]>** attribute value ### saveScreenshot @@ -1161,9 +1157,9 @@ I.saveScreenshot('debug.png'); #### Parameters -- `fileName` **[string][5]** file name to save. +* `fileName` **[string][5]** file name to save. -Returns **[Promise][6]<void>** +Returns **[Promise][6]\** ### scrollIntoView @@ -1177,8 +1173,8 @@ I.scrollIntoView('#submit', { behavior: "smooth", block: "center", inline: "cent #### Parameters -- `locator` **([string][5] \| [object][11])** located by CSS|XPath|strict locator. -- `scrollIntoViewOptions` **(ScrollIntoViewOptions | [boolean][7])** either alignToTop=true|false or scrollIntoViewOptions. See [https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView][17]. +* `locator` **([string][5] | [object][11])** located by CSS|XPath|strict locator. +* `scrollIntoViewOptions` **(ScrollIntoViewOptions | [boolean][7])** either alignToTop=true|false or scrollIntoViewOptions. See [https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView][17]. Returns **void** automatically synchronized promise through #recorderSupported only for web testing @@ -1194,7 +1190,7 @@ I.seeCheckboxIsChecked({css: '#signup_form input[type=checkbox]'}); #### Parameters -- `field` **([string][5] \| [object][11])** located by label|name|CSS|XPath|strict locator. +* `field` **([string][5] | [object][11])** located by label|name|CSS|XPath|strict locator. Returns **void** automatically synchronized promise through #recorder @@ -1209,7 +1205,7 @@ I.seeElement('#modal'); #### Parameters -- `locator` **([string][5] \| [object][11])** located by CSS|XPath|strict locator. +* `locator` **([string][5] | [object][11])** located by CSS|XPath|strict locator. Returns **void** automatically synchronized promise through #recorder @@ -1227,8 +1223,8 @@ I.seeInField('#searchform input','Search'); #### Parameters -- `field` **([string][5] \| [object][11])** located by label|name|CSS|XPath|strict locator. -- `value` **([string][5] \| [object][11])** value to check. +* `field` **([string][5] | [object][11])** located by label|name|CSS|XPath|strict locator. +* `value` **([string][5] | [object][11])** value to check. Returns **void** automatically synchronized promise through #recorder @@ -1245,8 +1241,8 @@ I.see('Register', {css: 'form.register'}); // use strict locator #### Parameters -- `text` **[string][5]** expected on page. -- `context` **([string][5]? | [object][11])** (optional, `null` by default) element located by CSS|Xpath|strict locator in which to search for text. (optional, default `null`) +* `text` **[string][5]** expected on page. +* `context` **([string][5]? | [object][11])** (optional, `null` by default) element located by CSS|Xpath|strict locator in which to search for text. (optional, default `null`) Returns **void** automatically synchronized promise through #recorder @@ -1273,8 +1269,8 @@ I.selectOption('Which OS do you use?', ['Android', 'iOS']); #### Parameters -- `select` **([string][5] \| [object][11])** field located by label|name|CSS|XPath|strict locator. -- `option` **([string][5] \| [Array][8]<any>)** visible text or value of option. +* `select` **([string][5] | [object][11])** field located by label|name|CSS|XPath|strict locator. +* `option` **([string][5] | [Array][8]\)** visible text or value of option. Returns **void** automatically synchronized promise through #recorderSupported only for web testing @@ -1290,8 +1286,8 @@ I.waitForElement('.btn.continue', 5); // wait for 5 secs #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. -- `sec` **[number][10]?** (optional, `1` by default) time in seconds to wait (optional, default `null`) +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. +* `sec` **[number][10]?** (optional, `1` by default) time in seconds to wait (optional, default `null`) Returns **void** automatically synchronized promise through #recorder @@ -1306,8 +1302,8 @@ I.waitForVisible('#popup'); #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. -- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait (optional, default `1`) +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. +* `sec` **[number][10]** (optional, `1` by default) time in seconds to wait (optional, default `1`) Returns **void** automatically synchronized promise through #recorder @@ -1322,8 +1318,8 @@ I.waitForInvisible('#popup'); #### Parameters -- `locator` **([string][5] \| [object][11])** element located by CSS|XPath|strict locator. -- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait (optional, default `1`) +* `locator` **([string][5] | [object][11])** element located by CSS|XPath|strict locator. +* `sec` **[number][10]** (optional, `1` by default) time in seconds to wait (optional, default `1`) Returns **void** automatically synchronized promise through #recorder @@ -1340,9 +1336,9 @@ I.waitForText('Thank you, form has been submitted', 5, '#modal'); #### Parameters -- `text` **[string][5]** to wait for. -- `sec` **[number][10]** (optional, `1` by default) time in seconds to wait (optional, default `1`) -- `context` **([string][5] \| [object][11])?** (optional) element located by CSS|XPath|strict locator. (optional, default `null`) +* `text` **[string][5]** to wait for. +* `sec` **[number][10]** (optional, `1` by default) time in seconds to wait (optional, default `1`) +* `context` **([string][5] | [object][11])?** (optional) element located by CSS|XPath|strict locator. (optional, default `null`) Returns **void** automatically synchronized promise through #recorder diff --git a/docs/helpers/Detox.md b/docs/helpers/Detox.md index 45401848e..4d5c46204 100644 --- a/docs/helpers/Detox.md +++ b/docs/helpers/Detox.md @@ -27,6 +27,7 @@ Comparing to Appium, Detox runs faster and more stable but requires an additiona 2. [Build an application][3] using `detox build` command. 3. Install [CodeceptJS][4] and detox-helper: + npm i @codeceptjs/detox-helper --save @@ -73,21 +74,22 @@ helpers: { configuration: '', } } + ``` It's important to specify a package name under `require` section and current detox configuration taken from `package.json`. Options: -- `configuration` - a detox configuration name. Required. -- `reloadReactNative` - should be enabled for React Native applications. -- `reuse` - reuse application for tests. By default, Detox reinstalls and relaunches app. -- `registerGlobals` - (default: true) Register Detox helper functions `by`, `element`, `expect`, `waitFor` globally. -- `url` - URL to open via deep-link each time the app is launched (android) or immediately afterwards (iOS). Useful for opening a bundle URL at the beginning of tests when working with Expo. +* `configuration` - a detox configuration name. Required. +* `reloadReactNative` - should be enabled for React Native applications. +* `reuse` - reuse application for tests. By default, Detox reinstalls and relaunches app. +* `registerGlobals` - (default: true) Register Detox helper functions `by`, `element`, `expect`, `waitFor` globally. +* `url` - URL to open via deep-link each time the app is launched (android) or immediately afterwards (iOS). Useful for opening a bundle URL at the beginning of tests when working with Expo. ### Parameters -- `config` +* `config` ### appendField @@ -100,8 +102,8 @@ I.appendField('name', 'davert'); #### Parameters -- `field` **([string][5] \| [object][6])** -- `value` **[string][5]** +* `field` **([string][5] | [object][6])** +* `value` **[string][5]** ### checkIfElementExists @@ -114,8 +116,8 @@ I.checkIfElementExists('~edit', '#menu'); // element inside #menu #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `context` **([string][5] \| [object][6] | null)** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `context` **([string][5] | [object][6] | null)** context element (optional, default `null`) ### clearField @@ -128,7 +130,7 @@ I.clearField('~name'); #### Parameters -- `field` **([string][5] \| [object][6])** an input element to clear +* `field` **([string][5] | [object][6])** an input element to clear ### click @@ -149,8 +151,8 @@ I.click({ ios: 'Save', android: 'SAVE' }, '#main'); // different texts on iOS an #### Parameters -- `locator` **([string][5] \| [object][6])** -- `context` **([string][5] \| [object][6] | null)** (optional, default `null`) +* `locator` **([string][5] | [object][6])** +* `context` **([string][5] | [object][6] | null)** (optional, default `null`) ### clickAtPoint @@ -164,9 +166,9 @@ I.clickAtPoint('~save', 10, 10); // locate by accessibility id #### Parameters -- `locator` **([string][5] \| [object][6])** -- `x` **[number][8]** horizontal offset (optional, default `0`) -- `y` **[number][8]** vertical offset (optional, default `0`) +* `locator` **([string][5] | [object][6])** +* `x` **[number][8]** horizontal offset (optional, default `0`) +* `y` **[number][8]** vertical offset (optional, default `0`) ### dontSee @@ -181,8 +183,8 @@ I.dontSee('Record deleted', '~message'); #### Parameters -- `text` **[string][5]** to check invisibility -- `context` **([string][5] \| [object][6] | null)** element in which to search for text (optional, default `null`) +* `text` **[string][5]** to check invisibility +* `context` **([string][5] | [object][6] | null)** element in which to search for text (optional, default `null`) ### dontSeeElement @@ -196,8 +198,8 @@ I.dontSeeElement('~edit', '#menu'); // element inside #menu #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `context` **([string][5] \| [object][6] | null)** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `context` **([string][5] | [object][6] | null)** context element (optional, default `null`) ### dontSeeElementExists @@ -211,8 +213,8 @@ I.dontSeeElementExist('~edit', '#menu'); // element inside #menu #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `context` **([string][5] \| [object][6])** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `context` **([string][5] | [object][6])** context element (optional, default `null`) ### fillField @@ -227,8 +229,8 @@ I.fillField({ android: 'NAME', ios: 'name' }, 'davert'); #### Parameters -- `field` **([string][5] \| [object][6])** an input element to fill in -- `value` **[string][5]** value to fill +* `field` **([string][5] | [object][6])** an input element to fill in +* `value` **[string][5]** value to fill ### goBack @@ -275,9 +277,9 @@ I.longPress('Update', 2, '#menu'); // locate by text inside #menu, hold for 2 se #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `sec` **[number][8]** number of seconds to hold tap -- `context` **([string][5] \| [object][6] | null)** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `sec` **[number][8]** number of seconds to hold tap +* `context` **([string][5] | [object][6] | null)** context element (optional, default `null`) ### multiTap @@ -296,9 +298,9 @@ I.multiTap('Update', 2, '#menu'); // locate by id #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `num` **[number][8]** number of taps -- `context` **([string][5] \| [object][6] | null)** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `num` **[number][8]** number of taps +* `context` **([string][5] | [object][6] | null)** context element (optional, default `null`) ### relaunchApp @@ -321,7 +323,7 @@ I.runOnAndroid(() => { #### Parameters -- `fn` **[Function][10]** a function which will be executed on android +* `fn` **[Function][10]** a function which will be executed on android ### runOnIOS @@ -336,7 +338,7 @@ I.runOnIOS(() => { #### Parameters -- `fn` **[Function][10]** a function which will be executed on iOS +* `fn` **[Function][10]** a function which will be executed on iOS ### saveScreenshot @@ -348,7 +350,7 @@ I.saveScreenshot('main-window.png'); #### Parameters -- `name` **[string][5]** +* `name` **[string][5]** ### scrollDown @@ -360,7 +362,7 @@ I.scrollDown('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** +* `locator` **([string][5] | [object][6])** ### scrollLeft @@ -372,7 +374,7 @@ I.scrollLeft('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** +* `locator` **([string][5] | [object][6])** ### scrollRight @@ -384,7 +386,7 @@ I.scrollRight('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** +* `locator` **([string][5] | [object][6])** ### scrollToElement @@ -392,10 +394,10 @@ Scrolls within a scrollable container to an element. #### Parameters -- `targetLocator` **([string][5] \| [object][6])** Locator of the element to scroll to -- `containerLocator` **([string][5] \| [object][6])** Locator of the scrollable container -- `direction` **[string][5]** 'up' or 'down' (optional, default `'down'`) -- `offset` **[number][8]** Offset for scroll, can be adjusted based on need (optional, default `100`) +* `targetLocator` **([string][5] | [object][6])** Locator of the element to scroll to +* `containerLocator` **([string][5] | [object][6])** Locator of the scrollable container +* `direction` **[string][5]** 'up' or 'down' (optional, default `'down'`) +* `offset` **[number][8]** Offset for scroll, can be adjusted based on need (optional, default `100`) ### scrollUp @@ -407,7 +409,7 @@ I.scrollUp('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** +* `locator` **([string][5] | [object][6])** ### see @@ -422,8 +424,8 @@ I.see('Record deleted', '~message'); #### Parameters -- `text` **[string][5]** to check visibility -- `context` **([string][5] \| [object][6] | null)** element inside which to search for text (optional, default `null`) +* `text` **[string][5]** to check visibility +* `context` **([string][5] | [object][6] | null)** element inside which to search for text (optional, default `null`) ### seeElement @@ -437,8 +439,8 @@ I.seeElement('~edit', '#menu'); // element inside #menu #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `context` **([string][5] \| [object][6] | null)** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `context` **([string][5] | [object][6] | null)** context element (optional, default `null`) ### seeElementExists @@ -452,8 +454,8 @@ I.seeElementExists('~edit', '#menu'); // element inside #menu #### Parameters -- `locator` **([string][5] \| [object][6])** element to locate -- `context` **([string][5] \| [object][6])** context element (optional, default `null`) +* `locator` **([string][5] | [object][6])** element to locate +* `context` **([string][5] | [object][6])** context element (optional, default `null`) ### setLandscapeOrientation @@ -490,8 +492,8 @@ I.swipeUp('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** an element on which to perform swipe -- `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) +* `locator` **([string][5] | [object][6])** an element on which to perform swipe +* `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) ### swipeLeft @@ -504,8 +506,8 @@ I.swipeUp('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** an element on which to perform swipe -- `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) +* `locator` **([string][5] | [object][6])** an element on which to perform swipe +* `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) ### swipeRight @@ -518,8 +520,8 @@ I.swipeUp('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** an element on which to perform swipe -- `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) +* `locator` **([string][5] | [object][6])** an element on which to perform swipe +* `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) ### swipeUp @@ -532,8 +534,8 @@ I.swipeUp('#container'); #### Parameters -- `locator` **([string][5] \| [object][6])** an element on which to perform swipe -- `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) +* `locator` **([string][5] | [object][6])** an element on which to perform swipe +* `speed` **[string][5]** a speed to perform: `slow` or `fast`. (optional, default `'slow'`) ### tap @@ -554,8 +556,8 @@ I.tap({ ios: 'Save', android: 'SAVE' }, '#main'); // different texts on iOS and #### Parameters -- `locator` **([string][5] \| [object][6])** -- `context` **([string][5] \| [object][6] | null)** (optional, default `null`) +* `locator` **([string][5] | [object][6])** +* `context` **([string][5] | [object][6] | null)** (optional, default `null`) ### tapByLabel @@ -571,8 +573,8 @@ I.tapByLabel('Login', '#nav'); // locate by text inside #nav #### Parameters -- `locator` **([string][5] \| [object][6])** -- `context` **([string][5] \| [object][6] | null)** (optional, default `null`) +* `locator` **([string][5] | [object][6])** +* `context` **([string][5] | [object][6] | null)** (optional, default `null`) ### tapReturnKey @@ -587,7 +589,7 @@ I.tapReturnKey({ android: 'NAME', ios: 'name' }); #### Parameters -- `field` **([string][5] \| [object][6])** an input element to fill in +* `field` **([string][5] | [object][6])** an input element to fill in ### wait @@ -599,7 +601,7 @@ I.wait(2); // waits for 2 seconds #### Parameters -- `sec` **[number][8]** number of seconds to wait +* `sec` **[number][8]** number of seconds to wait ### waitForElement @@ -611,8 +613,8 @@ I.waitForElement('#message', 1); // wait for 1 second #### Parameters -- `locator` **([string][5] \| [object][6])** an element to wait for -- `sec` **[number][8]** number of seconds to wait, 5 by default (optional, default `5`) +* `locator` **([string][5] | [object][6])** an element to wait for +* `sec` **[number][8]** number of seconds to wait, 5 by default (optional, default `5`) ### waitForElementVisible @@ -624,8 +626,8 @@ I.waitForElementVisible('#message', 1); // wait for 1 second #### Parameters -- `locator` **([string][5] \| [object][6])** an element to wait for -- `sec` **[number][8]** number of seconds to wait (optional, default `5`) +* `locator` **([string][5] | [object][6])** an element to wait for +* `sec` **[number][8]** number of seconds to wait (optional, default `5`) ### waitToHide @@ -637,8 +639,8 @@ I.waitToHide('#message', 2); // wait for 2 seconds #### Parameters -- `locator` **([string][5] \| [object][6])** an element to wait for -- `sec` **[number][8]** number of seconds to wait (optional, default `5`) +* `locator` **([string][5] | [object][6])** an element to wait for +* `sec` **[number][8]** number of seconds to wait (optional, default `5`) [1]: https://github.com/wix/Detox diff --git a/docs/helpers/ExpectHelper.md b/docs/helpers/ExpectHelper.md index 7ffe415ab..3a0659b29 100644 --- a/docs/helpers/ExpectHelper.md +++ b/docs/helpers/ExpectHelper.md @@ -31,33 +31,33 @@ Zero-configuration when paired with other helpers like REST, Playwright: #### Parameters -- `targetData` **any** -- `aboveThan` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `aboveThan` **any** +* `customErrorMsg` **any?** ### expectBelow #### Parameters -- `targetData` **any** -- `belowThan` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `belowThan` **any** +* `customErrorMsg` **any?** ### expectContain #### Parameters -- `actualValue` **any** -- `expectedValueToContain` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValueToContain` **any** +* `customErrorMsg` **any?** ### expectDeepEqual #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `customErrorMsg` **any?** ### expectDeepEqualExcluding @@ -65,10 +65,10 @@ expects members of two JSON objects are deeply equal excluding some properties #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `fieldsToExclude` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `fieldsToExclude` **any** +* `customErrorMsg` **any?** ### expectDeepIncludeMembers @@ -76,9 +76,9 @@ expects an array to be a superset of another array #### Parameters -- `superset` **any** -- `set` **any** -- `customErrorMsg` **any?** +* `superset` **any** +* `set` **any** +* `customErrorMsg` **any?** ### expectDeepMembers @@ -86,104 +86,104 @@ expects members of two arrays are deeply equal #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `customErrorMsg` **any?** ### expectEmpty #### Parameters -- `targetData` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `customErrorMsg` **any?** ### expectEndsWith #### Parameters -- `actualValue` **any** -- `expectedValueToEndWith` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValueToEndWith` **any** +* `customErrorMsg` **any?** ### expectEqual #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `customErrorMsg` **any?** ### expectEqualIgnoreCase #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `customErrorMsg` **any?** ### expectFalse #### Parameters -- `targetData` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `customErrorMsg` **any?** ### expectHasAProperty #### Parameters -- `targetData` **any** -- `propertyName` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `propertyName` **any** +* `customErrorMsg` **any?** ### expectHasProperty #### Parameters -- `targetData` **any** -- `propertyName` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `propertyName` **any** +* `customErrorMsg` **any?** ### expectJsonSchema #### Parameters -- `targetData` **any** -- `jsonSchema` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `jsonSchema` **any** +* `customErrorMsg` **any?** ### expectJsonSchemaUsingAJV #### Parameters -- `targetData` **any** -- `jsonSchema` **any** -- `customErrorMsg` **any?** -- `ajvOptions` **any?** Pass AJV options +* `targetData` **any** +* `jsonSchema` **any** +* `customErrorMsg` **any?** +* `ajvOptions` **any?** Pass AJV options ### expectLengthAboveThan #### Parameters -- `targetData` **any** -- `lengthAboveThan` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `lengthAboveThan` **any** +* `customErrorMsg` **any?** ### expectLengthBelowThan #### Parameters -- `targetData` **any** -- `lengthBelowThan` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `lengthBelowThan` **any** +* `customErrorMsg` **any?** ### expectLengthOf #### Parameters -- `targetData` **any** -- `length` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `length` **any** +* `customErrorMsg` **any?** ### expectMatchesPattern @@ -191,85 +191,85 @@ expects a JSON object matches a provided pattern #### Parameters -- `actualValue` **any** -- `expectedPattern` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedPattern` **any** +* `customErrorMsg` **any?** ### expectMatchRegex #### Parameters -- `targetData` **any** -- `regex` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `regex` **any** +* `customErrorMsg` **any?** ### expectNotContain #### Parameters -- `actualValue` **any** -- `expectedValueToNotContain` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValueToNotContain` **any** +* `customErrorMsg` **any?** ### expectNotDeepEqual #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `customErrorMsg` **any?** ### expectNotEndsWith #### Parameters -- `actualValue` **any** -- `expectedValueToNotEndWith` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValueToNotEndWith` **any** +* `customErrorMsg` **any?** ### expectNotEqual #### Parameters -- `actualValue` **any** -- `expectedValue` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValue` **any** +* `customErrorMsg` **any?** ### expectNotStartsWith #### Parameters -- `actualValue` **any** -- `expectedValueToNotStartWith` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValueToNotStartWith` **any** +* `customErrorMsg` **any?** ### expectStartsWith #### Parameters -- `actualValue` **any** -- `expectedValueToStartWith` **any** -- `customErrorMsg` **any?** +* `actualValue` **any** +* `expectedValueToStartWith` **any** +* `customErrorMsg` **any?** ### expectToBeA #### Parameters -- `targetData` **any** -- `type` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `type` **any** +* `customErrorMsg` **any?** ### expectToBeAn #### Parameters -- `targetData` **any** -- `type` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `type` **any** +* `customErrorMsg` **any?** ### expectTrue #### Parameters -- `targetData` **any** -- `customErrorMsg` **any?** +* `targetData` **any** +* `customErrorMsg` **any?** diff --git a/docs/helpers/FileSystem.md b/docs/helpers/FileSystem.md index 0af9843db..cd3d1351a 100644 --- a/docs/helpers/FileSystem.md +++ b/docs/helpers/FileSystem.md @@ -40,7 +40,7 @@ Starts from a current directory #### Parameters -- `openPath` **[string][1]** +* `openPath` **[string][1]** ### dontSeeFileContentsEqual @@ -48,8 +48,8 @@ Checks that contents of file found by `seeFile` doesn't equal to text. #### Parameters -- `text` **[string][1]** -- `encoding` **[string][1]** +* `text` **[string][1]** +* `encoding` **[string][1]** ### dontSeeInThisFile @@ -57,8 +57,8 @@ Checks that file found by `seeFile` doesn't include text. #### Parameters -- `text` **[string][1]** -- `encoding` **[string][1]** +* `text` **[string][1]** +* `encoding` **[string][1]** ### grabFileNames @@ -77,7 +77,7 @@ Checks that file exists #### Parameters -- `name` **[string][1]** +* `name` **[string][1]** ### seeFileContentsEqual @@ -85,8 +85,8 @@ Checks that contents of file found by `seeFile` equal to text. #### Parameters -- `text` **[string][1]** -- `encoding` **[string][1]** +* `text` **[string][1]** +* `encoding` **[string][1]** ### seeFileContentsEqualReferenceFile @@ -94,9 +94,9 @@ Checks that contents of the file found by `seeFile` equal to contents of the fil #### Parameters -- `pathToReferenceFile` **[string][1]** -- `encoding` **[string][1]** -- `encodingReference` **[string][1]** +* `pathToReferenceFile` **[string][1]** +* `encoding` **[string][1]** +* `encodingReference` **[string][1]** ### seeFileNameMatching @@ -111,7 +111,7 @@ I.seeFileNameMatching('.pdf'); #### Parameters -- `text` **[string][1]** +* `text` **[string][1]** ### seeInThisFile @@ -119,8 +119,8 @@ Checks that file found by `seeFile` includes a text. #### Parameters -- `text` **[string][1]** -- `encoding` **[string][1]** +* `text` **[string][1]** +* `encoding` **[string][1]** ### waitForFile @@ -135,8 +135,8 @@ I.waitForFile('largeFilesName.txt', 10); // wait 10 seconds for file #### Parameters -- `name` **[string][1]** -- `sec` **[number][2]** seconds to wait +* `name` **[string][1]** +* `sec` **[number][2]** seconds to wait ### writeToFile @@ -144,8 +144,8 @@ Writes text to file #### Parameters -- `name` **[string][1]** -- `text` **[string][1]** +* `name` **[string][1]** +* `text` **[string][1]** [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String diff --git a/docs/helpers/GraphQL.md b/docs/helpers/GraphQL.md index 82d7bbfa5..7aa35c637 100644 --- a/docs/helpers/GraphQL.md +++ b/docs/helpers/GraphQL.md @@ -16,10 +16,10 @@ GraphQL helper allows to send additional requests to a GraphQl endpoint during a ## Configuration -- endpoint: GraphQL base URL -- timeout: timeout for requests in milliseconds. 10000ms by default -- defaultHeaders: a list of default headers -- onRequest: a async function which can update request object. +* endpoint: GraphQL base URL +* timeout: timeout for requests in milliseconds. 10000ms by default +* defaultHeaders: a list of default headers +* onRequest: a async function which can update request object. ## Example @@ -47,7 +47,7 @@ this.helpers['GraphQL']._executeQuery({ ### Parameters -- `config` +* `config` ### _executeQuery @@ -55,7 +55,7 @@ Executes query via axios call #### Parameters -- `request` **[object][2]** +* `request` **[object][2]** ### _prepareGraphQLRequest @@ -63,8 +63,8 @@ Prepares request for axios call #### Parameters -- `operation` **[object][2]** -- `headers` **[object][2]** +* `operation` **[object][2]** +* `headers` **[object][2]** Returns **[object][2]** graphQLRequest @@ -79,7 +79,7 @@ I.amBearerAuthenticated(secret('heregoestoken')) #### Parameters -- `accessToken` **([string][3] | CodeceptJS.Secret)** Bearer access token +* `accessToken` **([string][3] | CodeceptJS.Secret)** Bearer access token ### haveRequestHeaders @@ -87,7 +87,7 @@ Sets request headers for all requests of this test #### Parameters -- `headers` **[object][2]** headers list +* `headers` **[object][2]** headers list ### sendMutation @@ -113,10 +113,10 @@ I.sendMutation(` #### Parameters -- `mutation` **[String][3]** -- `variables` **[object][2]?** that may go along with the mutation -- `options` **[object][2]?** are additional query options -- `headers` **[object][2]?** +* `mutation` **[String][3]** +* `variables` **[object][2]?** that may go along with the mutation +* `options` **[object][2]?** are additional query options +* `headers` **[object][2]?** Returns **any** Promise @@ -126,6 +126,7 @@ Send query to GraphQL endpoint over http. Returns a response as a promise. ```js + const response = await I.sendQuery('{ users { name email }}'); // with variables const response = await I.sendQuery( @@ -137,10 +138,10 @@ const user = response.data.data; #### Parameters -- `query` **[String][3]** -- `variables` **[object][2]?** that may go along with the query -- `options` **[object][2]?** are additional query options -- `headers` **[object][2]?** +* `query` **[String][3]** +* `variables` **[object][2]?** that may go along with the query +* `options` **[object][2]?** are additional query options +* `headers` **[object][2]?** Returns **any** Promise diff --git a/docs/helpers/GraphQLDataFactory.md b/docs/helpers/GraphQLDataFactory.md index d7795e773..277cf2cd6 100644 --- a/docs/helpers/GraphQLDataFactory.md +++ b/docs/helpers/GraphQLDataFactory.md @@ -62,7 +62,7 @@ module.exports = new Factory((buildObj) => ({ input: { ...buildObj }, })) // 'attr'-id can be left out depending on the GraphQl resolvers - .attr('name', () => faker.name.findName()) + .attr('name', () => faker.person.findName()) .attr('email', () => faker.interact.email()) ``` @@ -74,11 +74,11 @@ Then configure GraphQLDataHelper to match factories and GraphQL schema: GraphQLDataFactory has following config options: -- `endpoint`: URL for the GraphQL server. -- `cleanup` (default: true): should inserted records be deleted up after tests -- `factories`: list of defined factories -- `headers`: list of headers -- `GraphQL`: configuration for GraphQL requests. +* `endpoint`: URL for the GraphQL server. +* `cleanup` (default: true): should inserted records be deleted up after tests +* `factories`: list of defined factories +* `headers`: list of headers +* `GraphQL`: configuration for GraphQL requests. See the example: @@ -121,16 +121,16 @@ For instance, to set timeout you should add: Factory contains operations - -- `operation`: The operation/mutation that needs to be performed for creating a record in the backend. +* `operation`: The operation/mutation that needs to be performed for creating a record in the backend. Each operation must have the following: -- `query`: The mutation(query) string. It is expected to use variables to send data with the query. -- `factory`: The path to factory file. The object built by the factory in this file will be passed - as the 'variables' object to go along with the mutation. -- `revert`: A function called with the data returned when an item is created. The object returned by - this function is will be used to later delete the items created. So, make sure RELEVANT DATA IS RETURNED - when a record is created by a mutation. +* `query`: The mutation(query) string. It is expected to use variables to send data with the query. +* `factory`: The path to factory file. The object built by the factory in this file will be passed + as the 'variables' object to go along with the mutation. +* `revert`: A function called with the data returned when an item is created. The object returned by + this function is will be used to later delete the items created. So, make sure RELEVANT DATA IS RETURNED + when a record is created by a mutation. ### Requests @@ -158,7 +158,7 @@ Data of created records are collected and used in the end of a test for the clea ### Parameters -- `config` +* `config` ### _requestCreate @@ -167,8 +167,8 @@ Can be replaced from a custom helper. #### Parameters -- `operation` **[string][4]** -- `variables` **any** to be sent along with the query +* `operation` **[string][4]** +* `variables` **any** to be sent along with the query ### _requestDelete @@ -177,8 +177,8 @@ Can be replaced from a custom helper. #### Parameters -- `operation` **[string][4]** -- `data` **any** of the record to be deleted. +* `operation` **[string][4]** +* `data` **any** of the record to be deleted. ### mutateData @@ -194,8 +194,8 @@ const user = await I.mutateData('createUser', { email: 'user@user.com'}); #### Parameters -- `operation` **[string][4]** to be performed -- `params` **any** predefined parameters +* `operation` **[string][4]** to be performed +* `params` **any** predefined parameters ### mutateMultiple @@ -211,9 +211,9 @@ I.mutateMultiple('createUser', 3, { age: 25 }); #### Parameters -- `operation` **[string][4]** -- `times` **[number][5]** -- `params` **any** +* `operation` **[string][4]** +* `times` **[number][5]** +* `params` **any** [1]: https://github.com/rosiejs/rosie diff --git a/docs/helpers/JSONResponse.md b/docs/helpers/JSONResponse.md index 1126ce146..8cc350f6a 100644 --- a/docs/helpers/JSONResponse.md +++ b/docs/helpers/JSONResponse.md @@ -13,15 +13,15 @@ title: JSONResponse This helper allows performing assertions on JSON responses paired with following helpers: -- REST -- GraphQL -- Playwright +* REST +* GraphQL +* Playwright It can check status codes, response data, response structure. ## Configuration -- `requestHelper` - a helper which will perform requests. `REST` by default, also `Playwright` or `GraphQL` can be used. Custom helpers must have `onResponse` hook in their config, which will be executed when request is performed. +* `requestHelper` - a helper which will perform requests. `REST` by default, also `Playwright` or `GraphQL` can be used. Custom helpers must have `onResponse` hook in their config, which will be executed when request is performed. ### Examples @@ -68,7 +68,7 @@ const response = this.helpers.JSONResponse.response; ### Parameters -- `config` +* `config` ### dontSeeResponseCodeIs @@ -80,7 +80,7 @@ I.dontSeeResponseCodeIs(500); #### Parameters -- `code` **[number][1]** +* `code` **[number][1]** ### dontSeeResponseContainsJson @@ -102,7 +102,7 @@ I.dontSeeResponseContainsJson({ user: 2 }); #### Parameters -- `json` **[object][2]** +* `json` **[object][2]** ### seeResponseCodeIs @@ -114,7 +114,7 @@ I.seeResponseCodeIs(200); #### Parameters -- `code` **[number][1]** +* `code` **[number][1]** ### seeResponseCodeIsClientError @@ -157,7 +157,7 @@ I.seeResponseContainsJson({ user: { email: 'jon@doe.com' } }); #### Parameters -- `json` **[object][2]** +* `json` **[object][2]** ### seeResponseContainsKeys @@ -179,7 +179,7 @@ I.seeResponseContainsKeys(['user']); #### Parameters -- `keys` **[array][3]** +* `keys` **[array][3]** ### seeResponseEquals @@ -193,7 +193,7 @@ I.seeResponseEquals({ error: 'Not allowed' }) #### Parameters -- `resp` **[object][2]** +* `resp` **[object][2]** ### seeResponseMatchesJsonSchema @@ -223,7 +223,7 @@ I.seeResponseMatchesJsonSchema(joi.object({ #### Parameters -- `fnOrSchema` **any** +* `fnOrSchema` **any** ### seeResponseValidByCallback @@ -239,7 +239,7 @@ I.seeResponseValidByCallback(({ data, status, expect }) => { #### Parameters -- `fn` **[function][6]** +* `fn` **[function][6]** [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number diff --git a/docs/helpers/MockRequest.md b/docs/helpers/MockRequest.md index 9ca18d101..3fd179964 100644 --- a/docs/helpers/MockRequest.md +++ b/docs/helpers/MockRequest.md @@ -17,11 +17,11 @@ Another way of using is to emulate requests from server by passing prepared data MockRequest helper works in these [modes][1]: -- passthrough (default) - mock prefefined HTTP requests -- record - record all requests into a file -- replay - replay all recorded requests from a file +* passthrough (default) - mock prefefined HTTP requests +* record - record all requests into a file +* replay - replay all recorded requests from a file -Combining record/replay modes allows testing websites with large datasets. +Combining record/replay modes allows testing websites with large datasets. To use in passthrough mode set rules to mock requests and they will be automatically intercepted and replaced: @@ -83,9 +83,9 @@ helpers: { } ``` -* * * +*** -**TROUBLESHOOTING**: Puppeteer does not mock requests in headless mode: +**TROUBLESHOOTING**: Puppeteer does not mock requests in headless mode: Problem: request mocking does not work and in debug mode you see this in output: @@ -104,7 +104,7 @@ Solution: update Puppeteer config to include `--disable-web-security` arguments: }, ``` -* * * +*** #### With WebDriver @@ -129,10 +129,10 @@ helpers: { To intercept API requests and mock them use following API -- [startMocking()][4] - to enable request interception -- [mockRequest()][5] - to define mock in a simple way -- [mockServer()][6] - to use PollyJS server API to define complex mocks -- [stopMocking()][7] - to stop intercepting requests and disable mocks. +* [startMocking()][4] - to enable request interception +* [mockRequest()][5] - to define mock in a simple way +* [mockServer()][6] - to use PollyJS server API to define complex mocks +* [stopMocking()][7] - to stop intercepting requests and disable mocks. Calling `mockRequest` or `mockServer` will start mocking, if it was not enabled yet. @@ -156,7 +156,7 @@ I.stopMocking(); > At this moment works only with Puppeteer Record & Replay mode allows you to record all xhr & fetch requests and save them to file. -On next runs those requests can be replayed. +On next runs those requests can be replayed. By default, it stores all passed requests, but this behavior can be customized with `I.mockServer` Set mode via enironment variable, `replay` mode by default: @@ -195,7 +195,7 @@ I.mockServer((server) => { To stop request recording/replaying use `I.stopMocking()`. -🎥 To record HTTP interactions execute tests with MOCK_MODE environment variable set as "record": +🎥 To record HTTP interactions execute tests with MOCK\_MODE environment variable set as "record": MOCK_MODE=record npx codeceptjs run --debug @@ -205,7 +205,7 @@ To stop request recording/replaying use `I.stopMocking()`. ### Parameters -- `config` +* `config` ### flushMocking @@ -234,10 +234,10 @@ I.mockRequest('GET', ['/secrets', '/v2/secrets'], 403); #### Parameters -- `method` **[string][8]** request method. Can be `GET`, `POST`, `PUT`, etc or `ANY`. -- `oneOrMoreUrls` **([string][8] \| [Array][9]<[string][8]>)** url(s) to mock. Can be exact URL, a pattern, or an array of URLs. -- `dataOrStatusCode` **([number][10] \| [string][8] \| [object][11])** status code when number provided. A response body otherwise -- `additionalData` **([string][8] \| [object][11])** response body when a status code is set by previous parameter. (optional, default `null`) +* `method` **[string][8]** request method. Can be `GET`, `POST`, `PUT`, etc or `ANY`. +* `oneOrMoreUrls` **([string][8] | [Array][9]<[string][8]>)** url(s) to mock. Can be exact URL, a pattern, or an array of URLs. +* `dataOrStatusCode` **([number][10] | [string][8] | [object][11])** status code when number provided. A response body otherwise +* `additionalData` **([string][8] | [object][11])** response body when a status code is set by previous parameter. (optional, default `null`) ### mockServer @@ -274,7 +274,7 @@ I.mockServer((server) => { #### Parameters -- `configFn` +* `configFn` ### passthroughMocking @@ -336,8 +336,8 @@ I.startMocking('users-loaded', { #### Parameters -- `title` **any** (optional, default `'Test'`) -- `config` (optional, default `{}`) +* `title` **any** (optional, default `'Test'`) +* `config` (optional, default `{}`) ### stopMocking diff --git a/docs/helpers/Nightmare.md b/docs/helpers/Nightmare.md index 0a6e8a3d0..ce1deb302 100644 --- a/docs/helpers/Nightmare.md +++ b/docs/helpers/Nightmare.md @@ -22,25 +22,26 @@ Requires `nightmare` package to be installed. This helper should be configured in codecept.conf.ts or codecept.conf.js -- `url` - base url of website to be tested -- `restart` - restart browser between tests. -- `disableScreenshots` - don't save screenshot on failure. -- `uniqueScreenshotNames` - option to prevent screenshot override if you have scenarios with the same name in different suites. -- `fullPageScreenshots` - make full page screenshots on failure. -- `keepBrowserState` - keep browser state between tests when `restart` set to false. -- `keepCookies` - keep cookies between tests when `restart` set to false. -- `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 500. -- `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000. -- `windowSize`: (optional) default window size. Set a dimension like `640x480`. +* `url` - base url of website to be tested +* `restart` - restart browser between tests. +* `disableScreenshots` - don't save screenshot on failure. +* `uniqueScreenshotNames` - option to prevent screenshot override if you have scenarios with the same name in different suites. +* `fullPageScreenshots` - make full page screenshots on failure. +* `keepBrowserState` - keep browser state between tests when `restart` set to false. +* `keepCookies` - keep cookies between tests when `restart` set to false. +* `waitForAction`: (optional) how long to wait after click, doubleClick or PressKey actions in ms. Default: 500. +* `waitForTimeout`: (optional) default wait* timeout in ms. Default: 1000. +* `windowSize`: (optional) default window size. Set a dimension like `640x480`. + -- options from [Nightmare configuration][2] +* options from [Nightmare configuration][2] ## Methods ### Parameters -- `config` +* `config` ### _locate @@ -64,7 +65,7 @@ let value = this.helpers['Nightmare']._locate({name: 'password'}).then(function( #### Parameters -- `locator` +* `locator` ### amOnPage @@ -79,8 +80,8 @@ I.amOnPage('/login'); // opens a login page #### Parameters -- `url` **[string][3]** url path or global url. -- `headers` **[object][4]?** list of request headers can be passed +* `url` **[string][3]** url path or global url. +* `headers` **[object][4]?** list of request headers can be passed Returns **void** automatically synchronized promise through #recorder @@ -97,8 +98,8 @@ I.appendField('password', secret('123456')); #### Parameters -- `field` **([string][3] | [object][4])** located by label|name|CSS|XPath|strict locator -- `value` **[string][3]** text value to append. +* `field` **([string][3] | [object][4])** located by label|name|CSS|XPath|strict locator +* `value` **[string][3]** text value to append. Returns **void** automatically synchronized promise through #recorder @@ -115,8 +116,8 @@ I.attachFile('form input[name=avatar]', 'data/avatar.jpg'); #### Parameters -- `locator` **([string][3] | [object][4])** field located by label|name|CSS|XPath|strict locator. -- `pathToFile` **[string][3]** local file path relative to codecept.conf.ts or codecept.conf.js config file. +* `locator` **([string][3] | [object][4])** field located by label|name|CSS|XPath|strict locator. +* `pathToFile` **[string][3]** local file path relative to codecept.conf.ts or codecept.conf.js config file. Returns **void** automatically synchronized promise through #recorderDoesn't work if the Chromium DevTools panel is open (as Chromium allows only one attachment to the debugger at a time. [See more][5]) @@ -135,8 +136,8 @@ I.checkOption('agree', '//form'); #### Parameters -- `field` **([string][3] | [object][4])** checkbox located by label | name | CSS | XPath | strict locator. -- `context` **([string][3]? | [object][4])** (optional, `null` by default) element located by CSS | XPath | strict locator. +* `field` **([string][3] | [object][4])** checkbox located by label | name | CSS | XPath | strict locator. +* `context` **([string][3]? | [object][4])** (optional, `null` by default) element located by CSS | XPath | strict locator. Returns **void** automatically synchronized promise through #recorder @@ -152,7 +153,7 @@ I.clearCookie('test'); // Playwright currently doesn't support clear a particula #### Parameters -- `cookie` **[string][3]?** (optional, `null` by default) cookie name +* `cookie` **[string][3]?** (optional, `null` by default) cookie name ### clearField @@ -166,8 +167,8 @@ I.clearField('#email'); #### Parameters -- `field` -- `editable` **([string][3] | [object][4])** field located by label|name|CSS|XPath|strict locator. +* `field` +* `editable` **([string][3] | [object][4])** field located by label|name|CSS|XPath|strict locator. Returns **void** automatically synchronized promise through #recorder. @@ -197,8 +198,8 @@ I.click({css: 'nav a.login'}); #### Parameters -- `locator` **([string][3] | [object][4])** clickable link or button located by text, or any element located by CSS|XPath|strict locator. -- `context` **([string][3]? | [object][4] | null)** (optional, `null` by default) element to search in CSS|XPath|Strict locator. +* `locator` **([string][3] | [object][4])** clickable link or button located by text, or any element located by CSS|XPath|strict locator. +* `context` **([string][3]? | [object][4] | null)** (optional, `null` by default) element to search in CSS|XPath|Strict locator. Returns **void** automatically synchronized promise through #recorder @@ -214,8 +215,8 @@ I.dontSee('Login', '.nav'); // no login inside .nav element #### Parameters -- `text` **[string][3]** which is not present. -- `context` **([string][3] | [object][4])?** (optional) element located by CSS|XPath|strict locator in which to perfrom search. +* `text` **[string][3]** which is not present. +* `context` **([string][3] | [object][4])?** (optional) element located by CSS|XPath|strict locator in which to perfrom search. Returns **void** automatically synchronized promise through #recorder @@ -231,7 +232,7 @@ I.dontSeeCheckboxIsChecked('agree'); // located by name #### Parameters -- `field` **([string][3] | [object][4])** located by label|name|CSS|XPath|strict locator. +* `field` **([string][3] | [object][4])** located by label|name|CSS|XPath|strict locator. Returns **void** automatically synchronized promise through #recorder @@ -245,7 +246,7 @@ I.dontSeeCookie('auth'); // no auth cookie #### Parameters -- `name` **[string][3]** cookie name. +* `name` **[string][3]** cookie name. Returns **void** automatically synchronized promise through #recorder @@ -261,7 +262,7 @@ I.dontSeeCurrentUrlEquals('http://mysite.com/login'); // absolute urls are also #### Parameters -- `url` **[string][3]** value to check. +* `url` **[string][3]** value to check. Returns **void** automatically synchronized promise through #recorder @@ -275,7 +276,7 @@ I.dontSeeElement('.modal'); // modal is not shown #### Parameters -- `locator` **([string][3] | [object][4])** located by CSS|XPath|Strict locator. +* `locator` **([string][3] | [object][4])** located by CSS|XPath|Strict locator. Returns **void** automatically synchronized promise through #recorder @@ -289,7 +290,7 @@ I.dontSeeElementInDOM('.nav'); // checks that element is not on page visible or #### Parameters -- `locator` **([string][3] | [object][4])** located by CSS|XPath|Strict locator. +* `locator` **([string][3] | [object][4])** located by CSS|XPath|Strict locator. Returns **void** automatically synchronized promise through #recorder @@ -299,7 +300,7 @@ Checks that current url does not contain a provided fragment. #### Parameters -- `url` **[string][3]** value to check. +* `url` **[string][3]** value to check. Returns **void** automatically synchronized promise through #recorder @@ -315,8 +316,8 @@ I.dontSeeInField({ css: 'form input.email' }, 'user@user.com'); // field by CSS #### Parameters -- `field` **([string][3] | [object][4])** located by label|name|CSS|XPath|strict locator. -- `value` **([string][3] | [object][4])** value to check. +* `field` **([string][3] | [object][4])** located by label|name|CSS|XPath|strict locator. +* `value` **([string][3] | [object][4])** value to check. Returns **void** automatically synchronized promise through #recorder @@ -330,8 +331,8 @@ I.dontSeeInSource(' + yarn add -D @faker-js/faker Add this plugin to config file: @@ -646,7 +647,7 @@ Scenario Outline: ... ### Parameters -- `config` +* `config` ## heal @@ -664,11 +665,11 @@ plugins: { More config options are available: -- `healLimit` - how many steps can be healed in a single test (default: 2) +* `healLimit` - how many steps can be healed in a single test (default: 2) ### Parameters -- `config` (optional, default `{}`) +* `config` (optional, default `{}`) ## pauseOnFail @@ -708,20 +709,20 @@ Run tests with plugin enabled: #### Configuration: -- `retries` - number of retries (by default 3), -- `when` - function, when to perform a retry (accepts error as parameter) -- `factor` - The exponential factor to use. Default is 1.5. -- `minTimeout` - The number of milliseconds before starting the first retry. Default is 1000. -- `maxTimeout` - The maximum number of milliseconds between two retries. Default is Infinity. -- `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false. -- `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes: - - `amOnPage` - - `wait*` - - `send*` - - `execute*` - - `run*` - - `have*` -- `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list. +* `retries` - number of retries (by default 3), +* `when` - function, when to perform a retry (accepts error as parameter) +* `factor` - The exponential factor to use. Default is 1.5. +* `minTimeout` - The number of milliseconds before starting the first retry. Default is 1000. +* `maxTimeout` - The maximum number of milliseconds between two retries. Default is Infinity. +* `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false. +* `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes: + * `amOnPage` + * `wait*` + * `send*` + * `execute*` + * `run*` + * `have*` +* `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list. You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`. To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well. @@ -753,7 +754,7 @@ Scenario('scenario tite', () => { ### Parameters -- `config` +* `config` ## retryTo @@ -806,13 +807,13 @@ Disables retryFailedStep plugin for steps inside a block; Use this plugin if: -- you need repeat a set of actions in flaky tests -- iframe was not rendered and you need to retry switching to it +* you need repeat a set of actions in flaky tests +* iframe was not rendered and you need to retry switching to it #### Configuration -- `pollInterval` - default interval between retries in ms. 200 by default. -- `registerGlobal` - to register `retryTo` function globally, true by default +* `pollInterval` - default interval between retries in ms. 200 by default. +* `registerGlobal` - to register `retryTo` function globally, true by default If `registerGlobal` is false you can use retryTo from the plugin: @@ -822,7 +823,7 @@ const retryTo = codeceptjs.container.plugins('retryTo'); ### Parameters -- `config` +* `config` ## screenshotOnFail @@ -846,12 +847,12 @@ plugins: { Possible config options: -- `uniqueScreenshotNames`: use unique names for screenshot. Default: false. -- `fullPageScreenshots`: make full page screenshots. Default: false. +* `uniqueScreenshotNames`: use unique names for screenshot. Default: false. +* `fullPageScreenshots`: make full page screenshots. Default: false. ### Parameters -- `config` +* `config` ## selenoid @@ -908,7 +909,7 @@ This is especially useful for Continous Integration server as you can configure 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. -_Sample browsers.json_ +*Sample browsers.json* ```js { @@ -968,7 +969,7 @@ When `allure` plugin is enabled a video is attached to report automatically. ### Parameters -- `config` +* `config` ## stepByStepReport @@ -994,17 +995,17 @@ Run tests with plugin enabled: Possible config options: -- `deleteSuccessful`: do not save screenshots for successfully executed tests. Default: true. -- `animateSlides`: should animation for slides to be used. Default: true. -- `ignoreSteps`: steps to ignore in report. Array of RegExps is expected. Recommended to skip `grab*` and `wait*` steps. -- `fullPageScreenshots`: should full page screenshots be used. Default: false. -- `output`: a directory where reports should be stored. Default: `output`. -- `screenshotsForAllureReport`: If Allure plugin is enabled this plugin attaches each saved screenshot to allure report. Default: false. -- \`disableScreenshotOnFail : Disables the capturing of screeshots after the failed step. Default: true. +* `deleteSuccessful`: do not save screenshots for successfully executed tests. Default: true. +* `animateSlides`: should animation for slides to be used. Default: true. +* `ignoreSteps`: steps to ignore in report. Array of RegExps is expected. Recommended to skip `grab*` and `wait*` steps. +* `fullPageScreenshots`: should full page screenshots be used. Default: false. +* `output`: a directory where reports should be stored. Default: `output`. +* `screenshotsForAllureReport`: If Allure plugin is enabled this plugin attaches each saved screenshot to allure report. Default: false. +* \`disableScreenshotOnFail : Disables the capturing of screeshots after the failed step. Default: true. ### Parameters -- `config` **any** +* `config` **any** ## stepTimeout @@ -1026,16 +1027,18 @@ Run tests with plugin enabled: #### Configuration: -- `timeout` - global step timeout, default 150 seconds -- `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false -- `noTimeoutSteps` - an array of steps with no timeout. Default: +* `timeout` - global step timeout, default 150 seconds + +* `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false - - `amOnPage` - - `wait*` +* `noTimeoutSteps` - an array of steps with no timeout. Default: + + * `amOnPage` + * `wait*` you could set your own noTimeoutSteps which would replace the default one. -- `customTimeoutSteps` - an array of step actions with custom timeout. Use it to override or extend noTimeoutSteps. +* `customTimeoutSteps` - an array of step actions with custom timeout. Use it to override or extend noTimeoutSteps. You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`. #### Example @@ -1059,7 +1062,7 @@ plugins: { ### Parameters -- `config` +* `config` ## subtitles @@ -1102,15 +1105,16 @@ Disables retryFailedStep plugin for steps inside a block; Use this plugin if: -- you need to perform multiple assertions inside a test -- there is A/B testing on a website you test -- there is "Accept Cookie" banner which may surprisingly appear on a page. +* you need to perform multiple assertions inside a test +* there is A/B testing on a website you test +* there is "Accept Cookie" banner which may surprisingly appear on a page. #### Usage #### Multiple Conditional Assertions ````js + Add assert requires first: ```js const assert = require('assert'); @@ -1121,15 +1125,18 @@ const result1 = await tryTo(() => I.see('Hello, user')); const result2 = await tryTo(() => I.seeElement('.welcome')); assert.ok(result1 && result2, 'Assertions were not succesful'); - ##### Optional click +```` + +##### Optional click - ```js - I.amOnPage('/'); - tryTo(() => I.click('Agree', '.cookies')); +```js +I.amOnPage('/'); +tryTo(() => I.click('Agree', '.cookies')); +```` #### Configuration -- `registerGlobal` - to register `tryTo` function globally, true by default +* `registerGlobal` - to register `tryTo` function globally, true by default If `registerGlobal` is false you can use tryTo from the plugin: @@ -1139,7 +1146,7 @@ const tryTo = codeceptjs.container.plugins('tryTo'); ### Parameters -- `config` +* `config` ## wdio @@ -1147,11 +1154,11 @@ Webdriverio services runner. This plugin allows to run webdriverio services like: -- selenium-standalone -- sauce -- testingbot -- browserstack -- appium +* selenium-standalone +* sauce +* testingbot +* browserstack +* appium A complete list of all available services can be found on [webdriverio website][19]. @@ -1165,7 +1172,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][20]. It is important to make sure it is compatible with current webdriverio version. Enable `wdio` plugin in plugins list and add `selenium-standalone` service: @@ -1199,18 +1206,18 @@ plugins: { } ``` -* * * +*** In the same manner additional services from webdriverio can be installed, enabled, and configured. #### Configuration -- `services` - list of enabled services -- ... - additional configuration passed into services. +* `services` - list of enabled services +* ... - additional configuration passed into services. ### Parameters -- `config` +* `config` [1]: https://github.com/cenfun/monocart-coverage-reports?tab=readme-ov-file#default-options diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..9884b4975 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,81 @@ +import globals from "globals"; +import path from "node:path"; +import {fileURLToPath} from "node:url"; +import js from "@eslint/js"; +import {FlatCompat} from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +export default [{ + ignores: ["test/data/output", "lib/css2xpath/*"], +}, ...compat.extends("airbnb-base"), { + languageOptions: { + globals: { + ...globals.node, + }, + + ecmaVersion: 2020, + sourceType: "commonjs", + }, + + rules: { + "func-names": 0, + "no-use-before-define": 0, + "no-unused-vars": 0, + "no-underscore-dangle": 0, + "no-undef": 0, + "prefer-destructuring": 0, + "no-param-reassign": 0, + "max-len": 0, + camelcase: 0, + "no-shadow": 0, + "consistent-return": 0, + "no-console": 0, + "global-require": 0, + "class-methods-use-this": 0, + "no-plusplus": 0, + "no-return-assign": 0, + "prefer-rest-params": 0, + "no-useless-escape": 0, + "no-restricted-syntax": 0, + "no-unused-expressions": 0, + "guard-for-in": 0, + "no-multi-assign": 0, + "require-yield": 0, + "prefer-spread": 0, + "import/no-dynamic-require": 0, + "no-continue": 0, + "no-mixed-operators": 0, + "default-case": 0, + "import/no-extraneous-dependencies": 0, + "no-cond-assign": 0, + "import/no-unresolved": 0, + "no-await-in-loop": 0, + "arrow-body-style": 0, + "no-loop-func": 0, + "arrow-parens": 0, + "default-param-last": 0, + semi: 0, + "operator-linebreak": 0, + "nonblock-statement-body-position": 0, + curly: 0, + "implicit-arrow-linebreak": 0, + indent: 0, + "object-curly-newline": 0, + "semi-style": 0, + "function-paren-newline": 0, + "prefer-template": 0, + "newline-per-chained-call": 0, + "prefer-arrow-callback": 0, + "no-bitwise": 0, + "prefer-const": 0, + "no-extra-semi": 0, + "max-classes-per-file": 0 + }, +}]; diff --git a/examples/fragments/Signin.js b/examples/fragments/Signin.js index 03382780a..7d7b018f0 100644 --- a/examples/fragments/Signin.js +++ b/examples/fragments/Signin.js @@ -1,4 +1,3 @@ -/* eslint-disable */ let I; module.exports = { diff --git a/examples/pages/Admin.js b/examples/pages/Admin.js index 03382780a..7d7b018f0 100644 --- a/examples/pages/Admin.js +++ b/examples/pages/Admin.js @@ -1,4 +1,3 @@ -/* eslint-disable */ let I; module.exports = { diff --git a/lib/codecept.js b/lib/codecept.js index fe749fd6e..47e0bbe5d 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -92,6 +92,9 @@ class Codecept { // debug mode global.debugMode = false; + + // mask sensitive data + global.maskSensitiveData = this.config.maskSensitiveData || false; } } diff --git a/lib/command/dryRun.js b/lib/command/dryRun.js index 0de23df4f..75734185f 100644 --- a/lib/command/dryRun.js +++ b/lib/command/dryRun.js @@ -7,7 +7,7 @@ const store = require('../store') const Container = require('../container') module.exports = async function (test, options) { - if (options.grep) process.env.grep = options.grep.toLowerCase() + if (options.grep) process.env.grep = options.grep const configFile = options.config let codecept @@ -60,35 +60,40 @@ function printTests(files) { let numOfTests = 0 let numOfSuites = 0 let outputString = '' - const filterBy = process.env.grep ? process.env.grep.toLowerCase() : undefined + const filterBy = process.env.grep + let filterRegex if (filterBy) { - for (const suite of mocha.suite.suites) { - const currentSuite = suite.title - if (suite.title.toLowerCase().includes(filterBy)) { - outputString += `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${mocha.suite.suites.length} tests\n` - numOfSuites++ - } - - for (test of suite.tests) { - if (test.title.toLowerCase().includes(filterBy)) { - numOfTests++ - outputString += `${colors.white.bold(test.parent.title)} -- ${output.styles.log(test.parent.file || '')} -- ${mocha.suite.suites.length} tests\n` - outputString += ` ${output.styles.scenario(figures.checkboxOff)} ${test.title}\n` - } - } + try { + filterRegex = new RegExp(filterBy, 'i') // Case-insensitive matching + } catch (err) { + console.error(`Invalid grep pattern: ${filterBy}`) + process.exit(1) } - numOfSuites = countSuites(outputString) - } else { - for (const suite of mocha.suite.suites) { - output.print( - `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${mocha.suite.suites.length} tests`, - ) + } + + for (const suite of mocha.suite.suites) { + const suiteMatches = filterRegex ? filterRegex.test(suite.title) : true + let suiteHasMatchingTests = false + + if (suiteMatches) { + outputString += `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')}\n` + suiteHasMatchingTests = true numOfSuites++ + } + + for (const test of suite.tests) { + const testMatches = filterRegex ? filterRegex.test(test.title) : true + + if (testMatches) { + if (!suiteMatches && !suiteHasMatchingTests) { + outputString += `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')}\n` + suiteHasMatchingTests = true + numOfSuites++ + } - for (test of suite.tests) { numOfTests++ - output.print(` ${output.styles.scenario(figures.checkboxOff)} ${test.title}`) + outputString += ` ${output.styles.scenario(figures.checkboxOff)} ${test.title}\n` } } } @@ -108,15 +113,5 @@ function printFooter() { function removeDuplicates(inputString) { const array = inputString.split('\n') const uniqueLines = [...new Set(array)] - const resultString = uniqueLines.join('\n') - - return resultString -} - -function countSuites(inputString) { - const array = inputString.split('\n') - - const uniqueLines = [...new Set(array)] - const res = uniqueLines.filter((item) => item.includes('-- ')) - return res.length + return uniqueLines.join('\n') } diff --git a/lib/command/gherkin/snippets.js b/lib/command/gherkin/snippets.js index e0f92e11a..c26236916 100644 --- a/lib/command/gherkin/snippets.js +++ b/lib/command/gherkin/snippets.js @@ -127,6 +127,6 @@ ${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () = if (!options.dryRun) { output.success(`Snippets added to ${output.colors.bold(stepFile)}`); - fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n'); // eslint-disable-line + fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n'); } }; diff --git a/lib/command/workers/runTests.js b/lib/command/workers/runTests.js index 48ce85127..0ab4af468 100644 --- a/lib/command/workers/runTests.js +++ b/lib/command/workers/runTests.js @@ -13,9 +13,8 @@ const container = require('../../container'); const { getConfig } = require('../utils'); const { tryOrDefault, deepMerge } = require('../../utils'); -// eslint-disable-next-line no-unused-vars let stdout = ''; -/* eslint-enable no-unused-vars */ + const stderr = ''; // Requiring of Codecept need to be after tty.getWindowSize is available. diff --git a/lib/heal.js b/lib/heal.js index b6071c456..e1a3ce51d 100644 --- a/lib/heal.js +++ b/lib/heal.js @@ -113,7 +113,7 @@ class Heal { }); if (typeof codeSnippet === 'string') { - const I = Container.support('I'); // eslint-disable-line + const I = Container.support('I'); await eval(codeSnippet); // eslint-disable-line } else if (typeof codeSnippet === 'function') { await codeSnippet(Container.support()); diff --git a/lib/helper/ApiDataFactory.js b/lib/helper/ApiDataFactory.js index ece565636..7026a6c62 100644 --- a/lib/helper/ApiDataFactory.js +++ b/lib/helper/ApiDataFactory.js @@ -51,7 +51,7 @@ const REST = require('./REST') * * module.exports = new Factory() * // no need to set id, it will be set by REST API - * .attr('author', () => faker.name.findName()) + * .attr('author', () => faker.person.findName()) * .attr('title', () => faker.lorem.sentence()) * .attr('body', () => faker.lorem.paragraph()); * ``` diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index 58b17335b..9c476cc56 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -434,8 +434,8 @@ class Appium extends Webdriver { async runOnIOS(caps, fn) { if (this.platform !== 'ios') return recorder.session.start('iOS-only actions') - await this._runWithCaps(caps, fn) - await recorder.add('restore from iOS session', () => recorder.session.restore()) + this._runWithCaps(caps, fn) + recorder.add('restore from iOS session', () => recorder.session.restore()) return recorder.promise() } @@ -476,8 +476,8 @@ class Appium extends Webdriver { async runOnAndroid(caps, fn) { if (this.platform !== 'android') return recorder.session.start('Android-only actions') - await this._runWithCaps(caps, fn) - await recorder.add('restore from Android session', () => recorder.session.restore()) + this._runWithCaps(caps, fn) + recorder.add('restore from Android session', () => recorder.session.restore()) return recorder.promise() } @@ -491,19 +491,17 @@ class Appium extends Webdriver { * }); * ``` * - * @param {*} fn */ - /* eslint-disable */ - async runInWeb(fn) { + + async runInWeb() { if (!this.isWeb) return recorder.session.start('Web-only actions') recorder.add('restore from Web session', () => recorder.session.restore(), true) return recorder.promise() } - /* eslint-enable */ - async _runWithCaps(caps, fn) { + _runWithCaps(caps, fn) { if (typeof caps === 'object') { for (const key in caps) { // skip if capabilities do not match @@ -1077,7 +1075,7 @@ class Appium extends Webdriver { * * Appium: support Android and iOS */ - /* eslint-disable */ + async swipe(locator, xoffset, yoffset, speed = 1000) { onlyForApps.call(this) const res = await this.browser.$(parseLocator.call(this, locator)) @@ -1087,7 +1085,6 @@ class Appium extends Webdriver { y: (await res.getLocation()).y + yoffset, }) } - /* eslint-enable */ /** * Perform a swipe on the screen. diff --git a/lib/helper/GraphQLDataFactory.js b/lib/helper/GraphQLDataFactory.js index e45f7fe19..d6453b7b3 100644 --- a/lib/helper/GraphQLDataFactory.js +++ b/lib/helper/GraphQLDataFactory.js @@ -55,7 +55,7 @@ const GraphQL = require('./GraphQL') * input: { ...buildObj }, * })) * // 'attr'-id can be left out depending on the GraphQl resolvers - * .attr('name', () => faker.name.findName()) + * .attr('name', () => faker.person.findName()) * .attr('email', () => faker.interact.email()) * ``` * For more options see [rosie documentation](https://github.com/rosiejs/rosie). diff --git a/lib/helper/MockServer.js b/lib/helper/MockServer.js deleted file mode 100644 index 7278c1021..000000000 --- a/lib/helper/MockServer.js +++ /dev/null @@ -1,221 +0,0 @@ -const { mock, settings } = require('pactum') - -/** - * ## Configuration - * - * This helper should be configured in codecept.conf.(js|ts) - * - * @typedef MockServerConfig - * @type {object} - * @prop {number} [port=9393] - Mock server port - * @prop {string} [host="0.0.0.0"] - Mock server host - * @prop {object} [httpsOpts] - key & cert values are the paths to .key and .crt files - */ -let config = { - port: 9393, - host: '0.0.0.0', - httpsOpts: { - key: '', - cert: '', - }, -} - -/** - * MockServer - * - * The MockServer Helper in CodeceptJS empowers you to mock any server or service via HTTP or HTTPS, making it an excellent tool for simulating REST endpoints and other HTTP-based APIs. - * - * - * - * #### Examples - * - * You can seamlessly integrate MockServer with other helpers like REST or Playwright. Here's a configuration example inside the `codecept.conf.js` file: - * - * ```javascript - * { - * helpers: { - * REST: {...}, - * MockServer: { - * // default mock server config - * port: 9393, - * host: '0.0.0.0', - * httpsOpts: { - * key: '', - * cert: '', - * }, - * }, - * } - * } - * ``` - * - * #### Adding Interactions - * - * Interactions add behavior to the mock server. Use the `I.addInteractionToMockServer()` method to include interactions. It takes an interaction object as an argument, containing request and response details. - * - * ```javascript - * I.addInteractionToMockServer({ - * request: { - * method: 'GET', - * path: '/api/hello' - * }, - * response: { - * status: 200, - * body: { - * 'say': 'hello to mock server' - * } - * } - * }); - * ``` - * - * #### Request Matching - * - * When a real request is sent to the mock server, it matches the received request with the interactions. If a match is found, it returns the specified response; otherwise, a 404 status code is returned. - * - * - Strong match on HTTP Method, Path, Query Params & JSON body. - * - Loose match on Headers. - * - * ##### Strong Match on Query Params - * - * You can send different responses based on query parameters: - * - * ```javascript - * I.addInteractionToMockServer({ - * request: { - * method: 'GET', - * path: '/api/users', - * queryParams: { - * id: 1 - * } - * }, - * response: { - * status: 200, - * body: 'user 1' - * } - * }); - * - * I.addInteractionToMockServer({ - * request: { - * method: 'GET', - * path: '/api/users', - * queryParams: { - * id: 2 - * } - * }, - * response: { - * status: 200, - * body: 'user 2' - * } - * }); - * ``` - * - * - GET to `/api/users?id=1` will return 'user 1'. - * - GET to `/api/users?id=2` will return 'user 2'. - * - For all other requests, it returns a 404 status code. - * - * ##### Loose Match on Body - * - * When `strict` is set to false, it performs a loose match on query params and response body: - * - * ```javascript - * I.addInteractionToMockServer({ - * strict: false, - * request: { - * method: 'POST', - * path: '/api/users', - * body: { - * name: 'john' - * } - * }, - * response: { - * status: 200 - * } - * }); - * ``` - * - * - POST to `/api/users` with the body containing `name` as 'john' will return a 200 status code. - * - POST to `/api/users` without the `name` property in the body will return a 404 status code. - * - * Happy testing with MockServer in CodeceptJS! 🚀 - * - * ## Methods - */ -class MockServer { - constructor(passedConfig) { - settings.setLogLevel('SILENT') - config = { ...passedConfig } - if (global.debugMode) { - settings.setLogLevel('VERBOSE') - } - } - - /** - * Start the mock server - * @param {number} [port] start the mock server with given port - * - * @returns void - */ - async startMockServer(port) { - const _config = { ...config } - if (port) _config.port = port - await mock.setDefaults(_config) - await mock.start() - } - - /** - * Stop the mock server - * - * @returns void - * - */ - async stopMockServer() { - await mock.stop() - } - - /** - * An interaction adds behavior to the mock server - * - * - * ```js - * I.addInteractionToMockServer({ - * request: { - * method: 'GET', - * path: '/api/hello' - * }, - * response: { - * status: 200, - * body: { - * 'say': 'hello to mock server' - * } - * } - * }); - * ``` - * ```js - * // with query params - * I.addInteractionToMockServer({ - * request: { - * method: 'GET', - * path: '/api/hello', - * queryParams: { - * id: 2 - * } - * }, - * response: { - * status: 200, - * body: { - * 'say': 'hello to mock server' - * } - * } - * }); - * ``` - * - * @param {CodeceptJS.MockInteraction|object} interaction add behavior to the mock server - * - * @returns void - * - */ - async addInteractionToMockServer(interaction) { - await mock.addInteraction(interaction) - } -} - -module.exports = MockServer diff --git a/lib/helper/Nightmare.js b/lib/helper/Nightmare.js index d89fc520a..1a6e90692 100644 --- a/lib/helper/Nightmare.js +++ b/lib/helper/Nightmare.js @@ -1324,7 +1324,7 @@ class Nightmare extends Helper { offsetY, ) } - // eslint-disable-next-line prefer-arrow-callback + return this.executeScript( function (x, y) { return window.scrollTo(x, y) @@ -1345,7 +1345,6 @@ class Nightmare extends Helper { * {{> scrollPageToBottom }} */ async scrollPageToBottom() { - /* eslint-disable prefer-arrow-callback, comma-dangle */ return this.executeScript(function () { const body = document.body const html = document.documentElement @@ -1354,21 +1353,19 @@ class Nightmare extends Helper { Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), ) }) - /* eslint-enable */ } /** * {{> grabPageScrollPosition }} */ async grabPageScrollPosition() { - /* eslint-disable comma-dangle */ function getScrollPosition() { return { x: window.pageXOffset, y: window.pageYOffset, } } - /* eslint-enable comma-dangle */ + return this.executeScript(getScrollPosition) } } diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 8e68fc74d..37c23dd35 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -1241,14 +1241,13 @@ class Playwright extends Helper { * {{> grabPageScrollPosition }} */ async grabPageScrollPosition() { - /* eslint-disable comma-dangle */ function getScrollPosition() { return { x: window.pageXOffset, y: window.pageYOffset, } } - /* eslint-enable comma-dangle */ + return this.executeScript(getScrollPosition) } @@ -1284,7 +1283,7 @@ class Playwright extends Helper { * ``` */ async _locate(locator) { - const context = (await this.context) || (await this._getContext()) + const context = await this._getContext() if (this.frame) return findElements(this.frame, locator) @@ -1300,7 +1299,7 @@ class Playwright extends Helper { * ``` */ async _locateElement(locator) { - const context = (await this.context) || (await this._getContext()) + const context = await this._getContext() return findElement(context, locator) } @@ -2737,7 +2736,7 @@ class Playwright extends Helper { } async _getContext() { - if (this.context && this.context.constructor.name === 'FrameLocator') { + if ((this.context && this.context.constructor.name === 'FrameLocator') || this.context) { return this.context } return this.page @@ -2839,11 +2838,14 @@ class Playwright extends Helper { } } else { // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented - // eslint-disable-next-line no-lonely-if + const _contextObject = this.frame ? this.frame : contextObject let count = 0 do { - waiter = await _contextObject.locator(`:has-text("${text}")`).first().isVisible() + waiter = await _contextObject + .locator(`:has-text(${JSON.stringify(text)})`) + .first() + .isVisible() if (waiter) break await this.wait(1) count += 1000 @@ -3435,7 +3437,7 @@ class Playwright extends Helper { } _logWebsocketMessages(message) { - this.webSocketMessages += message + this.webSocketMessages.push(message) } } @@ -3813,7 +3815,6 @@ function parseWindowSize(windowSize) { // List of key values to key definitions // https://github.com/puppeteer/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js const keyDefinitionMap = { - /* eslint-disable quote-props */ 0: 'Digit0', 1: 'Digit1', 2: 'Digit2', @@ -3861,7 +3862,6 @@ const keyDefinitionMap = { '\\': 'Backslash', ']': 'BracketRight', "'": 'Quote', - /* eslint-enable quote-props */ } function getNormalizedKey(key) { diff --git a/lib/helper/Protractor.js b/lib/helper/Protractor.js index 9b785d33e..0fab33d11 100644 --- a/lib/helper/Protractor.js +++ b/lib/helper/Protractor.js @@ -595,7 +595,6 @@ class Protractor extends Helper { async pressKey(key) { let modifier if (Array.isArray(key) && ~['Control', 'Command', 'Shift', 'Alt'].indexOf(key[0])) { - // eslint-disable-line no-bitwise modifier = Key[key[0].toUpperCase()] key = key[1] } @@ -1106,7 +1105,6 @@ class Protractor extends Helper { } let { width, height } = await this.browser.executeScript(() => ({ - // eslint-disable-line height: document.body.scrollHeight, width: document.body.scrollWidth, })) @@ -1614,7 +1612,7 @@ class Protractor extends Helper { } const elem = res[0] const location = await elem.getLocation() - /* eslint-disable prefer-arrow-callback */ + return this.executeScript( function (x, y) { return window.scrollTo(x, y) @@ -1622,10 +1620,8 @@ class Protractor extends Helper { location.x + offsetX, location.y + offsetY, ) - /* eslint-enable */ } - /* eslint-disable prefer-arrow-callback, comma-dangle */ return this.executeScript( function (x, y) { return window.scrollTo(x, y) @@ -1633,7 +1629,6 @@ class Protractor extends Helper { offsetX, offsetY, ) - /* eslint-enable */ } /** @@ -1647,7 +1642,6 @@ class Protractor extends Helper { * {{> scrollPageToBottom }} */ async scrollPageToBottom() { - /* eslint-disable prefer-arrow-callback, comma-dangle */ return this.executeScript(function () { const body = document.body const html = document.documentElement @@ -1656,21 +1650,19 @@ class Protractor extends Helper { Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), ) }) - /* eslint-enable */ } /** * {{> grabPageScrollPosition }} */ async grabPageScrollPosition() { - /* eslint-disable comma-dangle */ function getScrollPosition() { return { x: window.pageXOffset, y: window.pageYOffset, } } - /* eslint-enable comma-dangle */ + return this.executeScript(getScrollPosition) } diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 0c219144e..77cbf3587 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -861,14 +861,13 @@ class Puppeteer extends Helper { * {{> grabPageScrollPosition }} */ async grabPageScrollPosition() { - /* eslint-disable comma-dangle */ function getScrollPosition() { return { x: window.pageXOffset, y: window.pageYOffset, } } - /* eslint-enable comma-dangle */ + return this.executeScript(getScrollPosition) } @@ -2772,7 +2771,7 @@ class Puppeteer extends Helper { } _logWebsocketMessages(message) { - this.webSocketMessages += message + this.webSocketMessages.push(message) } } @@ -3106,7 +3105,6 @@ async function getClickablePoint(el) { // List of key values to key definitions // https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/lib/USKeyboardLayout.js const keyDefinitionMap = { - /* eslint-disable quote-props */ 0: 'Digit0', 1: 'Digit1', 2: 'Digit2', @@ -3154,7 +3152,6 @@ const keyDefinitionMap = { '\\': 'Backslash', ']': 'BracketRight', "'": 'Quote', - /* eslint-enable quote-props */ } function getNormalizedKey(key) { diff --git a/lib/helper/REST.js b/lib/helper/REST.js index 4d36be1d9..aca4f8437 100644 --- a/lib/helper/REST.js +++ b/lib/helper/REST.js @@ -408,6 +408,30 @@ class REST extends Helper { return this._executeRequest(request) } + + /** + * Sends DELETE request to API with payload. + * + * ```js + * I.sendDeleteRequestWithPayload('/api/users/1', { author: 'john' }); + * ``` + * + * @param {*} url + * @param {*} [payload={}] - the payload to be sent. By default it is sent as an empty object + * @param {object} [headers={}] - the headers object to be sent. By default, it is sent as an empty object + * + * @returns {Promise<*>} response + */ + async sendDeleteRequestWithPayload(url, payload = {}, headers = {}) { + const request = { + baseURL: this._url(url), + method: 'DELETE', + data: payload, + headers, + } + + return this._executeRequest(request) + } } module.exports = REST diff --git a/lib/helper/TestCafe.js b/lib/helper/TestCafe.js index 86f27bc5b..a853860df 100644 --- a/lib/helper/TestCafe.js +++ b/lib/helper/TestCafe.js @@ -1049,7 +1049,6 @@ class TestCafe extends Helper { } const getCookie = ClientFunction( () => { - // eslint-disable-next-line prefer-template const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)') return v ? v[2] : null }, @@ -1220,12 +1219,11 @@ async function waitForFunction(browserFn, waitTimeout) { }) const start = Date.now() - // eslint-disable-next-line no-constant-condition + while (true) { let result try { result = await browserFn() - // eslint-disable-next-line no-empty } catch (err) { throw new Error(`Error running function ${err.toString()}`) } diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index 9bd253405..98c5283e4 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -72,7 +72,6 @@ const webRoot = 'body' * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash. * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). * @prop {string} [logLevel=silent] - level of logging verbosity. Default: silent. Options: trace | debug | info | warn | error | silent. More info: https://webdriver.io/docs/configuration/#loglevel - * @prop {boolean} [devtoolsProtocol=false] - enable devtools protocol. Default: false. More info: https://webdriver.io/docs/automationProtocols/#devtools-protocol. */ const config = {} @@ -180,7 +179,6 @@ const config = {} * WebDriver : { * url: "http://localhost", * browser: "chrome", - * devtoolsProtocol: true, * desiredCapabilities: { * chromeOptions: { * args: [ "--headless", "--disable-gpu", "--no-sandbox" ] @@ -614,11 +612,6 @@ class WebDriver extends Helper { delete this.options.capabilities.hostname delete this.options.capabilities.port delete this.options.capabilities.path - if (this.options.devtoolsProtocol) { - if (!['chrome', 'chromium'].includes(this.options.browser.toLowerCase())) - throw Error('The devtools protocol is only working with Chrome or Chromium') - this.options.automationProtocol = 'devtools' - } this.browser = await webdriverio.remote(this.options) } } catch (err) { @@ -649,11 +642,6 @@ class WebDriver extends Helper { this.browser.capabilities.platformName = this.browser.capabilities.platformName.toLowerCase() } - if (this.options.automationProtocol) { - this.puppeteerBrowser = await this.browser.getPuppeteer() - this.page = (await this.puppeteerBrowser.pages())[0] - } - return this.browser } @@ -1143,10 +1131,6 @@ class WebDriver extends Helper { assertElementExists(res, field, 'Field') const elem = usingFirstElement(res) highlightActiveElement.call(this, elem) - if (this.options.automationProtocol) { - const curentValue = await elem.getValue() - return elem.setValue(curentValue + value.toString()) - } return elem.addValue(value.toString()) } @@ -1159,9 +1143,6 @@ class WebDriver extends Helper { assertElementExists(res, field, 'Field') const elem = usingFirstElement(res) highlightActiveElement.call(this, elem) - if (this.options.automationProtocol) { - return elem.setValue('') - } return elem.clearValue(getElementId(elem)) } @@ -1231,7 +1212,7 @@ class WebDriver extends Helper { const el = usingFirstElement(res) // Remote Upload (when running Selenium Server) - if (this.options.remoteFileUpload && !this.options.automationProtocol) { + if (this.options.remoteFileUpload) { try { this.debugSection('File', 'Uploading file to remote server') file = await this.browser.uploadFile(file) @@ -1793,7 +1774,7 @@ class WebDriver extends Helper { if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(offsetX, offsetY, elementId) const location = await elem.getLocation() assertElementExists(location, locator, 'Failed to receive', 'location') - /* eslint-disable prefer-arrow-callback */ + return this.browser.execute( function (x, y) { return window.scrollTo(x, y) @@ -1801,12 +1782,10 @@ class WebDriver extends Helper { location.x + offsetX, location.y + offsetY, ) - /* eslint-enable */ } if (this.browser.isMobile && !this.browser.isW3C) return this.browser.touchScroll(locator, offsetX, offsetY) - /* eslint-disable prefer-arrow-callback, comma-dangle */ return this.browser.execute( function (x, y) { return window.scrollTo(x, y) @@ -1814,7 +1793,6 @@ class WebDriver extends Helper { offsetX, offsetY, ) - /* eslint-enable */ } /** @@ -1873,7 +1851,6 @@ class WebDriver extends Helper { return this.browser.saveScreenshot(outputFile) } - /* eslint-disable prefer-arrow-callback, comma-dangle, prefer-const */ const originalWindowSize = await this.browser.getWindowSize() let { width, height } = await this.browser @@ -1886,7 +1863,6 @@ class WebDriver extends Helper { .then((res) => res) if (height < 100) height = 500 // errors for very small height - /* eslint-enable */ await this.browser.setWindowSize(width, height) this.debug(`Screenshot has been saved to ${outputFile}, size: ${width}x${height}`) @@ -2593,9 +2569,6 @@ class WebDriver extends Helper { async switchTo(locator) { this.browser.isInsideFrame = true if (Number.isInteger(locator)) { - if (this.options.automationProtocol) { - return this.browser.switchToFrame(locator + 1) - } return this.browser.switchToFrame(locator) } if (!locator) { @@ -2695,11 +2668,10 @@ class WebDriver extends Helper { */ scrollPageToTop() { const client = this.browser - /* eslint-disable prefer-arrow-callback */ + return client.execute(function () { window.scrollTo(0, 0) }) - /* eslint-enable */ } /** @@ -2707,7 +2679,7 @@ class WebDriver extends Helper { */ scrollPageToBottom() { const client = this.browser - /* eslint-disable prefer-arrow-callback, comma-dangle */ + return client.execute(function () { const body = document.body const html = document.documentElement @@ -2716,60 +2688,20 @@ class WebDriver extends Helper { Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), ) }) - /* eslint-enable */ } /** * {{> grabPageScrollPosition }} */ async grabPageScrollPosition() { - /* eslint-disable comma-dangle */ function getScrollPosition() { return { x: window.pageXOffset, y: window.pageYOffset, } } - /* eslint-enable comma-dangle */ - return this.executeScript(getScrollPosition) - } - - /** - * This method is **deprecated**. - * - * - * {{> setGeoLocation }} - */ - async setGeoLocation(latitude, longitude) { - if (!this.options.automationProtocol) { - console.log(`setGeoLocation deprecated: - * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#setgeolocation - * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`) - return - } - this.geoLocation = { latitude, longitude } - await this.browser.call(async () => { - const pages = await this.puppeteerBrowser.pages() - await pages[0].setGeolocation({ latitude, longitude }) - }) - } - - /** - * This method is **deprecated**. - * - * {{> grabGeoLocation }} - * - */ - async grabGeoLocation() { - if (!this.options.automationProtocol) { - console.log(`grabGeoLocation deprecated: - * This command is deprecated due to using deprecated JSON Wire Protocol command. More info: https://webdriver.io/docs/api/jsonwp/#getgeolocation - * Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration`) - return - } - if (!this.geoLocation) return 'No GeoLocation is set!' - return this.geoLocation + return this.executeScript(getScrollPosition) } /** @@ -2793,7 +2725,7 @@ class WebDriver extends Helper { * @param {*} caps * @param {*} fn */ - /* eslint-disable */ + runOnIOS(caps, fn) {} /** @@ -2802,135 +2734,13 @@ class WebDriver extends Helper { * @param {*} fn */ runOnAndroid(caps, fn) {} - /* eslint-enable */ /** * Placeholder for ~ locator only test case write once run on both Appium and WebDriver. */ - runInWeb(fn) { + async runInWeb(fn) { return fn() } - - /** - * - * _Note:_ Only works when devtoolsProtocol is enabled. - * - * {{> flushNetworkTraffics }} - */ - flushNetworkTraffics() { - if (!this.options.automationProtocol) { - console.log( - '* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration', - ) - return - } - this.requests = [] - } - - /** - * - * _Note:_ Only works when devtoolsProtocol is enabled. - * - * {{> stopRecordingTraffic }} - */ - stopRecordingTraffic() { - if (!this.options.automationProtocol) { - console.log( - '* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration', - ) - return - } - this.page.removeAllListeners('request') - this.recording = false - } - - /** - * - * _Note:_ Only works when devtoolsProtocol is enabled. - * - * {{> startRecordingTraffic }} - * - */ - async startRecordingTraffic() { - if (!this.options.automationProtocol) { - console.log( - '* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration', - ) - return - } - this.flushNetworkTraffics() - this.recording = true - this.recordedAtLeastOnce = true - - await this.page.setRequestInterception(true) - - this.page.on('request', (request) => { - const information = { - url: request.url(), - method: request.method(), - requestHeaders: request.headers(), - requestPostData: request.postData(), - response: request.response(), - } - - this.debugSection('REQUEST: ', JSON.stringify(information)) - - if (typeof information.requestPostData === 'object') { - information.requestPostData = JSON.parse(information.requestPostData) - } - request.continue() - this.requests.push(information) - }) - } - - /** - * - * _Note:_ Only works when devtoolsProtocol is enabled. - * - * {{> grabRecordedNetworkTraffics }} - */ - async grabRecordedNetworkTraffics() { - if (!this.options.automationProtocol) { - console.log( - '* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration', - ) - return - } - return grabRecordedNetworkTraffics.call(this) - } - - /** - * - * _Note:_ Only works when devtoolsProtocol is enabled. - * - * {{> seeTraffic }} - */ - async seeTraffic({ name, url, parameters, requestPostData, timeout = 10 }) { - if (!this.options.automationProtocol) { - console.log( - '* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration', - ) - return - } - await seeTraffic.call(this, ...arguments) - } - - /** - * - * _Note:_ Only works when devtoolsProtocol is enabled. - * - * {{> dontSeeTraffic }} - * - */ - dontSeeTraffic({ name, url }) { - if (!this.options.automationProtocol) { - console.log( - '* Switch to devtools protocol to use this command by setting devtoolsProtocol: true in the configuration', - ) - return - } - dontSeeTraffic.call(this, ...arguments) - } } async function proceedSee(assertType, text, context, strict = false) { @@ -3213,7 +3023,6 @@ function getElementId(el) { // List of known key values to unicode code points // https://www.w3.org/TR/webdriver/#keyboard-actions const keyUnicodeMap = { - /* eslint-disable quote-props */ Unidentified: '\uE000', Cancel: '\uE001', Clear: '\uE005', @@ -3328,7 +3137,6 @@ const keyUnicodeMap = { Semicolon: '\uE018', // ';' alias Slash: '/', // '/' alias ZenkakuHankaku: '\uE040', - /* eslint-enable quote-props */ } function convertKeyToRawKey(key) { diff --git a/lib/helper/extras/PlaywrightPropEngine.js b/lib/helper/extras/PlaywrightPropEngine.js index c74f17f19..5d9054159 100644 --- a/lib/helper/extras/PlaywrightPropEngine.js +++ b/lib/helper/extras/PlaywrightPropEngine.js @@ -2,7 +2,7 @@ module.exports.createValueEngine = () => { return { // Creates a selector that matches given target when queried at the root. // Can return undefined if unable to create one. - // eslint-disable-next-line no-unused-vars + create(root, target) { return null; }, @@ -29,7 +29,7 @@ module.exports.createDisabledEngine = () => { return { // Creates a selector that matches given target when queried at the root. // Can return undefined if unable to create one. - // eslint-disable-next-line no-unused-vars + create(root, target) { return null; }, diff --git a/lib/helper/network/utils.js b/lib/helper/network/utils.js index 7b34ec342..7e3c88bc9 100644 --- a/lib/helper/network/utils.js +++ b/lib/helper/network/utils.js @@ -34,7 +34,7 @@ const extractQueryObjects = (queryString) => { queryParameters.forEach((queryParameter) => { const keyValue = queryParameter.split('='); const queryObject = {}; - // eslint-disable-next-line prefer-destructuring + queryObject.name = keyValue[0]; queryObject.value = decodeURIComponent(keyValue[1]); queryObjects.push(queryObject); diff --git a/lib/helper/testcafe/testcafe-utils.js b/lib/helper/testcafe/testcafe-utils.js index 2d001b847..c44176e26 100644 --- a/lib/helper/testcafe/testcafe-utils.js +++ b/lib/helper/testcafe/testcafe-utils.js @@ -48,7 +48,7 @@ function getFuncBody(func) { const arrowIndex = fnStr.indexOf('=>'); if (arrowIndex >= 0) { fnStr = fnStr.slice(arrowIndex + 2); - // eslint-disable-next-line no-new-func + // eslint-disable-next-line no-eval return eval(`() => ${fnStr}`); } diff --git a/lib/locator.js b/lib/locator.js index eef8277f7..967ebc265 100644 --- a/lib/locator.js +++ b/lib/locator.js @@ -389,7 +389,7 @@ class Locator { } /** - * @param {CodeceptJS.LocatorOrString} locator + * @param {CodeceptJS.LocatorOrString} [locator] * @returns {Locator} */ Locator.build = (locator) => { diff --git a/lib/output.js b/lib/output.js index cb15f7e1d..833046446 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1,5 +1,6 @@ const colors = require('chalk'); const figures = require('figures'); +const { maskSensitiveData } = require('invisi-data') const styles = { error: colors.bgRed.white.bold, @@ -57,8 +58,9 @@ module.exports = { * @param {string} msg */ debug(msg) { + const _msg = isMaskedData() ? maskSensitiveData(msg) : msg if (outputLevel >= 2) { - print(' '.repeat(this.stepShift), styles.debug(`${figures.pointerSmall} ${msg}`)); + print(' '.repeat(this.stepShift), styles.debug(`${figures.pointerSmall} ${_msg}`)); } }, @@ -67,8 +69,9 @@ module.exports = { * @param {string} msg */ log(msg) { + const _msg = isMaskedData() ? maskSensitiveData(msg) : msg if (outputLevel >= 3) { - print(' '.repeat(this.stepShift), styles.log(truncate(` ${msg}`, this.spaceShift))); + print(' '.repeat(this.stepShift), styles.log(truncate(` ${_msg}`, this.spaceShift))); } }, @@ -117,10 +120,11 @@ module.exports = { stepLine = colors.green(truncate(stepLine, this.spaceShift)); } if (step.comment) { - stepLine += colors.grey(step.comment.split('\n').join('\n' + ' '.repeat(4))); // eslint-disable-line + stepLine += colors.grey(step.comment.split('\n').join('\n' + ' '.repeat(4))); } - print(' '.repeat(this.stepShift), truncate(stepLine, this.spaceShift)); + const _stepLine = isMaskedData() ? maskSensitiveData(stepLine) : stepLine + print(' '.repeat(this.stepShift), truncate(_stepLine, this.spaceShift)); }, /** @namespace */ @@ -167,10 +171,10 @@ module.exports = { scenario: { /** * @param {Mocha.Test} test - */ - /* eslint-disable */ + */ + started(test) {}, - /* eslint-enable */ + /** * @param {Mocha.Test} test */ @@ -254,3 +258,7 @@ function truncate(msg, gap = 0) { } return msg; } + +function isMaskedData() { + return global.maskSensitiveData === true || false +} diff --git a/lib/pause.js b/lib/pause.js index 8e6fb7f04..2b4c5e6ce 100644 --- a/lib/pause.js +++ b/lib/pause.js @@ -91,7 +91,7 @@ async function parseInput(cmd) { return nextStep(); } for (const k of Object.keys(registeredVariables)) { - eval(`var ${k} = registeredVariables['${k}'];`); // eslint-disable-line no-eval + eval(`var ${k} = registeredVariables['${k}'];`); } let executeCommand = Promise.resolve(); @@ -106,9 +106,9 @@ async function parseInput(cmd) { let isAiCommand = false; let $res; try { - // eslint-disable-next-line + const locate = global.locate; // enable locate in this context - // eslint-disable-next-line + const I = container.support('I'); if (cmd.trim().startsWith('=>')) { isCustomCommand = true; @@ -143,7 +143,7 @@ async function parseInput(cmd) { executeCommand = executeCommand.then(async () => { const cmd = getCmd(); if (!cmd) return; - return eval(cmd); // eslint-disable-line no-eval + return eval(cmd); }).catch((err) => { debug(err); if (isAiCommand) return; @@ -156,7 +156,7 @@ async function parseInput(cmd) { const val = await executeCommand; if (isCustomCommand) { - if (val !== undefined) console.log('Result', '$res=', val); // eslint-disable-line + if (val !== undefined) console.log('Result', '$res=', val); $res = val; } diff --git a/lib/plugin/coverage.js b/lib/plugin/coverage.js index d4e8da72d..9f2bd8e6e 100644 --- a/lib/plugin/coverage.js +++ b/lib/plugin/coverage.js @@ -143,9 +143,6 @@ module.exports = function (config) { const helper = helpers[helperName] - if (helperName === 'WebDriver' && !helper.config.devtoolsProtocol) - throw Error('Coverage is currently supporting the WebDriver running with Devtools protocol.') - const v8Helper = v8CoverageHelpers[helperName] const coverageOptions = { diff --git a/lib/secret.js b/lib/secret.js index 38786bcf6..2ea52a8da 100644 --- a/lib/secret.js +++ b/lib/secret.js @@ -1,4 +1,3 @@ -/* eslint-disable max-classes-per-file */ const { deepClone } = require('./utils'); const maskedString = '*****'; diff --git a/lib/step.js b/lib/step.js index c9184fad3..39d79c596 100644 --- a/lib/step.js +++ b/lib/step.js @@ -1,5 +1,5 @@ // TODO: place MetaStep in other file, disable rule -/* eslint-disable max-classes-per-file */ + const store = require('./store'); const Secret = require('./secret'); const event = require('./event'); diff --git a/lib/workers.js b/lib/workers.js index 429091b36..7ba617d9c 100644 --- a/lib/workers.js +++ b/lib/workers.js @@ -1,4 +1,3 @@ -/* eslint-disable max-classes-per-file */ const path = require('path'); const mkdirp = require('mkdirp'); const { Worker } = require('worker_threads'); diff --git a/package.json b/package.json index f1fb287d6..b26bbefd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeceptjs", - "version": "3.6.6", + "version": "3.6.8", "description": "Supercharged End 2 End Testing Framework for NodeJS", "keywords": [ "acceptance", @@ -35,7 +35,7 @@ }, "repository": "Codeception/codeceptjs", "scripts": { - "json-server": "./node_modules/json-server/bin/index.js test/data/rest/db.json -p 8010 --watch -m test/data/rest/headers.js", + "json-server": "json-server test/data/rest/db.json --host 0.0.0.0 -p 8010 --watch -m test/data/rest/headers.js", "json-server:graphql": "node test/data/graphql/index.js", "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.js", "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix", @@ -54,10 +54,8 @@ "test:unit:webbapi:puppeteer": "mocha test/helper/Puppeteer_test.js", "test:unit:webbapi:webDriver": "mocha test/helper/WebDriver_test.js", "test:unit:webbapi:webDriver:noSeleniumServer": "mocha test/helper/WebDriver.noSeleniumServer_test.js", - "test:unit:webbapi:webDriver:devtools": "mocha test/helper/WebDriver_devtools_test.js --exit", "test:unit:webbapi:testCafe": "mocha test/helper/TestCafe_test.js", "test:unit:expect": "mocha test/helper/Expect_test.js", - "test:unit:mockServer": "mocha test/helper/MockServer_test.js", "test:plugin": "mocha test/plugin/plugin_test.js", "def": "./runok.js def", "dev:graphql": "node test/data/graphql/index.js", @@ -72,11 +70,11 @@ "dependencies": { "@codeceptjs/configure": "1.0.1", "@codeceptjs/helper": "2.0.4", - "@cucumber/cucumber-expressions": "17", - "@cucumber/gherkin": "26", - "@cucumber/messages": "25.0.1", - "@xmldom/xmldom": "0.8.10", - "acorn": "8.12.1", + "@cucumber/cucumber-expressions": "18", + "@cucumber/gherkin": "30", + "@cucumber/messages": "27.0.0", + "@xmldom/xmldom": "0.9.5", + "acorn": "8.14.0", "arrify": "2.0.1", "axios": "1.7.7", "chai": "5.1.1", @@ -88,11 +86,10 @@ "chai-string": "1.5.0", "chalk": "4.1.2", "commander": "11.1.0", - "cross-spawn": "7.0.3", + "cross-spawn": "7.0.5", "css-to-xpath": "0.1.0", "csstoxpath": "1.6.0", - "devtools": "8.40.2", - "envinfo": "7.11.1", + "envinfo": "7.14.0", "escape-string-regexp": "4.0.0", "figures": "3.2.0", "fn-args": "4.0.0", @@ -105,70 +102,74 @@ "lodash.clonedeep": "4.5.0", "lodash.merge": "4.6.2", "mkdirp": "1.0.4", - "mocha": "10.7.3", - "monocart-coverage-reports": "2.10.3", + "mocha": "10.8.2", + "monocart-coverage-reports": "2.11.3", "ms": "2.1.3", "ora-classic": "5.4.2", - "pactum": "3.7.1", "parse-function": "5.6.10", - "parse5": "7.1.2", + "parse5": "7.2.1", "promise-retry": "1.1.1", "resq": "1.11.0", "sprintf-js": "1.1.1", - "uuid": "10.0" + "uuid": "11.0" }, "optionalDependencies": { "@codeceptjs/detox-helper": "1.1.2" }, "devDependencies": { "@codeceptjs/mock-request": "0.3.1", - "@faker-js/faker": "7.6.0", + "@eslint/eslintrc": "3.2.0", + "@eslint/js": "9.16.0", + "@faker-js/faker": "9.3.0", "@pollyjs/adapter-puppeteer": "6.0.6", "@pollyjs/core": "5.1.0", "@types/chai": "4.3.19", "@types/inquirer": "9.0.3", - "@types/node": "22.5.5", - "@wdio/sauce-service": "9.0.4", + "@types/node": "22.10.1", + "@wdio/sauce-service": "9.2.13", "@wdio/selenium-standalone-service": "8.3.2", - "@wdio/utils": "9.0.6", - "@xmldom/xmldom": "0.8.10", - "apollo-server-express": "2.25.3", + "@wdio/utils": "9.2.8", + "@xmldom/xmldom": "0.9.5", + "apollo-server-express": "3.13.0", "chai-as-promised": "7.1.2", "chai-subset": "1.6.0", + "cheerio": "^1.0.0", "contributor-faces": "1.1.0", - "documentation": "12.3.0", - "electron": "31.3.1", - "eslint": "8.57.0", + "documentation": "14.0.3", + "electron": "33.2.1", + "eslint": "9.16.0", "eslint-config-airbnb-base": "15.0.0", - "eslint-plugin-import": "2.30.0", + "eslint-plugin-import": "2.31.0", "eslint-plugin-mocha": "10.5.0", "expect": "29.7.0", - "express": "4.19.2", + "express": "4.21.1", + "globals": "15.12.0", "graphql": "16.9.0", - "husky": "9.1.5", + "husky": "9.1.7", "inquirer-test": "2.0.1", - "jsdoc": "4.0.3", + "invisi-data": "^1.0.0", + "jsdoc": "4.0.4", "jsdoc-typeof-plugin": "1.0.0", - "json-server": "0.10.1", - "playwright": "1.45.3", + "json-server": "0.17.4", + "playwright": "1.49.0", "prettier": "^3.3.2", - "puppeteer": "23.3.0", + "puppeteer": "23.8.0", "qrcode-terminal": "0.12.0", "rosie": "2.1.1", "runok": "0.9.3", "semver": "7.6.3", - "sinon": "18.0.0", + "sinon": "19.0.2", "sinon-chai": "3.7.0", - "testcafe": "3.5.0", - "ts-morph": "23.0.0", + "testcafe": "3.7.0", + "ts-morph": "24.0.0", "ts-node": "10.9.2", "tsd": "^0.31.0", "tsd-jsdoc": "2.5.0", - "typedoc": "0.26.7", - "typedoc-plugin-markdown": "4.2.6", - "typescript": "5.6.2", + "typedoc": "0.26.11", + "typedoc-plugin-markdown": "4.2.10", + "typescript": "5.7.2", "wdio-docker-service": "1.5.0", - "webdriverio": "8.39.1", + "webdriverio": "8.40.6", "xml2js": "0.6.2", "xpath": "0.0.34" }, diff --git a/runok.js b/runok.js index 6e9f09989..ca24886f6 100755 --- a/runok.js +++ b/runok.js @@ -2,7 +2,7 @@ const fs = require('fs') const path = require('path') const axios = require('axios') -const documentation = require('documentation') + const { stopOnFail, chdir, @@ -13,6 +13,10 @@ const contributors = require('contributor-faces') const { execSync } = require('node:child_process') const semver = require('semver') +let documentation + +import('documentation').then((mod) => (documentation = mod)) + const helperMarkDownFile = function (name) { return `docs/helpers/${name}.md` } diff --git a/test/acceptance/codecept.WebDriver.devtools.coverage.js b/test/acceptance/codecept.WebDriver.devtools.coverage.js deleted file mode 100644 index 3d771815d..000000000 --- a/test/acceptance/codecept.WebDriver.devtools.coverage.js +++ /dev/null @@ -1,49 +0,0 @@ -const TestHelper = require('../support/TestHelper') - -module.exports.config = { - tests: './*_test.js', - timeout: 10000, - output: './output', - helpers: { - WebDriver: { - url: TestHelper.siteUrl(), - browser: 'Chromium', - windowSize: '500x700', - devtoolsProtocol: true, - waitForTimeout: 5000, - capabilities: { - chromeOptions: { - args: ['--headless', '--disable-gpu', '--window-size=500,700'], - }, - }, - }, - ScreenshotSessionHelper: { - require: '../support/ScreenshotSessionHelper.js', - outputPath: './output', - }, - ExpectHelper: {}, - }, - include: {}, - mocha: {}, - name: 'acceptance', - plugins: { - screenshotOnFail: { - enabled: true, - }, - coverage: { - enabled: true, - debug: true, - name: 'CodeceptJS Coverage Report', - sourceFilter: '**/src/**', - sourcePath: { - 'todomvc-react/': '', - 'todomvc.com/examples/react/': '', - }, - outputDir: 'output/coverage', - }, - }, - gherkin: { - features: './gherkin/*.feature', - steps: ['./gherkin/steps.js'], - }, -} diff --git a/test/acceptance/codecept.WebDriver.devtools.js b/test/acceptance/codecept.WebDriver.devtools.js deleted file mode 100644 index 755e950d5..000000000 --- a/test/acceptance/codecept.WebDriver.devtools.js +++ /dev/null @@ -1,42 +0,0 @@ -const TestHelper = require('../support/TestHelper') - -module.exports.config = { - tests: './*_test.js', - timeout: 10000, - output: './output', - helpers: { - WebDriver: { - url: TestHelper.siteUrl(), - browser: 'Chromium', - windowSize: '500x700', - devtoolsProtocol: true, - waitForTimeout: 5000, - capabilities: { - chromeOptions: { - args: ['--headless', '--disable-gpu', '--window-size=500,700'], - }, - }, - }, - ScreenshotSessionHelper: { - require: '../support/ScreenshotSessionHelper.js', - outputPath: './output', - }, - ExpectHelper: {}, - }, - include: {}, - bootstrap: async () => - new Promise((done) => { - setTimeout(done, 5000) - }), // let's wait for selenium - mocha: {}, - name: 'acceptance', - plugins: { - screenshotOnFail: { - enabled: true, - }, - }, - gherkin: { - features: './gherkin/*.feature', - steps: ['./gherkin/steps.js'], - }, -} diff --git a/test/acceptance/config_test.js b/test/acceptance/config_test.js index 140d2a4c3..0f6258526 100644 --- a/test/acceptance/config_test.js +++ b/test/acceptance/config_test.js @@ -8,12 +8,12 @@ Scenario('change config 1 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { Scenario('change config 2 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/') - I.seeInCurrentUrl('github.com') -}).config({ url: 'https://github.com' }) + I.seeInCurrentUrl('codecept.io') +}).config({ url: 'https://codecept.io' }) Scenario('change config 3 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/') - I.dontSeeInCurrentUrl('github.com') + I.dontSeeInCurrentUrl('codecept.io') I.seeInCurrentUrl('google.com') }) @@ -26,7 +26,7 @@ Scenario('change config 4 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { Scenario('change config 5 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/') - I.dontSeeInCurrentUrl('github.com') + I.dontSeeInCurrentUrl('codecept.io') I.seeInCurrentUrl('google.com') }) @@ -38,10 +38,10 @@ Scenario('make API call and check response @Playwright', ({ I }) => { Scenario('change config 6 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/') - I.seeInCurrentUrl('github.com') + I.seeInCurrentUrl('codecept.io') }).config(async () => { await new Promise((r) => { setTimeout(r, 50) }) - return { url: 'https://github.com' } + return { url: 'https://codecept.io' } }) diff --git a/test/bdd/features/faker.feature b/test/bdd/features/faker.feature index b69fc5f02..ac2f9dcea 100644 --- a/test/bdd/features/faker.feature +++ b/test/bdd/features/faker.feature @@ -7,4 +7,4 @@ Feature: Faker examples Then "" returned "" in change to "" Examples: | product | customer | price | cashier | - | {{vehicle.vehicle}} | Dr. {{name.findName}} | {{commerce.price}} | cashier 2 | + | {{vehicle.vehicle}} | Dr. {{name.fullName}} | {{commerce.price}} | cashier 2 | diff --git a/test/data/graphql/index.js b/test/data/graphql/index.js index f3d372278..74ebac5a0 100644 --- a/test/data/graphql/index.js +++ b/test/data/graphql/index.js @@ -17,8 +17,9 @@ const server = new ApolloServer({ playground: true, }); -server.applyMiddleware({ app }); - -app.use(middleware); -app.use(router); -module.exports = app.listen(PORT, () => console.log(`test graphQL server listening on port ${PORT}...`)); +server.start().then(() => { + server.applyMiddleware({ app }); + app.use(middleware); + app.use(router); + module.exports = app.listen(PORT, () => console.log(`test graphQL server listening on port ${PORT}...`)); +}); diff --git a/test/data/graphql/users_factory.js b/test/data/graphql/users_factory.js index 95292e9f6..9b57de272 100644 --- a/test/data/graphql/users_factory.js +++ b/test/data/graphql/users_factory.js @@ -4,5 +4,5 @@ const { faker } = require('@faker-js/faker'); module.exports = new Factory(function (buildObject) { this.input = { ...buildObject }; }) - .attr('name', () => faker.name.fullName()) + .attr('name', () => faker.person.fullName()) .attr('email', () => faker.internet.email()); diff --git a/test/data/rest/posts_factory.js b/test/data/rest/posts_factory.js index c06402ecd..a731dafb8 100644 --- a/test/data/rest/posts_factory.js +++ b/test/data/rest/posts_factory.js @@ -2,6 +2,6 @@ const { Factory } = require('rosie'); const { faker } = require('@faker-js/faker'); module.exports = new Factory() - .attr('author', () => faker.name.findName()) + .attr('author', () => faker.person.fullName()) .attr('title', () => faker.lorem.sentence()) .attr('body', () => faker.lorem.paragraph()); diff --git a/test/data/sandbox/configs/commentStep/customHelper.js b/test/data/sandbox/configs/commentStep/customHelper.js index 4bdac5fa6..84fb53544 100644 --- a/test/data/sandbox/configs/commentStep/customHelper.js +++ b/test/data/sandbox/configs/commentStep/customHelper.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ // const Helper = require('../../lib/helper'); class CustomHelper extends Helper { diff --git a/test/data/sandbox/configs/testArtifacts/customHelper.js b/test/data/sandbox/configs/testArtifacts/customHelper.js index 1c215f42b..9eb4bd15c 100644 --- a/test/data/sandbox/configs/testArtifacts/customHelper.js +++ b/test/data/sandbox/configs/testArtifacts/customHelper.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ // const Helper = require('../../lib/helper'); class CustomHelper extends Helper { diff --git a/test/data/sandbox/features/step_definitions/my_other_steps.js b/test/data/sandbox/features/step_definitions/my_other_steps.js index 7605179b6..2f18a006e 100644 --- a/test/data/sandbox/features/step_definitions/my_other_steps.js +++ b/test/data/sandbox/features/step_definitions/my_other_steps.js @@ -1,7 +1,7 @@ const I = actor(); const axios = require('axios'); -Given('I have products in my cart', (table) => { // eslint-disable-line +Given('I have products in my cart', (table) => { for (const id in table.rows) { if (id < 1) { continue; diff --git a/test/data/sandbox/support/failureHelper.js b/test/data/sandbox/support/failureHelper.js index 24a2cb2a6..a1b48ce3d 100644 --- a/test/data/sandbox/support/failureHelper.js +++ b/test/data/sandbox/support/failureHelper.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ // const Helper = require('../../lib/helper'); class FailureHelper extends Helper { diff --git a/test/data/sandbox/testscenario_test.testscenario.js b/test/data/sandbox/testscenario_test.testscenario.js index 43791005e..0f6ca54c0 100644 --- a/test/data/sandbox/testscenario_test.testscenario.js +++ b/test/data/sandbox/testscenario_test.testscenario.js @@ -9,7 +9,6 @@ Scenario('Simple async/await test', async ({ I }) => { console.log(text); }); -// eslint-disable-next-line arrow-parens Scenario('Should understand async without brackets', async ({ I }) => { const text = await I.stringWithScenarioType('asyncbrackets'); console.log(text); diff --git a/test/docker-compose.yml b/test/docker-compose.yml index 98cca4a83..7c37d971d 100644 --- a/test/docker-compose.yml +++ b/test/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3' services: test-rest: <<: &test-service diff --git a/test/helper/AppiumV2_test.js b/test/helper/AppiumV2_test.js index 16272b84d..15e22c9f4 100644 --- a/test/helper/AppiumV2_test.js +++ b/test/helper/AppiumV2_test.js @@ -767,23 +767,23 @@ describe('Appium', function () { await app.see('Welcome to register a new User') }) - it('should execute only on Android @quick', async () => { + it('should execute only on Android @quick', () => { let platform = null - await app.runOnIOS(() => { + app.runOnIOS(() => { platform = 'ios' }) - await app.runOnAndroid(() => { + app.runOnAndroid(() => { platform = 'android' }) - await app.runOnAndroid({ platformVersion: '7.0' }, () => { + app.runOnAndroid({ platformVersion: '7.0' }, () => { platform = 'android7' }) assert.equal('android', platform) }) - it('should execute only on Android >= 5.0 @quick', async () => { - await app.runOnAndroid( + it('should execute only on Android >= 5.0 @quick', () => { + app.runOnAndroid( (caps) => caps.platformVersion >= 5, () => {}, ) diff --git a/test/helper/MockServer_test.js b/test/helper/MockServer_test.js deleted file mode 100644 index 1faa6af4e..000000000 --- a/test/helper/MockServer_test.js +++ /dev/null @@ -1,135 +0,0 @@ -const path = require('path') - -let expect -import('chai').then((chai) => { - expect = chai.expect -}) -const { like } = require('pactum-matchers') -const MockServer = require('../../lib/helper/MockServer') -const REST = require('../../lib/helper/REST') - -global.codeceptjs = require('../../lib') - -let I -let restClient -const port = 65000 -const api_url = `http://0.0.0.0:${port}` - -describe('MockServer Helper', function () { - this.timeout(3000) - this.retries(1) - - before(() => { - global.codecept_dir = path.join(__dirname, '/../data') - - I = new MockServer({ port }) - restClient = new REST({ - endpoint: api_url, - defaultHeaders: { - 'X-Test': 'test', - }, - }) - }) - - beforeEach(async () => { - await I.startMockServer() - }) - - afterEach(async () => { - await I.stopMockServer() - }) - - describe('#startMockServer', () => { - it('should start the mock server with custom port', async () => { - global.debugMode = true - await I.startMockServer(6789) - await I.stopMockServer() - global.debugMode = undefined - }) - }) - - describe('#addInteractionToMockServer', () => { - it('should return the correct response', async () => { - await I.addInteractionToMockServer({ - request: { - method: 'GET', - path: '/api/hello', - }, - response: { - status: 200, - body: { - say: 'hello to mock server', - }, - }, - }) - const res = await restClient.sendGetRequest('/api/hello') - expect(res.data).to.eql({ say: 'hello to mock server' }) - }) - - it('should return 404 when not found route', async () => { - const res = await restClient.sendGetRequest('/api/notfound') - expect(res.status).to.eql(404) - }) - - it('should return the strong Match on Query Params', async () => { - await I.addInteractionToMockServer({ - request: { - method: 'GET', - path: '/api/users', - queryParams: { - id: 1, - }, - }, - response: { - status: 200, - body: { - user: 1, - }, - }, - }) - - await I.addInteractionToMockServer({ - request: { - method: 'GET', - path: '/api/users', - queryParams: { - id: 2, - }, - }, - response: { - status: 200, - body: { - user: 2, - }, - }, - }) - let res = await restClient.sendGetRequest('/api/users?id=1') - expect(res.data).to.eql({ user: 1 }) - res = await restClient.sendGetRequest('/api/users?id=2') - expect(res.data).to.eql({ user: 2 }) - }) - - it('should check the stateful behavior', async () => { - await I.addInteractionToMockServer({ - request: { - method: 'GET', - path: '/api/projects/{id}', - pathParams: { - id: like('random-id'), - }, - }, - stores: { - ProjectId: 'req.pathParams.id', - }, - response: { - status: 200, - body: { - id: '$S{ProjectId}', - }, - }, - }) - const res = await restClient.sendGetRequest('/api/projects/10') - expect(res.data).to.eql({ id: '10' }) - }) - }) -}) diff --git a/test/helper/Playwright_test.js b/test/helper/Playwright_test.js index d36bd4347..b410244d4 100644 --- a/test/helper/Playwright_test.js +++ b/test/helper/Playwright_test.js @@ -37,7 +37,7 @@ describe('Playwright', function () { I = new Playwright({ url: siteUrl, - windowSize: '500x700', + // windowSize: '500x700', browser: process.env.BROWSER || 'chromium', show: false, waitForTimeout: 5000, @@ -205,6 +205,17 @@ describe('Playwright', function () { await I.waitToHide('h9') }) + + it('should wait for invisible combined with dontseeElement', async () => { + await I.amOnPage('https://codecept.io/') + await I.waitForVisible('.frameworks') + await I.waitForVisible('[alt="React"]') + await I.waitForVisible('.mountains') + await I._withinBegin('.mountains', async () => { + await I.dontSeeElement('[alt="React"]') + await I.waitForInvisible('[alt="React"]', 2) + }) + }) }) describe('#waitToHide', () => { diff --git a/test/helper/Puppeteer_test.js b/test/helper/Puppeteer_test.js index 9d3ff1aca..6410c4f14 100644 --- a/test/helper/Puppeteer_test.js +++ b/test/helper/Puppeteer_test.js @@ -32,7 +32,7 @@ describe('Puppeteer - BasicAuth', function () { I = new Puppeteer({ url: siteUrl, - windowSize: '500x700', + // windowSize: '500x700', show: false, waitForTimeout: 5000, waitForAction: 500, diff --git a/test/helper/TestCafe_test.js b/test/helper/TestCafe_test.js index 5481810f2..184c2df33 100644 --- a/test/helper/TestCafe_test.js +++ b/test/helper/TestCafe_test.js @@ -22,7 +22,7 @@ describe('TestCafe', function () { url: siteUrl, windowSize: '1000x700', show: false, - browser: 'chrome', + browser: 'chromium', restart: false, waitForTimeout: 5000, }) diff --git a/test/helper/WebDriver.noSeleniumServer_test.js b/test/helper/WebDriver.noSeleniumServer_test.js index af956c1da..208201865 100644 --- a/test/helper/WebDriver.noSeleniumServer_test.js +++ b/test/helper/WebDriver.noSeleniumServer_test.js @@ -1204,16 +1204,6 @@ describe('WebDriver - No Selenium server started', function () { }) }) - describe('GeoLocation', () => { - // deprecated JSON Wire method commands - it.skip('should set the geoLocation', async () => { - await wd.setGeoLocation(37.4043, -122.0748) - const geoLocation = await wd.grabGeoLocation() - assert.equal(geoLocation.latitude, 37.4043, 'The latitude is not properly set') - assert.equal(geoLocation.longitude, -122.0748, 'The longitude is not properly set') - }) - }) - describe('#grabElementBoundingRect', () => { it('should get the element size', async () => { await wd.amOnPage('/form/hidden') diff --git a/test/helper/WebDriver_devtools_test.js b/test/helper/WebDriver_devtools_test.js deleted file mode 100644 index 84dad90a1..000000000 --- a/test/helper/WebDriver_devtools_test.js +++ /dev/null @@ -1,1251 +0,0 @@ -const assert = require('assert') - -let expect -import('chai').then((chai) => { - expect = chai.expect -}) -const path = require('path') -const fs = require('fs') - -const TestHelper = require('../support/TestHelper') -const WebDriver = require('../../lib/helper/WebDriver') -const AssertionFailedError = require('../../lib/assert/error') -const webApiTests = require('./webapi') -const Secret = require('../../lib/secret') -global.codeceptjs = require('../../lib') - -const siteUrl = TestHelper.siteUrl() -let wd - -describe('WebDriver - Devtools Protocol', function () { - this.retries(1) - this.timeout(35000) - - before(() => { - global.codecept_dir = path.join(__dirname, '/../data') - try { - fs.unlinkSync(dataFile) - } catch (err) { - // continue regardless of error - } - - process.env.DevTools = 'true' - - wd = new WebDriver({ - url: siteUrl, - browser: 'Chromium', - windowSize: '500x700', - devtoolsProtocol: true, - waitForTimeout: 5000, - capabilities: { - chromeOptions: { - args: ['--headless', '--disable-gpu', '--window-size=500,700'], - }, - }, - customLocatorStrategies: { - customSelector: (selector) => ({ 'element-6066-11e4-a52e-4f735466cecf': `${selector}-foobar` }), - }, - }) - }) - - beforeEach(async () => { - webApiTests.init({ I: wd, siteUrl }) - this.wdBrowser = await wd._before() - return this.wdBrowser - }) - - afterEach(async () => wd._after()) - - // load common test suite - webApiTests.tests() - - describe('customLocatorStrategies', () => { - it('should locate through custom selector', async () => { - const el = await this.wdBrowser.custom$('customSelector', '.test') - expect(el.elementId).to.equal('.test-foobar') - }) - - it('should include the custom strategy', async () => { - expect(wd.customLocatorStrategies.customSelector).to.not.be.undefined - }) - - it('should be added to the browser locator strategies', async () => { - expect(this.wdBrowser.addLocatorStrategy).to.not.be.undefined - }) - - it('throws on invalid custom selector', async () => { - try { - await wd.waitForEnabled({ madeUpSelector: '#text' }, 2) - } catch (e) { - expect(e.message).to.include('Please define "customLocatorStrategies"') - } - }) - }) - - describe('open page : #amOnPage', () => { - it('should open main page of configured site', async () => { - await wd.amOnPage('/') - const url = await wd.grabCurrentUrl() - url.should.eql(`${siteUrl}/`) - }) - - it('should open any page of configured site', async () => { - await wd.amOnPage('/info') - const url = await wd.grabCurrentUrl() - url.should.eql(`${siteUrl}/info`) - }) - - it('should open absolute url', async () => { - await wd.amOnPage(siteUrl) - const url = await wd.grabCurrentUrl() - url.should.eql(`${siteUrl}/`) - }) - }) - - describe('see text : #see', () => { - it('should fail when text is not on site', async () => { - await wd.amOnPage('/') - - try { - await wd.see('Something incredible!') - } catch (e) { - e.should.be.instanceOf(AssertionFailedError) - e.inspect().should.include('web page') - } - - try { - await wd.dontSee('Welcome') - } catch (e) { - e.should.be.instanceOf(AssertionFailedError) - e.inspect().should.include('web page') - } - }) - }) - - describe('check fields: #seeInField, #seeCheckboxIsChecked, ...', () => { - it('should throw error if field is not empty', async () => { - await wd.amOnPage('/form/empty') - - try { - await wd.seeInField('#empty_input', 'Ayayay') - } catch (e) { - e.should.be.instanceOf(AssertionFailedError) - e.inspect().should.be.equal('expected fields by #empty_input to include "Ayayay"') - } - }) - - it('should check values in checkboxes', async () => { - await wd.amOnPage('/form/field_values') - await wd.dontSeeInField('checkbox[]', 'not seen one') - await wd.seeInField('checkbox[]', 'see test one') - await wd.dontSeeInField('checkbox[]', 'not seen two') - await wd.seeInField('checkbox[]', 'see test two') - await wd.dontSeeInField('checkbox[]', 'not seen three') - await wd.seeInField('checkbox[]', 'see test three') - }) - - it('should check values are the secret type in checkboxes', async () => { - await wd.amOnPage('/form/field_values') - await wd.dontSeeInField('checkbox[]', Secret.secret('not seen one')) - await wd.seeInField('checkbox[]', Secret.secret('see test one')) - await wd.dontSeeInField('checkbox[]', Secret.secret('not seen two')) - await wd.seeInField('checkbox[]', Secret.secret('see test two')) - await wd.dontSeeInField('checkbox[]', Secret.secret('not seen three')) - await wd.seeInField('checkbox[]', Secret.secret('see test three')) - }) - - it('should check values with boolean', async () => { - await wd.amOnPage('/form/field_values') - await wd.seeInField('checkbox1', true) - await wd.dontSeeInField('checkbox1', false) - await wd.seeInField('checkbox2', false) - await wd.dontSeeInField('checkbox2', true) - await wd.seeInField('radio2', true) - await wd.dontSeeInField('radio2', false) - await wd.seeInField('radio3', false) - await wd.dontSeeInField('radio3', true) - }) - - it('should check values in radio', async () => { - await wd.amOnPage('/form/field_values') - await wd.seeInField('radio1', 'see test one') - await wd.dontSeeInField('radio1', 'not seen one') - await wd.dontSeeInField('radio1', 'not seen two') - await wd.dontSeeInField('radio1', 'not seen three') - }) - - it('should return error when element has no value attribute', async () => { - await wd.amOnPage('https://codecept.io/quickstart') - - try { - await wd.seeInField('#search_input_react', 'WebDriver1') - } catch (e) { - e.should.be.instanceOf(Error) - } - }) - }) - - describe('Force Right Click: #forceRightClick', () => { - it('it should forceRightClick', async () => { - await wd.amOnPage('/form/rightclick') - await wd.dontSee('right clicked') - await wd.forceRightClick('Lorem Ipsum') - await wd.see('right clicked') - }) - - it('it should forceRightClick by locator', async () => { - await wd.amOnPage('/form/rightclick') - await wd.dontSee('right clicked') - await wd.forceRightClick('.context a') - await wd.see('right clicked') - }) - - it('it should forceRightClick by locator and context', async () => { - await wd.amOnPage('/form/rightclick') - await wd.dontSee('right clicked') - await wd.forceRightClick('Lorem Ipsum', '.context') - await wd.see('right clicked') - }) - }) - - describe.skip('#pressKey, #pressKeyDown, #pressKeyUp', () => { - it('should be able to send special keys to element', async () => { - await wd.amOnPage('/form/field') - await wd.appendField('Name', '-') - - await wd.pressKey(['Right Shift', 'Home']) - await wd.pressKey('Delete') - - // Sequence only executes up to first non-modifier key ('Digit1') - await wd.pressKey(['SHIFT_RIGHT', 'Digit1', 'Digit4']) - await wd.pressKey('1') - await wd.pressKey('2') - await wd.pressKey('3') - await wd.pressKey('ArrowLeft') - await wd.pressKey('Left Arrow') - await wd.pressKey('arrow_left') - await wd.pressKeyDown('Shift') - await wd.pressKey('a') - await wd.pressKey('KeyB') - await wd.pressKeyUp('ShiftLeft') - await wd.pressKey('C') - await wd.seeInField('Name', '!ABC123') - }) - - it('should use modifier key based on operating system', async () => { - await wd.amOnPage('/form/field') - await wd.fillField('Name', 'value that is cleared using select all shortcut') - - await wd.pressKey(['CommandOrControl', 'A']) - await wd.pressKey('Backspace') - await wd.dontSeeInField('Name', 'value that is cleared using select all shortcut') - }) - - it('should show correct numpad or punctuation key when Shift modifier is active', async () => { - await wd.amOnPage('/form/field') - await wd.fillField('Name', '') - - await wd.pressKey(';') - await wd.pressKey(['Shift', ';']) - await wd.pressKey(['Shift', 'Semicolon']) - await wd.pressKey('=') - await wd.pressKey(['Shift', '=']) - await wd.pressKey(['Shift', 'Equal']) - await wd.pressKey('*') - await wd.pressKey(['Shift', '*']) - await wd.pressKey(['Shift', 'Multiply']) - await wd.pressKey('+') - await wd.pressKey(['Shift', '+']) - await wd.pressKey(['Shift', 'Add']) - await wd.pressKey(',') - await wd.pressKey(['Shift', ',']) - await wd.pressKey(['Shift', 'Comma']) - await wd.pressKey(['Shift', 'NumpadComma']) - await wd.pressKey(['Shift', 'Separator']) - await wd.pressKey('-') - await wd.pressKey(['Shift', '-']) - await wd.pressKey(['Shift', 'Subtract']) - await wd.pressKey('.') - await wd.pressKey(['Shift', '.']) - await wd.pressKey(['Shift', 'Decimal']) - await wd.pressKey(['Shift', 'Period']) - await wd.pressKey('/') - await wd.pressKey(['Shift', '/']) - await wd.pressKey(['Shift', 'Divide']) - await wd.pressKey(['Shift', 'Slash']) - - await wd.seeInField('Name', ';::=++***+++,<<<<-_-.>.>/?/?') - }) - - it('should show correct number key when Shift modifier is active', async () => { - await wd.amOnPage('/form/field') - await wd.fillField('Name', '') - - await wd.pressKey('0') - await wd.pressKeyDown('Shift') - await wd.pressKey('0') - await wd.pressKey('Digit0') - await wd.pressKeyUp('Shift') - - await wd.pressKey('1') - await wd.pressKeyDown('Shift') - await wd.pressKey('1') - await wd.pressKey('Digit1') - await wd.pressKeyUp('Shift') - - await wd.pressKey('2') - await wd.pressKeyDown('Shift') - await wd.pressKey('2') - await wd.pressKey('Digit2') - await wd.pressKeyUp('Shift') - - await wd.pressKey('3') - await wd.pressKeyDown('Shift') - await wd.pressKey('3') - await wd.pressKey('Digit3') - await wd.pressKeyUp('Shift') - - await wd.pressKey('4') - await wd.pressKeyDown('Shift') - await wd.pressKey('4') - await wd.pressKey('Digit4') - await wd.pressKeyUp('Shift') - - await wd.pressKey('5') - await wd.pressKeyDown('Shift') - await wd.pressKey('5') - await wd.pressKey('Digit5') - await wd.pressKeyUp('Shift') - - await wd.pressKey('6') - await wd.pressKeyDown('Shift') - await wd.pressKey('6') - await wd.pressKey('Digit6') - await wd.pressKeyUp('Shift') - - await wd.pressKey('7') - await wd.pressKeyDown('Shift') - await wd.pressKey('7') - await wd.pressKey('Digit7') - await wd.pressKeyUp('Shift') - - await wd.pressKey('8') - await wd.pressKeyDown('Shift') - await wd.pressKey('8') - await wd.pressKey('Digit8') - await wd.pressKeyUp('Shift') - - await wd.pressKey('9') - await wd.pressKeyDown('Shift') - await wd.pressKey('9') - await wd.pressKey('Digit9') - await wd.pressKeyUp('Shift') - - await wd.seeInField('Name', '0))1!!2@@3##4$$5%%6^^7&&8**9((') - }) - }) - - describe('#seeInSource, #grabSource', () => { - it('should check for text to be in HTML source', async () => { - await wd.amOnPage('/') - await wd.seeInSource('TestEd Beta 2.0') - await wd.dontSeeInSource(' { - await wd.amOnPage('/') - const source = await wd.grabSource() - assert.notEqual(source.indexOf('TestEd Beta 2.0'), -1, 'Source html should be retrieved') - }) - - it('should grab the innerHTML for an element', async () => { - await wd.amOnPage('/') - const source = await wd.grabHTMLFrom('#area1') - assert.deepEqual( - source, - ` - Test Link -`, - ) - }) - }) - - describe('#seeTitleEquals', () => { - it('should check that title is equal to provided one', async () => { - await wd.amOnPage('/') - - try { - await wd.seeTitleEquals('TestEd Beta 2.0') - await wd.seeTitleEquals('TestEd Beta 2.') - } catch (e) { - assert.equal(e.message, 'expected web page title to be TestEd Beta 2., but found TestEd Beta 2.0') - } - }) - }) - - describe('#seeTextEquals', () => { - it('should check text is equal to provided one', async () => { - await wd.amOnPage('/') - await wd.seeTextEquals('Welcome to test app!', 'h1') - - try { - await wd.seeTextEquals('Welcome to test app', 'h1') - assert.equal(true, false, 'Throw an error because it should not get this far!') - } catch (e) { - e.should.be.instanceOf(Error) - e.message.should.be.equal('expected element h1 "Welcome to test app" to equal "Welcome to test app!"') - // e.should.be.instanceOf(AssertionFailedError); - } - }) - - it('should check text is not equal to empty string of element text', async () => { - await wd.amOnPage('https://codecept.io') - - try { - await wd.seeTextEquals('', '.logo') - await wd.seeTextEquals('This is not empty', '.logo') - } catch (e) { - e.should.be.instanceOf(Error) - e.message.should.be.equal('expected element .logo "This is not empty" to equal ""') - } - }) - }) - - describe('#waitForFunction', () => { - it('should wait for function returns true', async () => { - await wd.amOnPage('/form/wait_js') - await wd.waitForFunction(() => window.__waitJs, 3) - }) - - it('should pass arguments and wait for function returns true', async () => { - await wd.amOnPage('/form/wait_js') - await wd.waitForFunction((varName) => window[varName], ['__waitJs'], 3) - }) - }) - - describe('#waitForEnabled', () => { - it('should wait for input text field to be enabled', async () => { - await wd.amOnPage('/form/wait_enabled') - await wd.waitForEnabled('#text', 2) - await wd.fillField('#text', 'hello world') - await wd.seeInField('#text', 'hello world') - }) - - it('should wait for input text field to be enabled by xpath', async () => { - await wd.amOnPage('/form/wait_enabled') - await wd.waitForEnabled("//*[@name = 'test']", 2) - await wd.fillField('#text', 'hello world') - await wd.seeInField('#text', 'hello world') - }) - - it('should wait for a button to be enabled', async () => { - await wd.amOnPage('/form/wait_enabled') - await wd.waitForEnabled('#text', 2) - await wd.click('#button') - await wd.see('button was clicked') - }) - }) - - describe('#waitForValue', () => { - it('should wait for expected value for given locator', async () => { - await wd.amOnPage('/info') - await wd.waitForValue('//input[@name= "rus"]', 'Верно') - - try { - await wd.waitForValue('//input[@name= "rus"]', 'Верно3', 0.1) - throw Error('It should never get this far') - } catch (e) { - e.message.should.include( - 'element (//input[@name= "rus"]) is not in DOM or there is no element(//input[@name= "rus"]) with value "Верно3" after 0.1 sec', - ) - } - }) - - it('should wait for expected value for given css locator', async () => { - await wd.amOnPage('/form/wait_value') - await wd.seeInField('#text', 'Hamburg') - await wd.waitForValue('#text', 'Brisbane', 2.5) - await wd.seeInField('#text', 'Brisbane') - }) - - it('should wait for expected value for given xpath locator', async () => { - await wd.amOnPage('/form/wait_value') - await wd.seeInField('#text', 'Hamburg') - await wd.waitForValue('//input[@value = "Grüße aus Hamburg"]', 'Brisbane', 2.5) - await wd.seeInField('#text', 'Brisbane') - }) - - it('should only wait for one of the matching elements to contain the value given xpath locator', async () => { - await wd.amOnPage('/form/wait_value') - await wd.waitForValue('//input[@type = "text"]', 'Brisbane', 4) - await wd.seeInField('#text', 'Brisbane') - await wd.seeInField('#text2', 'London') - }) - - it('should only wait for one of the matching elements to contain the value given css locator', async () => { - await wd.amOnPage('/form/wait_value') - await wd.waitForValue('.inputbox', 'Brisbane', 4) - await wd.seeInField('#text', 'Brisbane') - await wd.seeInField('#text2', 'London') - }) - }) - - describe('#waitNumberOfVisibleElements', () => { - it('should wait for a specified number of elements on the page', () => { - return wd - .amOnPage('/info') - .then(() => wd.waitNumberOfVisibleElements('//div[@id = "grab-multiple"]//a', 3)) - .then(() => wd.waitNumberOfVisibleElements('//div[@id = "grab-multiple"]//a', 2, 0.1)) - .then(() => { - throw Error('It should never get this far') - }) - .catch((e) => { - e.message.should.include('The number of elements (//div[@id = "grab-multiple"]//a) is not 2 after 0.1 sec') - }) - }) - - it('should be no [object Object] in the error message', () => { - return wd - .amOnPage('/info') - .then(() => wd.waitNumberOfVisibleElements({ css: '//div[@id = "grab-multiple"]//a' }, 3)) - .then(() => { - throw Error('It should never get this far') - }) - .catch((e) => { - e.message.should.not.include('[object Object]') - }) - }) - - it('should wait for a specified number of elements on the page using a css selector', () => { - return wd - .amOnPage('/info') - .then(() => wd.waitNumberOfVisibleElements('#grab-multiple > a', 3)) - .then(() => wd.waitNumberOfVisibleElements('#grab-multiple > a', 2, 0.1)) - .then(() => { - throw Error('It should never get this far') - }) - .catch((e) => { - e.message.should.include('The number of elements (#grab-multiple > a) is not 2 after 0.1 sec') - }) - }) - - it('should wait for a specified number of elements which are not yet attached to the DOM', () => { - return wd - .amOnPage('/form/wait_num_elements') - .then(() => wd.waitNumberOfVisibleElements('.title', 2, 3)) - .then(() => wd.see('Hello')) - .then(() => wd.see('World')) - }) - - it('should wait for 0 number of visible elements', async () => { - await wd.amOnPage('/form/wait_invisible') - await wd.waitNumberOfVisibleElements('#step_1', 0) - }) - }) - - describe('#waitForVisible', () => { - it('should be no [object Object] in the error message', () => { - return wd - .amOnPage('/info') - .then(() => wd.waitForVisible('//div[@id = "grab-multiple"]//a', 3)) - .then(() => { - throw Error('It should never get this far') - }) - .catch((e) => { - e.message.should.not.include('[object Object]') - }) - }) - }) - - describe('#waitForInvisible', () => { - it('should be no [object Object] in the error message', () => { - return wd - .amOnPage('/info') - .then(() => wd.waitForInvisible('//div[@id = "grab-multiple"]//a', 3)) - .then(() => { - throw Error('It should never get this far') - }) - .catch((e) => { - e.message.should.not.include('[object Object]') - }) - }) - - it('should wait for a specified element to be invisible', () => { - return wd - .amOnPage('/form/wait_invisible') - .then(() => wd.waitForInvisible('#step1', 3)) - .then(() => wd.dontSeeElement('#step1')) - }) - }) - - describe('#moveCursorTo', () => { - it('should trigger hover event', async () => { - await wd.amOnPage('/form/hover') - await wd.moveCursorTo('#hover') - await wd.see('Hovered', '#show') - }) - - it('should not trigger hover event because of the offset is beyond the element', async () => { - await wd.amOnPage('/form/hover') - await wd.moveCursorTo('#hover', 120, 120) - await wd.dontSee('Hovered!', '#show') - }) - }) - - describe('#switchToNextTab, #switchToPreviousTab, #openNewTab, #closeCurrentTab, #closeOtherTabs, #grabNumberOfOpenTabs', () => { - it('should only have 1 tab open when the browser starts and navigates to the first page', async () => { - await wd.amOnPage('/') - const numPages = await wd.grabNumberOfOpenTabs() - assert.equal(numPages, 1) - }) - - it('should switch to next tab', async () => { - wd.amOnPage('/info') - const numPages = await wd.grabNumberOfOpenTabs() - assert.equal(numPages, 1) - - await wd.click('New tab') - await wd.switchToNextTab() - await wd.waitInUrl('/login') - const numPagesAfter = await wd.grabNumberOfOpenTabs() - assert.equal(numPagesAfter, 2) - }) - - it('should assert when there is no ability to switch to next tab', () => { - return wd - .amOnPage('/') - .then(() => wd.click('More info')) - .then(() => wd.wait(1)) // Wait is required because the url is change by previous statement (maybe related to #914) - .then(() => wd.switchToNextTab(2)) - .then(() => assert.equal(true, false, 'Throw an error if it gets this far (which it should not)!')) - .catch((e) => { - assert.equal(e.message, 'There is no ability to switch to next tab with offset 2') - }) - }) - - it('should close current tab', () => { - return wd - .amOnPage('/info') - .then(() => wd.click('New tab')) - .then(() => wd.switchToNextTab()) - .then(() => wd.seeInCurrentUrl('/login')) - .then(() => wd.grabNumberOfOpenTabs()) - .then((numPages) => assert.equal(numPages, 2)) - .then(() => wd.closeCurrentTab()) - .then(() => wd.seeInCurrentUrl('/info')) - .then(() => wd.grabNumberOfOpenTabs()) - }) - - it('should close other tabs', () => { - return wd - .amOnPage('/') - .then(() => wd.openNewTab()) - .then(() => wd.seeInCurrentUrl('about:blank')) - .then(() => wd.amOnPage('/info')) - .then(() => wd.click('New tab')) - .then(() => wd.switchToNextTab()) - .then(() => wd.seeInCurrentUrl('/login')) - .then(() => wd.closeOtherTabs()) - .then(() => wd.seeInCurrentUrl('/login')) - .then(() => wd.grabNumberOfOpenTabs()) - }) - - it('should open new tab', () => { - return wd - .amOnPage('/info') - .then(() => wd.openNewTab()) - .then(() => wd.waitInUrl('about:blank')) - .then(() => wd.grabNumberOfOpenTabs()) - .then((numPages) => assert.equal(numPages, 2)) - }) - - it('should switch to previous tab', () => { - return wd - .amOnPage('/info') - .then(() => wd.openNewTab()) - .then(() => wd.waitInUrl('about:blank')) - .then(() => wd.switchToPreviousTab()) - .then(() => wd.waitInUrl('/info')) - }) - - it('should assert when there is no ability to switch to previous tab', () => { - return wd - .amOnPage('/info') - .then(() => wd.openNewTab()) - .then(() => wd.waitInUrl('about:blank')) - .then(() => wd.switchToPreviousTab(2)) - .then(() => wd.waitInUrl('/info')) - .catch((e) => { - assert.equal(e.message, 'There is no ability to switch to previous tab with offset 2') - }) - }) - }) - - describe('popup : #acceptPopup, #seeInPopup, #cancelPopup', () => { - it('should accept popup window', () => { - return wd - .amOnPage('/form/popup') - .then(() => wd.click('Confirm')) - .then(() => wd.acceptPopup()) - .then(() => wd.see('Yes', '#result')) - }) - - it('should cancel popup', () => { - return wd - .amOnPage('/form/popup') - .then(() => wd.click('Confirm')) - .then(() => wd.cancelPopup()) - .then(() => wd.see('No', '#result')) - }) - - it('should check text in popup', () => { - return wd - .amOnPage('/form/popup') - .then(() => wd.click('Alert')) - .then(() => wd.seeInPopup('Really?')) - .then(() => wd.cancelPopup()) - }) - - it('should grab text from popup', () => { - return wd - .amOnPage('/form/popup') - .then(() => wd.click('Alert')) - .then(() => wd.grabPopupText()) - .then((text) => assert.equal(text, 'Really?')) - }) - - it('should return null if no popup is visible (do not throw an error)', () => { - return wd - .amOnPage('/form/popup') - .then(() => wd.grabPopupText()) - .then((text) => assert.equal(text, null)) - }) - }) - - describe('#waitForText', () => { - it('should return error if not present', () => { - return wd - .amOnPage('/dynamic') - .then(() => wd.waitForText('Nothing here', 1, '#text')) - .catch((e) => { - e.message.should.be.equal( - 'element (#text) is not in DOM or there is no element(#text) with text "Nothing here" after 1 sec', - ) - }) - }) - - it('should return error if waiting is too small', () => { - return wd - .amOnPage('/dynamic') - .then(() => wd.waitForText('Dynamic text', 0.1)) - .catch((e) => { - e.message.should.be.equal( - 'element (body) is not in DOM or there is no element(body) with text "Dynamic text" after 0.1 sec', - ) - }) - }) - }) - - describe('#seeNumberOfElements', () => { - it('should return 1 as count', async () => { - await wd.amOnPage('/') - await wd.seeNumberOfElements('#area1', 1) - }) - }) - - describe.skip('#switchTo', () => { - it('should switch reference to iframe content', async () => { - await wd.amOnPage('/iframe') - await wd.switchTo('[name="content"]') - await wd.see('Information\nLots of valuable data here') - }) - - it('should return error if iframe selector is invalid', async () => { - await wd.amOnPage('/iframe') - - try { - await wd.switchTo('#invalidIframeSelector') - } catch (e) { - e.should.be.instanceOf(Error) - e.message.should.be.equal('Element "#invalidIframeSelector" was not found by text|CSS|XPath') - } - }) - - it('should return error if iframe selector is not iframe', async () => { - await wd.amOnPage('/iframe') - - try { - await wd.switchTo('h1') - } catch (e) { - e.should.be.instanceOf(Error) - e.message.should.contain('no such frame') - } - }) - - it('should return to parent frame given a null locator', async () => { - await wd.amOnPage('/iframe') - await wd.switchTo('[name="content"]') - await wd.see('Information\nLots of valuable data here') - await wd.switchTo(null) - await wd.see('Iframe test') - }) - }) - - describe('click context', () => { - it('should click on inner text', async () => { - await wd.amOnPage('/form/checkbox') - await wd.click('Submit', '//input[@type = "submit"]') - await wd.waitInUrl('/form/complex') - }) - - it('should click on input in inner element', async () => { - await wd.amOnPage('/form/checkbox') - await wd.click('Submit', '//form') - await wd.waitInUrl('/form/complex') - }) - - it('should click by accessibility_id', async () => { - await wd.amOnPage('/info') - await wd.click('~index via aria-label') - await wd.see('Welcome to test app!') - }) - }) - - describe('window size #resizeWindow', () => { - it('should set initial window size', async () => { - await wd.amOnPage('/form/resize') - await wd.click('Window Size') - await wd.see('Height 700', '#height') - await wd.see('Width 500', '#width') - }) - - it('should set window size on new session', () => { - return wd - .amOnPage('/info') - .then(() => wd._session()) - .then((session) => - session.start().then((browser) => ({ - browser, - session, - })), - ) - .then(({ session, browser }) => session.loadVars(browser)) - .then(() => wd.amOnPage('/form/resize')) - .then(() => wd.click('Window Size')) - .then(() => wd.see('Height 700', '#height')) - .then(() => wd.see('Width 500', '#width')) - }) - - it.skip('should resize window to specific dimensions', async () => { - await wd.amOnPage('/form/resize') - await wd.resizeWindow(950, 600) - await wd.click('Window Size') - await wd.see('Height 600', '#height') - await wd.see('Width 950', '#width') - }) - - xit('should resize window to maximum screen dimensions', async () => { - await wd.amOnPage('/form/resize') - await wd.resizeWindow(500, 400) - await wd.click('Window Size') - await wd.see('Height 400', '#height') - await wd.see('Width 500', '#width') - await wd.resizeWindow('maximize') - await wd.click('Window Size') - await wd.dontSee('Height 400', '#height') - await wd.dontSee('Width 500', '#width') - }) - }) - - describe('SmartWait', () => { - before(() => (wd.options.smartWait = 3000)) - after(() => (wd.options.smartWait = 0)) - - it('should wait for element to appear', async () => { - await wd.amOnPage('/form/wait_element') - await wd.dontSeeElement('h1') - await wd.seeElement('h1') - }) - - it('should wait for clickable element appear', async () => { - await wd.amOnPage('/form/wait_clickable') - await wd.dontSeeElement('#click') - await wd.click('#click') - await wd.see('Hi!') - }) - - it('should wait for clickable context to appear', async () => { - await wd.amOnPage('/form/wait_clickable') - await wd.dontSeeElement('#linkContext') - await wd.click('Hello world', '#linkContext') - await wd.see('Hi!') - }) - - it('should wait for text context to appear', async () => { - await wd.amOnPage('/form/wait_clickable') - await wd.dontSee('Hello world') - await wd.see('Hello world', '#linkContext') - }) - - it('should work with grabbers', async () => { - await wd.amOnPage('/form/wait_clickable') - await wd.dontSee('Hello world') - const res = await wd.grabAttributeFrom('#click', 'id') - assert.equal(res, 'click') - }) - }) - - describe('#_locateClickable', () => { - it('should locate a button to click', async () => { - await wd.amOnPage('/form/checkbox') - const res = await wd._locateClickable('Submit') - res.length.should.be.equal(1) - }) - - it('should not locate a non-existing checkbox', async () => { - await wd.amOnPage('/form/checkbox') - const res = await wd._locateClickable('I disagree') - res.length.should.be.equal(0) - }) - }) - - describe('#_locateCheckable', () => { - it('should locate a checkbox', async () => { - await wd.amOnPage('/form/checkbox') - const res = await wd._locateCheckable('I Agree') - res.length.should.be.equal(1) - }) - - it('should not locate a non-existing checkbox', async () => { - await wd.amOnPage('/form/checkbox') - const res = await wd._locateCheckable('I disagree') - res.length.should.be.equal(0) - }) - }) - - describe('#_locateFields', () => { - it('should locate a field', async () => { - await wd.amOnPage('/form/field') - const res = await wd._locateFields('Name') - res.length.should.be.equal(1) - }) - - it('should not locate a non-existing field', async () => { - await wd.amOnPage('/form/field') - const res = await wd._locateFields('Mother-in-law') - res.length.should.be.equal(0) - }) - }) - - xdescribe('#grabBrowserLogs', () => { - it('should grab browser logs', async () => { - await wd.amOnPage('/') - await wd.executeScript(() => { - console.log('Test log entry') - }) - const logs = await wd.grabBrowserLogs() - console.log('lololo', logs) - - const matchingLogs = logs.filter((log) => log.message.indexOf('Test log entry') > -1) - assert.equal(matchingLogs.length, 1) - }) - - it('should grab browser logs across pages', async () => { - wd.amOnPage('/') - await wd.executeScript(() => { - console.log('Test log entry 1') - }) - await wd.openNewTab() - await wd.amOnPage('/info') - await wd.executeScript(() => { - console.log('Test log entry 2') - }) - - const logs = await wd.grabBrowserLogs() - - const matchingLogs = logs.filter((log) => log.message.indexOf('Test log entry') > -1) - assert.equal(matchingLogs.length, 2) - }) - }) - - describe('#dragAndDrop', () => { - it('Drag item from source to target (no iframe) @dragNdrop', async () => { - await wd.amOnPage('http://jqueryui.com/resources/demos/droppable/default.html') - await wd.seeElementInDOM('#draggable') - await wd.dragAndDrop('#draggable', '#droppable') - await wd.see('Dropped') - }) - - it.skip('Drag and drop from within an iframe', async () => { - await wd.amOnPage('http://jqueryui.com/droppable') - await wd.resizeWindow(700, 700) - await wd.switchTo('//iframe[@class="demo-frame"]') - await wd.seeElementInDOM('#draggable') - await wd.dragAndDrop('#draggable', '#droppable') - await wd.see('Dropped') - }) - }) - - describe('#switchTo frame', () => { - it('should switch to frame using name', async () => { - await wd.amOnPage('/iframe') - await wd.see('Iframe test', 'h1') - await wd.dontSee('Information', 'h1') - await wd.switchTo('iframe') - await wd.see('Information', 'h1') - await wd.dontSee('Iframe test', 'h1') - }) - - it('should switch to root frame', async () => { - await wd.amOnPage('/iframe') - await wd.see('Iframe test', 'h1') - await wd.dontSee('Information', 'h1') - await wd.switchTo('iframe') - await wd.see('Information', 'h1') - await wd.dontSee('Iframe test', 'h1') - await wd.switchTo() - await wd.see('Iframe test', 'h1') - }) - - it('should switch to frame using frame number', async () => { - await wd.amOnPage('/iframe') - await wd.see('Iframe test', 'h1') - await wd.dontSee('Information', 'h1') - await wd.switchTo(0) - await wd.see('Information', 'h1') - await wd.dontSee('Iframe test', 'h1') - }) - }) - - describe('#AttachFile', () => { - it('should attach to regular input element', async () => { - await wd.amOnPage('/form/file') - await wd.attachFile('Avatar', './app/avatar.jpg') - await wd.seeInField('Avatar', 'avatar.jpg') - }) - - it('should attach to invisible input element', async () => { - await wd.amOnPage('/form/file') - await wd.attachFile('hidden', '/app/avatar.jpg') - }) - }) - - describe('#dragSlider', () => { - it('should drag scrubber to given position', async () => { - await wd.amOnPage('/form/page_slider') - await wd.seeElementInDOM('#slidecontainer input') - - const before = await wd.grabValueFrom('#slidecontainer input') - await wd.dragSlider('#slidecontainer input', 20) - const after = await wd.grabValueFrom('#slidecontainer input') - - assert.notEqual(before, after) - }) - }) - - describe('#uncheckOption', () => { - it('should uncheck option that is currently checked', async () => { - await wd.amOnPage('/info') - await wd.uncheckOption('interesting') - await wd.dontSeeCheckboxIsChecked('interesting') - }) - - it('should NOT uncheck option that is NOT currently checked', async () => { - await wd.amOnPage('/info') - await wd.uncheckOption('interesting') - // Unchecking again should not affect the current 'unchecked' status - await wd.uncheckOption('interesting') - await wd.dontSeeCheckboxIsChecked('interesting') - }) - }) - - describe('allow back and forth between handles: #grabAllWindowHandles #grabCurrentWindowHandle #switchToWindow', () => { - it.skip('should open main page of configured site, open a popup, switch to main page, then switch to popup, close popup, and go back to main page', async () => { - await wd.amOnPage('/') - const handleBeforePopup = await wd.grabCurrentWindowHandle() - const urlBeforePopup = await wd.grabCurrentUrl() - - const allHandlesBeforePopup = await wd.grabAllWindowHandles() - allHandlesBeforePopup.length.should.eql(1) - - await wd.executeScript(() => { - window.open( - 'https://www.w3schools.com/', - 'new window', - 'toolbar=yes,scrollbars=yes,resizable=yes,width=400,height=400', - ) - }) - - const allHandlesAfterPopup = await wd.grabAllWindowHandles() - allHandlesAfterPopup.length.should.eql(1) - - await wd.switchToWindow(allHandlesAfterPopup[1]) - const urlAfterPopup = await wd.grabCurrentUrl() - urlAfterPopup.should.eql('https://www.w3schools.com/') - - handleBeforePopup.should.eql(allHandlesAfterPopup[0]) - await wd.switchToWindow(handleBeforePopup) - const currentURL = await wd.grabCurrentUrl() - currentURL.should.eql(urlBeforePopup) - - await wd.switchToWindow(allHandlesAfterPopup[1]) - const urlAfterSwitchBack = await wd.grabCurrentUrl() - urlAfterSwitchBack.should.eql('https://www.w3schools.com/') - await wd.closeCurrentTab() - - const allHandlesAfterPopupClosed = await wd.grabAllWindowHandles() - allHandlesAfterPopupClosed.length.should.eql(1) - const currentWindowHandle = await wd.grabCurrentWindowHandle() - currentWindowHandle.should.eql(handleBeforePopup) - }) - }) - - describe('#waitForClickable', () => { - it('should wait for clickable', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd.waitForClickable({ css: 'input#text' }) - }) - - it('should wait for clickable by XPath', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd.waitForClickable({ xpath: './/input[@id="text"]' }) - }) - - it('should fail for disabled element', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd - .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 #button still not clickable after 0.1 sec') - }) - }) - - it('should fail for disabled element by XPath', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd - .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 .//button[@id="button"] still not clickable after 0.1 sec') - }) - }) - - it('should fail for element not in viewport by top', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd - .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 #notInViewportTop still not clickable after 0.1 sec') - }) - }) - - it('should fail for element not in viewport by bottom', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd - .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 #notInViewportBottom still not clickable after 0.1 sec') - }) - }) - - it('should fail for element not in viewport by left', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd - .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 #notInViewportLeft still not clickable after 0.1 sec') - }) - }) - - it('should fail for element not in viewport by right', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd - .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 #notInViewportRight still not clickable after 0.1 sec') - }) - }) - - it('should fail for overlapping element', async () => { - await wd.amOnPage('/form/wait_for_clickable') - await wd.waitForClickable({ css: '#div2_button' }, 0.1) - await wd - .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 #div1_button still not clickable after 0.1 sec') - }) - }) - }) - - describe('GeoLocation', () => { - it('should set the geoLocation', async () => { - await wd.setGeoLocation(37.4043, -122.0748) - const geoLocation = await wd.grabGeoLocation() - assert.equal(geoLocation.latitude, 37.4043, 'The latitude is not properly set') - assert.equal(geoLocation.longitude, -122.0748, 'The longitude is not properly set') - }) - }) - - describe('#grabElementBoundingRect', () => { - it('should get the element size', async () => { - await wd.amOnPage('/form/hidden') - const size = await wd.grabElementBoundingRect('input[type=submit]') - expect(size.x).is.greaterThan(0) - expect(size.y).is.greaterThan(0) - expect(size.width).is.greaterThan(0) - expect(size.height).is.greaterThan(0) - }) - - it('should get the element width', async () => { - await wd.amOnPage('/form/hidden') - const width = await wd.grabElementBoundingRect('input[type=submit]', 'width') - expect(width).is.greaterThan(0) - }) - - it('should get the element height', async () => { - await wd.amOnPage('/form/hidden') - const height = await wd.grabElementBoundingRect('input[type=submit]', 'height') - expect(height).is.greaterThan(0) - }) - }) - - describe('#scrollIntoView', () => { - it.skip('should scroll element into viewport', async () => { - await wd.amOnPage('/form/scroll_into_view') - const element = await wd.browser.$('#notInViewportByDefault') - expect(await element.isDisplayedInViewport()).to.be.false - await wd.scrollIntoView('#notInViewportByDefault') - expect(await element.isDisplayedInViewport()).to.be.true - }) - }) - - describe('#useWebDriverTo', () => { - it('should return title', async () => { - await wd.amOnPage('/') - const title = await wd.useWebDriverTo('test', async ({ browser }) => { - return browser.getTitle() - }) - assert.equal('TestEd Beta 2.0', title) - }) - }) -}) diff --git a/test/helper/WebDriver_test.js b/test/helper/WebDriver_test.js index 830b6c4fb..4ad6a5977 100644 --- a/test/helper/WebDriver_test.js +++ b/test/helper/WebDriver_test.js @@ -1219,16 +1219,6 @@ describe('WebDriver', function () { }) }) - describe('GeoLocation', () => { - // deprecated JSON Wire method commands - it.skip('should set the geoLocation', async () => { - await wd.setGeoLocation(37.4043, -122.0748) - const geoLocation = await wd.grabGeoLocation() - assert.equal(geoLocation.latitude, 37.4043, 'The latitude is not properly set') - assert.equal(geoLocation.longitude, -122.0748, 'The longitude is not properly set') - }) - }) - describe('#grabElementBoundingRect', () => { it('should get the element size', async () => { await wd.amOnPage('/form/hidden') diff --git a/test/helper/webapi.js b/test/helper/webapi.js index 7d01fb482..da3be93e1 100644 --- a/test/helper/webapi.js +++ b/test/helper/webapi.js @@ -915,7 +915,6 @@ module.exports.tests = function () { it('should wait for cookie and throw error when cookie not found', async () => { if (isHelper('TestCafe')) return - if (process.env.DevTools) return await I.amOnPage('https://google.com') try { @@ -927,7 +926,6 @@ module.exports.tests = function () { it('should wait for cookie', async () => { if (isHelper('TestCafe')) return - if (process.env.DevTools) return await I.amOnPage('/') await I.setCookie({ @@ -1007,6 +1005,11 @@ module.exports.tests = function () { await I.waitForText('Dynamic text', 5, '//div[@id="text"]') }) + it('should wait for text with double quotes', async () => { + await I.amOnPage('/') + await I.waitForText('said: "debug!"', 5) + }) + it('should throw error when text not found', async () => { await I.amOnPage('/dynamic') await I.dontSee('Dynamic text') @@ -1498,7 +1501,7 @@ module.exports.tests = function () { }) it('should check css property for several elements', async function () { - if (isHelper('TestCafe') || process.env.BROWSER === 'firefox' || process.env.DevTools === 'true') this.skip() + if (isHelper('TestCafe') || process.env.BROWSER === 'firefox') this.skip() try { await I.amOnPage('/') diff --git a/test/plugin/plugin_test.js b/test/plugin/plugin_test.js index 2aca83c17..70d398f33 100644 --- a/test/plugin/plugin_test.js +++ b/test/plugin/plugin_test.js @@ -48,21 +48,6 @@ describe('CodeceptJS plugin', function () { }) }) - it('should generate the coverage report - WebDriver - Devtools protocol', (done) => { - exec(`${config_run_config('codecept.WebDriver.devtools.coverage.js', '@coverage')} --debug`, (err, stdout) => { - const lines = stdout.split('\n') - expect(lines).toEqual( - expect.arrayContaining([ - expect.stringContaining('writing output/coverage'), - expect.stringContaining('generated coverage reports:'), - expect.stringContaining('output/coverage/index.html'), - ]), - ) - expect(err).toBeFalsy() - done() - }) - }) - it('should retry to failure', (done) => { exec( `${config_run_config('codecept.Playwright.retryTo.js', 'Should fail after reached max retries')} --verbose`, diff --git a/test/rest/REST_test.js b/test/rest/REST_test.js index 7d80acd36..458a5af5e 100644 --- a/test/rest/REST_test.js +++ b/test/rest/REST_test.js @@ -109,6 +109,13 @@ describe('REST', () => { getResponse.data.should.be.empty }) + it('should send DELETE requests with payload', async () => { + await I.sendDeleteRequestWithPayload('/posts/1', { author: 'john' }) + const getResponse = await I.sendGetRequest('/posts') + + getResponse.data.should.be.empty + }) + it('should update request with onRequest', async () => { I.config.onRequest = (request) => (request.data = { name: 'Vasya' }) diff --git a/test/runner/dry_run_test.js b/test/runner/dry_run_test.js index 444de2fde..851c7fc49 100644 --- a/test/runner/dry_run_test.js +++ b/test/runner/dry_run_test.js @@ -117,6 +117,25 @@ describe('dry-run command', () => { }) }) + it('should run feature files with regex grep', (done) => { + exec(codecept_run_config('codecept.bdd.js') + ' --steps --grep "(?=.*Checkout process)"', (err, stdout) => { + //eslint-disable-line + expect(stdout).toContain('Checkout process') // feature + expect(stdout).toContain('-- before checkout --') + expect(stdout).toContain('-- after checkout --') + // expect(stdout).toContain('In order to buy products'); // test name + expect(stdout).toContain('Given I have product with $600 price') + expect(stdout).toContain('And I have product with $1000 price') + expect(stdout).toContain('Then I should see that total number of products is 2') + expect(stdout).toContain('And my order amount is $1600') + expect(stdout).not.toContain('I add item 600') // 'Given' actor's non-gherkin step check + expect(stdout).not.toContain('I see sum 1600') // 'And' actor's non-gherkin step check + expect(stdout).toContain('No tests were executed') + expect(err).toBeFalsy() + done() + }) + }) + it('should print substeps in debug mode', (done) => { exec(codecept_run_config('codecept.bdd.js') + ' --debug --grep "Checkout process @important"', (err, stdout) => { //eslint-disable-line diff --git a/test/unit/html_test.js b/test/unit/html_test.js index 66049ecfd..e781ba9ab 100644 --- a/test/unit/html_test.js +++ b/test/unit/html_test.js @@ -5,8 +5,7 @@ let expect import('chai').then((chai) => { expect = chai.expect }) -const xpath = require('xpath') -const Dom = require('@xmldom/xmldom').DOMParser +const cheerio = require('cheerio') const { scanForErrorMessages, removeNonInteractiveElements, minifyHtml, splitByChunks } = require('../../lib/html') const opts = { @@ -53,20 +52,23 @@ describe('HTML module', () => { describe('#removeNonInteractiveElements', () => { it('should cut out all non-interactive elements from GitHub HTML', async () => { - // Call the function with the loaded HTML html = fs.readFileSync(path.join(__dirname, '../data/github.html'), 'utf8') const result = removeNonInteractiveElements(html, opts) - let doc = new Dom().parseFromString(result) - const nodes = xpath.select('//input[@name="q"]', doc) + + let $ = cheerio.load(result) + + const nodes = $('input[name="q"]') expect(nodes).to.have.length(1) expect(result).not.to.include('Let’s build from here') + const minified = await minifyHtml(result) - doc = new Dom().parseFromString(minified) - const nodes2 = xpath.select('//input[@name="q"]', doc) + $ = cheerio.load(minified) + + const nodes2 = $('input[name="q"]') expect(nodes2).to.have.length(1) }) - it('should keep interactive html elements', () => { + it('should keep interactive HTML elements', () => { html = `
@@ -74,8 +76,7 @@ describe('HTML module', () => {

Privacy Preference Center

- +
` @@ -88,8 +89,7 @@ describe('HTML module', () => {
  • -
  • - - aaa - - -
  • + + aaa + + +
` const result = await minifyHtml(removeNonInteractiveElements(html, opts)) @@ -113,7 +113,6 @@ describe('HTML module', () => { }) it('should cut out all non-interactive elements from HTML', () => { - // Call the function with the loaded HTML html = fs.readFileSync(path.join(__dirname, '../data/checkout.html'), 'utf8') const result = removeNonInteractiveElements(html, opts) expect(result).to.include('Name on card') @@ -122,46 +121,34 @@ describe('HTML module', () => { it('should allow adding new elements', () => { const html = '
Hey
' - - const result = removeNonInteractiveElements(html, { - textElements: ['h6'], - }) - + const result = removeNonInteractiveElements(html, { textElements: ['h6'] }) expect(result).to.include('
Hey
') }) it('should cut out all non-interactive elements from GitLab HTML', () => { - // Call the function with the loaded HTML html = fs.readFileSync(path.join(__dirname, '../data/gitlab.html'), 'utf8') - // console.log(html); const result = removeNonInteractiveElements(html, opts) - result.should.include('Get free trial') result.should.include('Sign in') result.should.include(' { - // Call the function with the loaded HTML html = fs.readFileSync(path.join(__dirname, '../data/testomat.html'), 'utf8') - // console.log(html); const result = removeNonInteractiveElements(html, opts) result.should.include(' describe('Locator', () => { beforeEach(() => { - doc = new DOMParser().parseFromString(xml, 'application/xhtml+xml') + doc = new DOMParser().parseFromString(xml, 'text/xml') }) describe('constructor', () => { diff --git a/test/unit/parser_test.js b/test/unit/parser_test.js index 331d3b50e..5748fca2c 100644 --- a/test/unit/parser_test.js +++ b/test/unit/parser_test.js @@ -4,7 +4,6 @@ import('chai').then((chai) => { }) const parser = require('../../lib/parser') -/* eslint-disable no-unused-vars */ class Obj { method1(locator, sec) {} diff --git a/test/unit/utils_test.js b/test/unit/utils_test.js index 1a4aef63f..77a968806 100644 --- a/test/unit/utils_test.js +++ b/test/unit/utils_test.js @@ -14,7 +14,7 @@ describe('utils', () => { it('not exists', () => expect(utils.fileExists('not_utils.js')).to.be.false) it('not exists if file used as directory', () => expect(utils.fileExists(`${__filename}/not_utils.js`)).to.be.false) }) - /* eslint-disable no-unused-vars */ + describe('#getParamNames', () => { it('fn#1', () => expect(utils.getParamNames((a, b) => {})).eql(['a', 'b'])) it('fn#2', () => expect(utils.getParamNames((I, userPage) => {})).eql(['I', 'userPage'])) @@ -23,7 +23,6 @@ describe('utils', () => { it('should handle trailing comma', () => expect(utils.getParamNames((I, trailing, comma) => {})).eql(['I', 'trailing', 'comma'])) }) - /* eslint-enable no-unused-vars */ describe('#methodsOfObject', () => { it('should get methods', () => { diff --git a/translations/de-DE.js b/translations/de-DE.js index aee1be9b2..7a84c985d 100644 --- a/translations/de-DE.js +++ b/translations/de-DE.js @@ -68,6 +68,7 @@ module.exports = { sendGetRequest: 'mache_einen_get_request', sendPutRequest: 'mache_einen_put_request', sendDeleteRequest: 'mache_einen_delete_request', + sendDeleteRequestWithPayload: 'mache_einen_delete_request_mit_payload', sendPostRequest: 'mache_einen_post_request', switchTo: 'wechlse_in_iframe', }, diff --git a/translations/fr-FR.js b/translations/fr-FR.js index 6cb6a7a14..de67a063f 100644 --- a/translations/fr-FR.js +++ b/translations/fr-FR.js @@ -70,7 +70,7 @@ module.exports = { scrollTo: 'défileVers', sendGetRequest: 'envoieLaRequêteGet', sendPutRequest: 'envoieLaRequêtePut', - sendDeleteRequest: 'envoieLaRequêteDelete', + sendDeleteRequest: 'envoieLaRequêteDeleteAvecPayload', sendPostRequest: 'envoieLaRequêtePost', }, } diff --git a/typings/index.d.ts b/typings/index.d.ts index 89523177a..9b8acc91c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -114,6 +114,14 @@ declare namespace CodeceptJS { * ``` */ emptyOutputFolder?: boolean; + /** + * mask sensitive data in output logs + * + * ```js + * maskSensitiveData: true + * ``` + */ + maskSensitiveData?: boolean; /** * Pattern to filter tests by name. * This option is useful if you plan to use multiple configs for different environments. @@ -554,7 +562,7 @@ declare const xScenario: CodeceptJS.IScenario; declare const xFeature: CodeceptJS.IFeature; declare function Data(data: any): CodeceptJS.IData; declare function xData(data: any): CodeceptJS.IData; -declare function defineParameterType(options: CodeceptJS.IParameterTypeDefinition): void +declare function DefineParameterType(options: CodeceptJS.IParameterTypeDefinition): void // Hooks declare const BeforeSuite: CodeceptJS.IHook; @@ -599,7 +607,7 @@ declare namespace NodeJS { Given: typeof Given; When: typeof When; Then: typeof Then; - DefineParameterType: typeof defineParameterType + DefineParameterType: typeof DefineParameterType } } diff --git a/typings/tests/helpers/WebDriverIO.types.ts b/typings/tests/helpers/WebDriverIO.types.ts index 137f55e7e..449d2dafc 100644 --- a/typings/tests/helpers/WebDriverIO.types.ts +++ b/typings/tests/helpers/WebDriverIO.types.ts @@ -405,11 +405,6 @@ expectType(wd.scrollPageToTop()); expectType(wd.scrollPageToBottom()); -expectError(wd.setGeoLocation()); -expectError(wd.setGeoLocation(num)); -expectType(wd.setGeoLocation(num, num)); -expectType(wd.setGeoLocation(num, num, num)); - expectError(wd.dontSeeCookie()); expectType(wd.dontSeeCookie(str)); @@ -480,8 +475,6 @@ psp.then( }, ); -expectType>(wd.grabGeoLocation()); - expectError(wd.grabElementBoundingRect()); //expectType>(wd.grabElementBoundingRect('h3')); //expectType>(wd.grabElementBoundingRect('h3', 'width'));