From 4497e8b3098657a8f46b1fe94c9a4be88624f6dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 10:07:36 +0000 Subject: [PATCH 01/38] chore(deps): bump docker/build-push-action from 1 to 6 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 1 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v1...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 35aaaf72b..4dfcda903 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,7 +17,7 @@ jobs: - name: Check out the repo with latest code uses: actions/checkout@v4 - name: Push latest to Docker Hub - uses: docker/build-push-action@v4 # Info: https://github.com/docker/build-push-action/tree/releases/v1#tags + uses: docker/build-push-action@v6 # Info: https://github.com/docker/build-push-action/tree/releases/v1#tags with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} @@ -36,7 +36,7 @@ jobs: ref: ${{ env.TAG }} - name: Push current tag to Docker Hub - uses: docker/build-push-action@v1 # Info: https://github.com/docker/build-push-action/tree/releases/v1#tags + uses: docker/build-push-action@v6 # Info: https://github.com/docker/build-push-action/tree/releases/v1#tags with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} From 5a9b3f396253ba2756199d2245a379ca1a49fd02 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 19 Jun 2024 10:09:42 +0200 Subject: [PATCH 02/38] update docs/changelog --- docs/changelog.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index a2ecb9c23..08573196a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,40 @@ layout: Section # Releases +## 3.6.4 + +❤️ Thanks all to those who contributed to make this release! ❤️ + +🛩️ *Features* +* feat(rest): print curl ([#4396](https://github.com/codeceptjs/CodeceptJS/issues/4396)) - by **[kobenguyent](https://github.com/kobenguyent)** + +``` +Config: + +... +REST: { + ... + printCurl: true, + ... +} +... + +› [CURL Request] curl --location --request POST https://httpbin.org/post -H ... +``` + +* feat(AI): Generate PageObject, added types, shell improvement ([#4319](https://github.com/codeceptjs/CodeceptJS/issues/4319)) - by **[davert](https://github.com/davert)** + * added `askForPageObject` method to generate PageObjects on the fly + * improved AI types + * interactive shell improved to restore history + +![Screenshot from 2024-06-17 02-47-37](https://github.com/codeceptjs/CodeceptJS/assets/220264/12acd2c7-18d1-4105-a24b-84070ec4d393) + +🐛 *Bug Fixes* +* fix(heal): wrong priority ([#4394](https://github.com/codeceptjs/CodeceptJS/issues/4394)) - by **[kobenguyent](https://github.com/kobenguyent)** + +📖 *Documentation* +* AI docs improvements + ## 3.6.3 ❤️ Thanks all to those who contributed to make this release! ❤️ From 8e90c9a03444f68fe03ad2f9d08d0166ef87d57b Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 19 Jun 2024 10:13:27 +0200 Subject: [PATCH 03/38] fix: wrong username in changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1e748b2..19188f500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ REST: { › [CURL Request] curl --location --request POST https://httpbin.org/post -H ... ``` -* feat(AI): Generate PageObject, added types, shell improvement (#4319) - by @davert +* feat(AI): Generate PageObject, added types, shell improvement (#4319) - by @DavertMik * added `askForPageObject` method to generate PageObjects on the fly * improved AI types * interactive shell improved to restore history @@ -30,7 +30,7 @@ REST: { * fix(heal): wrong priority (#4394) - by @kobenguyent 📖 *Documentation* -* AI docs improvements +* AI docs improvements by @DavertMik ## 3.6.3 From ac75d948533609523057be3a52539a25edf1c2f1 Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Wed, 19 Jun 2024 10:18:43 +0200 Subject: [PATCH 04/38] update docs/changelog --- docs/changelog.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 08573196a..7ef0f19da 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -28,7 +28,7 @@ REST: { › [CURL Request] curl --location --request POST https://httpbin.org/post -H ... ``` -* feat(AI): Generate PageObject, added types, shell improvement ([#4319](https://github.com/codeceptjs/CodeceptJS/issues/4319)) - by **[davert](https://github.com/davert)** +* feat(AI): Generate PageObject, added types, shell improvement ([#4319](https://github.com/codeceptjs/CodeceptJS/issues/4319)) - by **[DavertMik](https://github.com/DavertMik)** * added `askForPageObject` method to generate PageObjects on the fly * improved AI types * interactive shell improved to restore history @@ -39,7 +39,7 @@ REST: { * fix(heal): wrong priority ([#4394](https://github.com/codeceptjs/CodeceptJS/issues/4394)) - by **[kobenguyent](https://github.com/kobenguyent)** 📖 *Documentation* -* AI docs improvements +* AI docs improvements by **[DavertMik](https://github.com/DavertMik)** ## 3.6.3 From 4b1a3b4159fe4c7ef5767ab61537d052921da00c Mon Sep 17 00:00:00 2001 From: KobeN <7845001+kobenguyent@users.noreply.github.com> Date: Fri, 21 Jun 2024 07:07:33 +0200 Subject: [PATCH 05/38] styles: apply prettier (#4397) --- .eslintrc.js | 15 + .husky/pre-commit | 2 +- bin/codecept.js | 147 +- lib/assert/empty.js | 38 +- lib/assert/equal.js | 62 +- lib/assert/error.js | 28 +- lib/assert/include.js | 84 +- lib/assert/throws.js | 24 +- lib/assert/truth.js | 35 +- lib/command/configMigrate.js | 109 +- lib/command/definitions.js | 176 +- lib/command/dryRun.js | 128 +- lib/command/generate.js | 372 +-- lib/command/info.js | 76 +- lib/command/init.js | 575 ++-- lib/command/interactive.js | 64 +- lib/command/list.js | 52 +- lib/command/run-multiple.js | 206 +- lib/command/run-rerun.js | 44 +- lib/command/run-workers.js | 126 +- lib/command/run.js | 50 +- lib/command/utils.js | 127 +- lib/data/context.js | 120 +- lib/data/dataScenarioConfig.js | 94 +- lib/data/dataTableArgument.js | 58 +- lib/data/table.js | 46 +- lib/helper/AI.js | 132 +- lib/helper/ApiDataFactory.js | 141 +- lib/helper/Appium.js | 788 ++--- lib/helper/ExpectHelper.js | 462 ++- lib/helper/FileSystem.js | 155 +- lib/helper/GraphQL.js | 87 +- lib/helper/GraphQLDataFactory.js | 99 +- lib/helper/JSONResponse.js | 126 +- lib/helper/Mochawesome.js | 56 +- lib/helper/MockServer.js | 24 +- lib/helper/Nightmare.js | 1236 ++++---- lib/helper/Playwright.js | 2531 +++++++++-------- lib/helper/Protractor.js | 1292 +++++---- lib/helper/Puppeteer.js | 2356 +++++++-------- lib/helper/REST.js | 159 +- lib/helper/TestCafe.js | 981 ++++--- lib/helper/WebDriver.js | 2450 ++++++++-------- lib/interfaces/bdd.js | 89 +- lib/interfaces/featureConfig.js | 38 +- lib/interfaces/gherkin.js | 233 +- lib/interfaces/scenarioConfig.js | 58 +- lib/listener/artifacts.js | 18 +- lib/listener/config.js | 47 +- lib/listener/exit.js | 24 +- lib/listener/helpers.js | 84 +- lib/listener/mocha.js | 22 +- lib/listener/retry.js | 62 +- lib/listener/steps.js | 101 +- lib/listener/timeout.js | 106 +- lib/plugin/allure.js | 28 +- lib/plugin/autoDelay.js | 65 +- lib/plugin/autoLogin.js | 136 +- lib/plugin/commentStep.js | 36 +- lib/plugin/coverage.js | 169 +- lib/plugin/customLocator.js | 39 +- lib/plugin/debugErrors.js | 48 +- lib/plugin/eachElement.js | 74 +- lib/plugin/fakerTransform.js | 12 +- lib/plugin/heal.js | 129 +- lib/plugin/pauseOnFail.js | 20 +- lib/plugin/retryFailedStep.js | 69 +- lib/plugin/retryTo.js | 56 +- lib/plugin/screenshotOnFail.js | 193 +- lib/plugin/selenoid.js | 248 +- lib/plugin/standardActingHelpers.js | 10 +- lib/plugin/stepByStepReport.js | 192 +- lib/plugin/stepTimeout.js | 45 +- lib/plugin/subtitles.js | 68 +- lib/plugin/tryTo.js | 68 +- lib/plugin/wdio.js | 149 +- lib/template/heal.js | 25 +- package.json | 4 +- prettier.config.js | 9 + runok.js | 550 ++-- .../codecept.Playwright.coverage.js | 4 +- test/acceptance/codecept.Playwright.js | 4 +- .../acceptance/codecept.Playwright.retryTo.js | 4 +- test/acceptance/codecept.Puppeteer.js | 9 +- test/acceptance/codecept.Testcafe.js | 4 +- .../codecept.WebDriver.devtools.coverage.js | 4 +- .../acceptance/codecept.WebDriver.devtools.js | 11 +- test/acceptance/codecept.WebDriver.js | 11 +- test/acceptance/config_test.js | 62 +- test/acceptance/coverage_test.js | 111 +- test/acceptance/react_test.js | 52 +- test/acceptance/retryTo_test.js | 40 +- test/acceptance/session_test.js | 333 +-- test/acceptance/within_test.js | 180 +- test/bdd/codecept.faker.js | 9 +- test/data/I.js | 4 +- test/data/dummy_page.js | 4 +- test/data/fake_driver.js | 8 +- test/data/helper.js | 32 +- test/graphql/GraphQLDataFactory_test.js | 138 +- test/graphql/GraphQL_test.js | 172 +- test/helper/AppiumV2Web_test.js | 190 +- test/helper/AppiumV2_ios_test.js | 233 +- test/helper/AppiumV2_test.js | 892 +++--- test/helper/AppiumWeb_test.js | 190 +- test/helper/Appium_test.js | 892 +++--- test/helper/Expect_test.js | 352 +-- test/helper/JSONResponse_test.js | 204 +- test/helper/MockServer_test.js | 98 +- test/helper/Playwright_test.js | 2088 +++++++------- test/helper/Puppeteer_test.js | 1775 ++++++------ test/helper/TestCafe_test.js | 109 +- .../helper/WebDriver.noSeleniumServer_test.js | 1619 ++++++----- test/helper/WebDriver_devtools_test.js | 1573 +++++----- test/helper/WebDriver_test.js | 1633 +++++------ test/helper/webapi.js | 2218 ++++++++------- test/plugin/plugin_test.js | 99 +- test/rest/ApiDataFactory_test.js | 192 +- test/rest/REST_test.js | 371 ++- test/runner/bdd_test.js | 511 ++-- test/runner/before_failure_test.js | 54 +- test/runner/bootstrap_test.js | 132 +- test/runner/codecept_test.js | 482 ++-- test/runner/comment_step_test.js | 56 +- test/runner/consts.js | 10 +- test/runner/definitions_test.js | 451 +-- test/runner/dry_run_test.js | 286 +- test/runner/gherkin_test.js | 89 +- test/runner/help_test.js | 52 +- test/runner/init_test.js | 108 +- test/runner/interface_test.js | 240 +- test/runner/list_test.js | 26 +- test/runner/pageobject_test.js | 191 +- test/runner/retry_hooks_test.js | 79 +- test/runner/run_multiple_test.js | 390 +-- test/runner/run_rerun_test.js | 133 +- test/runner/run_workers_test.js | 304 +- test/runner/scenario_stale_test.js | 30 +- test/runner/session_test.js | 112 +- test/runner/skip_test.js | 40 +- test/runner/step_timeout_test.js | 89 +- test/runner/timeout_test.js | 133 +- test/runner/todo_test.js | 54 +- test/runner/translation_test.js | 22 +- test/runner/within_test.js | 155 +- test/support/ScreenshotSessionHelper.js | 26 +- test/support/TestHelper.js | 18 +- test/support/setup.js | 6 +- test/unit/actor_test.js | 206 +- test/unit/ai_test.js | 100 +- test/unit/assert_test.js | 34 +- test/unit/bdd_test.js | 474 ++- test/unit/config_test.js | 74 +- test/unit/container_test.js | 325 +-- test/unit/heal_test.js | 130 +- test/unit/html_test.js | 155 +- test/unit/locator_test.js | 368 ++- test/unit/output_test.js | 86 +- test/unit/parser_test.js | 49 +- test/unit/recorder_test.js | 176 +- test/unit/scenario_test.js | 139 +- test/unit/secret_test.js | 24 +- test/unit/steps_test.js | 356 +-- test/unit/ui_test.js | 310 +- test/unit/utils_test.js | 349 ++- test/unit/worker_test.js | 232 +- translations/de-DE.js | 2 +- translations/fr-FR.js | 2 +- translations/index.js | 18 +- translations/it-IT.js | 2 +- translations/ja-JP.js | 2 +- translations/pl-PL.js | 2 +- translations/pt-BR.js | 2 +- translations/ru-RU.js | 2 +- translations/zh-CN.js | 2 +- translations/zh-TW.js | 2 +- typings/fixDefFiles.js | 10 +- 177 files changed, 22176 insertions(+), 20917 deletions(-) create mode 100644 prettier.config.js diff --git a/.eslintrc.js b/.eslintrc.js index 8b8c9405f..b38236895 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,6 +43,21 @@ module.exports = { '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/.husky/pre-commit b/.husky/pre-commit index 1eda87251..635d81b70 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -npm run lint && npm run dtslint +npm run prettier && npm run lint && npm run dtslint diff --git a/bin/codecept.js b/bin/codecept.js index 9477d809c..9a0ff7db0 100755 --- a/bin/codecept.js +++ b/bin/codecept.js @@ -1,8 +1,8 @@ #!/usr/bin/env node -const program = require('commander'); -const Codecept = require('../lib/codecept'); -const { print, error } = require('../lib/output'); -const { printError } = require('../lib/command/utils'); +const program = require('commander') +const Codecept = require('../lib/codecept') +const { print, error } = require('../lib/output') +const { printError } = require('../lib/command/utils') const commandFlags = { ai: { @@ -29,104 +29,120 @@ const commandFlags = { flag: '--steps', description: 'show step-by-step execution', }, -}; - -const errorHandler = (fn) => async (...args) => { - try { - await fn(...args); - } catch (e) { - printError(e); - process.exitCode = 1; +} + +const errorHandler = + (fn) => + async (...args) => { + try { + await fn(...args) + } catch (e) { + printError(e) + process.exitCode = 1 + } } -}; if (process.versions.node && process.versions.node.split('.') && process.versions.node.split('.')[0] < 12) { - error('NodeJS >= 12 is required to run.'); - print(); - print('Please upgrade your NodeJS engine'); - print(`Current NodeJS version: ${process.version}`); - process.exit(1); + error('NodeJS >= 12 is required to run.') + print() + print('Please upgrade your NodeJS engine') + print(`Current NodeJS version: ${process.version}`) + process.exit(1) } -program.usage(' [options]'); -program.version(Codecept.version()); +program.usage(' [options]') +program.version(Codecept.version()) -program.command('init [path]') +program + .command('init [path]') .description('Creates dummy config in current dir or [path]') - .action(errorHandler(require('../lib/command/init'))); + .action(errorHandler(require('../lib/command/init'))) -program.command('migrate [path]') +program + .command('migrate [path]') .description('Migrate json config to js config in current dir or [path]') - .action(errorHandler(require('../lib/command/configMigrate'))); + .action(errorHandler(require('../lib/command/configMigrate'))) -program.command('shell [path]') +program + .command('shell [path]') .alias('sh') .description('Interactive shell') .option(commandFlags.verbose.flag, commandFlags.verbose.description) .option(commandFlags.profile.flag, commandFlags.profile.description) .option(commandFlags.ai.flag, commandFlags.ai.description) .option(commandFlags.config.flag, commandFlags.config.description) - .action(errorHandler(require('../lib/command/interactive'))); + .action(errorHandler(require('../lib/command/interactive'))) -program.command('list [path]') +program + .command('list [path]') .alias('l') .description('List all actions for I.') - .action(errorHandler(require('../lib/command/list'))); + .action(errorHandler(require('../lib/command/list'))) -program.command('def [path]') +program + .command('def [path]') .description('Generates TypeScript definitions for all I actions.') .option(commandFlags.config.flag, commandFlags.config.description) .option('-o, --output [folder]', 'target folder to paste definitions') - .action(errorHandler(require('../lib/command/definitions'))); + .action(errorHandler(require('../lib/command/definitions'))) -program.command('gherkin:init [path]') +program + .command('gherkin:init [path]') .alias('bdd:init') .description('Prepare CodeceptJS to run feature files.') .option(commandFlags.config.flag, commandFlags.config.description) - .action(errorHandler(require('../lib/command/gherkin/init'))); + .action(errorHandler(require('../lib/command/gherkin/init'))) -program.command('gherkin:steps [path]') +program + .command('gherkin:steps [path]') .alias('bdd:steps') .description('Prints all defined gherkin steps.') .option(commandFlags.config.flag, commandFlags.config.description) - .action(errorHandler(require('../lib/command/gherkin/steps'))); + .action(errorHandler(require('../lib/command/gherkin/steps'))) -program.command('gherkin:snippets [path]') +program + .command('gherkin:snippets [path]') .alias('bdd:snippets') .description('Generate step definitions from steps.') .option('--dry-run', "don't save snippets to file") .option(commandFlags.config.flag, commandFlags.config.description) .option('--feature [file]', 'feature files(s) to scan') .option('--path [file]', 'file in which to place the new snippets') - .action(errorHandler(require('../lib/command/gherkin/snippets'))); + .action(errorHandler(require('../lib/command/gherkin/snippets'))) -program.command('generate:test [path]') +program + .command('generate:test [path]') .alias('gt') .description('Generates an empty test') - .action(errorHandler(require('../lib/command/generate').test)); + .action(errorHandler(require('../lib/command/generate').test)) -program.command('generate:pageobject [path]') +program + .command('generate:pageobject [path]') .alias('gpo') .description('Generates an empty page object') - .action(errorHandler(require('../lib/command/generate').pageObject)); + .action(errorHandler(require('../lib/command/generate').pageObject)) -program.command('generate:object [path]') +program + .command('generate:object [path]') .alias('go') .option('--type, -t [kind]', 'type of object to be created') .description('Generates an empty support object (page/step/fragment)') - .action(errorHandler(require('../lib/command/generate').pageObject)); + .action(errorHandler(require('../lib/command/generate').pageObject)) -program.command('generate:helper [path]') +program + .command('generate:helper [path]') .alias('gh') .description('Generates a new helper') - .action(errorHandler(require('../lib/command/generate').helper)); + .action(errorHandler(require('../lib/command/generate').helper)) -program.command('generate:heal [path]') +program + .command('generate:heal [path]') .alias('gr') .description('Generates basic heal recipes') - .action(errorHandler(require('../lib/command/generate').heal)); + .action(errorHandler(require('../lib/command/generate').heal)) -program.command('run [test]') +program + .command('run [test]') .description('Executes tests') // codecept-only options @@ -162,9 +178,10 @@ program.command('run [test]') .option('--recursive', 'include sub directories') .option('--trace', 'trace function calls') .option('--child ', 'option for child processes') - .action(errorHandler(require('../lib/command/run'))); + .action(errorHandler(require('../lib/command/run'))) -program.command('run-workers [selectedRuns...]') +program + .command('run-workers [selectedRuns...]') .description('Executes tests in workers') .option(commandFlags.config.flag, commandFlags.config.description) .option('-g, --grep ', 'only run tests matching ') @@ -180,9 +197,10 @@ program.command('run-workers [selectedRuns...]') .option('-p, --plugins ', 'enable plugins, comma-separated') .option('-O, --reporter-options ', 'reporter-specific options') .option('-R, --reporter ', 'specify the reporter to use') - .action(errorHandler(require('../lib/command/run-workers'))); + .action(errorHandler(require('../lib/command/run-workers'))) -program.command('run-multiple [suites...]') +program + .command('run-multiple [suites...]') .description('Executes tests multiple') .option(commandFlags.config.flag, commandFlags.config.description) .option(commandFlags.profile.flag, commandFlags.profile.description) @@ -205,14 +223,16 @@ program.command('run-multiple [suites...]') // mocha options .option('--colors', 'force enabling of colors') - .action(errorHandler(require('../lib/command/run-multiple'))); + .action(errorHandler(require('../lib/command/run-multiple'))) -program.command('info [path]') +program + .command('info [path]') .description('Print debugging information concerning the local environment') .option('-c, --config', 'your config file path') - .action(errorHandler(require('../lib/command/info'))); + .action(errorHandler(require('../lib/command/info'))) -program.command('dry-run [test]') +program + .command('dry-run [test]') .description('Prints step-by-step scenario for a test without actually running it') .option('-p, --plugins ', 'enable plugins, comma-separated') .option('--bootstrap', 'enable bootstrap & teardown scripts for dry-run') @@ -226,9 +246,10 @@ program.command('dry-run [test]') .option(commandFlags.steps.flag, commandFlags.steps.description) .option(commandFlags.verbose.flag, commandFlags.verbose.description) .option(commandFlags.debug.flag, commandFlags.debug.description) - .action(errorHandler(require('../lib/command/dryRun'))); + .action(errorHandler(require('../lib/command/dryRun'))) -program.command('run-rerun [test]') +program + .command('run-rerun [test]') .description('Executes tests in more than one test suite run') // codecept-only options @@ -263,15 +284,15 @@ program.command('run-rerun [test]') .option('--trace', 'trace function calls') .option('--child ', 'option for child processes') - .action(require('../lib/command/run-rerun')); + .action(require('../lib/command/run-rerun')) program.on('command:*', (cmd) => { - console.log(`\nUnknown command ${cmd}\n`); - program.outputHelp(); -}); + console.log(`\nUnknown command ${cmd}\n`) + program.outputHelp() +}) if (process.argv.length <= 2) { - program.outputHelp(); + program.outputHelp() } else { - program.parse(process.argv); + program.parse(process.argv) } diff --git a/lib/assert/empty.js b/lib/assert/empty.js index 2b1fd67fe..859bcbfa7 100644 --- a/lib/assert/empty.js +++ b/lib/assert/empty.js @@ -1,43 +1,43 @@ -const Assertion = require('../assert'); -const AssertionFailedError = require('./error'); -const { template } = require('../utils'); -const output = require('../output'); +const Assertion = require('../assert') +const AssertionFailedError = require('./error') +const { template } = require('../utils') +const output = require('../output') class EmptinessAssertion extends Assertion { constructor(params) { super((value) => { if (Array.isArray(value)) { - return value.length === 0; + return value.length === 0 } - return !value; - }, params); - this.params.type = 'to be empty'; + return !value + }, params) + this.params.type = 'to be empty' } getException() { if (Array.isArray(this.params.value)) { - this.params.value = `[${this.params.value.join(', ')}]`; + this.params.value = `[${this.params.value.join(', ')}]` } - const err = new AssertionFailedError(this.params, "{{customMessage}}expected {{subject}} '{{value}}' {{type}}"); + const err = new AssertionFailedError(this.params, "{{customMessage}}expected {{subject}} '{{value}}' {{type}}") err.cliMessage = () => { const msg = err.template .replace('{{value}}', output.colors.bold('{{value}}')) - .replace('{{subject}}', output.colors.bold('{{subject}}')); - return template(msg, this.params); - }; - return err; + .replace('{{subject}}', output.colors.bold('{{subject}}')) + return template(msg, this.params) + } + return err } addAssertParams() { - this.params.value = this.params.actual = arguments[0]; - this.params.expected = []; - this.params.customMessage = arguments[1] ? `${arguments[1]}\n\n` : ''; + this.params.value = this.params.actual = arguments[0] + this.params.expected = [] + this.params.customMessage = arguments[1] ? `${arguments[1]}\n\n` : '' } } module.exports = { Assertion: EmptinessAssertion, - empty: subject => new EmptinessAssertion({ subject }), -}; + empty: (subject) => new EmptinessAssertion({ subject }), +} diff --git a/lib/assert/equal.js b/lib/assert/equal.js index 17db64e5c..f8ef0a9d9 100644 --- a/lib/assert/equal.js +++ b/lib/assert/equal.js @@ -1,55 +1,57 @@ -const Assertion = require('../assert'); -const AssertionFailedError = require('./error'); -const { template } = require('../utils'); -const output = require('../output'); +const Assertion = require('../assert') +const AssertionFailedError = require('./error') +const { template } = require('../utils') +const output = require('../output') class EqualityAssertion extends Assertion { constructor(params) { const comparator = function (a, b) { if (b.length === 0) { - b = ''; + b = '' } - return a === b; - }; - super(comparator, params); - this.params.type = 'to equal'; + return a === b + } + super(comparator, params) + this.params.type = 'to equal' } getException() { - const params = this.params; - params.jar = template(params.jar, params); - const err = new AssertionFailedError(params, '{{customMessage}}expected {{jar}} "{{expected}}" {{type}} "{{actual}}"'); - err.showDiff = false; + const params = this.params + params.jar = template(params.jar, params) + const err = new AssertionFailedError( + params, + '{{customMessage}}expected {{jar}} "{{expected}}" {{type}} "{{actual}}"', + ) + err.showDiff = false if (typeof err.cliMessage === 'function') { - err.message = err.cliMessage(); + err.message = err.cliMessage() } err.cliMessage = () => { - const msg = err.template - .replace('{{jar}}', output.colors.bold('{{jar}}')); - return template(msg, this.params); - }; - return err; + const msg = err.template.replace('{{jar}}', output.colors.bold('{{jar}}')) + return template(msg, this.params) + } + return err } addAssertParams() { - this.params.expected = arguments[0]; - this.params.actual = arguments[1]; - this.params.customMessage = arguments[2] ? `${arguments[2]}\n\n` : ''; + this.params.expected = arguments[0] + this.params.actual = arguments[1] + this.params.customMessage = arguments[2] ? `${arguments[2]}\n\n` : '' } } module.exports = { Assertion: EqualityAssertion, - equals: jar => new EqualityAssertion({ jar }), + equals: (jar) => new EqualityAssertion({ jar }), urlEquals: (baseUrl) => { - const assert = new EqualityAssertion({ jar: 'url of current page' }); + const assert = new EqualityAssertion({ jar: 'url of current page' }) assert.comparator = function (expected, actual) { if (expected.indexOf('http') !== 0) { - actual = actual.slice(actual.indexOf(baseUrl) + baseUrl.length); + actual = actual.slice(actual.indexOf(baseUrl) + baseUrl.length) } - return actual === expected; - }; - return assert; + return actual === expected + } + return assert }, - fileEquals: file => new EqualityAssertion({ file, jar: 'contents of {{file}}' }), -}; + fileEquals: (file) => new EqualityAssertion({ file, jar: 'contents of {{file}}' }), +} diff --git a/lib/assert/error.js b/lib/assert/error.js index 072d82372..ed72233ec 100644 --- a/lib/assert/error.js +++ b/lib/assert/error.js @@ -1,4 +1,4 @@ -const subs = require('../utils').template; +const subs = require('../utils').template /** * Assertion errors, can provide a detailed error messages. @@ -6,27 +6,27 @@ const subs = require('../utils').template; * inspect() and cliMessage() added to display errors with params. */ function AssertionFailedError(params, template) { - this.params = params; - this.template = template; + this.params = params + this.template = template // this.message = "AssertionFailedError"; // this.showDiff = true; // @todo cut assert things nicer - this.showDiff = true; + this.showDiff = true - this.actual = this.params.actual; - this.expected = this.params.expected; + this.actual = this.params.actual + this.expected = this.params.expected this.inspect = () => { - const params = this.params || {}; - const msg = params.customMessage || ''; - return msg + subs(this.template, params); - }; + const params = this.params || {} + const msg = params.customMessage || '' + return msg + subs(this.template, params) + } - this.cliMessage = () => this.inspect(); + this.cliMessage = () => this.inspect() } -AssertionFailedError.prototype = Object.create(Error.prototype); -AssertionFailedError.constructor = AssertionFailedError; +AssertionFailedError.prototype = Object.create(Error.prototype) +AssertionFailedError.constructor = AssertionFailedError -module.exports = AssertionFailedError; +module.exports = AssertionFailedError diff --git a/lib/assert/include.js b/lib/assert/include.js index 6019bcc59..3899f4ca0 100644 --- a/lib/assert/include.js +++ b/lib/assert/include.js @@ -1,78 +1,78 @@ -const Assertion = require('../assert'); -const AssertionFailedError = require('./error'); -const { template } = require('../utils'); -const output = require('../output'); +const Assertion = require('../assert') +const AssertionFailedError = require('./error') +const { template } = require('../utils') +const output = require('../output') -const MAX_LINES = 10; +const MAX_LINES = 10 class InclusionAssertion extends Assertion { constructor(params) { - params.jar = params.jar || 'string'; + params.jar = params.jar || 'string' const comparator = function (needle, haystack) { if (Array.isArray(haystack)) { - return haystack.filter(part => part.indexOf(needle) >= 0).length > 0; + return haystack.filter((part) => part.indexOf(needle) >= 0).length > 0 } - return haystack.indexOf(needle) >= 0; - }; - super(comparator, params); - this.params.type = 'to include'; + return haystack.indexOf(needle) >= 0 + } + super(comparator, params) + this.params.type = 'to include' } getException() { - const params = this.params; - params.jar = template(params.jar, params); - const err = new AssertionFailedError(params, '{{customMessage}}expected {{jar}} {{type}} "{{needle}}"'); - err.expected = params.needle; - err.actual = params.haystack; + const params = this.params + params.jar = template(params.jar, params) + const err = new AssertionFailedError(params, '{{customMessage}}expected {{jar}} {{type}} "{{needle}}"') + err.expected = params.needle + err.actual = params.haystack if (Array.isArray(this.params.haystack)) { - this.params.haystack = this.params.haystack.join('\n___(next element)___\n'); + this.params.haystack = this.params.haystack.join('\n___(next element)___\n') } err.cliMessage = function () { const msg = this.template .replace('{{jar}}', output.colors.bold('{{jar}}')) - .replace('{{needle}}', output.colors.bold('{{needle}}')); - return template(msg, this.params); - }; - return err; + .replace('{{needle}}', output.colors.bold('{{needle}}')) + return template(msg, this.params) + } + return err } getFailedAssertion() { - const err = this.getException(); - const lines = this.params.haystack.split('\n'); + const err = this.getException() + const lines = this.params.haystack.split('\n') if (lines.length > MAX_LINES) { - const more = lines.length - MAX_LINES; - err.actual = `${lines.slice(0, MAX_LINES).join('\n')}\n--( ${more} lines more )---`; + const more = lines.length - MAX_LINES + err.actual = `${lines.slice(0, MAX_LINES).join('\n')}\n--( ${more} lines more )---` } - return err; + return err } getFailedNegation() { - this.params.type = 'not to include'; - const err = this.getException(); - const pattern = new RegExp(`^.*?\n?^.*?\n?^.*?${escapeRegExp(this.params.needle)}.*?$\n?.*$\n?.*$`, 'm'); - const matched = this.params.haystack.match(pattern); - if (!matched) return err; - err.actual = matched[0].replace(this.params.needle, output.colors.bold(this.params.needle)); - err.actual = `------\n${err.actual}\n------`; - return err; + this.params.type = 'not to include' + const err = this.getException() + const pattern = new RegExp(`^.*?\n?^.*?\n?^.*?${escapeRegExp(this.params.needle)}.*?$\n?.*$\n?.*$`, 'm') + const matched = this.params.haystack.match(pattern) + if (!matched) return err + err.actual = matched[0].replace(this.params.needle, output.colors.bold(this.params.needle)) + err.actual = `------\n${err.actual}\n------` + return err } addAssertParams() { - this.params.needle = arguments[0]; - this.params.haystack = arguments[1]; - this.params.customMessage = arguments[2] ? `${arguments[2]}\n\n` : ''; + this.params.needle = arguments[0] + this.params.haystack = arguments[1] + this.params.customMessage = arguments[2] ? `${arguments[2]}\n\n` : '' } } module.exports = { Assertion: InclusionAssertion, includes: (needleType) => { - needleType = needleType || 'string'; - return new InclusionAssertion({ jar: needleType }); + needleType = needleType || 'string' + return new InclusionAssertion({ jar: needleType }) }, - fileIncludes: file => new InclusionAssertion({ file, jar: 'file {{file}}' }), -}; + fileIncludes: (file) => new InclusionAssertion({ file, jar: 'file {{file}}' }), +} function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&') } diff --git a/lib/assert/throws.js b/lib/assert/throws.js index c5b97f007..c0e92276c 100644 --- a/lib/assert/throws.js +++ b/lib/assert/throws.js @@ -1,20 +1,22 @@ function errorThrown(actual, expected) { - if (!expected) return null; - if (!actual) throw new Error(`Expected ${expected} error to be thrown`); - const msg = actual.inspect ? actual.inspect() : actual.toString(); + if (!expected) return null + if (!actual) throw new Error(`Expected ${expected} error to be thrown`) + const msg = actual.inspect ? actual.inspect() : actual.toString() if (expected instanceof RegExp) { - if (msg.match(expected)) return null; - throw new Error(`Expected error to be thrown with message matching ${expected} while '${msg}' caught`); + if (msg.match(expected)) return null + throw new Error(`Expected error to be thrown with message matching ${expected} while '${msg}' caught`) } if (typeof expected === 'string') { - if (msg === expected) return null; - throw new Error(`Expected error to be thrown with message ${expected} while '${msg}' caught`); + if (msg === expected) return null + throw new Error(`Expected error to be thrown with message ${expected} while '${msg}' caught`) } if (typeof expected === 'object') { - if (actual.constructor.name !== expected.constructor.name) throw new Error(`Expected ${expected} error to be thrown but ${actual} was caught`); - if (expected.message && expected.message !== msg) throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`); + if (actual.constructor.name !== expected.constructor.name) + throw new Error(`Expected ${expected} error to be thrown but ${actual} was caught`) + if (expected.message && expected.message !== msg) + throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`) } - return null; + return null } -module.exports = errorThrown; +module.exports = errorThrown diff --git a/lib/assert/truth.js b/lib/assert/truth.js index 0d72ba491..51dbe2def 100644 --- a/lib/assert/truth.js +++ b/lib/assert/truth.js @@ -1,37 +1,36 @@ -const Assertion = require('../assert'); -const AssertionFailedError = require('./error'); -const { template } = require('../utils'); -const output = require('../output'); +const Assertion = require('../assert') +const AssertionFailedError = require('./error') +const { template } = require('../utils') +const output = require('../output') class TruthAssertion extends Assertion { constructor(params) { super((value) => { if (Array.isArray(value)) { - return value.filter(val => !!val).length > 0; + return value.filter((val) => !!val).length > 0 } - return !!value; - }, params); - this.params.type = this.params.type || 'to be true'; + return !!value + }, params) + this.params.type = this.params.type || 'to be true' } getException() { - const err = new AssertionFailedError(this.params, '{{customMessage}}expected {{subject}} {{type}}'); + const err = new AssertionFailedError(this.params, '{{customMessage}}expected {{subject}} {{type}}') err.cliMessage = () => { - const msg = err.template - .replace('{{subject}}', output.colors.bold('{{subject}}')); - return template(msg, this.params); - }; - return err; + const msg = err.template.replace('{{subject}}', output.colors.bold('{{subject}}')) + return template(msg, this.params) + } + return err } addAssertParams() { - this.params.value = this.params.actual = arguments[0]; - this.params.expected = true; - this.params.customMessage = arguments[1] ? `${arguments[1]}\n\n` : ''; + this.params.value = this.params.actual = arguments[0] + this.params.expected = true + this.params.customMessage = arguments[1] ? `${arguments[1]}\n\n` : '' } } module.exports = { Assertion: TruthAssertion, truth: (subject, type) => new TruthAssertion({ subject, type }), -}; +} diff --git a/lib/command/configMigrate.js b/lib/command/configMigrate.js index b06815359..0101c9cc2 100644 --- a/lib/command/configMigrate.js +++ b/lib/command/configMigrate.js @@ -1,71 +1,76 @@ -const colors = require('chalk'); -const fs = require('fs'); -const inquirer = require('inquirer'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const util = require('util'); +const colors = require('chalk') +const fs = require('fs') +const inquirer = require('inquirer') +const mkdirp = require('mkdirp') +const path = require('path') +const util = require('util') -const { print, success, error } = require('../output'); -const { fileExists } = require('../utils'); -const { getTestRoot } = require('./utils'); +const { print, success, error } = require('../output') +const { fileExists } = require('../utils') +const { getTestRoot } = require('./utils') module.exports = function (initPath) { - const testsPath = getTestRoot(initPath); + const testsPath = getTestRoot(initPath) - print(); - print(` Welcome to ${colors.magenta.bold('CodeceptJS')} configuration migration tool`); - print(` It will help you switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format at ease`); - print(); + print() + print(` Welcome to ${colors.magenta.bold('CodeceptJS')} configuration migration tool`) + print( + ` It will help you switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format at ease`, + ) + print() if (!path) { - print('No config file is specified.'); - print(`Test root is assumed to be ${colors.yellow.bold(testsPath)}`); - print('----------------------------------'); + print('No config file is specified.') + print(`Test root is assumed to be ${colors.yellow.bold(testsPath)}`) + print('----------------------------------') } else { - print(`Migrating ${colors.magenta.bold('.js')} config to ${colors.bold(testsPath)}`); + print(`Migrating ${colors.magenta.bold('.js')} config to ${colors.bold(testsPath)}`) } if (!fileExists(testsPath)) { - print(`Directory ${testsPath} does not exist, creating...`); - mkdirp.sync(testsPath); + print(`Directory ${testsPath} does not exist, creating...`) + mkdirp.sync(testsPath) } - const configFile = path.join(testsPath, 'codecept.conf.js'); + const configFile = path.join(testsPath, 'codecept.conf.js') if (fileExists(configFile)) { - error(`Config is already created at ${configFile}`); - return; + error(`Config is already created at ${configFile}`) + return } - inquirer.prompt([{ - name: 'configFile', - type: 'confirm', - message: `Would you like to switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format?`, - default: true, - }, - { - name: 'delete', - type: 'confirm', - message: `Would you like to delete ${colors.cyan.bold('.json')} config format afterwards?`, - default: true, - }, - ]).then((result) => { - if (result.configFile) { - const jsonConfigFile = path.join(testsPath, 'codecept.js'); - const config = JSON.parse(fs.readFileSync(jsonConfigFile, 'utf8')); - config.name = testsPath.split(path.sep).pop(); + inquirer + .prompt([ + { + name: 'configFile', + type: 'confirm', + message: `Would you like to switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format?`, + default: true, + }, + { + name: 'delete', + type: 'confirm', + message: `Would you like to delete ${colors.cyan.bold('.json')} config format afterwards?`, + default: true, + }, + ]) + .then((result) => { + if (result.configFile) { + const jsonConfigFile = path.join(testsPath, 'codecept.js') + const config = JSON.parse(fs.readFileSync(jsonConfigFile, 'utf8')) + config.name = testsPath.split(path.sep).pop() - const finish = () => { - fs.writeFileSync(configFile, `exports.config = ${util.inspect(config, false, 4, false)}`, 'utf-8'); - success(`Config is successfully migrated at ${configFile}`); + const finish = () => { + fs.writeFileSync(configFile, `exports.config = ${util.inspect(config, false, 4, false)}`, 'utf-8') + success(`Config is successfully migrated at ${configFile}`) - if (result.delete) { - if (fileExists(jsonConfigFile)) { - fs.unlinkSync(jsonConfigFile); - success('JSON config file is deleted!'); + if (result.delete) { + if (fileExists(jsonConfigFile)) { + fs.unlinkSync(jsonConfigFile) + success('JSON config file is deleted!') + } } } - }; - finish(); - } - }); -}; + finish() + } + }) +} diff --git a/lib/command/definitions.js b/lib/command/definitions.js index 3751fe5fa..cfa4ae459 100644 --- a/lib/command/definitions.js +++ b/lib/command/definitions.js @@ -1,11 +1,11 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require('fs') +const path = require('path') -const { getConfig, getTestRoot } = require('./utils'); -const Codecept = require('../codecept'); -const container = require('../container'); -const output = require('../output'); -const actingHelpers = [...require('../plugin/standardActingHelpers'), 'REST']; +const { getConfig, getTestRoot } = require('./utils') +const Codecept = require('../codecept') +const container = require('../container') +const output = require('../output') +const actingHelpers = [...require('../plugin/standardActingHelpers'), 'REST'] /** * Prepare data and generate content of definitions file @@ -29,32 +29,27 @@ const getDefinitionsFileContent = ({ importPaths, translations, }) => { - const getHelperListFragment = ({ - hasCustomHelper, - hasCustomStepsFile, - }) => { + const getHelperListFragment = ({ hasCustomHelper, hasCustomStepsFile }) => { if (hasCustomHelper && hasCustomStepsFile) { - return `${['ReturnType', 'WithTranslation'].join(', ')}`; + return `${['ReturnType', 'WithTranslation'].join(', ')}` } if (hasCustomStepsFile) { - return 'ReturnType'; + return 'ReturnType' } - return 'WithTranslation'; - }; + return 'WithTranslation' + } const helpersListFragment = getHelperListFragment({ hasCustomHelper, hasCustomStepsFile, - }); + }) - const importPathsFragment = importPaths.join('\n'); - const supportObjectsTypeFragment = convertMapToType(supportObject); - const methodsTypeFragment = helperNames.length > 0 - ? `interface Methods extends ${helperNames.join(', ')} {}` - : ''; - const translatedActionsFragment = JSON.stringify(translations.vocabulary.actions, null, 2); + const importPathsFragment = importPaths.join('\n') + const supportObjectsTypeFragment = convertMapToType(supportObject) + const methodsTypeFragment = helperNames.length > 0 ? `interface Methods extends ${helperNames.join(', ')} {}` : '' + const translatedActionsFragment = JSON.stringify(translations.vocabulary.actions, null, 2) return generateDefinitionsContent({ helpersListFragment, @@ -62,8 +57,8 @@ const getDefinitionsFileContent = ({ supportObjectsTypeFragment, methodsTypeFragment, translatedActionsFragment, - }); -}; + }) +} /** * Generate content for definitions file from fragments @@ -96,83 +91,83 @@ declare namespace CodeceptJS { interface Actions ${translatedActionsFragment} } } -`; -}; +` +} /** @type {Array} */ -const helperNames = []; +const helperNames = [] /** @type {Array} */ -const customHelpers = []; +const customHelpers = [] module.exports = function (genPath, options) { - const configFile = options.config || genPath; + const configFile = options.config || genPath /** @type {string} */ - const testsPath = getTestRoot(configFile); - const config = getConfig(configFile); - if (!config) return; + const testsPath = getTestRoot(configFile) + const config = getConfig(configFile) + if (!config) return /** @type {Object} */ - const helperPaths = {}; + const helperPaths = {} /** @type {Object} */ - const supportPaths = {}; + const supportPaths = {} /** @type {boolean} */ - let hasCustomStepsFile = false; + let hasCustomStepsFile = false /** @type {boolean} */ - let hasCustomHelper = false; + let hasCustomHelper = false /** @type {string} */ - const targetFolderPath = options.output && getTestRoot(options.output) || testsPath; + const targetFolderPath = (options.output && getTestRoot(options.output)) || testsPath - const codecept = new Codecept(config, {}); - codecept.init(testsPath); + const codecept = new Codecept(config, {}) + codecept.init(testsPath) - const helpers = container.helpers(); - const translations = container.translation(); + const helpers = container.helpers() + const translations = container.translation() for (const name in helpers) { - const require = codecept.config.helpers[name].require; + const require = codecept.config.helpers[name].require if (require) { - helperPaths[name] = require; - helperNames.push(name); + helperPaths[name] = require + helperNames.push(name) } else { - const fullBasedPromised = codecept.config.fullPromiseBased; - helperNames.push(fullBasedPromised === true ? `${name}Ts` : name); + const fullBasedPromised = codecept.config.fullPromiseBased + helperNames.push(fullBasedPromised === true ? `${name}Ts` : name) } if (!actingHelpers.includes(name)) { - customHelpers.push(name); + customHelpers.push(name) } } - let autoLogin; + let autoLogin if (config.plugins.autoLogin) { - autoLogin = config.plugins.autoLogin.inject; + autoLogin = config.plugins.autoLogin.inject } - const supportObject = new Map(); - supportObject.set('I', 'I'); - supportObject.set('current', 'any'); + const supportObject = new Map() + supportObject.set('I', 'I') + supportObject.set('current', 'any') if (translations.loaded) { - supportObject.set(translations.I, translations.I); + supportObject.set(translations.I, translations.I) } if (autoLogin) { - supportObject.set(autoLogin, 'any'); + supportObject.set(autoLogin, 'any') } if (customHelpers.length > 0) { - hasCustomHelper = true; + hasCustomHelper = true } for (const name in codecept.config.include) { - const includePath = codecept.config.include[name]; + const includePath = codecept.config.include[name] if (name === 'I' || name === translations.I) { - hasCustomStepsFile = true; - supportPaths.steps_file = includePath; - continue; + hasCustomStepsFile = true + supportPaths.steps_file = includePath + continue } - supportPaths[name] = includePath; - supportObject.set(name, name); + supportPaths[name] = includePath + supportObject.set(name, name) } let definitionsFileContent = getDefinitionsFileContent({ @@ -182,31 +177,34 @@ module.exports = function (genPath, options) { translations, hasCustomStepsFile, hasCustomHelper, - }); + }) // add aliases for translations if (translations.loaded) { - const namespaceTranslationAliases = []; - namespaceTranslationAliases.push(`interface ${translations.vocabulary.I} extends WithTranslation {}`); + const namespaceTranslationAliases = [] + namespaceTranslationAliases.push(`interface ${translations.vocabulary.I} extends WithTranslation {}`) - namespaceTranslationAliases.push(' namespace Translation {'); - definitionsFileContent = definitionsFileContent.replace('namespace Translation {', namespaceTranslationAliases.join('\n')); + namespaceTranslationAliases.push(' namespace Translation {') + definitionsFileContent = definitionsFileContent.replace( + 'namespace Translation {', + namespaceTranslationAliases.join('\n'), + ) - const translationAliases = []; + const translationAliases = [] if (translations.vocabulary.contexts) { - Object.keys(translations.vocabulary.contexts).forEach(k => { - translationAliases.push(`declare const ${translations.vocabulary.contexts[k]}: typeof ${k};`); - }); + Object.keys(translations.vocabulary.contexts).forEach((k) => { + translationAliases.push(`declare const ${translations.vocabulary.contexts[k]}: typeof ${k};`) + }) } - definitionsFileContent += `\n${translationAliases.join('\n')}`; + definitionsFileContent += `\n${translationAliases.join('\n')}` } - fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsFileContent); - output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs'); - output.print('Definitions were generated in steps.d.ts'); -}; + fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsFileContent) + output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs') + output.print('Definitions were generated in steps.d.ts') +} /** * Returns the relative path from the to the targeted folder. @@ -215,13 +213,13 @@ module.exports = function (genPath, options) { * @param {string} testsPath */ function getPath(originalPath, targetFolderPath, testsPath) { - const parsedPath = path.parse(originalPath); + const parsedPath = path.parse(originalPath) // Remove typescript extension if exists. - if (parsedPath.base.endsWith('.d.ts')) parsedPath.base = parsedPath.base.substring(0, parsedPath.base.length - 5); - else if (parsedPath.ext === '.ts') parsedPath.base = parsedPath.name; + if (parsedPath.base.endsWith('.d.ts')) parsedPath.base = parsedPath.base.substring(0, parsedPath.base.length - 5) + else if (parsedPath.ext === '.ts') parsedPath.base = parsedPath.name - if (!parsedPath.dir.startsWith('.')) return path.posix.join(parsedPath.dir, parsedPath.base); + if (!parsedPath.dir.startsWith('.')) return path.posix.join(parsedPath.dir, parsedPath.base) const relativePath = path.posix.relative( targetFolderPath.split(path.sep).join(path.posix.sep), path.posix.join( @@ -229,9 +227,9 @@ function getPath(originalPath, targetFolderPath, testsPath) { parsedPath.dir.split(path.sep).join(path.posix.sep), parsedPath.base.split(path.sep).join(path.posix.sep), ), - ); + ) - return relativePath.startsWith('.') ? relativePath : `./${relativePath}`; + return relativePath.startsWith('.') ? relativePath : `./${relativePath}` } /** @@ -245,19 +243,19 @@ function getPath(originalPath, targetFolderPath, testsPath) { * @returns {Array} */ function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue) { - const importStrings = []; + const importStrings = [] for (const name in pathsToType) { - const relativePath = getPath(pathsToType[name], targetFolderPath, testsPath); - importStrings.push(`type ${name} = typeof import('${relativePath}');`); + const relativePath = getPath(pathsToType[name], targetFolderPath, testsPath) + importStrings.push(`type ${name} = typeof import('${relativePath}');`) } for (const name in pathsToValue) { - const relativePath = getPath(pathsToValue[name], targetFolderPath, testsPath); - importStrings.push(`type ${name} = import('${relativePath}');`); + const relativePath = getPath(pathsToValue[name], targetFolderPath, testsPath) + importStrings.push(`type ${name} = import('${relativePath}');`) } - return importStrings; + return importStrings } /** @@ -266,5 +264,7 @@ function getImportString(testsPath, targetFolderPath, pathsToType, pathsToValue) * @returns {string} */ function convertMapToType(map) { - return `{ ${Array.from(map).map(([key, value]) => `${key}: ${value}`).join(', ')} }`; + return `{ ${Array.from(map) + .map(([key, value]) => `${key}: ${value}`) + .join(', ')} }` } diff --git a/lib/command/dryRun.js b/lib/command/dryRun.js index f6fe2a490..0de23df4f 100644 --- a/lib/command/dryRun.js +++ b/lib/command/dryRun.js @@ -1,120 +1,122 @@ -const { getConfig, getTestRoot } = require('./utils'); -const Config = require('../config'); -const Codecept = require('../codecept'); -const output = require('../output'); -const event = require('../event'); -const store = require('../store'); -const Container = require('../container'); +const { getConfig, getTestRoot } = require('./utils') +const Config = require('../config') +const Codecept = require('../codecept') +const output = require('../output') +const event = require('../event') +const store = require('../store') +const Container = require('../container') module.exports = async function (test, options) { - if (options.grep) process.env.grep = options.grep.toLowerCase(); - const configFile = options.config; - let codecept; + if (options.grep) process.env.grep = options.grep.toLowerCase() + const configFile = options.config + let codecept - const testRoot = getTestRoot(configFile); - let config = getConfig(configFile); + const testRoot = getTestRoot(configFile) + let config = getConfig(configFile) if (options.override) { - config = Config.append(JSON.parse(options.override)); + config = Config.append(JSON.parse(options.override)) } if (config.plugins) { // disable all plugins by default, they can be enabled with -p option for (const plugin in config.plugins) { // if `-p all` is passed, then enabling all plugins, otherwise plugins could be enabled by `-p customLocator,commentStep,tryTo` - config.plugins[plugin].enabled = options.plugins === 'all'; + config.plugins[plugin].enabled = options.plugins === 'all' } } try { - codecept = new Codecept(config, options); - codecept.init(testRoot); + codecept = new Codecept(config, options) + codecept.init(testRoot) - if (options.bootstrap) await codecept.bootstrap(); + if (options.bootstrap) await codecept.bootstrap() - codecept.loadTests(); - store.dryRun = true; + codecept.loadTests() + store.dryRun = true if (!options.steps && !options.verbose && !options.debug) { - printTests(codecept.testFiles); - return; + printTests(codecept.testFiles) + return } - event.dispatcher.on(event.all.result, printFooter); - codecept.run(test); + event.dispatcher.on(event.all.result, printFooter) + codecept.run(test) } catch (err) { - console.error(err); - process.exit(1); + console.error(err) + process.exit(1) } -}; +} function printTests(files) { - const figures = require('figures'); - const colors = require('chalk'); + const figures = require('figures') + const colors = require('chalk') - output.print(output.styles.debug(`Tests from ${global.codecept_dir}:`)); - output.print(); + output.print(output.styles.debug(`Tests from ${global.codecept_dir}:`)) + output.print() - const mocha = Container.mocha(); - mocha.files = files; - mocha.loadFiles(); + const mocha = Container.mocha() + mocha.files = files + mocha.loadFiles() - let numOfTests = 0; - let numOfSuites = 0; - let outputString = ''; - const filterBy = process.env.grep ? process.env.grep.toLowerCase() : undefined; + let numOfTests = 0 + let numOfSuites = 0 + let outputString = '' + const filterBy = process.env.grep ? process.env.grep.toLowerCase() : undefined if (filterBy) { for (const suite of mocha.suite.suites) { - const currentSuite = suite.title; + 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++; + 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`; + 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` } } } - numOfSuites = countSuites(outputString); + 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`); - numOfSuites++; + output.print( + `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${mocha.suite.suites.length} tests`, + ) + numOfSuites++ for (test of suite.tests) { - numOfTests++; - output.print(` ${output.styles.scenario(figures.checkboxOff)} ${test.title}`); + numOfTests++ + output.print(` ${output.styles.scenario(figures.checkboxOff)} ${test.title}`) } } } - output.print(removeDuplicates(outputString)); - output.print(''); - output.success(` Total: ${numOfSuites} suites | ${numOfTests} tests `); - printFooter(); - process.exit(0); + output.print(removeDuplicates(outputString)) + output.print('') + output.success(` Total: ${numOfSuites} suites | ${numOfTests} tests `) + printFooter() + process.exit(0) } function printFooter() { - output.print(); - output.print('--- DRY MODE: No tests were executed ---'); + output.print() + output.print('--- DRY MODE: No tests were executed ---') } function removeDuplicates(inputString) { - const array = inputString.split('\n'); - const uniqueLines = [...new Set(array)]; - const resultString = uniqueLines.join('\n'); + const array = inputString.split('\n') + const uniqueLines = [...new Set(array)] + const resultString = uniqueLines.join('\n') - return resultString; + return resultString } function countSuites(inputString) { - const array = inputString.split('\n'); + const array = inputString.split('\n') - const uniqueLines = [...new Set(array)]; - const res = uniqueLines.filter(item => item.includes('-- ')); - return res.length; + const uniqueLines = [...new Set(array)] + const res = uniqueLines.filter((item) => item.includes('-- ')) + return res.length } diff --git a/lib/command/generate.js b/lib/command/generate.js index 8d4011731..914b11bca 100644 --- a/lib/command/generate.js +++ b/lib/command/generate.js @@ -1,80 +1,80 @@ -const colors = require('chalk'); -const fs = require('fs'); -const inquirer = require('inquirer'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const { - fileExists, ucfirst, lcfirst, beautify, -} = require('../utils'); -const output = require('../output'); -const generateDefinitions = require('./definitions'); -const { - getConfig, getTestRoot, safeFileWrite, readConfig, -} = require('./utils'); - -let extension = 'js'; +const colors = require('chalk') +const fs = require('fs') +const inquirer = require('inquirer') +const mkdirp = require('mkdirp') +const path = require('path') +const { fileExists, ucfirst, lcfirst, beautify } = require('../utils') +const output = require('../output') +const generateDefinitions = require('./definitions') +const { getConfig, getTestRoot, safeFileWrite, readConfig } = require('./utils') + +let extension = 'js' const testTemplate = `Feature('{{feature}}'); Scenario('test something', async ({ {{actor}} }) => { }); -`; +` // generates empty test module.exports.test = function (genPath) { - const testsPath = getTestRoot(genPath); - global.codecept_dir = testsPath; - const config = getConfig(testsPath); - if (!config) return; - - output.print('Creating a new test...'); - output.print('----------------------'); - - const defaultExt = config.tests.match(/([^\*/]*?)$/)[1] || `_test.${extension}`; - - return inquirer.prompt([ - { - type: 'input', - name: 'feature', - message: 'Feature which is being tested (ex: account, login, etc)', - validate: (val) => !!val, - }, - { - type: 'input', - message: 'Filename of a test', - name: 'filename', - default(answers) { - return (answers.feature).replace(' ', '_') + defaultExt; + const testsPath = getTestRoot(genPath) + global.codecept_dir = testsPath + const config = getConfig(testsPath) + if (!config) return + + output.print('Creating a new test...') + output.print('----------------------') + + const defaultExt = config.tests.match(/([^\*/]*?)$/)[1] || `_test.${extension}` + + return inquirer + .prompt([ + { + type: 'input', + name: 'feature', + message: 'Feature which is being tested (ex: account, login, etc)', + validate: (val) => !!val, }, - }, - ]).then((result) => { - const testFilePath = path.dirname(path.join(testsPath, config.tests)).replace(/\*\*$/, ''); - let testFile = path.join(testFilePath, result.filename); - const ext = path.extname(testFile); - if (!ext) testFile += defaultExt; - const dir = path.dirname(testFile); - if (!fileExists(dir)) mkdirp.sync(dir); - let testContent = testTemplate.replace('{{feature}}', result.feature); - - const container = require('../container'); - container.create(config, {}); - // translate scenario test - if (container.translation().loaded) { - const vocabulary = container.translation().vocabulary; - testContent = testContent.replace('{{actor}}', container.translation().I); - if (vocabulary.contexts.Feature) testContent = testContent.replace('Feature', vocabulary.contexts.Feature); - if (vocabulary.contexts.Scenario) testContent = testContent.replace('Scenario', vocabulary.contexts.Scenario); - output.print(`Test was created in ${colors.bold(config.translation)} localization. See: https://codecept.io/translation/`); - } else { - testContent = testContent.replace('{{actor}}', 'I'); - } - if (!config.fullPromiseBased) testContent = testContent.replace('async', ''); + { + type: 'input', + message: 'Filename of a test', + name: 'filename', + default(answers) { + return answers.feature.replace(' ', '_') + defaultExt + }, + }, + ]) + .then((result) => { + const testFilePath = path.dirname(path.join(testsPath, config.tests)).replace(/\*\*$/, '') + let testFile = path.join(testFilePath, result.filename) + const ext = path.extname(testFile) + if (!ext) testFile += defaultExt + const dir = path.dirname(testFile) + if (!fileExists(dir)) mkdirp.sync(dir) + let testContent = testTemplate.replace('{{feature}}', result.feature) + + const container = require('../container') + container.create(config, {}) + // translate scenario test + if (container.translation().loaded) { + const vocabulary = container.translation().vocabulary + testContent = testContent.replace('{{actor}}', container.translation().I) + if (vocabulary.contexts.Feature) testContent = testContent.replace('Feature', vocabulary.contexts.Feature) + if (vocabulary.contexts.Scenario) testContent = testContent.replace('Scenario', vocabulary.contexts.Scenario) + output.print( + `Test was created in ${colors.bold(config.translation)} localization. See: https://codecept.io/translation/`, + ) + } else { + testContent = testContent.replace('{{actor}}', 'I') + } + if (!config.fullPromiseBased) testContent = testContent.replace('async', '') - if (!safeFileWrite(testFile, testContent)) return; - output.success(`\nTest for ${result.filename} was created in ${testFile}`); - }); -}; + if (!safeFileWrite(testFile, testContent)) return + output.success(`\nTest for ${result.filename} was created in ${testFile}`) + }) +} const pageObjectTemplate = `const { I } = inject(); @@ -82,7 +82,7 @@ module.exports = { // insert your locators and methods here } -`; +` const poModuleTemplateTS = `const { I } = inject(); @@ -90,7 +90,7 @@ export = { // insert your locators and methods here } -`; +` const poClassTemplate = `const { I } = inject(); @@ -105,96 +105,101 @@ class {{name}} { // For inheritance module.exports = new {{name}}(); export = {{name}}; -`; +` module.exports.pageObject = function (genPath, opts) { - const testsPath = getTestRoot(genPath); - const config = getConfig(testsPath); - const kind = opts.T || 'page'; - if (!config) return; + const testsPath = getTestRoot(genPath) + const config = getConfig(testsPath) + const kind = opts.T || 'page' + if (!config) return - let configFile = path.join(testsPath, `codecept.conf.${extension}`); + let configFile = path.join(testsPath, `codecept.conf.${extension}`) if (!fileExists(configFile)) { - extension = 'ts'; - configFile = path.join(testsPath, `codecept.conf.${extension}`); + extension = 'ts' + configFile = path.join(testsPath, `codecept.conf.${extension}`) } - output.print(`Creating a new ${kind} object`); - output.print('--------------------------'); - - return inquirer.prompt([ - { - type: 'input', - name: 'name', - message: `Name of a ${kind} object`, - validate: (val) => !!val, - }, - { - type: 'input', - name: 'filename', - message: 'Where should it be stored', - default: answers => `./${kind}s/${answers.name}.${extension}`, - }, - { - type: 'list', - name: 'objectType', - message: 'What is your preferred object type', - choices: ['module', 'class'], - default: 'module', - }, - ]).then((result) => { - const pageObjectFile = path.join(testsPath, result.filename); - const dir = path.dirname(pageObjectFile); - if (!fileExists(dir)) fs.mkdirSync(dir); - - let actor = 'actor'; - - if (config.include.I) { - let actorPath = config.include.I; - if (actorPath.charAt(0) === '.') { // relative path - actorPath = path.relative(dir, path.dirname(path.join(testsPath, actorPath))) + actorPath.substring(1); // get an upper level + output.print(`Creating a new ${kind} object`) + output.print('--------------------------') + + return inquirer + .prompt([ + { + type: 'input', + name: 'name', + message: `Name of a ${kind} object`, + validate: (val) => !!val, + }, + { + type: 'input', + name: 'filename', + message: 'Where should it be stored', + default: (answers) => `./${kind}s/${answers.name}.${extension}`, + }, + { + type: 'list', + name: 'objectType', + message: 'What is your preferred object type', + choices: ['module', 'class'], + default: 'module', + }, + ]) + .then((result) => { + const pageObjectFile = path.join(testsPath, result.filename) + const dir = path.dirname(pageObjectFile) + if (!fileExists(dir)) fs.mkdirSync(dir) + + let actor = 'actor' + + if (config.include.I) { + let actorPath = config.include.I + if (actorPath.charAt(0) === '.') { + // relative path + actorPath = path.relative(dir, path.dirname(path.join(testsPath, actorPath))) + actorPath.substring(1) // get an upper level + } + actor = `require('${actorPath}')` } - actor = `require('${actorPath}')`; - } - const name = lcfirst(result.name) + ucfirst(kind); - if (result.objectType === 'module' && extension === 'ts') { - if (!safeFileWrite(pageObjectFile, poModuleTemplateTS.replace('{{actor}}', actor))) return; - } else if (result.objectType === 'module' && extension === 'js') { - if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return; - } else if (result.objectType === 'class') { - const content = poClassTemplate.replace(/{{actor}}/g, actor).replace(/{{name}}/g, name); - if (!safeFileWrite(pageObjectFile, content)) return; - } + const name = lcfirst(result.name) + ucfirst(kind) + if (result.objectType === 'module' && extension === 'ts') { + if (!safeFileWrite(pageObjectFile, poModuleTemplateTS.replace('{{actor}}', actor))) return + } else if (result.objectType === 'module' && extension === 'js') { + if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return + } else if (result.objectType === 'class') { + const content = poClassTemplate.replace(/{{actor}}/g, actor).replace(/{{name}}/g, name) + if (!safeFileWrite(pageObjectFile, content)) return + } - let data = readConfig(configFile); - config.include[name] = result.filename; + let data = readConfig(configFile) + config.include[name] = result.filename - if (!data) throw Error('Config file is empty'); - const currentInclude = `${data.match(/include:[\s\S][^\}]*/i)[0]}\n ${name}:${JSON.stringify(config.include[name])}`; + if (!data) throw Error('Config file is empty') + const currentInclude = `${data.match(/include:[\s\S][^\}]*/i)[0]}\n ${name}:${JSON.stringify(config.include[name])}` - data = data.replace(/include:[\s\S][^\}]*/i, `${currentInclude},`); + data = data.replace(/include:[\s\S][^\}]*/i, `${currentInclude},`) - fs.writeFileSync(configFile, beautify(data), 'utf-8'); + fs.writeFileSync(configFile, beautify(data), 'utf-8') - output.success(`${ucfirst(kind)} object for ${result.name} was created in ${pageObjectFile}`); - output.print(`Your config file (${colors.cyan('include')} section) has included the new created PO: + output.success(`${ucfirst(kind)} object for ${result.name} was created in ${pageObjectFile}`) + output.print(`Your config file (${colors.cyan('include')} section) has included the new created PO: include: { ... ${name}: '${result.filename}', - },`); + },`) - output.print(`Use ${output.colors.bold(colors.cyan(name))} as parameter in test scenarios to access this object:`); - output.print(`\nScenario('my new test', ({ I, ${name} })) { /** ... */ }\n`); + output.print(`Use ${output.colors.bold(colors.cyan(name))} as parameter in test scenarios to access this object:`) + output.print(`\nScenario('my new test', ({ I, ${name} })) { /** ... */ }\n`) - try { - generateDefinitions(testsPath, {}); - } catch (_err) { - output.print(`Run ${colors.green('npx codeceptjs def')} to update your types to get auto-completion for object.`); - } - }); -}; + try { + generateDefinitions(testsPath, {}) + } catch (_err) { + output.print( + `Run ${colors.green('npx codeceptjs def')} to update your types to get auto-completion for object.`, + ) + } + }) +} const helperTemplate = `const Helper = require('@codeceptjs/helper'); @@ -222,59 +227,64 @@ class {{name}} extends Helper { } module.exports = {{name}}; -`; +` module.exports.helper = function (genPath) { - const testsPath = getTestRoot(genPath); - - output.print('Creating a new helper'); - output.print('--------------------------'); - - return inquirer.prompt([{ - type: 'input', - name: 'name', - message: 'Name of a Helper', - validate: (val) => !!val, - }, { - type: 'input', - name: 'filename', - message: 'Where should it be stored', - default: answers => `./${answers.name.toLowerCase()}_helper.${extension}`, - }]).then((result) => { - const name = ucfirst(result.name); - const helperFile = path.join(testsPath, result.filename); - const dir = path.dirname(helperFile); - if (!fileExists(dir)) fs.mkdirSync(dir); - - if (!safeFileWrite(helperFile, helperTemplate.replace(/{{name}}/g, name))) return; - output.success(`Helper for ${name} was created in ${helperFile}`); - output.print(`Update your config file (add to ${colors.cyan('helpers')} section): + const testsPath = getTestRoot(genPath) + + output.print('Creating a new helper') + output.print('--------------------------') + + return inquirer + .prompt([ + { + type: 'input', + name: 'name', + message: 'Name of a Helper', + validate: (val) => !!val, + }, + { + type: 'input', + name: 'filename', + message: 'Where should it be stored', + default: (answers) => `./${answers.name.toLowerCase()}_helper.${extension}`, + }, + ]) + .then((result) => { + const name = ucfirst(result.name) + const helperFile = path.join(testsPath, result.filename) + const dir = path.dirname(helperFile) + if (!fileExists(dir)) fs.mkdirSync(dir) + + if (!safeFileWrite(helperFile, helperTemplate.replace(/{{name}}/g, name))) return + output.success(`Helper for ${name} was created in ${helperFile}`) + output.print(`Update your config file (add to ${colors.cyan('helpers')} section): helpers: { ${name}: { require: '${result.filename}', }, }, - `); - }); -}; + `) + }) +} -const healTemplate = fs.readFileSync(path.join(__dirname, '../template/heal.js'), 'utf8').toString(); +const healTemplate = fs.readFileSync(path.join(__dirname, '../template/heal.js'), 'utf8').toString() module.exports.heal = function (genPath) { - const testsPath = getTestRoot(genPath); + const testsPath = getTestRoot(genPath) - let configFile = path.join(testsPath, `codecept.conf.${extension}`); + let configFile = path.join(testsPath, `codecept.conf.${extension}`) if (!fileExists(configFile)) { - configFile = path.join(testsPath, `codecept.conf.${extension}`); - if (fileExists(configFile)) extension = 'ts'; + configFile = path.join(testsPath, `codecept.conf.${extension}`) + if (fileExists(configFile)) extension = 'ts' } - output.print('Creating basic heal recipes'); - output.print(`Add your own custom recipes to ./heal.${extension} file`); - output.print('Require this file in the config file and enable heal plugin:'); - output.print('--------------------------'); + output.print('Creating basic heal recipes') + output.print(`Add your own custom recipes to ./heal.${extension} file`) + output.print('Require this file in the config file and enable heal plugin:') + output.print('--------------------------') output.print(` require('./heal') @@ -286,9 +296,9 @@ exports.config = { } } } - `); + `) - const healFile = path.join(testsPath, `heal.${extension}`); - if (!safeFileWrite(healFile, healTemplate)) return; - output.success(`Heal recipes were created in ${healFile}`); -}; + const healFile = path.join(testsPath, `heal.${extension}`) + if (!safeFileWrite(healFile, healTemplate)) return + output.success(`Heal recipes were created in ${healFile}`) +} diff --git a/lib/command/info.js b/lib/command/info.js index 933a469a6..55f9856c3 100644 --- a/lib/command/info.js +++ b/lib/command/info.js @@ -1,42 +1,44 @@ -const envinfo = require('envinfo'); +const envinfo = require('envinfo') -const { getConfig, getTestRoot } = require('./utils'); -const Codecept = require('../codecept'); -const output = require('../output'); +const { getConfig, getTestRoot } = require('./utils') +const Codecept = require('../codecept') +const output = require('../output') module.exports = async function (path) { - const testsPath = getTestRoot(path); - const config = getConfig(testsPath); - const codecept = new Codecept(config, {}); - codecept.init(testsPath); + const testsPath = getTestRoot(path) + const config = getConfig(testsPath) + const codecept = new Codecept(config, {}) + codecept.init(testsPath) - output.print('\n Environment information:-\n'); - const info = {}; - info.codeceptVersion = Codecept.version(); - info.nodeInfo = await envinfo.helpers.getNodeInfo(); - info.osInfo = await envinfo.helpers.getOSInfo(); - info.cpuInfo = await envinfo.helpers.getCPUInfo(); - info.chromeInfo = await envinfo.helpers.getChromeInfo(); - info.edgeInfo = await envinfo.helpers.getEdgeInfo(); - info.firefoxInfo = await envinfo.helpers.getFirefoxInfo(); - info.safariInfo = await envinfo.helpers.getSafariInfo(); - const { helpers, plugins } = config; - info.helpers = helpers || "You don't use any helpers"; - info.plugins = plugins || "You don't have any enabled plugins"; + output.print('\n Environment information:-\n') + const info = {} + info.codeceptVersion = Codecept.version() + info.nodeInfo = await envinfo.helpers.getNodeInfo() + info.osInfo = await envinfo.helpers.getOSInfo() + info.cpuInfo = await envinfo.helpers.getCPUInfo() + info.chromeInfo = await envinfo.helpers.getChromeInfo() + info.edgeInfo = await envinfo.helpers.getEdgeInfo() + info.firefoxInfo = await envinfo.helpers.getFirefoxInfo() + info.safariInfo = await envinfo.helpers.getSafariInfo() + const { helpers, plugins } = config + info.helpers = helpers || "You don't use any helpers" + info.plugins = plugins || "You don't have any enabled plugins" for (const [key, value] of Object.entries(info)) { if (Array.isArray(value)) { - output.print(`${key}: ${value[1]}`); + output.print(`${key}: ${value[1]}`) } else { - output.print(`${key}: ${JSON.stringify(value, null, ' ')}`); + output.print(`${key}: ${JSON.stringify(value, null, ' ')}`) } } - output.print('***************************************'); - output.print('If you have questions ask them in our Slack: http://bit.ly/chat-codeceptjs'); - output.print('Or ask them on our discussion board: https://codecept.discourse.group/'); - output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues'); - output.print('***************************************'); -}; + output.print('***************************************') + output.print('If you have questions ask them in our Slack: http://bit.ly/chat-codeceptjs') + output.print('Or ask them on our discussion board: https://codecept.discourse.group/') + output.print( + 'Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues', + ) + output.print('***************************************') +} module.exports.getMachineInfo = async () => { const info = { @@ -47,17 +49,17 @@ module.exports.getMachineInfo = async () => { edgeInfo: await envinfo.helpers.getEdgeInfo(), firefoxInfo: await envinfo.helpers.getFirefoxInfo(), safariInfo: await envinfo.helpers.getSafariInfo(), - }; + } - output.print('***************************************'); + output.print('***************************************') for (const [key, value] of Object.entries(info)) { if (Array.isArray(value)) { - output.print(`${key}: ${value[1]}`); + output.print(`${key}: ${value[1]}`) } else { - output.print(`${key}: ${JSON.stringify(value, null, ' ')}`); + output.print(`${key}: ${JSON.stringify(value, null, ' ')}`) } } - output.print('If you need more detailed info, just run this: npx codeceptjs info'); - output.print('***************************************'); - return info; -}; + output.print('If you need more detailed info, just run this: npx codeceptjs info') + output.print('***************************************') + return info +} diff --git a/lib/command/init.js b/lib/command/init.js index 0e1321646..dc7027a77 100644 --- a/lib/command/init.js +++ b/lib/command/init.js @@ -1,37 +1,37 @@ -const colors = require('chalk'); -const fs = require('fs'); -const inquirer = require('inquirer'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const { inspect } = require('util'); -const spawn = require('cross-spawn'); - -const { print, success, error } = require('../output'); -const { fileExists, beautify, installedLocally } = require('../utils'); -const { getTestRoot } = require('./utils'); -const generateDefinitions = require('./definitions'); -const { test: generateTest } = require('./generate'); -const isLocal = require('../utils').installedLocally(); +const colors = require('chalk') +const fs = require('fs') +const inquirer = require('inquirer') +const mkdirp = require('mkdirp') +const path = require('path') +const { inspect } = require('util') +const spawn = require('cross-spawn') + +const { print, success, error } = require('../output') +const { fileExists, beautify, installedLocally } = require('../utils') +const { getTestRoot } = require('./utils') +const generateDefinitions = require('./definitions') +const { test: generateTest } = require('./generate') +const isLocal = require('../utils').installedLocally() const defaultConfig = { tests: './*_test.js', output: '', helpers: {}, include: {}, -}; +} -const helpers = ['Playwright', 'WebDriver', 'Puppeteer', 'REST', 'GraphQL', 'Appium', 'TestCafe']; -const translations = Object.keys(require('../../translations')); +const helpers = ['Playwright', 'WebDriver', 'Puppeteer', 'REST', 'GraphQL', 'Appium', 'TestCafe'] +const translations = Object.keys(require('../../translations')) -const noTranslation = 'English (no localization)'; -translations.unshift(noTranslation); +const noTranslation = 'English (no localization)' +translations.unshift(noTranslation) -const packages = []; -let isTypeScript = false; -let extension = 'js'; +const packages = [] +let isTypeScript = false +let extension = 'js' -const requireCodeceptConfigure = "const { setHeadlessWhen, setCommonPlugins } = require('@codeceptjs/configure');"; -const importCodeceptConfigure = "import { setHeadlessWhen, setCommonPlugins } from '@codeceptjs/configure';"; +const requireCodeceptConfigure = "const { setHeadlessWhen, setCommonPlugins } = require('@codeceptjs/configure');" +const importCodeceptConfigure = "import { setHeadlessWhen, setCommonPlugins } from '@codeceptjs/configure';" const configHeader = ` // turn on headless mode when running with HEADLESS=true environment variable @@ -41,7 +41,7 @@ setHeadlessWhen(process.env.HEADLESS); // enable all common plugins https://github.com/codeceptjs/configure#setcommonplugins setCommonPlugins(); -`; +` const defaultActor = `// in this file you can append custom step methods to 'I' object @@ -53,7 +53,7 @@ module.exports = function() { }); } -`; +` const defaultActorTs = `// in this file you can append custom step methods to 'I' object @@ -65,354 +65,357 @@ export = function() { }); } -`; +` module.exports = function (initPath) { - const testsPath = getTestRoot(initPath); - - print(); - print(` Welcome to ${colors.magenta.bold('CodeceptJS')} initialization tool`); - print(' It will prepare and configure a test environment for you'); - print(); - print(' Useful links:'); - print(); - print(' 👉 How to start testing ASAP: https://codecept.io/quickstart/#init'); - print(' 👉 How to select helper: https://codecept.io/basics/#architecture'); - print(' 👉 TypeScript setup: https://codecept.io/typescript/#getting-started'); - print(); + const testsPath = getTestRoot(initPath) + + print() + print(` Welcome to ${colors.magenta.bold('CodeceptJS')} initialization tool`) + print(' It will prepare and configure a test environment for you') + print() + print(' Useful links:') + print() + print(' 👉 How to start testing ASAP: https://codecept.io/quickstart/#init') + print(' 👉 How to select helper: https://codecept.io/basics/#architecture') + print(' 👉 TypeScript setup: https://codecept.io/typescript/#getting-started') + print() if (!path) { - print('No test root specified.'); - print(`Test root is assumed to be ${colors.yellow.bold(testsPath)}`); - print('----------------------------------'); + print('No test root specified.') + print(`Test root is assumed to be ${colors.yellow.bold(testsPath)}`) + print('----------------------------------') } else { - print(`Installing to ${colors.bold(testsPath)}`); + print(`Installing to ${colors.bold(testsPath)}`) } if (!fileExists(testsPath)) { - print(`Directory ${testsPath} does not exist, creating...`); - mkdirp.sync(testsPath); + print(`Directory ${testsPath} does not exist, creating...`) + mkdirp.sync(testsPath) } - const configFile = path.join(testsPath, 'codecept.conf.js'); + const configFile = path.join(testsPath, 'codecept.conf.js') if (fileExists(configFile)) { - error(`Config is already created at ${configFile}`); - return; + error(`Config is already created at ${configFile}`) + return } - const typeScriptconfigFile = path.join(testsPath, 'codecept.conf.ts'); + const typeScriptconfigFile = path.join(testsPath, 'codecept.conf.ts') if (fileExists(typeScriptconfigFile)) { - error(`Config is already created at ${typeScriptconfigFile}`); - return; + error(`Config is already created at ${typeScriptconfigFile}`) + return } - inquirer.prompt([ - { - name: 'typescript', - type: 'confirm', - default: false, - message: 'Do you plan to write tests in TypeScript?', - }, - { - name: 'tests', - type: 'input', - default: (answers) => `./*_test.${answers.typescript ? 'ts' : 'js'}`, - message: 'Where are your tests located?', - }, - { - name: 'helper', - type: 'list', - choices: helpers, - default: 'Playwright', - message: 'What helpers do you want to use?', - }, - { - name: 'jsonResponse', - type: 'confirm', - default: true, - message: 'Do you want to use JSONResponse helper for assertions on JSON responses? http://bit.ly/3ASVPy9', - when: (answers) => ['GraphQL', 'REST'].includes(answers.helper) === true, - }, - { - name: 'output', - default: './output', - message: 'Where should logs, screenshots, and reports to be stored?', - }, - { - name: 'translation', - type: 'list', - message: 'Do you want to enable localization for tests? http://bit.ly/3GNUBbh', - choices: translations, - }, - ]).then((result) => { - if (result.typescript === true) { - isTypeScript = true; - extension = isTypeScript === true ? 'ts' : 'js'; - packages.push('typescript'); - packages.push('ts-node'); - packages.push('@types/node'); - } - - const config = defaultConfig; - config.name = testsPath.split(path.sep).pop(); - config.output = result.output; + inquirer + .prompt([ + { + name: 'typescript', + type: 'confirm', + default: false, + message: 'Do you plan to write tests in TypeScript?', + }, + { + name: 'tests', + type: 'input', + default: (answers) => `./*_test.${answers.typescript ? 'ts' : 'js'}`, + message: 'Where are your tests located?', + }, + { + name: 'helper', + type: 'list', + choices: helpers, + default: 'Playwright', + message: 'What helpers do you want to use?', + }, + { + name: 'jsonResponse', + type: 'confirm', + default: true, + message: 'Do you want to use JSONResponse helper for assertions on JSON responses? http://bit.ly/3ASVPy9', + when: (answers) => ['GraphQL', 'REST'].includes(answers.helper) === true, + }, + { + name: 'output', + default: './output', + message: 'Where should logs, screenshots, and reports to be stored?', + }, + { + name: 'translation', + type: 'list', + message: 'Do you want to enable localization for tests? http://bit.ly/3GNUBbh', + choices: translations, + }, + ]) + .then((result) => { + if (result.typescript === true) { + isTypeScript = true + extension = isTypeScript === true ? 'ts' : 'js' + packages.push('typescript') + packages.push('ts-node') + packages.push('@types/node') + } - config.tests = result.tests; - if (isTypeScript) { - config.tests = `${config.tests.replace(/\.js$/, `.${extension}`)}`; - } + const config = defaultConfig + config.name = testsPath.split(path.sep).pop() + config.output = result.output - // create a directory tests if it is included in tests path - const matchResults = config.tests.match(/[^*.]+/); - if (matchResults) { - mkdirp.sync(path.join(testsPath, matchResults[0])); - } - - if (result.translation !== noTranslation) config.translation = result.translation; + config.tests = result.tests + if (isTypeScript) { + config.tests = `${config.tests.replace(/\.js$/, `.${extension}`)}` + } - const helperName = result.helper; - config.helpers[helperName] = {}; + // create a directory tests if it is included in tests path + const matchResults = config.tests.match(/[^*.]+/) + if (matchResults) { + mkdirp.sync(path.join(testsPath, matchResults[0])) + } - if (result.jsonResponse === true) { - config.helpers.JSONResponse = {}; - } + if (result.translation !== noTranslation) config.translation = result.translation - let helperConfigs = []; + const helperName = result.helper + config.helpers[helperName] = {} - try { - const Helper = require(`../helper/${helperName}`); - if (Helper._checkRequirements) { - packages.concat(Helper._checkRequirements()); + if (result.jsonResponse === true) { + config.helpers.JSONResponse = {} } - if (!Helper._config()) return; - helperConfigs = helperConfigs.concat(Helper._config().map((config) => { - config.message = `[${helperName}] ${config.message}`; - config.name = `${helperName}_${config.name}`; - config.type = config.type || 'input'; - return config; - })); - } catch (err) { - error(err); - } + let helperConfigs = [] - const finish = async () => { - // create steps file by default - // no extra step file for typescript (as it doesn't match TS conventions) - const stepFile = `./steps_file.${extension}`; - fs.writeFileSync(path.join(testsPath, stepFile), extension === 'ts' ? defaultActorTs : defaultActor); + try { + const Helper = require(`../helper/${helperName}`) + if (Helper._checkRequirements) { + packages.concat(Helper._checkRequirements()) + } - if (isTypeScript) { - config.include = _actorTranslation('./steps_file', config.translation); - } else { - config.include = _actorTranslation(stepFile, config.translation); + if (!Helper._config()) return + helperConfigs = helperConfigs.concat( + Helper._config().map((config) => { + config.message = `[${helperName}] ${config.message}` + config.name = `${helperName}_${config.name}` + config.type = config.type || 'input' + return config + }), + ) + } catch (err) { + error(err) } - print(`Steps file created at ${stepFile}`); + const finish = async () => { + // create steps file by default + // no extra step file for typescript (as it doesn't match TS conventions) + const stepFile = `./steps_file.${extension}` + fs.writeFileSync(path.join(testsPath, stepFile), extension === 'ts' ? defaultActorTs : defaultActor) - let configSource; - const hasConfigure = isLocal && !initPath; - - if (isTypeScript) { - configSource = beautify(`export const config : CodeceptJS.MainConfig = ${inspect(config, false, 4, false)}`); + if (isTypeScript) { + config.include = _actorTranslation('./steps_file', config.translation) + } else { + config.include = _actorTranslation(stepFile, config.translation) + } - if (hasConfigure) configSource = importCodeceptConfigure + configHeader + configSource; + print(`Steps file created at ${stepFile}`) - fs.writeFileSync(typeScriptconfigFile, configSource, 'utf-8'); - print(`Config created at ${typeScriptconfigFile}`); - } else { - configSource = beautify(`/** @type {CodeceptJS.MainConfig} */\nexports.config = ${inspect(config, false, 4, false)}`); + let configSource + const hasConfigure = isLocal && !initPath - if (hasConfigure) configSource = requireCodeceptConfigure + configHeader + configSource; + if (isTypeScript) { + configSource = beautify(`export const config : CodeceptJS.MainConfig = ${inspect(config, false, 4, false)}`) - fs.writeFileSync(configFile, configSource, 'utf-8'); - print(`Config created at ${configFile}`); - } + if (hasConfigure) configSource = importCodeceptConfigure + configHeader + configSource - if (config.output) { - if (!fileExists(config.output)) { - mkdirp.sync(path.join(testsPath, config.output)); - print(`Directory for temporary output files created at '${config.output}'`); + fs.writeFileSync(typeScriptconfigFile, configSource, 'utf-8') + print(`Config created at ${typeScriptconfigFile}`) } else { - print(`Directory for temporary output files is already created at '${config.output}'`); + configSource = beautify( + `/** @type {CodeceptJS.MainConfig} */\nexports.config = ${inspect(config, false, 4, false)}`, + ) + + if (hasConfigure) configSource = requireCodeceptConfigure + configHeader + configSource + + fs.writeFileSync(configFile, configSource, 'utf-8') + print(`Config created at ${configFile}`) } - } - const jsconfig = { - compilerOptions: { - allowJs: true, - }, - }; - - const tsconfig = { - 'ts-node': { - files: true, - }, - compilerOptions: { - target: 'es2018', - lib: ['es2018', 'DOM'], - esModuleInterop: true, - module: 'commonjs', - strictNullChecks: false, - types: ['codeceptjs', 'node'], - declaration: true, - skipLibCheck: true, - }, - exclude: ['node_modules'], - }; + if (config.output) { + if (!fileExists(config.output)) { + mkdirp.sync(path.join(testsPath, config.output)) + print(`Directory for temporary output files created at '${config.output}'`) + } else { + print(`Directory for temporary output files is already created at '${config.output}'`) + } + } - if (isTypeScript) { - const tsconfigJson = beautify(JSON.stringify(tsconfig)); - const tsconfigFile = path.join(testsPath, 'tsconfig.json'); - if (fileExists(tsconfigFile)) { - print(`tsconfig.json already exists at ${tsconfigFile}`); - } else { - fs.writeFileSync(tsconfigFile, tsconfigJson); + const jsconfig = { + compilerOptions: { + allowJs: true, + }, } - } else { - const jsconfigJson = beautify(JSON.stringify(jsconfig)); - const jsconfigFile = path.join(testsPath, 'jsconfig.json'); - if (fileExists(jsconfigFile)) { - print(`jsconfig.json already exists at ${jsconfigFile}`); + + const tsconfig = { + 'ts-node': { + files: true, + }, + compilerOptions: { + target: 'es2018', + lib: ['es2018', 'DOM'], + esModuleInterop: true, + module: 'commonjs', + strictNullChecks: false, + types: ['codeceptjs', 'node'], + declaration: true, + skipLibCheck: true, + }, + exclude: ['node_modules'], + } + + if (isTypeScript) { + const tsconfigJson = beautify(JSON.stringify(tsconfig)) + const tsconfigFile = path.join(testsPath, 'tsconfig.json') + if (fileExists(tsconfigFile)) { + print(`tsconfig.json already exists at ${tsconfigFile}`) + } else { + fs.writeFileSync(tsconfigFile, tsconfigJson) + } } else { - fs.writeFileSync(jsconfigFile, jsconfigJson); - print(`Intellisense enabled in ${jsconfigFile}`); + const jsconfigJson = beautify(JSON.stringify(jsconfig)) + const jsconfigFile = path.join(testsPath, 'jsconfig.json') + if (fileExists(jsconfigFile)) { + print(`jsconfig.json already exists at ${jsconfigFile}`) + } else { + fs.writeFileSync(jsconfigFile, jsconfigJson) + print(`Intellisense enabled in ${jsconfigFile}`) + } } - } - const generateDefinitionsManually = colors.bold(`To get auto-completion support, please generate type definitions: ${colors.green('npx codeceptjs def')}`); + const generateDefinitionsManually = colors.bold( + `To get auto-completion support, please generate type definitions: ${colors.green('npx codeceptjs def')}`, + ) + + if (packages) { + try { + install(packages) + } catch (err) { + print(colors.bold.red(err.toString())) + print() + print(colors.bold.red('Please install next packages manually:')) + print(`npm i ${packages.join(' ')} --save-dev`) + print() + print('Things to do after missing packages installed:') + print('☑', generateDefinitionsManually) + print('☑ Create first test:', colors.green('npx codeceptjs gt')) + print(colors.bold.magenta('Find more information at https://codecept.io')) + return + } + } - if (packages) { try { - install(packages); + generateDefinitions(testsPath, {}) } catch (err) { - print(colors.bold.red(err.toString())); - print(); - print(colors.bold.red('Please install next packages manually:')); - print(`npm i ${packages.join(' ')} --save-dev`); - print(); - print('Things to do after missing packages installed:'); - print('☑', generateDefinitionsManually); - print('☑ Create first test:', colors.green('npx codeceptjs gt')); - print(colors.bold.magenta('Find more information at https://codecept.io')); - return; + print(colors.bold.red("Couldn't generate type definitions")) + print(colors.red(err.toString())) + print('Skipping type definitions...') + print(generateDefinitionsManually) } - } - try { - generateDefinitions(testsPath, {}); - } catch (err) { - print(colors.bold.red('Couldn\'t generate type definitions')); - print(colors.red(err.toString())); - print('Skipping type definitions...'); - print(generateDefinitionsManually); + print('') + success(' Almost ready... Next step:') + + const generatedTest = generateTest(testsPath) + if (!generatedTest) return + generatedTest.then(() => { + print('\n--') + print(colors.bold.green('CodeceptJS Installed! Enjoy supercharged testing! 🤩')) + print(colors.bold.magenta('Find more information at https://codecept.io')) + print() + }) } - print(''); - success(' Almost ready... Next step:'); - - const generatedTest = generateTest(testsPath); - if (!generatedTest) return; - generatedTest.then(() => { - print('\n--'); - print(colors.bold.green('CodeceptJS Installed! Enjoy supercharged testing! 🤩')); - print(colors.bold.magenta('Find more information at https://codecept.io')); - print(); - }); - }; - - print('Configure helpers...'); - inquirer.prompt(helperConfigs).then(async (helperResult) => { - if (helperResult.Playwright_browser === 'electron') { - delete helperResult.Playwright_url; - delete helperResult.Playwright_show; - - helperResult.Playwright_electron = { - executablePath: '// require("electron") or require("electron-forge")', - args: ['path/to/your/main.js'], - }; - } + print('Configure helpers...') + inquirer.prompt(helperConfigs).then(async (helperResult) => { + if (helperResult.Playwright_browser === 'electron') { + delete helperResult.Playwright_url + delete helperResult.Playwright_show - Object.keys(helperResult).forEach((key) => { - const parts = key.split('_'); - const helperName = parts[0]; - const configName = parts[1]; - if (!configName) return; - config.helpers[helperName][configName] = helperResult[key]; - }); - - print(''); - await finish(); - }); - }); -}; + helperResult.Playwright_electron = { + executablePath: '// require("electron") or require("electron-forge")', + args: ['path/to/your/main.js'], + } + } + + Object.keys(helperResult).forEach((key) => { + const parts = key.split('_') + const helperName = parts[0] + const configName = parts[1] + if (!configName) return + config.helpers[helperName][configName] = helperResult[key] + }) + + print('') + await finish() + }) + }) +} function install(dependencies) { - let command; - let args; + let command + let args if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) { - dependencies.push('codeceptjs'); - throw new Error("Error: 'package.json' file not found. Generate it with 'npm init -y' command."); + dependencies.push('codeceptjs') + throw new Error("Error: 'package.json' file not found. Generate it with 'npm init -y' command.") } if (!installedLocally()) { - console.log('CodeceptJS should be installed locally'); - dependencies.push('codeceptjs'); + console.log('CodeceptJS should be installed locally') + dependencies.push('codeceptjs') } - console.log('Installing packages: ', colors.green(dependencies.join(', '))); + console.log('Installing packages: ', colors.green(dependencies.join(', '))) if (fileExists('yarn.lock')) { - command = 'yarnpkg'; - args = ['add', '-D', '--exact']; - [].push.apply(args, dependencies); + command = 'yarnpkg' + args = ['add', '-D', '--exact'] + ;[].push.apply(args, dependencies) - args.push('--cwd'); - args.push(process.cwd()); + args.push('--cwd') + args.push(process.cwd()) } else { - command = 'npm'; - args = [ - 'install', - '--save-dev', - '--loglevel', - 'error', - ].concat(dependencies); + command = 'npm' + args = ['install', '--save-dev', '--loglevel', 'error'].concat(dependencies) } if (process.env._INIT_DRY_RUN_INSTALL) { - args.push('--dry-run'); + args.push('--dry-run') } - const { status } = spawn.sync(command, args, { stdio: 'inherit' }); + const { status } = spawn.sync(command, args, { stdio: 'inherit' }) if (status !== 0) { - throw new Error(`${command} ${args.join(' ')} failed`); + throw new Error(`${command} ${args.join(' ')} failed`) } - return true; + return true } function _actorTranslation(stepFile, translationSelected) { - let actor; + let actor for (const translationAvailable of translations) { if (actor) { - break; + break } if (translationSelected === translationAvailable) { - const nameOfActor = require('../../translations')[translationAvailable].I; + const nameOfActor = require('../../translations')[translationAvailable].I actor = { [nameOfActor]: stepFile, - }; + } } } if (!actor) { actor = { I: stepFile, - }; + } } - return actor; + return actor } diff --git a/lib/command/interactive.js b/lib/command/interactive.js index e3a33536a..7f92cbec8 100644 --- a/lib/command/interactive.js +++ b/lib/command/interactive.js @@ -1,55 +1,55 @@ -const { getConfig, getTestRoot } = require('./utils'); -const recorder = require('../recorder'); -const Codecept = require('../codecept'); -const Container = require('../container'); -const event = require('../event'); -const output = require('../output'); -const webHelpers = require('../plugin/standardActingHelpers'); +const { getConfig, getTestRoot } = require('./utils') +const recorder = require('../recorder') +const Codecept = require('../codecept') +const Container = require('../container') +const event = require('../event') +const output = require('../output') +const webHelpers = require('../plugin/standardActingHelpers') module.exports = async function (path, options) { // Backward compatibility for --profile - process.profile = options.profile; - process.env.profile = options.profile; - const configFile = options.config; + process.profile = options.profile + process.env.profile = options.profile + const configFile = options.config - const config = getConfig(configFile); - const testsPath = getTestRoot(configFile); + const config = getConfig(configFile) + const testsPath = getTestRoot(configFile) - const codecept = new Codecept(config, options); - codecept.init(testsPath); + const codecept = new Codecept(config, options) + codecept.init(testsPath) try { - await codecept.bootstrap(); + await codecept.bootstrap() - if (options.verbose) output.level(3); + if (options.verbose) output.level(3) - output.print('Starting interactive shell for current suite...'); - recorder.start(); + output.print('Starting interactive shell for current suite...') + recorder.start() event.emit(event.suite.before, { fullTitle: () => 'Interactive Shell', tests: [], - }); + }) event.emit(event.test.before, { title: '', artifacts: {}, - }); + }) - const enabledHelpers = Container.helpers(); + const enabledHelpers = Container.helpers() for (const helperName of Object.keys(enabledHelpers)) { if (webHelpers.includes(helperName)) { - const I = enabledHelpers[helperName]; - recorder.add(() => I.amOnPage('/')); - recorder.catchWithoutStop(e => output.print(`Error while loading home page: ${e.message}}`)); - break; + const I = enabledHelpers[helperName] + recorder.add(() => I.amOnPage('/')) + recorder.catchWithoutStop((e) => output.print(`Error while loading home page: ${e.message}}`)) + break } } - require('../pause')(); + require('../pause')() // recorder.catchWithoutStop((err) => console.log(err.stack)); - recorder.add(() => event.emit(event.test.after, {})); - recorder.add(() => event.emit(event.suite.after, {})); - recorder.add(() => event.emit(event.all.result, {})); - recorder.add(() => codecept.teardown()); + recorder.add(() => event.emit(event.test.after, {})) + recorder.add(() => event.emit(event.suite.after, {})) + recorder.add(() => event.emit(event.all.result, {})) + recorder.add(() => codecept.teardown()) } catch (err) { - output.error(`Error while running bootstrap file :${err}`); + output.error(`Error while running bootstrap file :${err}`) } -}; +} diff --git a/lib/command/list.js b/lib/command/list.js index 3a14e5942..47a1047c9 100644 --- a/lib/command/list.js +++ b/lib/command/list.js @@ -1,36 +1,36 @@ -const { getConfig, getTestRoot } = require('./utils'); -const Codecept = require('../codecept'); -const container = require('../container'); -const { getParamsToString } = require('../parser'); -const { methodsOfObject } = require('../utils'); -const output = require('../output'); +const { getConfig, getTestRoot } = require('./utils') +const Codecept = require('../codecept') +const container = require('../container') +const { getParamsToString } = require('../parser') +const { methodsOfObject } = require('../utils') +const output = require('../output') module.exports = function (path) { - const testsPath = getTestRoot(path); - const config = getConfig(testsPath); - const codecept = new Codecept(config, {}); - codecept.init(testsPath); + const testsPath = getTestRoot(path) + const config = getConfig(testsPath) + const codecept = new Codecept(config, {}) + codecept.init(testsPath) - output.print('List of test actions: -- '); - const helpers = container.helpers(); - const supportI = container.support('I'); - const actions = []; + output.print('List of test actions: -- ') + const helpers = container.helpers() + const supportI = container.support('I') + const actions = [] for (const name in helpers) { - const helper = helpers[name]; + const helper = helpers[name] methodsOfObject(helper).forEach((action) => { - const params = getParamsToString(helper[action]); - actions[action] = 1; - output.print(` ${output.colors.grey(name)} I.${output.colors.bold(action)}(${params})`); - }); + const params = getParamsToString(helper[action]) + actions[action] = 1 + output.print(` ${output.colors.grey(name)} I.${output.colors.bold(action)}(${params})`) + }) } for (const name in supportI) { if (actions[name]) { - continue; + continue } - const actor = supportI[name]; - const params = getParamsToString(actor); - output.print(` I.${output.colors.bold(name)}(${params})`); + const actor = supportI[name] + const params = getParamsToString(actor) + output.print(` I.${output.colors.bold(name)}(${params})`) } - output.print('PS: Actions are retrieved from enabled helpers. '); - output.print('Implement custom actions in your helper classes.'); -}; + output.print('PS: Actions are retrieved from enabled helpers. ') + output.print('Implement custom actions in your helper classes.') +} diff --git a/lib/command/run-multiple.js b/lib/command/run-multiple.js index 228cbb36c..0600b4372 100644 --- a/lib/command/run-multiple.js +++ b/lib/command/run-multiple.js @@ -1,186 +1,206 @@ -const { fork } = require('child_process'); -const path = require('path'); -const crypto = require('crypto'); - -const runHook = require('../hooks'); -const event = require('../event'); -const collection = require('./run-multiple/collection'); -const { clearString, replaceValueDeep } = require('../utils'); -const { - getConfig, getTestRoot, fail, -} = require('./utils'); - -const runner = path.join(__dirname, '/../../bin/codecept'); -let config; -const childOpts = {}; -const copyOptions = ['override', 'steps', 'reporter', 'verbose', 'config', 'reporter-options', 'grep', 'fgrep', 'invert', 'debug', 'plugins', 'colors']; -let overrides = {}; +const { fork } = require('child_process') +const path = require('path') +const crypto = require('crypto') + +const runHook = require('../hooks') +const event = require('../event') +const collection = require('./run-multiple/collection') +const { clearString, replaceValueDeep } = require('../utils') +const { getConfig, getTestRoot, fail } = require('./utils') + +const runner = path.join(__dirname, '/../../bin/codecept') +let config +const childOpts = {} +const copyOptions = [ + 'override', + 'steps', + 'reporter', + 'verbose', + 'config', + 'reporter-options', + 'grep', + 'fgrep', + 'invert', + 'debug', + 'plugins', + 'colors', +] +let overrides = {} // codeceptjs run-multiple smoke:chrome regression:firefox - will launch smoke run in chrome and regression in firefox // codeceptjs run-multiple smoke:chrome regression - will launch smoke run in chrome and regression in firefox and chrome // codeceptjs run-multiple --all - will launch all runs // codeceptjs run-multiple smoke regression' -let runId = 1; -let subprocessCount = 0; -let totalSubprocessCount = 0; -let processesDone; +let runId = 1 +let subprocessCount = 0 +let totalSubprocessCount = 0 +let processesDone module.exports = async function (selectedRuns, options) { // registering options globally to use in config if (options.profile) { - process.env.profile = options.profile; + process.env.profile = options.profile } - const configFile = options.config; + const configFile = options.config - const testRoot = getTestRoot(configFile); - global.codecept_dir = testRoot; + const testRoot = getTestRoot(configFile) + global.codecept_dir = testRoot // copy opts to run Object.keys(options) - .filter(key => copyOptions.indexOf(key) > -1) + .filter((key) => copyOptions.indexOf(key) > -1) .forEach((key) => { - childOpts[key] = options[key]; - }); + childOpts[key] = options[key] + }) try { - overrides = JSON.parse(childOpts.override); - delete childOpts.override; + overrides = JSON.parse(childOpts.override) + delete childOpts.override } catch (e) { - overrides = {}; + overrides = {} } config = { ...getConfig(configFile), ...overrides, - }; + } if (!config.multiple) { - fail('Multiple runs not configured, add "multiple": { /../ } section to config'); + fail('Multiple runs not configured, add "multiple": { /../ } section to config') } - selectedRuns = options.all ? Object.keys(config.multiple) : selectedRuns; + selectedRuns = options.all ? Object.keys(config.multiple) : selectedRuns if (!selectedRuns.length) { - fail('No runs provided. Use --all option to run all configured runs'); + fail('No runs provided. Use --all option to run all configured runs') } - await runHook(config.bootstrapAll, 'bootstrapAll'); + await runHook(config.bootstrapAll, 'bootstrapAll') - event.emit(event.multiple.before, null); - if (options.config) { // update paths to config path + event.emit(event.multiple.before, null) + if (options.config) { + // update paths to config path if (config.tests) { - config.tests = path.resolve(testRoot, config.tests); + config.tests = path.resolve(testRoot, config.tests) } if (config.gherkin && config.gherkin.features) { - config.gherkin.features = path.resolve(testRoot, config.gherkin.features); + config.gherkin.features = path.resolve(testRoot, config.gherkin.features) } } if (options.features) { - config.tests = ''; + config.tests = '' } if (options.tests && config.gherkin) { - config.gherkin.features = ''; + config.gherkin.features = '' } const childProcessesPromise = new Promise((resolve) => { - processesDone = resolve; - }); + processesDone = resolve + }) - const runsToExecute = []; + const runsToExecute = [] collection.createRuns(selectedRuns, config).forEach((run) => { - const runName = run.getOriginalName() || run.getName(); - const runConfig = run.getConfig(); - runsToExecute.push(executeRun(runName, runConfig)); - }); + const runName = run.getOriginalName() || run.getName() + const runConfig = run.getConfig() + runsToExecute.push(executeRun(runName, runConfig)) + }) if (!runsToExecute.length) { - fail('Nothing scheduled for execution'); + fail('Nothing scheduled for execution') } // Execute all forks - totalSubprocessCount = runsToExecute.length; - runsToExecute.forEach(runToExecute => runToExecute.call(this)); + totalSubprocessCount = runsToExecute.length + runsToExecute.forEach((runToExecute) => runToExecute.call(this)) return childProcessesPromise.then(async () => { // fire hook - await runHook(config.teardownAll, 'teardownAll'); - event.emit(event.multiple.after, null); - }); -}; + await runHook(config.teardownAll, 'teardownAll') + event.emit(event.multiple.after, null) + }) +} function executeRun(runName, runConfig) { // clone config - let overriddenConfig = { ...config }; + let overriddenConfig = { ...config } // get configuration - const browserConfig = runConfig.browser; - const browserName = browserConfig.browser; + const browserConfig = runConfig.browser + const browserName = browserConfig.browser for (const key in browserConfig) { - overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key]); + overriddenConfig.helpers = replaceValueDeep(overriddenConfig.helpers, key, browserConfig[key]) } - let outputDir = `${runName}_`; + let outputDir = `${runName}_` if (browserConfig.outputName) { - outputDir += typeof browserConfig.outputName === 'function' ? browserConfig.outputName() : browserConfig.outputName; + outputDir += typeof browserConfig.outputName === 'function' ? browserConfig.outputName() : browserConfig.outputName } else { - const hash = crypto.createHash('sha256'); - hash.update(JSON.stringify(browserConfig)); - outputDir += hash.digest('hex'); + const hash = crypto.createHash('sha256') + hash.update(JSON.stringify(browserConfig)) + outputDir += hash.digest('hex') } - outputDir += `_${runId}`; + outputDir += `_${runId}` - outputDir = clearString(outputDir); + outputDir = clearString(outputDir) // tweaking default output directories and for mochawesome - overriddenConfig = replaceValueDeep(overriddenConfig, 'output', path.join(config.output, outputDir)); - overriddenConfig = replaceValueDeep(overriddenConfig, 'reportDir', path.join(config.output, outputDir)); - overriddenConfig = replaceValueDeep(overriddenConfig, 'mochaFile', path.join(config.output, outputDir, `${browserName}_report.xml`)); + overriddenConfig = replaceValueDeep(overriddenConfig, 'output', path.join(config.output, outputDir)) + overriddenConfig = replaceValueDeep(overriddenConfig, 'reportDir', path.join(config.output, outputDir)) + overriddenConfig = replaceValueDeep( + overriddenConfig, + 'mochaFile', + path.join(config.output, outputDir, `${browserName}_report.xml`), + ) // override tests configuration if (overriddenConfig.tests) { - overriddenConfig.tests = runConfig.tests; + overriddenConfig.tests = runConfig.tests } if (overriddenConfig.gherkin && runConfig.gherkin && runConfig.gherkin.features) { - overriddenConfig.gherkin.features = runConfig.gherkin.features; + overriddenConfig.gherkin.features = runConfig.gherkin.features } // override grep param and collect all params - const params = ['run', - '--child', `${runId++}.${runName}:${browserName}`, - '--override', JSON.stringify(overriddenConfig), - ]; + const params = [ + 'run', + '--child', + `${runId++}.${runName}:${browserName}`, + '--override', + JSON.stringify(overriddenConfig), + ] Object.keys(childOpts).forEach((key) => { - params.push(`--${key}`); - if (childOpts[key] !== true) params.push(childOpts[key]); - }); + params.push(`--${key}`) + if (childOpts[key] !== true) params.push(childOpts[key]) + }) if (runConfig.grep) { - params.push('--grep'); - params.push(runConfig.grep); + params.push('--grep') + params.push(runConfig.grep) } const onProcessEnd = (errorCode) => { if (errorCode !== 0) { - process.exitCode = errorCode; + process.exitCode = errorCode } - subprocessCount += 1; + subprocessCount += 1 if (subprocessCount === totalSubprocessCount) { - processesDone(); + processesDone() } - return errorCode; - }; + return errorCode + } // Return function of fork for later execution - return () => fork(runner, params, { stdio: [0, 1, 2, 'ipc'] }) - .on('exit', (code) => { - return onProcessEnd(code); - }) - .on('error', () => { - return onProcessEnd(1); - }); + return () => + fork(runner, params, { stdio: [0, 1, 2, 'ipc'] }) + .on('exit', (code) => { + return onProcessEnd(code) + }) + .on('error', () => { + return onProcessEnd(1) + }) } diff --git a/lib/command/run-rerun.js b/lib/command/run-rerun.js index a104bbaa6..62f17bdae 100644 --- a/lib/command/run-rerun.js +++ b/lib/command/run-rerun.js @@ -1,38 +1,38 @@ -const { getConfig, getTestRoot } = require('./utils'); -const { printError, createOutputDir } = require('./utils'); -const Config = require('../config'); -const Codecept = require('../rerun'); +const { getConfig, getTestRoot } = require('./utils') +const { printError, createOutputDir } = require('./utils') +const Config = require('../config') +const Codecept = require('../rerun') module.exports = async function (test, options) { // registering options globally to use in config // Backward compatibility for --profile - process.profile = options.profile; - process.env.profile = options.profile; - const configFile = options.config; + process.profile = options.profile + process.env.profile = options.profile + const configFile = options.config - let config = getConfig(configFile); + let config = getConfig(configFile) if (options.override) { - config = Config.append(JSON.parse(options.override)); + config = Config.append(JSON.parse(options.override)) } - const testRoot = getTestRoot(configFile); - createOutputDir(config, testRoot); + const testRoot = getTestRoot(configFile) + createOutputDir(config, testRoot) function processError(err) { - printError(err); - process.exit(1); + printError(err) + process.exit(1) } - const codecept = new Codecept(config, options); + const codecept = new Codecept(config, options) try { - codecept.init(testRoot); + codecept.init(testRoot) - await codecept.bootstrap(); - codecept.loadTests(test); - await codecept.run(); + await codecept.bootstrap() + codecept.loadTests(test) + await codecept.run() } catch (err) { - printError(err); - process.exitCode = 1; + printError(err) + process.exitCode = 1 } finally { - await codecept.teardown(); + await codecept.teardown() } -}; +} diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js index 4b968b971..caae6f6cd 100644 --- a/lib/command/run-workers.js +++ b/lib/command/run-workers.js @@ -1,89 +1,89 @@ // For Node version >=10.5.0, have to use experimental flag -const { tryOrDefault } = require('../utils'); -const output = require('../output'); -const store = require('../store'); -const event = require('../event'); -const Workers = require('../workers'); +const { tryOrDefault } = require('../utils') +const output = require('../output') +const store = require('../store') +const event = require('../event') +const Workers = require('../workers') module.exports = async function (workerCount, selectedRuns, options) { - process.env.profile = options.profile; - - const suiteArr = []; - const passedTestArr = []; - const failedTestArr = []; - const skippedTestArr = []; - const stepArr = []; - - const { config: testConfig, override = '' } = options; - const overrideConfigs = tryOrDefault(() => JSON.parse(override), {}); - const by = options.suites ? 'suite' : 'test'; - delete options.parent; + process.env.profile = options.profile + + const suiteArr = [] + const passedTestArr = [] + const failedTestArr = [] + const skippedTestArr = [] + const stepArr = [] + + const { config: testConfig, override = '' } = options + const overrideConfigs = tryOrDefault(() => JSON.parse(override), {}) + const by = options.suites ? 'suite' : 'test' + delete options.parent const config = { by, testConfig, options, selectedRuns, - }; + } - const numberOfWorkers = parseInt(workerCount, 10); + const numberOfWorkers = parseInt(workerCount, 10) - output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`); - output.print(`Running tests in ${output.styles.bold(numberOfWorkers)} workers...`); - output.print(); + output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`) + output.print(`Running tests in ${output.styles.bold(numberOfWorkers)} workers...`) + output.print() - const workers = new Workers(numberOfWorkers, config); - workers.overrideConfig(overrideConfigs); + const workers = new Workers(numberOfWorkers, config) + workers.overrideConfig(overrideConfigs) workers.on(event.suite.before, (suite) => { - suiteArr.push(suite); - }); + suiteArr.push(suite) + }) workers.on(event.step.passed, (step) => { - stepArr.push(step); - }); + stepArr.push(step) + }) workers.on(event.step.failed, (step) => { - stepArr.push(step); - }); + stepArr.push(step) + }) workers.on(event.test.failed, (test) => { - failedTestArr.push(test); - output.test.failed(test); - }); + failedTestArr.push(test) + output.test.failed(test) + }) workers.on(event.test.passed, (test) => { - passedTestArr.push(test); - output.test.passed(test); - }); + passedTestArr.push(test) + output.test.passed(test) + }) workers.on(event.test.skipped, (test) => { - skippedTestArr.push(test); - output.test.skipped(test); - }); + skippedTestArr.push(test) + output.test.skipped(test) + }) workers.on(event.all.result, () => { // expose test stats after all workers finished their execution function addStepsToTest(test, stepArr) { - stepArr.test.steps.forEach(step => { + stepArr.test.steps.forEach((step) => { if (test.steps.length === 0) { - test.steps.push(step); + test.steps.push(step) } - }); + }) } - stepArr.forEach(step => { - passedTestArr.forEach(test => { + stepArr.forEach((step) => { + passedTestArr.forEach((test) => { if (step.test.title === test.title) { - addStepsToTest(test, step); + addStepsToTest(test, step) } - }); + }) - failedTestArr.forEach(test => { + failedTestArr.forEach((test) => { if (step.test.title === test.title) { - addStepsToTest(test, step); + addStepsToTest(test, step) } - }); - }); + }) + }) event.dispatcher.emit(event.workers.result, { suites: suiteArr, @@ -92,24 +92,24 @@ module.exports = async function (workerCount, selectedRuns, options) { failed: failedTestArr, skipped: skippedTestArr, }, - }); - workers.printResults(); - }); + }) + workers.printResults() + }) try { - if (options.verbose || options.debug) store.debugMode = true; + if (options.verbose || options.debug) store.debugMode = true if (options.verbose) { - global.debugMode = true; - const { getMachineInfo } = require('./info'); - await getMachineInfo(); + global.debugMode = true + const { getMachineInfo } = require('./info') + await getMachineInfo() } - await workers.bootstrapAll(); - await workers.run(); + await workers.bootstrapAll() + await workers.run() } catch (err) { - output.error(err); - process.exit(1); + output.error(err) + process.exit(1) } finally { - await workers.teardownAll(); + await workers.teardownAll() } -}; +} diff --git a/lib/command/run.js b/lib/command/run.js index ec3248e24..e76257404 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -1,48 +1,46 @@ -const { - getConfig, printError, getTestRoot, createOutputDir, -} = require('./utils'); -const Config = require('../config'); -const store = require('../store'); -const Codecept = require('../codecept'); +const { getConfig, printError, getTestRoot, createOutputDir } = require('./utils') +const Config = require('../config') +const store = require('../store') +const Codecept = require('../codecept') module.exports = async function (test, options) { // registering options globally to use in config // Backward compatibility for --profile // TODO: remove in CodeceptJS 4 - process.profile = options.profile; + process.profile = options.profile if (options.profile) { - process.env.profile = options.profile; + process.env.profile = options.profile } - if (options.verbose || options.debug) store.debugMode = true; + if (options.verbose || options.debug) store.debugMode = true - const configFile = options.config; + const configFile = options.config - let config = getConfig(configFile); + let config = getConfig(configFile) if (options.override) { - config = Config.append(JSON.parse(options.override)); + config = Config.append(JSON.parse(options.override)) } - const testRoot = getTestRoot(configFile); - createOutputDir(config, testRoot); + const testRoot = getTestRoot(configFile) + createOutputDir(config, testRoot) - const codecept = new Codecept(config, options); + const codecept = new Codecept(config, options) try { - codecept.init(testRoot); - await codecept.bootstrap(); - codecept.loadTests(test); + codecept.init(testRoot) + await codecept.bootstrap() + codecept.loadTests(test) if (options.verbose) { - global.debugMode = true; - const { getMachineInfo } = require('./info'); - await getMachineInfo(); + global.debugMode = true + const { getMachineInfo } = require('./info') + await getMachineInfo() } - await codecept.run(); + await codecept.run() } catch (err) { - printError(err); - process.exitCode = 1; + printError(err) + process.exitCode = 1 } finally { - await codecept.teardown(); + await codecept.teardown() } -}; +} diff --git a/lib/command/utils.js b/lib/command/utils.js index 60ceee8a1..cb5620b79 100644 --- a/lib/command/utils.js +++ b/lib/command/utils.js @@ -1,117 +1,118 @@ -const fs = require('fs'); -const path = require('path'); -const util = require('util'); -const mkdirp = require('mkdirp'); +const fs = require('fs') +const path = require('path') +const util = require('util') +const mkdirp = require('mkdirp') -const output = require('../output'); -const { fileExists, beautify } = require('../utils'); +const output = require('../output') +const { fileExists, beautify } = require('../utils') // alias to deep merge -module.exports.deepMerge = require('../utils').deepMerge; +module.exports.deepMerge = require('../utils').deepMerge module.exports.getConfig = function (configFile) { try { - return require('../config').load(configFile); + return require('../config').load(configFile) } catch (err) { - fail(err.stack); + fail(err.stack) } -}; +} module.exports.readConfig = function (configFile) { try { - const data = fs.readFileSync(configFile, 'utf8'); - return data; + const data = fs.readFileSync(configFile, 'utf8') + return data } catch (err) { - output.error(err); + output.error(err) } -}; +} function getTestRoot(currentPath) { - if (!currentPath) currentPath = '.'; - if (!path.isAbsolute(currentPath)) currentPath = path.join(process.cwd(), currentPath); - currentPath = fs.lstatSync(currentPath).isDirectory() || !path.extname(currentPath) ? currentPath : path.dirname(currentPath); - return currentPath; + if (!currentPath) currentPath = '.' + if (!path.isAbsolute(currentPath)) currentPath = path.join(process.cwd(), currentPath) + currentPath = + fs.lstatSync(currentPath).isDirectory() || !path.extname(currentPath) ? currentPath : path.dirname(currentPath) + return currentPath } -module.exports.getTestRoot = getTestRoot; +module.exports.getTestRoot = getTestRoot function fail(msg) { - output.error(msg); - process.exit(1); + output.error(msg) + process.exit(1) } -module.exports.fail = fail; +module.exports.fail = fail function updateConfig(testsPath, config, extension) { - const configFile = path.join(testsPath, `codecept.conf.${extension}`); + const configFile = path.join(testsPath, `codecept.conf.${extension}`) if (!fileExists(configFile)) { - const msg = `codecept.conf.${extension} config can\'t be updated automatically`; - console.log(); - console.log(`${output.colors.bold.red(msg)}`); - console.log(`${output.colors.bold.red('Please update it manually:')}`); - console.log(); - console.log(config); - console.log(); - return; + const msg = `codecept.conf.${extension} config can\'t be updated automatically` + console.log() + console.log(`${output.colors.bold.red(msg)}`) + console.log(`${output.colors.bold.red('Please update it manually:')}`) + console.log() + console.log(config) + console.log() + return } - console.log(`${output.colors.yellow('Updating configuration file...')}`); - return fs.writeFileSync(configFile, beautify(`exports.config = ${util.inspect(config, false, 4, false)}`), 'utf-8'); + console.log(`${output.colors.yellow('Updating configuration file...')}`) + return fs.writeFileSync(configFile, beautify(`exports.config = ${util.inspect(config, false, 4, false)}`), 'utf-8') } -module.exports.updateConfig = updateConfig; +module.exports.updateConfig = updateConfig function safeFileWrite(file, contents) { if (fileExists(file)) { - output.error(`File ${file} already exist, skipping...`); - return false; + output.error(`File ${file} already exist, skipping...`) + return false } - fs.writeFileSync(file, contents); - return true; + fs.writeFileSync(file, contents) + return true } -module.exports.safeFileWrite = safeFileWrite; +module.exports.safeFileWrite = safeFileWrite module.exports.captureStream = (stream) => { - let oldStream; - let buffer = ''; + let oldStream + let buffer = '' return { startCapture() { - buffer = ''; - oldStream = stream.write.bind(stream); - stream.write = chunk => (buffer += chunk); + buffer = '' + oldStream = stream.write.bind(stream) + stream.write = (chunk) => (buffer += chunk) }, stopCapture() { - if (oldStream !== undefined) stream.write = oldStream; + if (oldStream !== undefined) stream.write = oldStream }, getData: () => buffer, - }; -}; + } +} module.exports.printError = (err) => { - output.print(''); - output.error(err.message); - output.print(''); - output.print(output.colors.grey(err.stack.replace(err.message, ''))); -}; + output.print('') + output.error(err.message) + output.print('') + output.print(output.colors.grey(err.stack.replace(err.message, ''))) +} module.exports.createOutputDir = (config, testRoot) => { - let outputDir; - if (path.isAbsolute(config.output)) outputDir = config.output; - else outputDir = path.join(testRoot, config.output); + let outputDir + if (path.isAbsolute(config.output)) outputDir = config.output + else outputDir = path.join(testRoot, config.output) if (!fileExists(outputDir)) { - output.print(`creating output directory: ${outputDir}`); - mkdirp.sync(outputDir); + output.print(`creating output directory: ${outputDir}`) + mkdirp.sync(outputDir) } -}; +} module.exports.findConfigFile = (testsPath) => { - const extensions = ['js', 'ts']; + const extensions = ['js', 'ts'] for (const ext of extensions) { - const configFile = path.join(testsPath, `codecept.conf.${ext}`); + const configFile = path.join(testsPath, `codecept.conf.${ext}`) if (fileExists(configFile)) { - return configFile; + return configFile } } - return null; -}; + return null +} diff --git a/lib/data/context.js b/lib/data/context.js index 4d6d21bdf..82f3897f8 100644 --- a/lib/data/context.js +++ b/lib/data/context.js @@ -1,129 +1,129 @@ -const { isGenerator } = require('../utils'); -const DataTable = require('./table'); -const DataScenarioConfig = require('./dataScenarioConfig'); -const Secret = require('../secret'); +const { isGenerator } = require('../utils') +const DataTable = require('./table') +const DataScenarioConfig = require('./dataScenarioConfig') +const Secret = require('../secret') module.exports = function (context) { context.Data = function (dataTable) { - const data = detectDataType(dataTable); + const data = detectDataType(dataTable) return { Scenario(title, opts, fn) { - const scenarios = []; + const scenarios = [] if (typeof opts === 'function' && !fn) { - fn = opts; - opts = {}; + fn = opts + opts = {} } - opts.data = data.map(dataRow => dataRow.data); + opts.data = data.map((dataRow) => dataRow.data) data.forEach((dataRow) => { - const dataTitle = replaceTitle(title, dataRow); + const dataTitle = replaceTitle(title, dataRow) if (dataRow.skip) { - context.xScenario(dataTitle); + context.xScenario(dataTitle) } else { - scenarios.push(context.Scenario(dataTitle, opts, fn) - .inject({ current: dataRow.data })); + scenarios.push(context.Scenario(dataTitle, opts, fn).inject({ current: dataRow.data })) } - }); - maskSecretInTitle(scenarios); - return new DataScenarioConfig(scenarios); + }) + maskSecretInTitle(scenarios) + return new DataScenarioConfig(scenarios) }, only: { Scenario(title, opts, fn) { - const scenarios = []; + const scenarios = [] if (typeof opts === 'function' && !fn) { - fn = opts; - opts = {}; + fn = opts + opts = {} } - opts.data = data.map(dataRow => dataRow.data); + opts.data = data.map((dataRow) => dataRow.data) data.forEach((dataRow) => { - const dataTitle = replaceTitle(title, dataRow); + const dataTitle = replaceTitle(title, dataRow) if (dataRow.skip) { - context.xScenario(dataTitle); + context.xScenario(dataTitle) } else { - scenarios.push(context.Scenario.only(dataTitle, opts, fn) - .inject({ current: dataRow.data })); + scenarios.push(context.Scenario.only(dataTitle, opts, fn).inject({ current: dataRow.data })) } - }); - maskSecretInTitle(scenarios); - return new DataScenarioConfig(scenarios); + }) + maskSecretInTitle(scenarios) + return new DataScenarioConfig(scenarios) }, }, - }; - }; + } + } context.xData = function (dataTable) { - const data = detectDataType(dataTable); + const data = detectDataType(dataTable) return { Scenario: (title) => { data.forEach((dataRow) => { - const dataTitle = replaceTitle(title, dataRow); - context.xScenario(dataTitle); - }); - return new DataScenarioConfig([]); + const dataTitle = replaceTitle(title, dataRow) + context.xScenario(dataTitle) + }) + return new DataScenarioConfig([]) }, - }; - }; -}; + } + } +} function replaceTitle(title, dataRow) { if (typeof dataRow.data !== 'object') { - return `${title} | {${JSON.stringify(dataRow.data)}}`; + return `${title} | {${JSON.stringify(dataRow.data)}}` } // if `dataRow` is object and has own `toString()` method, // it should be printed - if (Object.prototype.toString.call(dataRow.data) === (Object()).toString() - && dataRow.data.toString() !== (Object()).toString()) { - return `${title} | ${dataRow.data}`; + if ( + Object.prototype.toString.call(dataRow.data) === Object().toString() && + dataRow.data.toString() !== Object().toString() + ) { + return `${title} | ${dataRow.data}` } - return `${title} | ${JSON.stringify(dataRow.data)}`; + return `${title} | ${JSON.stringify(dataRow.data)}` } function isTableDataRow(row) { - const has = Object.prototype.hasOwnProperty; - return has.call(row, 'data') && has.call(row, 'skip'); + const has = Object.prototype.hasOwnProperty + return has.call(row, 'data') && has.call(row, 'skip') } function detectDataType(dataTable) { if (dataTable instanceof DataTable) { - return dataTable.rows; + return dataTable.rows } if (isGenerator(dataTable)) { - const data = []; + const data = [] for (const dataRow of dataTable()) { data.push({ data: dataRow, - }); + }) } - return data; + return data } if (typeof dataTable === 'function') { - return dataTable(); + return dataTable() } if (Array.isArray(dataTable)) { return dataTable.map((item) => { if (isTableDataRow(item)) { - return item; + return item } return { data: item, skip: false, - }; - }); + } + }) } - throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function'); + throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function') } function maskSecretInTitle(scenarios) { - scenarios.forEach(scenario => { - const res = []; + scenarios.forEach((scenario) => { + const res = [] - scenario.test.title.split(',').forEach(item => { - res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"')); - }); + scenario.test.title.split(',').forEach((item) => { + res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"')) + }) - scenario.test.title = res.join(','); - }); + scenario.test.title = res.join(',') + }) } diff --git a/lib/data/dataScenarioConfig.js b/lib/data/dataScenarioConfig.js index 9cb9173f7..9d6977819 100644 --- a/lib/data/dataScenarioConfig.js +++ b/lib/data/dataScenarioConfig.js @@ -1,84 +1,84 @@ class DataScenarioConfig { constructor(scenarios) { - this.scenarios = scenarios; + this.scenarios = scenarios } /** - * Declares that test throws error. - * Can pass an Error object or regex matching expected message. - * - * @param {*} err - */ + * Declares that test throws error. + * Can pass an Error object or regex matching expected message. + * + * @param {*} err + */ throws(err) { - this.scenarios.forEach(scenario => scenario.throws(err)); - return this; + this.scenarios.forEach((scenario) => scenario.throws(err)) + return this } /** - * Declares that test should fail. - * If test passes - throws an error. - * Can pass an Error object or regex matching expected message. - * - */ + * Declares that test should fail. + * If test passes - throws an error. + * Can pass an Error object or regex matching expected message. + * + */ fails() { - this.scenarios.forEach(scenario => scenario.fails()); - return this; + this.scenarios.forEach((scenario) => scenario.fails()) + return this } /** - * Retry this test for x times - * - * @param {*} retries - */ + * Retry this test for x times + * + * @param {*} retries + */ retry(retries) { - this.scenarios.forEach(scenario => scenario.retry(retries)); - return this; + this.scenarios.forEach((scenario) => scenario.retry(retries)) + return this } /** - * Set timeout for this test - * @param {*} timeout - */ + * Set timeout for this test + * @param {*} timeout + */ timeout(timeout) { - this.scenarios.forEach(scenario => scenario.timeout(timeout)); - return this; + this.scenarios.forEach((scenario) => scenario.timeout(timeout)) + return this } /** - * Configures a helper. - * Helper name can be omitted and values will be applied to first helper. - */ + * Configures a helper. + * Helper name can be omitted and values will be applied to first helper. + */ config(helper, obj) { - this.scenarios.forEach(scenario => scenario.config(helper, obj)); - return this; + this.scenarios.forEach((scenario) => scenario.config(helper, obj)) + return this } /** - * Append a tag name to scenario title - * @param {*} tagName - */ + * Append a tag name to scenario title + * @param {*} tagName + */ tag(tagName) { - this.scenarios.forEach(scenario => scenario.tag(tagName)); - return this; + this.scenarios.forEach((scenario) => scenario.tag(tagName)) + return this } /** - * Pass in additional objects to inject into test - * @param {*} obj - */ + * Pass in additional objects to inject into test + * @param {*} obj + */ inject(obj) { - this.scenarios.forEach(scenario => scenario.inject(obj)); - return this; + this.scenarios.forEach((scenario) => scenario.inject(obj)) + return this } /** - * Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection - * @param {*} dependencies - */ + * Dynamically injects dependencies, see https://codecept.io/pageobjects/#dynamic-injection + * @param {*} dependencies + */ injectDependencies(dependencies) { - this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies)); - return this; + this.scenarios.forEach((scenario) => scenario.injectDependencies(dependencies)) + return this } } -module.exports = DataScenarioConfig; +module.exports = DataScenarioConfig diff --git a/lib/data/dataTableArgument.js b/lib/data/dataTableArgument.js index b32953483..7d1a72d32 100644 --- a/lib/data/dataTableArgument.js +++ b/lib/data/dataTableArgument.js @@ -7,60 +7,60 @@ class DataTableArgument { constructor(gherkinDataTable) { this.rawData = gherkinDataTable.rows.map((row) => { return row.cells.map((cell) => { - return cell.value; - }); - }); + return cell.value + }) + }) } /** Returns the table as a 2-D array - * @returns {string[][]} - */ + * @returns {string[][]} + */ raw() { - return this.rawData.slice(0); + return this.rawData.slice(0) } /** Returns the table as a 2-D array, without the first row - * @returns {string[][]} - */ + * @returns {string[][]} + */ rows() { - const copy = this.raw(); - copy.shift(); - return copy; + const copy = this.raw() + copy.shift() + return copy } /** Returns an array of objects where each row is converted to an object (column header is the key) - * @returns {any[]} - */ + * @returns {any[]} + */ hashes() { - const copy = this.raw(); - const header = copy.shift(); + const copy = this.raw() + const header = copy.shift() return copy.map((row) => { - const r = {}; - row.forEach((cell, index) => r[header[index]] = cell); - return r; - }); + const r = {} + row.forEach((cell, index) => (r[header[index]] = cell)) + return r + }) } /** Returns an object where each row corresponds to an entry * (first column is the key, second column is the value) - * @returns {Record} - */ + * @returns {Record} + */ rowsHash() { - const rows = this.raw(); - const everyRowHasTwoColumns = rows.every((row) => row.length === 2); + const rows = this.raw() + const everyRowHasTwoColumns = rows.every((row) => row.length === 2) if (!everyRowHasTwoColumns) { - throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns'); + throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns') } /** @type {Record} */ - const result = {}; - rows.forEach((x) => (result[x[0]] = x[1])); - return result; + const result = {} + rows.forEach((x) => (result[x[0]] = x[1])) + return result } /** Transposed the data */ transpose() { - this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i])); + this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i])) } } -module.exports = DataTableArgument; +module.exports = DataTableArgument diff --git a/lib/data/table.js b/lib/data/table.js index 20981a65a..841e444dc 100644 --- a/lib/data/table.js +++ b/lib/data/table.js @@ -4,40 +4,46 @@ class DataTable { /** @param {Array<*>} array */ constructor(array) { - this.array = array; - this.rows = new Array(0); + this.array = array + this.rows = new Array(0) } /** @param {Array<*>} array */ add(array) { - if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`); - const tempObj = {}; - let arrayCounter = 0; + if (array.length !== this.array.length) + throw new Error( + `There is too many elements in given data array. Please provide data in this format: ${this.array}`, + ) + const tempObj = {} + let arrayCounter = 0 this.array.forEach((elem) => { - tempObj[elem] = array[arrayCounter]; - tempObj.toString = () => JSON.stringify(tempObj); - arrayCounter++; - }); - this.rows.push({ skip: false, data: tempObj }); + tempObj[elem] = array[arrayCounter] + tempObj.toString = () => JSON.stringify(tempObj) + arrayCounter++ + }) + this.rows.push({ skip: false, data: tempObj }) } /** @param {Array<*>} array */ xadd(array) { - if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`); - const tempObj = {}; - let arrayCounter = 0; + if (array.length !== this.array.length) + throw new Error( + `There is too many elements in given data array. Please provide data in this format: ${this.array}`, + ) + const tempObj = {} + let arrayCounter = 0 this.array.forEach((elem) => { - tempObj[elem] = array[arrayCounter]; - tempObj.toString = () => JSON.stringify(tempObj); - arrayCounter++; - }); - this.rows.push({ skip: true, data: tempObj }); + tempObj[elem] = array[arrayCounter] + tempObj.toString = () => JSON.stringify(tempObj) + arrayCounter++ + }) + this.rows.push({ skip: true, data: tempObj }) } /** @param {Function} func */ filter(func) { - return this.rows.filter(row => func(row.data)); + return this.rows.filter((row) => func(row.data)) } } -module.exports = DataTable; +module.exports = DataTable diff --git a/lib/helper/AI.js b/lib/helper/AI.js index b00d33e67..afa88782a 100644 --- a/lib/helper/AI.js +++ b/lib/helper/AI.js @@ -1,14 +1,14 @@ -const Helper = require('@codeceptjs/helper'); -const ora = require('ora-classic'); -const fs = require('fs'); -const path = require('path'); -const ai = require('../ai'); -const standardActingHelpers = require('../plugin/standardActingHelpers'); -const Container = require('../container'); -const { splitByChunks, minifyHtml } = require('../html'); -const { beautify } = require('../utils'); -const output = require('../output'); -const { registerVariable } = require('../pause'); +const Helper = require('@codeceptjs/helper') +const ora = require('ora-classic') +const fs = require('fs') +const path = require('path') +const ai = require('../ai') +const standardActingHelpers = require('../plugin/standardActingHelpers') +const Container = require('../container') +const { splitByChunks, minifyHtml } = require('../html') +const { beautify } = require('../utils') +const output = require('../output') +const { registerVariable } = require('../pause') /** * AI Helper for CodeceptJS. @@ -26,22 +26,22 @@ const { registerVariable } = require('../pause'); */ class AI extends Helper { constructor(config) { - super(config); - this.aiAssistant = ai; + super(config) + this.aiAssistant = ai this.options = { chunkSize: 80000, - }; - this.options = { ...this.options, ...config }; + } + this.options = { ...this.options, ...config } } _beforeSuite() { - const helpers = Container.helpers(); + const helpers = Container.helpers() for (const helperName of standardActingHelpers) { if (Object.keys(helpers).indexOf(helperName) > -1) { - this.helper = helpers[helperName]; - break; + this.helper = helpers[helperName] + break } } } @@ -58,30 +58,34 @@ class AI extends Helper { * @returns {Promise} - A Promise that resolves to the generated responses from the GPT model, joined by newlines. */ async askGptOnPage(prompt) { - const html = await this.helper.grabSource(); + const html = await this.helper.grabSource() - const htmlChunks = splitByChunks(html, this.options.chunkSize); + const htmlChunks = splitByChunks(html, this.options.chunkSize) - if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`); + if (htmlChunks.length > 1) this.debug(`Splitting HTML into ${htmlChunks.length} chunks`) - const responses = []; + const responses = [] for (const chunk of htmlChunks) { const messages = [ { role: 'user', content: prompt }, { role: 'user', content: `Within this HTML: ${minifyHtml(chunk)}` }, - ]; + ] - if (htmlChunks.length > 1) messages.push({ role: 'user', content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment' }); + if (htmlChunks.length > 1) + messages.push({ + role: 'user', + content: 'If action is not possible on this page, do not propose anything, I will send another HTML fragment', + }) - const response = await this._processAIRequest(messages); + const response = await this._processAIRequest(messages) - output.print(response); + output.print(response) - responses.push(response); + responses.push(response) } - return responses.join('\n\n'); + return responses.join('\n\n') } /** @@ -97,18 +101,18 @@ class AI extends Helper { * @returns {Promise} - A Promise that resolves to the generated response from the GPT model. */ async askGptOnPageFragment(prompt, locator) { - const html = await this.helper.grabHTMLFrom(locator); + const html = await this.helper.grabHTMLFrom(locator) const messages = [ { role: 'user', content: prompt }, { role: 'user', content: `Within this HTML: ${minifyHtml(html)}` }, - ]; + ] - const response = await this._processAIRequest(messages); + const response = await this._processAIRequest(messages) - output.print(response); + output.print(response) - return response; + return response } /** @@ -117,15 +121,13 @@ class AI extends Helper { * @returns {Promise} - A Promise that resolves to the generated response from the GPT model. */ async askGptGeneralPrompt(prompt) { - const messages = [ - { role: 'user', content: prompt }, - ]; + const messages = [{ role: 'user', content: prompt }] - const response = await this._processAIRequest(messages); + const response = await this._processAIRequest(messages) - output.print(response); + output.print(response) - return response; + return response } /** @@ -154,48 +156,48 @@ class AI extends Helper { * @returns {Promise} A promise that resolves to the requested page object. */ async askForPageObject(pageName, extraPrompt = null, locator = null) { - const html = locator ? await this.helper.grabHTMLFrom(locator) : await this.helper.grabSource(); + const html = locator ? await this.helper.grabHTMLFrom(locator) : await this.helper.grabSource() - const spinner = ora(' Processing AI request...').start(); - await this.aiAssistant.setHtmlContext(html); - const response = await this.aiAssistant.generatePageObject(extraPrompt, locator); - spinner.stop(); + const spinner = ora(' Processing AI request...').start() + await this.aiAssistant.setHtmlContext(html) + const response = await this.aiAssistant.generatePageObject(extraPrompt, locator) + spinner.stop() if (!response[0]) { - output.error('No response from AI'); - return ''; + output.error('No response from AI') + return '' } - const code = beautify(response[0]); + const code = beautify(response[0]) - output.print('----- Generated PageObject ----'); - output.print(code); - output.print('-------------------------------'); + output.print('----- Generated PageObject ----') + output.print(code) + output.print('-------------------------------') - const fileName = path.join(output_dir, `${pageName}Page-${Date.now()}.js`); + const fileName = path.join(output_dir, `${pageName}Page-${Date.now()}.js`) - output.print(output.styles.bold(`Page object for ${pageName} is saved to ${output.styles.bold(fileName)}`)); - fs.writeFileSync(fileName, code); + output.print(output.styles.bold(`Page object for ${pageName} is saved to ${output.styles.bold(fileName)}`)) + fs.writeFileSync(fileName, code) try { - registerVariable('page', require(fileName)); - output.success('Page object registered for this session as `page` variable'); - output.print('Use `=>page.methodName()` in shell to run methods of page object'); - output.print('Use `click(page.locatorName)` to check locators of page object'); + registerVariable('page', require(fileName)) + output.success('Page object registered for this session as `page` variable') + output.print('Use `=>page.methodName()` in shell to run methods of page object') + output.print('Use `click(page.locatorName)` to check locators of page object') } catch (err) { - output.error('Error while registering page object'); - output.error(err.message); + output.error('Error while registering page object') + output.error(err.message) } - return code; + return code } async _processAIRequest(messages) { - const spinner = ora(' Processing AI request...').start(); - const response = await this.aiAssistant.createCompletion(messages); - spinner.stop(); - return response; + const spinner = ora(' Processing AI request...').start() + const response = await this.aiAssistant.createCompletion(messages) + spinner.stop() + return response } } -module.exports = AI; +module.exports = AI diff --git a/lib/helper/ApiDataFactory.js b/lib/helper/ApiDataFactory.js index e8f9c5dd6..ece565636 100644 --- a/lib/helper/ApiDataFactory.js +++ b/lib/helper/ApiDataFactory.js @@ -1,7 +1,7 @@ -const path = require('path'); +const path = require('path') -const Helper = require('@codeceptjs/helper'); -const REST = require('./REST'); +const Helper = require('@codeceptjs/helper') +const REST = require('./REST') /** * Helper for managing remote data using REST API. @@ -184,67 +184,67 @@ const REST = require('./REST'); */ class ApiDataFactory extends Helper { constructor(config) { - super(config); + super(config) const defaultConfig = { cleanup: true, REST: {}, factories: {}, returnId: false, - }; - this.config = Object.assign(defaultConfig, this.config); + } + this.config = Object.assign(defaultConfig, this.config) - if (this.config.headers) this.config.REST.defaultHeaders = this.config.headers; - if (this.config.onRequest) this.config.REST.onRequest = this.config.onRequest; - this.restHelper = new REST(Object.assign(this.config.REST, { endpoint: this.config.endpoint })); - this.factories = this.config.factories; + if (this.config.headers) this.config.REST.defaultHeaders = this.config.headers + if (this.config.onRequest) this.config.REST.onRequest = this.config.onRequest + this.restHelper = new REST(Object.assign(this.config.REST, { endpoint: this.config.endpoint })) + this.factories = this.config.factories for (const factory in this.factories) { - const factoryConfig = this.factories[factory]; + const factoryConfig = this.factories[factory] if (!factoryConfig.uri && !factoryConfig.create) { throw new Error(`Uri for factory "${factory}" is not defined. Please set "uri" parameter: "factories": { "${factory}": { "uri": ... - `); + `) } - if (!factoryConfig.create) factoryConfig.create = { post: factoryConfig.uri }; - if (!factoryConfig.delete) factoryConfig.delete = { delete: `${factoryConfig.uri}/{id}` }; + if (!factoryConfig.create) factoryConfig.create = { post: factoryConfig.uri } + if (!factoryConfig.delete) factoryConfig.delete = { delete: `${factoryConfig.uri}/{id}` } - this.factories[factory] = factoryConfig; + this.factories[factory] = factoryConfig } - this.created = {}; - Object.keys(this.factories).forEach(f => this.created[f] = []); + this.created = {} + Object.keys(this.factories).forEach((f) => (this.created[f] = [])) } static _checkRequirements() { try { - require('axios'); - require('rosie'); + require('axios') + require('rosie') } catch (e) { - return ['axios', 'rosie']; + return ['axios', 'rosie'] } } _after() { if (!this.config.cleanup || this.config.cleanup === false) { - return Promise.resolve(); + return Promise.resolve() } - const promises = []; + const promises = [] // clean up all created items for (const factoryName in this.created) { - const createdItems = this.created[factoryName]; - if (!createdItems.length) continue; - this.debug(`Deleting ${createdItems.length} ${factoryName}(s)`); + const createdItems = this.created[factoryName] + if (!createdItems.length) continue + this.debug(`Deleting ${createdItems.length} ${factoryName}(s)`) for (const id in createdItems) { - promises.push(this._requestDelete(factoryName, createdItems[id])); + promises.push(this._requestDelete(factoryName, createdItems[id])) } } - return Promise.all(promises); + return Promise.all(promises) } /** @@ -266,9 +266,9 @@ class ApiDataFactory extends Helper { * @returns {Promise<*>} */ have(factory, params, options) { - const item = this._createItem(factory, params, options); - this.debug(`Creating ${factory} ${JSON.stringify(item)}`); - return this._requestCreate(factory, item); + const item = this._createItem(factory, params, options) + this.debug(`Creating ${factory} ${JSON.stringify(item)}`) + return this._requestCreate(factory, item) } /** @@ -291,27 +291,27 @@ class ApiDataFactory extends Helper { * @param {*} [options] */ haveMultiple(factory, times, params, options) { - const promises = []; + const promises = [] for (let i = 0; i < times; i++) { - promises.push(this.have(factory, params, options)); + promises.push(this.have(factory, params, options)) } - return Promise.all(promises); + return Promise.all(promises) } _createItem(model, data, options) { if (!this.factories[model]) { - throw new Error(`Factory ${model} is not defined in config`); + throw new Error(`Factory ${model} is not defined in config`) } - let modulePath = this.factories[model].factory; + let modulePath = this.factories[model].factory try { try { - require.resolve(modulePath); + require.resolve(modulePath) } catch (e) { - modulePath = path.join(global.codecept_dir, modulePath); + modulePath = path.join(global.codecept_dir, modulePath) } // check if the new syntax `export default new Factory()` is used and loads the builder, otherwise loads the module that used old syntax `module.exports = new Factory()`. - const builder = require(modulePath).default || require(modulePath); - return builder.build(data, options); + const builder = require(modulePath).default || require(modulePath) + return builder.build(data, options) } catch (err) { throw new Error(`Couldn't load factory file from ${modulePath}, check that @@ -322,17 +322,17 @@ class ApiDataFactory extends Helper { points to valid factory file. Factory file should export an object with build method. -Current file error: ${err.message}`); +Current file error: ${err.message}`) } } _fetchId(body, factory) { if (this.config.factories[factory].fetchId) { - return this.config.factories[factory].fetchId(body); + return this.config.factories[factory].fetchId(body) } - if (body.id) return body.id; - if (body[factory] && body[factory].id) return body[factory].id; - return null; + if (body.id) return body.id + if (body[factory] && body[factory].id) return body[factory].id + return null } /** @@ -343,27 +343,27 @@ Current file error: ${err.message}`); * @param {*} data */ _requestCreate(factory, data) { - let request = createRequestFromFunction(this.factories[factory].create, data); + let request = createRequestFromFunction(this.factories[factory].create, data) if (!request) { - const method = Object.keys(this.factories[factory].create)[0]; - const url = this.factories[factory].create[method]; + const method = Object.keys(this.factories[factory].create)[0] + const url = this.factories[factory].create[method] request = { method, url, data, - }; + } } - request.baseURL = this.config.endpoint; + request.baseURL = this.config.endpoint return this.restHelper._executeRequest(request).then((resp) => { - const id = this._fetchId(resp.data, factory); - this.created[factory].push(id); - this.debugSection('Created', `Id: ${id}`); - if (this.config.returnId) return id; - return resp.data; - }); + const id = this._fetchId(resp.data, factory) + this.created[factory].push(id) + this.debugSection('Created', `Id: ${id}`) + if (this.config.returnId) return id + return resp.data + }) } /** @@ -374,37 +374,40 @@ Current file error: ${err.message}`); * @param {*} id */ _requestDelete(factory, id) { - if (!this.factories[factory].delete) return; - let request = createRequestFromFunction(this.factories[factory].delete, id); + if (!this.factories[factory].delete) return + let request = createRequestFromFunction(this.factories[factory].delete, id) if (!request) { - const method = Object.keys(this.factories[factory].delete)[0]; + const method = Object.keys(this.factories[factory].delete)[0] - const url = this.factories[factory].delete[method].replace('{id}', id); + const url = this.factories[factory].delete[method].replace('{id}', id) request = { method, url, - }; + } } - request.baseURL = this.config.endpoint; + request.baseURL = this.config.endpoint if (request.url.match(/^undefined/)) { - return this.debugSection('Please configure the delete request in your ApiDataFactory helper', 'delete: () => ({ method: \'DELETE\', url: \'/api/users\' })'); + return this.debugSection( + 'Please configure the delete request in your ApiDataFactory helper', + "delete: () => ({ method: 'DELETE', url: '/api/users' })", + ) } return this.restHelper._executeRequest(request).then(() => { - const idx = this.created[factory].indexOf(id); - this.debugSection('Deleted Id', `Id: ${id}`); - this.created[factory].splice(idx, 1); - }); + const idx = this.created[factory].indexOf(id) + this.debugSection('Deleted Id', `Id: ${id}`) + this.created[factory].splice(idx, 1) + }) } } -module.exports = ApiDataFactory; +module.exports = ApiDataFactory function createRequestFromFunction(param, data) { - if (typeof param !== 'function') return; - return param(data); + if (typeof param !== 'function') return + return param(data) } diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index b6f2953c4..58b17335b 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -1,26 +1,26 @@ -let webdriverio; +let webdriverio -const fs = require('fs'); -const axios = require('axios').default; -const { v4: uuidv4 } = require('uuid'); +const fs = require('fs') +const axios = require('axios').default +const { v4: uuidv4 } = require('uuid') -const Webdriver = require('./WebDriver'); -const AssertionFailedError = require('../assert/error'); -const { truth } = require('../assert/truth'); -const recorder = require('../recorder'); -const Locator = require('../locator'); -const ConnectionRefused = require('./errors/ConnectionRefused'); +const Webdriver = require('./WebDriver') +const AssertionFailedError = require('../assert/error') +const { truth } = require('../assert/truth') +const recorder = require('../recorder') +const Locator = require('../locator') +const ConnectionRefused = require('./errors/ConnectionRefused') -const mobileRoot = '//*'; -const webRoot = 'body'; +const mobileRoot = '//*' +const webRoot = 'body' const supportedPlatform = { android: 'Android', iOS: 'iOS', -}; +} const vendorPrefix = { appium: 'appium', -}; +} /** * Appium helper extends [Webdriver](http://codecept.io/helpers/WebDriver/) helper. @@ -175,19 +175,21 @@ class Appium extends Webdriver { // @ts-ignore constructor(config) { - super(config); + super(config) - this.isRunning = false; + this.isRunning = false if (config.appiumV2 === true) { - this.appiumV2 = true; + this.appiumV2 = true } - this.axios = axios.create(); + this.axios = axios.create() - webdriverio = require('webdriverio'); + webdriverio = require('webdriverio') if (!config.appiumV2) { - console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Please migrating to Appium 2.x by adding appiumV2: true to your config.'); - console.log('More info: https://bit.ly/appium-v2-migration'); - console.log('This Appium 1.x support will be removed in next major release.'); + console.log( + 'The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Please migrating to Appium 2.x by adding appiumV2: true to your config.', + ) + console.log('More info: https://bit.ly/appium-v2-migration') + console.log('This Appium 1.x support will be removed in next major release.') } } @@ -204,7 +206,7 @@ class Appium extends Webdriver { } } } - `); + `) } // set defaults @@ -225,166 +227,174 @@ class Appium extends Webdriver { timeouts: { script: 0, // ms }, - }; + } // override defaults with config - config = Object.assign(defaults, config); + config = Object.assign(defaults, config) - config.baseUrl = config.url || config.baseUrl; + config.baseUrl = config.url || config.baseUrl if (config.desiredCapabilities && Object.keys(config.desiredCapabilities).length) { - config.capabilities = this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities; + config.capabilities = + this.appiumV2 === true ? this._convertAppiumV2Caps(config.desiredCapabilities) : config.desiredCapabilities } if (this.appiumV2) { - config.capabilities[`${vendorPrefix.appium}:deviceName`] = config[`${vendorPrefix.appium}:device`] || config.capabilities[`${vendorPrefix.appium}:deviceName`]; - config.capabilities[`${vendorPrefix.appium}:browserName`] = config[`${vendorPrefix.appium}:browser`] || config.capabilities[`${vendorPrefix.appium}:browserName`]; - config.capabilities[`${vendorPrefix.appium}:app`] = config[`${vendorPrefix.appium}:app`] || config.capabilities[`${vendorPrefix.appium}:app`]; - config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] = config[`${vendorPrefix.appium}:tunnelIdentifier`] || config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`]; // Adding the code to connect to sauce labs via sauce tunnel + config.capabilities[`${vendorPrefix.appium}:deviceName`] = + config[`${vendorPrefix.appium}:device`] || config.capabilities[`${vendorPrefix.appium}:deviceName`] + config.capabilities[`${vendorPrefix.appium}:browserName`] = + config[`${vendorPrefix.appium}:browser`] || config.capabilities[`${vendorPrefix.appium}:browserName`] + config.capabilities[`${vendorPrefix.appium}:app`] = + config[`${vendorPrefix.appium}:app`] || config.capabilities[`${vendorPrefix.appium}:app`] + config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] = + config[`${vendorPrefix.appium}:tunnelIdentifier`] || + config.capabilities[`${vendorPrefix.appium}:tunnelIdentifier`] // Adding the code to connect to sauce labs via sauce tunnel } else { - config.capabilities.deviceName = config.device || config.capabilities.deviceName; - config.capabilities.browserName = config.browser || config.capabilities.browserName; - config.capabilities.app = config.app || config.capabilities.app; - config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier; // Adding the code to connect to sauce labs via sauce tunnel + config.capabilities.deviceName = config.device || config.capabilities.deviceName + config.capabilities.browserName = config.browser || config.capabilities.browserName + config.capabilities.app = config.app || config.capabilities.app + config.capabilities.tunnelIdentifier = config.tunnelIdentifier || config.capabilities.tunnelIdentifier // Adding the code to connect to sauce labs via sauce tunnel } - config.capabilities.platformName = config.platform || config.capabilities.platformName; - config.waitForTimeoutInSeconds = config.waitForTimeout / 1000; // convert to seconds + config.capabilities.platformName = config.platform || config.capabilities.platformName + config.waitForTimeoutInSeconds = config.waitForTimeout / 1000 // convert to seconds // [CodeceptJS compatible] transform host to hostname - config.hostname = config.host || config.hostname; + config.hostname = config.host || config.hostname if (!config.app && config.capabilities.browserName) { - this.isWeb = true; - this.root = webRoot; + this.isWeb = true + this.root = webRoot } else { - this.isWeb = false; - this.root = mobileRoot; + this.isWeb = false + this.root = mobileRoot } - this.platform = null; + this.platform = null if (config.capabilities[`${vendorPrefix.appium}:platformName`]) { - this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase(); + this.platform = config.capabilities[`${vendorPrefix.appium}:platformName`].toLowerCase() } if (config.capabilities.platformName) { - this.platform = config.capabilities.platformName.toLowerCase(); + this.platform = config.capabilities.platformName.toLowerCase() } - return config; + return config } _convertAppiumV2Caps(capabilities) { - const _convertedCaps = {}; + const _convertedCaps = {} for (const [key, value] of Object.entries(capabilities)) { if (!key.startsWith(vendorPrefix.appium)) { if (key !== 'platformName' && key !== 'bstack:options') { - _convertedCaps[`${vendorPrefix.appium}:${key}`] = value; + _convertedCaps[`${vendorPrefix.appium}:${key}`] = value } else { - _convertedCaps[`${key}`] = value; + _convertedCaps[`${key}`] = value } } else { - _convertedCaps[`${key}`] = value; + _convertedCaps[`${key}`] = value } } - return _convertedCaps; + return _convertedCaps } static _config() { - return [{ - name: 'app', - message: 'Application package. Path to file or url', - default: 'http://localhost', - }, { - name: 'platform', - message: 'Mobile Platform', - type: 'list', - choices: ['iOS', supportedPlatform.android], - default: supportedPlatform.android, - }, { - name: 'device', - message: 'Device to run tests on', - default: 'emulator', - }]; + return [ + { + name: 'app', + message: 'Application package. Path to file or url', + default: 'http://localhost', + }, + { + name: 'platform', + message: 'Mobile Platform', + type: 'list', + choices: ['iOS', supportedPlatform.android], + default: supportedPlatform.android, + }, + { + name: 'device', + message: 'Device to run tests on', + default: 'emulator', + }, + ] } async _startBrowser() { if (this.appiumV2 === true) { - this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities); - this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities); + this.options.capabilities = this._convertAppiumV2Caps(this.options.capabilities) + this.options.desiredCapabilities = this._convertAppiumV2Caps(this.options.desiredCapabilities) } try { if (this.options.multiremote) { - this.browser = await webdriverio.multiremote(this.options.multiremote); + this.browser = await webdriverio.multiremote(this.options.multiremote) } else { - this.browser = await webdriverio.remote(this.options); + this.browser = await webdriverio.remote(this.options) } } catch (err) { if (err.toString().indexOf('ECONNREFUSED')) { - throw new ConnectionRefused(err); + throw new ConnectionRefused(err) } - throw err; + throw err } - this.$$ = this.browser.$$.bind(this.browser); + this.$$ = this.browser.$$.bind(this.browser) - this.isRunning = true; + this.isRunning = true if (this.options.timeouts && this.isWeb) { - await this.defineTimeout(this.options.timeouts); + await this.defineTimeout(this.options.timeouts) } if (this.options.windowSize === 'maximize' && !this.platform) { - const res = await this.browser.execute('return [screen.width, screen.height]'); + const res = await this.browser.execute('return [screen.width, screen.height]') return this.browser.windowHandleSize({ width: res.value[0], height: res.value[1], - }); + }) } if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0 && !this.platform) { - const dimensions = this.options.windowSize.split('x'); + const dimensions = this.options.windowSize.split('x') await this.browser.windowHandleSize({ width: dimensions[0], height: dimensions[1], - }); + }) } } async _after() { - if (!this.isRunning) return; + if (!this.isRunning) return if (this.options.restart) { - this.isRunning = false; - return this.browser.deleteSession(); + this.isRunning = false + return this.browser.deleteSession() } if (this.isWeb && !this.platform) { - return super._after(); + return super._after() } } async _withinBegin(context) { if (this.isWeb) { - return super._withinBegin(context); + return super._withinBegin(context) } if (context === 'webview') { - return this.switchToWeb(); + return this.switchToWeb() } if (typeof context === 'object') { - if (context.web) return this.switchToWeb(context.web); - if (context.webview) return this.switchToWeb(context.webview); + if (context.web) return this.switchToWeb(context.web) + if (context.webview) return this.switchToWeb(context.webview) } - return this.switchToContext(context); + return this.switchToContext(context) } _withinEnd() { if (this.isWeb) { - return super._withinEnd(); + return super._withinEnd() } - return this.switchToNative(); + return this.switchToNative() } _buildAppiumEndpoint() { - const { - protocol, port, hostname, path, - } = this.browser.options; + const { protocol, port, hostname, path } = this.browser.options // Build path to Appium REST API endpoint - return `${protocol}://${hostname}:${port}${path}`; + return `${protocol}://${hostname}:${port}${path}` } /** @@ -422,11 +432,11 @@ class Appium extends Webdriver { * @param {*} fn */ 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()); - return recorder.promise(); + 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()) + return recorder.promise() } /** @@ -464,11 +474,11 @@ class Appium extends Webdriver { * @param {*} fn */ 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()); - return recorder.promise(); + 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()) + return recorder.promise() } /** @@ -485,11 +495,11 @@ class Appium extends Webdriver { */ /* eslint-disable */ async runInWeb(fn) { - if (!this.isWeb) return; - recorder.session.start('Web-only actions'); + if (!this.isWeb) return + recorder.session.start('Web-only actions') - recorder.add('restore from Web session', () => recorder.session.restore(), true); - return recorder.promise(); + recorder.add('restore from Web session', () => recorder.session.restore(), true) + return recorder.promise() } /* eslint-enable */ @@ -498,21 +508,21 @@ class Appium extends Webdriver { for (const key in caps) { // skip if capabilities do not match if (this.config.desiredCapabilities[key] !== caps[key]) { - return; + return } } } if (typeof caps === 'function') { if (!fn) { - fn = caps; + fn = caps } else { // skip if capabilities are checked inside a function - const enabled = caps(this.config.desiredCapabilities); - if (!enabled) return; + const enabled = caps(this.config.desiredCapabilities) + if (!enabled) return } } - fn(); + fn() } /** @@ -528,9 +538,9 @@ class Appium extends Webdriver { * Appium: support only Android */ async checkIfAppIsInstalled(bundleId) { - onlyForApps.call(this, supportedPlatform.android); + onlyForApps.call(this, supportedPlatform.android) - return this.browser.isAppInstalled(bundleId); + return this.browser.isAppInstalled(bundleId) } /** @@ -546,9 +556,9 @@ class Appium extends Webdriver { * Appium: support only Android */ async seeAppIsInstalled(bundleId) { - onlyForApps.call(this, supportedPlatform.android); - const res = await this.browser.isAppInstalled(bundleId); - return truth(`app ${bundleId}`, 'to be installed').assert(res); + onlyForApps.call(this, supportedPlatform.android) + const res = await this.browser.isAppInstalled(bundleId) + return truth(`app ${bundleId}`, 'to be installed').assert(res) } /** @@ -564,9 +574,9 @@ class Appium extends Webdriver { * Appium: support only Android */ async seeAppIsNotInstalled(bundleId) { - onlyForApps.call(this, supportedPlatform.android); - const res = await this.browser.isAppInstalled(bundleId); - return truth(`app ${bundleId}`, 'not to be installed').negate(res); + onlyForApps.call(this, supportedPlatform.android) + const res = await this.browser.isAppInstalled(bundleId) + return truth(`app ${bundleId}`, 'not to be installed').negate(res) } /** @@ -581,8 +591,8 @@ class Appium extends Webdriver { * Appium: support only Android */ async installApp(path) { - onlyForApps.call(this, supportedPlatform.android); - return this.browser.installApp(path); + onlyForApps.call(this, supportedPlatform.android) + return this.browser.installApp(path) } /** @@ -598,13 +608,13 @@ class Appium extends Webdriver { * @param {string} [bundleId] ID of bundle */ async removeApp(appId, bundleId) { - onlyForApps.call(this, supportedPlatform.android); + onlyForApps.call(this, supportedPlatform.android) return this.axios({ method: 'post', url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/device/remove_app`, data: { appId, bundleId }, - }); + }) } /** @@ -616,11 +626,11 @@ class Appium extends Webdriver { * */ async resetApp() { - onlyForApps.call(this); + onlyForApps.call(this) return this.axios({ method: 'post', url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/appium/app/reset`, - }); + }) } /** @@ -635,9 +645,9 @@ class Appium extends Webdriver { * Appium: support only Android */ async seeCurrentActivityIs(currentActivity) { - onlyForApps.call(this, supportedPlatform.android); - const res = await this.browser.getCurrentActivity(); - return truth('current activity', `to be ${currentActivity}`).assert(res === currentActivity); + onlyForApps.call(this, supportedPlatform.android) + const res = await this.browser.getCurrentActivity() + return truth('current activity', `to be ${currentActivity}`).assert(res === currentActivity) } /** @@ -652,9 +662,9 @@ class Appium extends Webdriver { * Appium: support only Android */ async seeDeviceIsLocked() { - onlyForApps.call(this, supportedPlatform.android); - const res = await this.browser.isLocked(); - return truth('device', 'to be locked').assert(res); + onlyForApps.call(this, supportedPlatform.android) + const res = await this.browser.isLocked() + return truth('device', 'to be locked').assert(res) } /** @@ -669,9 +679,9 @@ class Appium extends Webdriver { * Appium: support only Android */ async seeDeviceIsUnlocked() { - onlyForApps.call(this, supportedPlatform.android); - const res = await this.browser.isLocked(); - return truth('device', 'to be locked').negate(res); + onlyForApps.call(this, supportedPlatform.android) + const res = await this.browser.isLocked() + return truth('device', 'to be locked').negate(res) } /** @@ -689,15 +699,15 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async seeOrientationIs(orientation) { - onlyForApps.call(this); + onlyForApps.call(this) const res = await this.axios({ method: 'get', url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`, - }); + }) - const currentOrientation = res.data.value; - return truth('orientation', `to be ${orientation}`).assert(currentOrientation === orientation); + const currentOrientation = res.data.value + return truth('orientation', `to be ${orientation}`).assert(currentOrientation === orientation) } /** @@ -713,13 +723,13 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async setOrientation(orientation) { - onlyForApps.call(this); + onlyForApps.call(this) return this.axios({ method: 'post', url: `${this._buildAppiumEndpoint()}/session/${this.browser.sessionId}/orientation`, data: { orientation }, - }); + }) } /** @@ -734,8 +744,8 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async grabAllContexts() { - onlyForApps.call(this); - return this.browser.getContexts(); + onlyForApps.call(this) + return this.browser.getContexts() } /** @@ -750,8 +760,8 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async grabContext() { - onlyForApps.call(this); - return this.browser.getContext(); + onlyForApps.call(this) + return this.browser.getContext() } /** @@ -766,8 +776,8 @@ class Appium extends Webdriver { * Appium: support only Android */ async grabCurrentActivity() { - onlyForApps.call(this, supportedPlatform.android); - return this.browser.getCurrentActivity(); + onlyForApps.call(this, supportedPlatform.android) + return this.browser.getCurrentActivity() } /** @@ -784,14 +794,14 @@ class Appium extends Webdriver { * Appium: support only Android */ async grabNetworkConnection() { - onlyForApps.call(this, supportedPlatform.android); - const res = await this.browser.getNetworkConnection(); + onlyForApps.call(this, supportedPlatform.android) + const res = await this.browser.getNetworkConnection() return { value: res, inAirplaneMode: res.inAirplaneMode, hasWifi: res.hasWifi, hasData: res.hasData, - }; + } } /** @@ -806,10 +816,10 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async grabOrientation() { - onlyForApps.call(this); - const res = await this.browser.orientation(); - this.debugSection('Orientation', res); - return res; + onlyForApps.call(this) + const res = await this.browser.orientation() + this.debugSection('Orientation', res) + return res } /** @@ -824,10 +834,10 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async grabSettings() { - onlyForApps.call(this); - const res = await this.browser.getSettings(); - this.debugSection('Settings', JSON.stringify(res)); - return res; + onlyForApps.call(this) + const res = await this.browser.getSettings() + this.debugSection('Settings', JSON.stringify(res)) + return res } /** @@ -836,7 +846,7 @@ class Appium extends Webdriver { * @param {*} context the context to switch to */ async switchToContext(context) { - return this.browser.switchContext(context); + return this.browser.switchContext(context) } /** @@ -856,17 +866,17 @@ class Appium extends Webdriver { * @param {string} [context] */ async switchToWeb(context) { - this.isWeb = true; - this.defaultContext = 'body'; + this.isWeb = true + this.defaultContext = 'body' - if (context) return this.switchToContext(context); - const contexts = await this.grabAllContexts(); - this.debugSection('Contexts', contexts.toString()); + if (context) return this.switchToContext(context) + const contexts = await this.grabAllContexts() + this.debugSection('Contexts', contexts.toString()) for (const idx in contexts) { - if (contexts[idx].match(/^WEBVIEW/)) return this.switchToContext(contexts[idx]); + if (contexts[idx].match(/^WEBVIEW/)) return this.switchToContext(contexts[idx]) } - throw new Error('No WEBVIEW could be guessed, please specify one in params'); + throw new Error('No WEBVIEW could be guessed, please specify one in params') } /** @@ -883,11 +893,11 @@ class Appium extends Webdriver { * @return {Promise} */ async switchToNative(context = null) { - this.isWeb = false; - this.defaultContext = '//*'; + this.isWeb = false + this.defaultContext = '//*' - if (context) return this.switchToContext(context); - return this.switchToContext('NATIVE_APP'); + if (context) return this.switchToContext(context) + return this.switchToContext('NATIVE_APP') } /** @@ -904,8 +914,8 @@ class Appium extends Webdriver { * @return {Promise} */ async startActivity(appPackage, appActivity) { - onlyForApps.call(this, supportedPlatform.android); - return this.browser.startActivity(appPackage, appActivity); + onlyForApps.call(this, supportedPlatform.android) + return this.browser.startActivity(appPackage, appActivity) } /** @@ -930,8 +940,8 @@ class Appium extends Webdriver { * @return {Promise} */ async setNetworkConnection(value) { - onlyForApps.call(this, supportedPlatform.android); - return this.browser.setNetworkConnection(value); + onlyForApps.call(this, supportedPlatform.android) + return this.browser.setNetworkConnection(value) } /** @@ -946,8 +956,8 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async setSettings(settings) { - onlyForApps.call(this); - return this.browser.settings(settings); + onlyForApps.call(this) + return this.browser.settings(settings) } /** @@ -968,9 +978,9 @@ class Appium extends Webdriver { * @param {string} [key] Optional key */ async hideDeviceKeyboard(strategy, key) { - onlyForApps.call(this); - strategy = strategy || 'tapOutside'; - return this.browser.hideKeyboard(strategy, key); + onlyForApps.call(this) + strategy = strategy || 'tapOutside' + return this.browser.hideKeyboard(strategy, key) } /** @@ -987,8 +997,8 @@ class Appium extends Webdriver { * Appium: support only Android */ async sendDeviceKeyEvent(keyValue) { - onlyForApps.call(this, supportedPlatform.android); - return this.browser.pressKeyCode(keyValue); + onlyForApps.call(this, supportedPlatform.android) + return this.browser.pressKeyCode(keyValue) } /** @@ -1003,8 +1013,8 @@ class Appium extends Webdriver { * Appium: support only Android */ async openNotifications() { - onlyForApps.call(this, supportedPlatform.android); - return this.browser.openNotifications(); + onlyForApps.call(this, supportedPlatform.android) + return this.browser.openNotifications() } /** @@ -1023,13 +1033,13 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async makeTouchAction(locator, action) { - onlyForApps.call(this); - const element = await this.browser.$(parseLocator.call(this, locator)); + onlyForApps.call(this) + const element = await this.browser.$(parseLocator.call(this, locator)) return this.browser.touchAction({ action, element, - }); + }) } /** @@ -1046,7 +1056,7 @@ class Appium extends Webdriver { * @param {*} locator */ async tap(locator) { - return this.makeTouchAction(locator, 'tap'); + return this.makeTouchAction(locator, 'tap') } /** @@ -1069,10 +1079,13 @@ class Appium extends Webdriver { */ /* eslint-disable */ async swipe(locator, xoffset, yoffset, speed = 1000) { - onlyForApps.call(this); - const res = await this.browser.$(parseLocator.call(this, locator)); + onlyForApps.call(this) + const res = await this.browser.$(parseLocator.call(this, locator)) // if (!res.length) throw new ElementNotFound(locator, 'was not found in UI'); - return this.performSwipe(await res.getLocation(), { x: (await res.getLocation()).x + xoffset, y: (await res.getLocation()).y + yoffset }); + return this.performSwipe(await res.getLocation(), { + x: (await res.getLocation()).x + xoffset, + y: (await res.getLocation()).y + yoffset, + }) } /* eslint-enable */ @@ -1089,42 +1102,44 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async performSwipe(from, to) { - await this.browser.performActions([{ - id: uuidv4(), - type: 'pointer', - parameters: { - pointerType: 'touch', - }, - actions: [ - { - duration: 0, - x: from.x, - y: from.y, - type: 'pointerMove', - origin: 'viewport', - }, - { - button: 1, - type: 'pointerDown', - }, - { - duration: 200, - type: 'pause', - }, - { - duration: 600, - x: to.x, - y: to.y, - type: 'pointerMove', - origin: 'viewport', - }, - { - button: 1, - type: 'pointerUp', + await this.browser.performActions([ + { + id: uuidv4(), + type: 'pointer', + parameters: { + pointerType: 'touch', }, - ], - }]); - await this.browser.pause(1000); + actions: [ + { + duration: 0, + x: from.x, + y: from.y, + type: 'pointerMove', + origin: 'viewport', + }, + { + button: 1, + type: 'pointerDown', + }, + { + duration: 200, + type: 'pause', + }, + { + duration: 600, + x: to.x, + y: to.y, + type: 'pointerMove', + origin: 'viewport', + }, + { + button: 1, + type: 'pointerUp', + }, + ], + }, + ]) + await this.browser.pause(1000) } /** @@ -1145,14 +1160,14 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async swipeDown(locator, yoffset = 1000, speed) { - onlyForApps.call(this); + onlyForApps.call(this) if (!speed) { - speed = yoffset; - yoffset = 100; + speed = yoffset + yoffset = 100 } - return this.swipe(parseLocator.call(this, locator), 0, yoffset, speed); + return this.swipe(parseLocator.call(this, locator), 0, yoffset, speed) } /** @@ -1174,13 +1189,13 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async swipeLeft(locator, xoffset = 1000, speed) { - onlyForApps.call(this); + onlyForApps.call(this) if (!speed) { - speed = xoffset; - xoffset = 100; + speed = xoffset + xoffset = 100 } - return this.swipe(parseLocator.call(this, locator), -xoffset, 0, speed); + return this.swipe(parseLocator.call(this, locator), -xoffset, 0, speed) } /** @@ -1201,13 +1216,13 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async swipeRight(locator, xoffset = 1000, speed) { - onlyForApps.call(this); + onlyForApps.call(this) if (!speed) { - speed = xoffset; - xoffset = 100; + speed = xoffset + xoffset = 100 } - return this.swipe(parseLocator.call(this, locator), xoffset, 0, speed); + return this.swipe(parseLocator.call(this, locator), xoffset, 0, speed) } /** @@ -1228,14 +1243,14 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async swipeUp(locator, yoffset = 1000, speed) { - onlyForApps.call(this); + onlyForApps.call(this) if (!speed) { - speed = yoffset; - yoffset = 100; + speed = yoffset + yoffset = 100 } - return this.swipe(parseLocator.call(this, locator), 0, -yoffset, speed); + return this.swipe(parseLocator.call(this, locator), 0, -yoffset, speed) } /** @@ -1262,55 +1277,66 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async swipeTo(searchableLocator, scrollLocator, direction, timeout, offset, speed) { - onlyForApps.call(this); - direction = direction || 'down'; + onlyForApps.call(this) + direction = direction || 'down' switch (direction) { case 'down': - direction = 'swipeDown'; - break; + direction = 'swipeDown' + break case 'up': - direction = 'swipeUp'; - break; + direction = 'swipeUp' + break case 'left': - direction = 'swipeLeft'; - break; + direction = 'swipeLeft' + break case 'right': - direction = 'swipeRight'; - break; + direction = 'swipeRight' + break } - timeout = timeout || this.options.waitForTimeoutInSeconds; - - const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds`; - const browser = this.browser; - let err = false; - let currentSource; - return browser.waitUntil(() => { - if (err) { - return new Error(`Scroll to the end and element ${searchableLocator} was not found`); - } - return browser.$$(parseLocator.call(this, searchableLocator)) - .then(els => els.length && els[0].isDisplayed()) - .then((res) => { - if (res) { - return true; + timeout = timeout || this.options.waitForTimeoutInSeconds + + const errorMsg = `element ("${searchableLocator}") still not visible after ${timeout}seconds` + const browser = this.browser + let err = false + let currentSource + return browser + .waitUntil( + () => { + if (err) { + return new Error(`Scroll to the end and element ${searchableLocator} was not found`) } - return this[direction](scrollLocator, offset, speed).getSource().then((source) => { - if (source === currentSource) { - err = true; - } else { - currentSource = source; - return false; - } - }); - }); - }, timeout * 1000, errorMsg) + return browser + .$$(parseLocator.call(this, searchableLocator)) + .then((els) => els.length && els[0].isDisplayed()) + .then((res) => { + if (res) { + return true + } + return this[direction](scrollLocator, offset, speed) + .getSource() + .then((source) => { + if (source === currentSource) { + err = true + } else { + currentSource = source + return false + } + }) + }) + }, + timeout * 1000, + errorMsg, + ) .catch((e) => { if (e.message.indexOf('timeout') && e.type !== 'NoSuchElement') { - throw new AssertionFailedError({ customMessage: `Scroll to the end and element ${searchableLocator} was not found` }, ''); + throw new AssertionFailedError( + { customMessage: `Scroll to the end and element ${searchableLocator} was not found` }, + '', + ) } else { - throw e; + throw e } - }); + }) } /** @@ -1342,8 +1368,8 @@ class Appium extends Webdriver { * @param {Array} actions Array of touch actions */ async touchPerform(actions) { - onlyForApps.call(this); - return this.browser.touchPerform(actions); + onlyForApps.call(this) + return this.browser.touchPerform(actions) } /** @@ -1362,13 +1388,15 @@ class Appium extends Webdriver { * Appium: support Android and iOS */ async pullFile(path, dest) { - onlyForApps.call(this); - return this.browser.pullFile(path).then(res => fs.writeFile(dest, Buffer.from(res, 'base64'), (err) => { - if (err) { - return false; - } - return true; - })); + onlyForApps.call(this) + return this.browser.pullFile(path).then((res) => + fs.writeFile(dest, Buffer.from(res, 'base64'), (err) => { + if (err) { + return false + } + return true + }), + ) } /** @@ -1383,8 +1411,8 @@ class Appium extends Webdriver { * Appium: support only iOS */ async shakeDevice() { - onlyForApps.call(this, 'iOS'); - return this.browser.shake(); + onlyForApps.call(this, 'iOS') + return this.browser.shake() } /** @@ -1401,8 +1429,8 @@ class Appium extends Webdriver { * Appium: support only iOS */ async rotate(x, y, duration, radius, rotation, touchCount) { - onlyForApps.call(this, 'iOS'); - return this.browser.rotate(x, y, duration, radius, rotation, touchCount); + onlyForApps.call(this, 'iOS') + return this.browser.rotate(x, y, duration, radius, rotation, touchCount) } /** @@ -1415,8 +1443,8 @@ class Appium extends Webdriver { * Appium: support only iOS */ async setImmediateValue(id, value) { - onlyForApps.call(this, 'iOS'); - return this.browser.setImmediateValue(id, value); + onlyForApps.call(this, 'iOS') + return this.browser.setImmediateValue(id, value) } /** @@ -1434,9 +1462,9 @@ class Appium extends Webdriver { * TODO: not tested */ async simulateTouchId(match) { - onlyForApps.call(this, 'iOS'); - match = match || true; - return this.browser.touchId(match); + onlyForApps.call(this, 'iOS') + match = match || true + return this.browser.touchId(match) } /** @@ -1451,8 +1479,8 @@ class Appium extends Webdriver { * Appium: support both Android and iOS */ async closeApp() { - onlyForApps.call(this); - return this.browser.closeApp(); + onlyForApps.call(this) + return this.browser.closeApp() } /** @@ -1460,8 +1488,8 @@ class Appium extends Webdriver { * */ async appendField(field, value) { - if (this.isWeb) return super.appendField(field, value); - return super.appendField(parseLocator.call(this, field), value); + if (this.isWeb) return super.appendField(field, value) + return super.appendField(parseLocator.call(this, field), value) } /** @@ -1469,8 +1497,8 @@ class Appium extends Webdriver { * */ async checkOption(field) { - if (this.isWeb) return super.checkOption(field); - return super.checkOption(parseLocator.call(this, field)); + if (this.isWeb) return super.checkOption(field) + return super.checkOption(parseLocator.call(this, field)) } /** @@ -1478,8 +1506,8 @@ class Appium extends Webdriver { * */ async click(locator, context) { - if (this.isWeb) return super.click(locator, context); - return super.click(parseLocator.call(this, locator), parseLocator.call(this, context)); + if (this.isWeb) return super.click(locator, context) + return super.click(parseLocator.call(this, locator), parseLocator.call(this, context)) } /** @@ -1487,16 +1515,16 @@ class Appium extends Webdriver { * */ async dontSeeCheckboxIsChecked(field) { - if (this.isWeb) return super.dontSeeCheckboxIsChecked(field); - return super.dontSeeCheckboxIsChecked(parseLocator.call(this, field)); + if (this.isWeb) return super.dontSeeCheckboxIsChecked(field) + return super.dontSeeCheckboxIsChecked(parseLocator.call(this, field)) } /** * {{> dontSeeElement }} */ async dontSeeElement(locator) { - if (this.isWeb) return super.dontSeeElement(locator); - return super.dontSeeElement(parseLocator.call(this, locator)); + if (this.isWeb) return super.dontSeeElement(locator) + return super.dontSeeElement(parseLocator.call(this, locator)) } /** @@ -1504,17 +1532,17 @@ class Appium extends Webdriver { * */ async dontSeeInField(field, value) { - const _value = (typeof value === 'boolean') ? value : value.toString(); - if (this.isWeb) return super.dontSeeInField(field, _value); - return super.dontSeeInField(parseLocator.call(this, field), _value); + const _value = typeof value === 'boolean' ? value : value.toString() + if (this.isWeb) return super.dontSeeInField(field, _value) + return super.dontSeeInField(parseLocator.call(this, field), _value) } /** * {{> dontSee }} */ async dontSee(text, context = null) { - if (this.isWeb) return super.dontSee(text, context); - return super.dontSee(text, parseLocator.call(this, context)); + if (this.isWeb) return super.dontSee(text, context) + return super.dontSee(text, parseLocator.call(this, context)) } /** @@ -1522,9 +1550,9 @@ class Appium extends Webdriver { * */ async fillField(field, value) { - value = value.toString(); - if (this.isWeb) return super.fillField(field, value); - return super.fillField(parseLocator.call(this, field), value); + value = value.toString() + if (this.isWeb) return super.fillField(field, value) + return super.fillField(parseLocator.call(this, field), value) } /** @@ -1532,8 +1560,8 @@ class Appium extends Webdriver { * */ async grabTextFromAll(locator) { - if (this.isWeb) return super.grabTextFromAll(locator); - return super.grabTextFromAll(parseLocator.call(this, locator)); + if (this.isWeb) return super.grabTextFromAll(locator) + return super.grabTextFromAll(parseLocator.call(this, locator)) } /** @@ -1541,16 +1569,16 @@ class Appium extends Webdriver { * */ async grabTextFrom(locator) { - if (this.isWeb) return super.grabTextFrom(locator); - return super.grabTextFrom(parseLocator.call(this, locator)); + if (this.isWeb) return super.grabTextFrom(locator) + return super.grabTextFrom(parseLocator.call(this, locator)) } /** * {{> grabNumberOfVisibleElements }} */ async grabNumberOfVisibleElements(locator) { - if (this.isWeb) return super.grabNumberOfVisibleElements(locator); - return super.grabNumberOfVisibleElements(parseLocator.call(this, locator)); + if (this.isWeb) return super.grabNumberOfVisibleElements(locator) + return super.grabNumberOfVisibleElements(parseLocator.call(this, locator)) } /** @@ -1559,8 +1587,8 @@ class Appium extends Webdriver { * {{> grabAttributeFrom }} */ async grabAttributeFrom(locator, attr) { - if (this.isWeb) return super.grabAttributeFrom(locator, attr); - return super.grabAttributeFrom(parseLocator.call(this, locator), attr); + if (this.isWeb) return super.grabAttributeFrom(locator, attr) + return super.grabAttributeFrom(parseLocator.call(this, locator), attr) } /** @@ -1568,8 +1596,8 @@ class Appium extends Webdriver { * {{> grabAttributeFromAll }} */ async grabAttributeFromAll(locator, attr) { - if (this.isWeb) return super.grabAttributeFromAll(locator, attr); - return super.grabAttributeFromAll(parseLocator.call(this, locator), attr); + if (this.isWeb) return super.grabAttributeFromAll(locator, attr) + return super.grabAttributeFromAll(parseLocator.call(this, locator), attr) } /** @@ -1577,8 +1605,8 @@ class Appium extends Webdriver { * */ async grabValueFromAll(locator) { - if (this.isWeb) return super.grabValueFromAll(locator); - return super.grabValueFromAll(parseLocator.call(this, locator)); + if (this.isWeb) return super.grabValueFromAll(locator) + return super.grabValueFromAll(parseLocator.call(this, locator)) } /** @@ -1586,8 +1614,8 @@ class Appium extends Webdriver { * */ async grabValueFrom(locator) { - if (this.isWeb) return super.grabValueFrom(locator); - return super.grabValueFrom(parseLocator.call(this, locator)); + if (this.isWeb) return super.grabValueFrom(locator) + return super.grabValueFrom(parseLocator.call(this, locator)) } /** @@ -1602,7 +1630,7 @@ class Appium extends Webdriver { * @return {Promise} */ async saveScreenshot(fileName) { - return super.saveScreenshot(fileName, false); + return super.saveScreenshot(fileName, false) } /** @@ -1611,7 +1639,7 @@ class Appium extends Webdriver { * Supported only for web testing */ async scrollIntoView(locator, scrollIntoViewOptions) { - if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions); + if (this.isWeb) return super.scrollIntoView(locator, scrollIntoViewOptions) } /** @@ -1619,8 +1647,8 @@ class Appium extends Webdriver { * */ async seeCheckboxIsChecked(field) { - if (this.isWeb) return super.seeCheckboxIsChecked(field); - return super.seeCheckboxIsChecked(parseLocator.call(this, field)); + if (this.isWeb) return super.seeCheckboxIsChecked(field) + return super.seeCheckboxIsChecked(parseLocator.call(this, field)) } /** @@ -1628,8 +1656,8 @@ class Appium extends Webdriver { * */ async seeElement(locator) { - if (this.isWeb) return super.seeElement(locator); - return super.seeElement(parseLocator.call(this, locator)); + if (this.isWeb) return super.seeElement(locator) + return super.seeElement(parseLocator.call(this, locator)) } /** @@ -1637,9 +1665,9 @@ class Appium extends Webdriver { * */ async seeInField(field, value) { - const _value = (typeof value === 'boolean') ? value : value.toString(); - if (this.isWeb) return super.seeInField(field, _value); - return super.seeInField(parseLocator.call(this, field), _value); + const _value = typeof value === 'boolean' ? value : value.toString() + if (this.isWeb) return super.seeInField(field, _value) + return super.seeInField(parseLocator.call(this, field), _value) } /** @@ -1647,8 +1675,8 @@ class Appium extends Webdriver { * */ async see(text, context) { - if (this.isWeb) return super.see(text, context); - return super.see(text, parseLocator.call(this, context)); + if (this.isWeb) return super.see(text, context) + return super.see(text, parseLocator.call(this, context)) } /** @@ -1657,8 +1685,8 @@ class Appium extends Webdriver { * Supported only for web testing */ async selectOption(select, option) { - if (this.isWeb) return super.selectOption(select, option); - throw new Error('Should be used only in Web context. In native context use \'click\' method instead'); + if (this.isWeb) return super.selectOption(select, option) + throw new Error("Should be used only in Web context. In native context use 'click' method instead") } /** @@ -1666,8 +1694,8 @@ class Appium extends Webdriver { * */ async waitForElement(locator, sec = null) { - if (this.isWeb) return super.waitForElement(locator, sec); - return super.waitForElement(parseLocator.call(this, locator), sec); + if (this.isWeb) return super.waitForElement(locator, sec) + return super.waitForElement(parseLocator.call(this, locator), sec) } /** @@ -1675,8 +1703,8 @@ class Appium extends Webdriver { * */ async waitForVisible(locator, sec = null) { - if (this.isWeb) return super.waitForVisible(locator, sec); - return super.waitForVisible(parseLocator.call(this, locator), sec); + if (this.isWeb) return super.waitForVisible(locator, sec) + return super.waitForVisible(parseLocator.call(this, locator), sec) } /** @@ -1684,8 +1712,8 @@ class Appium extends Webdriver { * */ async waitForInvisible(locator, sec = null) { - if (this.isWeb) return super.waitForInvisible(locator, sec); - return super.waitForInvisible(parseLocator.call(this, locator), sec); + if (this.isWeb) return super.waitForInvisible(locator, sec) + return super.waitForInvisible(parseLocator.call(this, locator), sec) } /** @@ -1693,74 +1721,76 @@ class Appium extends Webdriver { * */ async waitForText(text, sec = null, context = null) { - if (this.isWeb) return super.waitForText(text, sec, context); - return super.waitForText(text, sec, parseLocator.call(this, context)); + if (this.isWeb) return super.waitForText(text, sec, context) + return super.waitForText(text, sec, parseLocator.call(this, context)) } } function parseLocator(locator) { - if (!locator) return null; + if (!locator) return null if (typeof locator === 'object') { if (locator.web && this.isWeb) { - return parseLocator.call(this, locator.web); + return parseLocator.call(this, locator.web) } if (locator.android && this.platform === 'android') { if (typeof locator.android === 'string') { - return parseLocator.call(this, locator.android); + return parseLocator.call(this, locator.android) } // The locator is an Android DataMatcher or ViewMatcher locator so return as is - return locator.android; + return locator.android } if (locator.ios && this.platform === 'ios') { - return parseLocator.call(this, locator.ios); + return parseLocator.call(this, locator.ios) } } if (typeof locator === 'string') { - if (locator[0] === '~') return locator; - if (locator.substr(0, 2) === '//') return locator; + if (locator[0] === '~') return locator + if (locator.substr(0, 2) === '//') return locator if (locator[0] === '#' && !this.isWeb) { // hook before webdriverio supports native # locators - return parseLocator.call(this, { id: locator.slice(1) }); + return parseLocator.call(this, { id: locator.slice(1) }) } if (this.platform === 'android' && !this.isWeb) { - const isNativeLocator = /^\-?android=?/.exec(locator); - return isNativeLocator - ? locator - : `android=new UiSelector().text("${locator}")`; + const isNativeLocator = /^\-?android=?/.exec(locator) + return isNativeLocator ? locator : `android=new UiSelector().text("${locator}")` } } - locator = new Locator(locator, 'xpath'); - if (locator.type === 'css' && !this.isWeb) throw new Error('Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id'); - if (locator.type === 'name' && !this.isWeb) throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id"); - if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return `//*[@resource-id='${locator.value}']`; - return locator.simplify(); + locator = new Locator(locator, 'xpath') + if (locator.type === 'css' && !this.isWeb) + throw new Error( + 'Unable to use css locators in apps. Locator strategies for this request: xpath, id, class name or accessibility id', + ) + if (locator.type === 'name' && !this.isWeb) + throw new Error("Can't locate element by name in Native context. Use either ID, class name or accessibility id") + if (locator.type === 'id' && !this.isWeb && this.platform === 'android') return `//*[@resource-id='${locator.value}']` + return locator.simplify() } // in the end of a file function onlyForApps(expectedPlatform) { - const stack = new Error().stack || ''; - const re = /Appium.(\w+)/g; - const caller = stack.split('\n')[2].trim(); - const m = re.exec(caller); + const stack = new Error().stack || '' + const re = /Appium.(\w+)/g + const caller = stack.split('\n')[2].trim() + const m = re.exec(caller) if (!m) { - throw new Error(`Invalid caller ${caller}`); + throw new Error(`Invalid caller ${caller}`) } - const callerName = m[1] || m[2]; + const callerName = m[1] || m[2] if (!expectedPlatform) { if (!this.platform) { - throw new Error(`${callerName} method can be used only with apps`); + throw new Error(`${callerName} method can be used only with apps`) } } else if (this.platform !== expectedPlatform.toLowerCase()) { - throw new Error(`${callerName} method can be used only with ${expectedPlatform} apps`); + throw new Error(`${callerName} method can be used only with ${expectedPlatform} apps`) } } -module.exports = Appium; +module.exports = Appium diff --git a/lib/helper/ExpectHelper.js b/lib/helper/ExpectHelper.js index c6a9dd27d..8d891e0ac 100644 --- a/lib/helper/ExpectHelper.js +++ b/lib/helper/ExpectHelper.js @@ -1,15 +1,15 @@ -const output = require('../output'); +const output = require('../output') -let expect; +let expect -import('chai').then(chai => { - expect = chai.expect; - chai.use(require('chai-string')); +import('chai').then((chai) => { + expect = chai.expect + chai.use(require('chai-string')) // @ts-ignore - chai.use(require('chai-exclude')); - chai.use(require('chai-match-pattern')); - chai.use(require('chai-json-schema')); -}); + chai.use(require('chai-exclude')) + chai.use(require('chai-match-pattern')) + chai.use(require('chai-json-schema')) +}) /** * This helper allows performing assertions based on Chai. @@ -32,27 +32,27 @@ import('chai').then(chai => { */ class ExpectHelper { /** - * - * @param {*} actualValue - * @param {*} expectedValue - * @param {*} [customErrorMsg] - */ + * + * @param {*} actualValue + * @param {*} expectedValue + * @param {*} [customErrorMsg] + */ expectEqual(actualValue, expectedValue, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to equal "${JSON.stringify(expectedValue)}"`); - return expect(actualValue, customErrorMsg).to.equal(expectedValue); + output.step(`I expect "${JSON.stringify(actualValue)}" to equal "${JSON.stringify(expectedValue)}"`) + return expect(actualValue, customErrorMsg).to.equal(expectedValue) } /** - * - * @param {*} actualValue - * @param {*} expectedValue - * @param {*} [customErrorMsg] - */ + * + * @param {*} actualValue + * @param {*} expectedValue + * @param {*} [customErrorMsg] + */ expectNotEqual(actualValue, expectedValue, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to not equal "${JSON.stringify(expectedValue)}"`); - return expect(actualValue, customErrorMsg).not.to.equal(expectedValue); + output.step(`I expect "${JSON.stringify(actualValue)}" to not equal "${JSON.stringify(expectedValue)}"`) + return expect(actualValue, customErrorMsg).not.to.equal(expectedValue) } /** @@ -64,362 +64,328 @@ class ExpectHelper { */ expectDeepEqual(actualValue, expectedValue, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to deep equal "${JSON.stringify(expectedValue)}"`); - return expect(actualValue, customErrorMsg).to.deep.equal(expectedValue); + output.step(`I expect "${JSON.stringify(actualValue)}" to deep equal "${JSON.stringify(expectedValue)}"`) + return expect(actualValue, customErrorMsg).to.deep.equal(expectedValue) } /** - * - * @param {*} actualValue - * @param {*} expectedValue - * @param {*} [customErrorMsg] - */ + * + * @param {*} actualValue + * @param {*} expectedValue + * @param {*} [customErrorMsg] + */ expectNotDeepEqual(actualValue, expectedValue, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to not deep equal "${JSON.stringify(expectedValue)}"`); - return expect(actualValue, customErrorMsg).to.not.deep.equal(expectedValue); + output.step(`I expect "${JSON.stringify(actualValue)}" to not deep equal "${JSON.stringify(expectedValue)}"`) + return expect(actualValue, customErrorMsg).to.not.deep.equal(expectedValue) } /** - * - * @param {*} actualValue - * @param {*} expectedValueToContain - * @param {*} [customErrorMsg] - */ + * + * @param {*} actualValue + * @param {*} expectedValueToContain + * @param {*} [customErrorMsg] + */ expectContain(actualValue, expectedValueToContain, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to contain "${JSON.stringify(expectedValueToContain)}"`); - return expect(actualValue, customErrorMsg).to.contain( - expectedValueToContain, - ); + output.step(`I expect "${JSON.stringify(actualValue)}" to contain "${JSON.stringify(expectedValueToContain)}"`) + return expect(actualValue, customErrorMsg).to.contain(expectedValueToContain) } /** - * - * @param {*} actualValue - * @param {*} expectedValueToNotContain - * @param {*} [customErrorMsg] - */ - expectNotContain( - actualValue, - expectedValueToNotContain, - customErrorMsg = '', - ) { + * + * @param {*} actualValue + * @param {*} expectedValueToNotContain + * @param {*} [customErrorMsg] + */ + expectNotContain(actualValue, expectedValueToNotContain, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to not contain "${JSON.stringify(expectedValueToNotContain)}"`); - return expect(actualValue, customErrorMsg).not.to.contain( - expectedValueToNotContain, - ); + output.step( + `I expect "${JSON.stringify(actualValue)}" to not contain "${JSON.stringify(expectedValueToNotContain)}"`, + ) + return expect(actualValue, customErrorMsg).not.to.contain(expectedValueToNotContain) } /** - * - * @param {*} actualValue - * @param {*} expectedValueToStartWith - * @param {*} [customErrorMsg] - */ + * + * @param {*} actualValue + * @param {*} expectedValueToStartWith + * @param {*} [customErrorMsg] + */ expectStartsWith(actualValue, expectedValueToStartWith, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to start with "${JSON.stringify(expectedValueToStartWith)}"`); - return expect(actualValue, customErrorMsg).to.startsWith( - expectedValueToStartWith, - ); + output.step(`I expect "${JSON.stringify(actualValue)}" to start with "${JSON.stringify(expectedValueToStartWith)}"`) + return expect(actualValue, customErrorMsg).to.startsWith(expectedValueToStartWith) } /** - * - * @param {*} actualValue - * @param {*} expectedValueToNotStartWith - * @param {*} [customErrorMsg] - */ - expectNotStartsWith( - actualValue, - expectedValueToNotStartWith, - customErrorMsg = '', - ) { + * + * @param {*} actualValue + * @param {*} expectedValueToNotStartWith + * @param {*} [customErrorMsg] + */ + expectNotStartsWith(actualValue, expectedValueToNotStartWith, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to not start with "${JSON.stringify(expectedValueToNotStartWith)}"`); - return expect(actualValue, customErrorMsg).not.to.startsWith( - expectedValueToNotStartWith, - ); + output.step( + `I expect "${JSON.stringify(actualValue)}" to not start with "${JSON.stringify(expectedValueToNotStartWith)}"`, + ) + return expect(actualValue, customErrorMsg).not.to.startsWith(expectedValueToNotStartWith) } /** - * @param {*} actualValue - * @param {*} expectedValueToEndWith - * @param {*} [customErrorMsg] - */ + * @param {*} actualValue + * @param {*} expectedValueToEndWith + * @param {*} [customErrorMsg] + */ expectEndsWith(actualValue, expectedValueToEndWith, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to end with "${JSON.stringify(expectedValueToEndWith)}"`); - return expect(actualValue, customErrorMsg).to.endsWith( - expectedValueToEndWith, - ); + output.step(`I expect "${JSON.stringify(actualValue)}" to end with "${JSON.stringify(expectedValueToEndWith)}"`) + return expect(actualValue, customErrorMsg).to.endsWith(expectedValueToEndWith) } /** - * @param {*} actualValue - * @param {*} expectedValueToNotEndWith - * @param {*} [customErrorMsg] - */ - expectNotEndsWith( - actualValue, - expectedValueToNotEndWith, - customErrorMsg = '', - ) { + * @param {*} actualValue + * @param {*} expectedValueToNotEndWith + * @param {*} [customErrorMsg] + */ + expectNotEndsWith(actualValue, expectedValueToNotEndWith, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to not end with "${JSON.stringify(expectedValueToNotEndWith)}"`); - return expect(actualValue, customErrorMsg).not.to.endsWith( - expectedValueToNotEndWith, - ); + output.step( + `I expect "${JSON.stringify(actualValue)}" to not end with "${JSON.stringify(expectedValueToNotEndWith)}"`, + ) + return expect(actualValue, customErrorMsg).not.to.endsWith(expectedValueToNotEndWith) } /** - * @param {*} targetData - * @param {*} jsonSchema - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} jsonSchema + * @param {*} [customErrorMsg] + */ expectJsonSchema(targetData, jsonSchema, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to match this JSON schema "${JSON.stringify(jsonSchema)}"`); + output.step(`I expect "${JSON.stringify(targetData)}" to match this JSON schema "${JSON.stringify(jsonSchema)}"`) - return expect(targetData, customErrorMsg).to.be.jsonSchema(jsonSchema); + return expect(targetData, customErrorMsg).to.be.jsonSchema(jsonSchema) } /** - * @param {*} targetData - * @param {*} jsonSchema - * @param {*} [customErrorMsg] - * @param {*} [ajvOptions] Pass AJV options - */ - expectJsonSchemaUsingAJV( - targetData, - jsonSchema, - customErrorMsg = '', - ajvOptions = { allErrors: true }, - ) { + * @param {*} targetData + * @param {*} jsonSchema + * @param {*} [customErrorMsg] + * @param {*} [ajvOptions] Pass AJV options + */ + expectJsonSchemaUsingAJV(targetData, jsonSchema, customErrorMsg = '', ajvOptions = { allErrors: true }) { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to match this JSON schema using AJV "${JSON.stringify(jsonSchema)}"`); - chai.use(require('chai-json-schema-ajv').create(ajvOptions)); - return expect(targetData, customErrorMsg).to.be.jsonSchema(jsonSchema); + output.step( + `I expect "${JSON.stringify(targetData)}" to match this JSON schema using AJV "${JSON.stringify(jsonSchema)}"`, + ) + chai.use(require('chai-json-schema-ajv').create(ajvOptions)) + return expect(targetData, customErrorMsg).to.be.jsonSchema(jsonSchema) } /** - * @param {*} targetData - * @param {*} propertyName - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} propertyName + * @param {*} [customErrorMsg] + */ expectHasProperty(targetData, propertyName, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to have property: "${JSON.stringify(propertyName)}"`); - return expect(targetData, customErrorMsg).to.have.property(propertyName); + output.step(`I expect "${JSON.stringify(targetData)}" to have property: "${JSON.stringify(propertyName)}"`) + return expect(targetData, customErrorMsg).to.have.property(propertyName) } /** - * @param {*} targetData - * @param {*} propertyName - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} propertyName + * @param {*} [customErrorMsg] + */ expectHasAProperty(targetData, propertyName, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to have a property: "${JSON.stringify(propertyName)}"`); - return expect(targetData, customErrorMsg).to.have.a.property(propertyName); + output.step(`I expect "${JSON.stringify(targetData)}" to have a property: "${JSON.stringify(propertyName)}"`) + return expect(targetData, customErrorMsg).to.have.a.property(propertyName) } /** - * @param {*} targetData - * @param {*} type - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} type + * @param {*} [customErrorMsg] + */ expectToBeA(targetData, type, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be a "${JSON.stringify(type)}"`); - return expect(targetData, customErrorMsg).to.be.a(type); + output.step(`I expect "${JSON.stringify(targetData)}" to be a "${JSON.stringify(type)}"`) + return expect(targetData, customErrorMsg).to.be.a(type) } /** - * @param {*} targetData - * @param {*} type - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} type + * @param {*} [customErrorMsg] + */ expectToBeAn(targetData, type, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be an "${JSON.stringify(type)}"`); - return expect(targetData, customErrorMsg).to.be.an(type); + output.step(`I expect "${JSON.stringify(targetData)}" to be an "${JSON.stringify(type)}"`) + return expect(targetData, customErrorMsg).to.be.an(type) } /** - * @param {*} targetData - * @param {*} regex - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} regex + * @param {*} [customErrorMsg] + */ expectMatchRegex(targetData, regex, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to match the regex "${JSON.stringify(regex)}"`); - return expect(targetData, customErrorMsg).to.match(regex); + output.step(`I expect "${JSON.stringify(targetData)}" to match the regex "${JSON.stringify(regex)}"`) + return expect(targetData, customErrorMsg).to.match(regex) } /** - * @param {*} targetData - * @param {*} length - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} length + * @param {*} [customErrorMsg] + */ expectLengthOf(targetData, length, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to have length of "${JSON.stringify(length)}"`); - return expect(targetData, customErrorMsg).to.have.lengthOf(length); + output.step(`I expect "${JSON.stringify(targetData)}" to have length of "${JSON.stringify(length)}"`) + return expect(targetData, customErrorMsg).to.have.lengthOf(length) } /** - * @param {*} targetData - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} [customErrorMsg] + */ expectEmpty(targetData, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be empty`); - return expect(targetData, customErrorMsg).to.be.empty; + output.step(`I expect "${JSON.stringify(targetData)}" to be empty`) + return expect(targetData, customErrorMsg).to.be.empty } /** - * @param {*} targetData - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} [customErrorMsg] + */ expectTrue(targetData, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be true`); - return expect(targetData, customErrorMsg).to.be.true; + output.step(`I expect "${JSON.stringify(targetData)}" to be true`) + return expect(targetData, customErrorMsg).to.be.true } /** - * @param {*} targetData - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} [customErrorMsg] + */ expectFalse(targetData, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be false`); - return expect(targetData, customErrorMsg).to.be.false; + output.step(`I expect "${JSON.stringify(targetData)}" to be false`) + return expect(targetData, customErrorMsg).to.be.false } /** - * @param {*} targetData - * @param {*} aboveThan - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} aboveThan + * @param {*} [customErrorMsg] + */ expectAbove(targetData, aboveThan, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be above ${JSON.stringify(aboveThan)}`); - return expect(targetData, customErrorMsg).to.be.above(aboveThan); + output.step(`I expect "${JSON.stringify(targetData)}" to be above ${JSON.stringify(aboveThan)}`) + return expect(targetData, customErrorMsg).to.be.above(aboveThan) } /** - * @param {*} targetData - * @param {*} belowThan - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} belowThan + * @param {*} [customErrorMsg] + */ expectBelow(targetData, belowThan, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to be below ${JSON.stringify(belowThan)}`); - return expect(targetData, customErrorMsg).to.be.below(belowThan); + output.step(`I expect "${JSON.stringify(targetData)}" to be below ${JSON.stringify(belowThan)}`) + return expect(targetData, customErrorMsg).to.be.below(belowThan) } /** - * @param {*} targetData - * @param {*} lengthAboveThan - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} lengthAboveThan + * @param {*} [customErrorMsg] + */ expectLengthAboveThan(targetData, lengthAboveThan, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to have length of above ${JSON.stringify(lengthAboveThan)}`); - return expect(targetData, customErrorMsg).to.have.lengthOf.above( - lengthAboveThan, - ); + output.step(`I expect "${JSON.stringify(targetData)}" to have length of above ${JSON.stringify(lengthAboveThan)}`) + return expect(targetData, customErrorMsg).to.have.lengthOf.above(lengthAboveThan) } /** - * @param {*} targetData - * @param {*} lengthBelowThan - * @param {*} [customErrorMsg] - */ + * @param {*} targetData + * @param {*} lengthBelowThan + * @param {*} [customErrorMsg] + */ expectLengthBelowThan(targetData, lengthBelowThan, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(targetData)}" to have length of below ${JSON.stringify(lengthBelowThan)}`); - return expect(targetData, customErrorMsg).to.have.lengthOf.below( - lengthBelowThan, - ); + output.step(`I expect "${JSON.stringify(targetData)}" to have length of below ${JSON.stringify(lengthBelowThan)}`) + return expect(targetData, customErrorMsg).to.have.lengthOf.below(lengthBelowThan) } /** - * @param {*} actualValue - * @param {*} expectedValue - * @param {*} [customErrorMsg] - */ + * @param {*} actualValue + * @param {*} expectedValue + * @param {*} [customErrorMsg] + */ expectEqualIgnoreCase(actualValue, expectedValue, customErrorMsg = '') { // @ts-ignore - output.step(`I expect and ingore case "${JSON.stringify(actualValue)}" to equal "${JSON.stringify(expectedValue)}"`); - return expect(actualValue, customErrorMsg).to.equalIgnoreCase( - expectedValue, - ); + output.step(`I expect and ingore case "${JSON.stringify(actualValue)}" to equal "${JSON.stringify(expectedValue)}"`) + return expect(actualValue, customErrorMsg).to.equalIgnoreCase(expectedValue) } /** - * expects members of two arrays are deeply equal - * @param {*} actualValue - * @param {*} expectedValue - * @param {*} [customErrorMsg] - */ + * expects members of two arrays are deeply equal + * @param {*} actualValue + * @param {*} expectedValue + * @param {*} [customErrorMsg] + */ expectDeepMembers(actualValue, expectedValue, customErrorMsg = '') { // @ts-ignore - output.step(`I expect members of "${JSON.stringify(actualValue)}" and "${JSON.stringify(expectedValue)}" arrays are deeply equal`); - return expect(actualValue, customErrorMsg).to.have.deep.members( - expectedValue, - ); + output.step( + `I expect members of "${JSON.stringify(actualValue)}" and "${JSON.stringify(expectedValue)}" arrays are deeply equal`, + ) + return expect(actualValue, customErrorMsg).to.have.deep.members(expectedValue) } /** - * expects an array to be a superset of another array - * @param {*} superset - * @param {*} set - * @param {*} [customErrorMsg] - */ + * expects an array to be a superset of another array + * @param {*} superset + * @param {*} set + * @param {*} [customErrorMsg] + */ expectDeepIncludeMembers(superset, set, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(superset)}" array to be a superset of "${JSON.stringify(set)}" array`); - return expect(superset, customErrorMsg).to.deep.include.members( - set, - ); + output.step(`I expect "${JSON.stringify(superset)}" array to be a superset of "${JSON.stringify(set)}" array`) + return expect(superset, customErrorMsg).to.deep.include.members(set) } /** - * expects members of two JSON objects are deeply equal excluding some properties - * @param {*} actualValue - * @param {*} expectedValue - * @param {*} fieldsToExclude - * @param {*} [customErrorMsg] - */ - expectDeepEqualExcluding( - actualValue, - expectedValue, - fieldsToExclude, - customErrorMsg = '', - ) { + * expects members of two JSON objects are deeply equal excluding some properties + * @param {*} actualValue + * @param {*} expectedValue + * @param {*} fieldsToExclude + * @param {*} [customErrorMsg] + */ + expectDeepEqualExcluding(actualValue, expectedValue, fieldsToExclude, customErrorMsg = '') { // @ts-ignore - output.step(`I expect members of "${JSON.stringify(actualValue)}" and "${JSON.stringify(expectedValue)}" JSON objects are deeply equal excluding properties: ${JSON.stringify(fieldsToExclude)}`); - return expect(actualValue, customErrorMsg) - .excludingEvery(fieldsToExclude) - .to.deep.equal(expectedValue); + output.step( + `I expect members of "${JSON.stringify(actualValue)}" and "${JSON.stringify(expectedValue)}" JSON objects are deeply equal excluding properties: ${JSON.stringify(fieldsToExclude)}`, + ) + return expect(actualValue, customErrorMsg).excludingEvery(fieldsToExclude).to.deep.equal(expectedValue) } /** - * expects a JSON object matches a provided pattern - * @param {*} actualValue - * @param {*} expectedPattern - * @param {*} [customErrorMsg] - */ + * expects a JSON object matches a provided pattern + * @param {*} actualValue + * @param {*} expectedPattern + * @param {*} [customErrorMsg] + */ expectMatchesPattern(actualValue, expectedPattern, customErrorMsg = '') { // @ts-ignore - output.step(`I expect "${JSON.stringify(actualValue)}" to match the ${JSON.stringify(expectedPattern)} pattern`); - return expect(actualValue, customErrorMsg).to.matchPattern(expectedPattern); + output.step(`I expect "${JSON.stringify(actualValue)}" to match the ${JSON.stringify(expectedPattern)} pattern`) + return expect(actualValue, customErrorMsg).to.matchPattern(expectedPattern) } } -module.exports = ExpectHelper; +module.exports = ExpectHelper diff --git a/lib/helper/FileSystem.js b/lib/helper/FileSystem.js index 830936c7d..68d6d645d 100644 --- a/lib/helper/FileSystem.js +++ b/lib/helper/FileSystem.js @@ -1,11 +1,11 @@ -const assert = require('assert'); -const path = require('path'); -const fs = require('fs'); +const assert = require('assert') +const path = require('path') +const fs = require('fs') -const Helper = require('@codeceptjs/helper'); -const { fileExists } = require('../utils'); -const { fileIncludes } = require('../assert/include'); -const { fileEquals } = require('../assert/equal'); +const Helper = require('@codeceptjs/helper') +const { fileExists } = require('../utils') +const { fileIncludes } = require('../assert/include') +const { fileEquals } = require('../assert/equal') /** * Helper for testing filesystem. @@ -32,13 +32,13 @@ const { fileEquals } = require('../assert/equal'); */ class FileSystem extends Helper { constructor() { - super(); - this.dir = global.codecept_dir; - this.file = ''; + super() + this.dir = global.codecept_dir + this.file = '' } _before() { - this.debugSection('Dir', this.dir); + this.debugSection('Dir', this.dir) } /** @@ -47,8 +47,8 @@ class FileSystem extends Helper { * @param {string} openPath */ amInPath(openPath) { - this.dir = path.join(global.codecept_dir, openPath); - this.debugSection('Dir', this.dir); + this.dir = path.join(global.codecept_dir, openPath) + this.debugSection('Dir', this.dir) } /** @@ -57,7 +57,7 @@ class FileSystem extends Helper { * @param {string} text */ writeToFile(name, text) { - fs.writeFileSync(path.join(this.dir, name), text); + fs.writeFileSync(path.join(this.dir, name), text) } /** @@ -65,9 +65,9 @@ class FileSystem extends Helper { * @param {string} name */ seeFile(name) { - this.file = path.join(this.dir, name); - this.debugSection('File', this.file); - assert.ok(fileExists(this.file), `File ${name} not found in ${this.dir}`); + this.file = path.join(this.dir, name) + this.debugSection('File', this.file) + assert.ok(fileExists(this.file), `File ${name} not found in ${this.dir}`) } /** @@ -83,31 +83,31 @@ class FileSystem extends Helper { * @param {number} [sec=1] seconds to wait */ async waitForFile(name, sec = 1) { - if (sec === 0) assert.fail('Use `seeFile` instead of waiting 0 seconds!'); - const waitTimeout = sec * 1000; - this.file = path.join(this.dir, name); - this.debugSection('File', this.file); + if (sec === 0) assert.fail('Use `seeFile` instead of waiting 0 seconds!') + const waitTimeout = sec * 1000 + this.file = path.join(this.dir, name) + this.debugSection('File', this.file) return isFileExists(this.file, waitTimeout).catch(() => { - throw new Error(`file (${name}) still not present in directory ${this.dir} after ${waitTimeout / 1000} sec`); - }); + throw new Error(`file (${name}) still not present in directory ${this.dir} after ${waitTimeout / 1000} sec`) + }) } /** - * Checks that file with a name including given text exists in the current directory. - * - *```js - * I.handleDownloads(); - * I.click('Download as PDF'); - * I.amInPath('output/downloads'); - * I.seeFileNameMatching('.pdf'); - * ``` - * @param {string} text - */ + * Checks that file with a name including given text exists in the current directory. + * + *```js + * I.handleDownloads(); + * I.click('Download as PDF'); + * I.amInPath('output/downloads'); + * I.seeFileNameMatching('.pdf'); + * ``` + * @param {string} text + */ seeFileNameMatching(text) { assert.ok( - this.grabFileNames().some(file => file.includes(text)), + this.grabFileNames().some((file) => file.includes(text)), `File name which contains ${text} not found in ${this.dir}`, - ); + ) } /** @@ -116,8 +116,8 @@ class FileSystem extends Helper { * @param {string} [encoding='utf8'] */ seeInThisFile(text, encoding = 'utf8') { - const content = getFileContents(this.file, encoding); - fileIncludes(this.file).assert(text, content); + const content = getFileContents(this.file, encoding) + fileIncludes(this.file).assert(text, content) } /** @@ -126,8 +126,8 @@ class FileSystem extends Helper { * @param {string} [encoding='utf8'] */ dontSeeInThisFile(text, encoding = 'utf8') { - const content = getFileContents(this.file, encoding); - fileIncludes(this.file).negate(text, content); + const content = getFileContents(this.file, encoding) + fileIncludes(this.file).negate(text, content) } /** @@ -136,8 +136,8 @@ class FileSystem extends Helper { * @param {string} [encoding='utf8'] */ seeFileContentsEqual(text, encoding = 'utf8') { - const content = getFileContents(this.file, encoding); - fileEquals(this.file).assert(text, content); + const content = getFileContents(this.file, encoding) + fileEquals(this.file).assert(text, content) } /** @@ -147,11 +147,11 @@ class FileSystem extends Helper { * @param {string} [encodingReference='utf8'] */ seeFileContentsEqualReferenceFile(pathToReferenceFile, encoding = 'utf8', encodingReference = '') { - const content = getFileContents(this.file, encoding); - assert.ok(fileExists(pathToReferenceFile), `Reference file ${pathToReferenceFile} not found.`); - encodingReference = encodingReference || encoding; - const expectedContent = getFileContents(pathToReferenceFile, encodingReference); - fileEquals(this.file).assert(expectedContent, content); + const content = getFileContents(this.file, encoding) + assert.ok(fileExists(pathToReferenceFile), `Reference file ${pathToReferenceFile} not found.`) + encodingReference = encodingReference || encoding + const expectedContent = getFileContents(pathToReferenceFile, encodingReference) + fileEquals(this.file).assert(expectedContent, content) } /** @@ -160,27 +160,26 @@ class FileSystem extends Helper { * @param {string} [encoding='utf8'] */ dontSeeFileContentsEqual(text, encoding = 'utf8') { - const content = getFileContents(this.file, encoding); - fileEquals(this.file).negate(text, content); + const content = getFileContents(this.file, encoding) + fileEquals(this.file).negate(text, content) } /** - * Returns file names in current directory. - * - * ```js - * I.handleDownloads(); - * I.click('Download Files'); - * I.amInPath('output/downloads'); - * const downloadedFileNames = I.grabFileNames(); - * ``` - */ + * Returns file names in current directory. + * + * ```js + * I.handleDownloads(); + * I.click('Download Files'); + * I.amInPath('output/downloads'); + * const downloadedFileNames = I.grabFileNames(); + * ``` + */ grabFileNames() { - return fs.readdirSync(this.dir) - .filter(item => !fs.lstatSync(path.join(this.dir, item)).isDirectory()); + return fs.readdirSync(this.dir).filter((item) => !fs.lstatSync(path.join(this.dir, item)).isDirectory()) } } -module.exports = FileSystem; +module.exports = FileSystem /** * @param {string} file @@ -189,9 +188,9 @@ module.exports = FileSystem; * @returns {string} */ function getFileContents(file, encoding = 'utf8') { - if (!file) assert.fail('No files were opened, please use seeFile action'); - if (encoding === '') assert.fail('Encoding is an empty string, please set a valid encoding'); - return fs.readFileSync(file, encoding); + if (!file) assert.fail('No files were opened, please use seeFile action') + if (encoding === '') assert.fail('Encoding is an empty string, please set a valid encoding') + return fs.readFileSync(file, encoding) } /** @@ -201,28 +200,28 @@ function getFileContents(file, encoding = 'utf8') { * @returns {Promise} */ function isFileExists(file, timeout) { - return new Promise(((resolve, reject) => { + return new Promise((resolve, reject) => { const timer = setTimeout(() => { - watcher.close(); - reject(new Error('File did not exists and was not created during the timeout.')); - }, timeout); + watcher.close() + reject(new Error('File did not exists and was not created during the timeout.')) + }, timeout) - const dir = path.dirname(file); - const basename = path.basename(file); + const dir = path.dirname(file) + const basename = path.basename(file) const watcher = fs.watch(dir, (eventType, filename) => { if (eventType === 'rename' && filename === basename) { - clearTimeout(timer); - watcher.close(); - resolve(); + clearTimeout(timer) + watcher.close() + resolve() } - }); + }) fs.access(file, fs.constants.R_OK, (err) => { if (!err) { - clearTimeout(timer); - watcher.close(); - resolve(); + clearTimeout(timer) + watcher.close() + resolve() } - }); - })); + }) + }) } diff --git a/lib/helper/GraphQL.js b/lib/helper/GraphQL.js index fb87f16a2..4a5f39c29 100644 --- a/lib/helper/GraphQL.js +++ b/lib/helper/GraphQL.js @@ -1,5 +1,5 @@ -const axios = require('axios').default; -const Helper = require('@codeceptjs/helper'); +const axios = require('axios').default +const Helper = require('@codeceptjs/helper') /** * GraphQL helper allows to send additional requests to a GraphQl endpoint during acceptance tests. @@ -38,31 +38,35 @@ const Helper = require('@codeceptjs/helper'); */ class GraphQL extends Helper { constructor(config) { - super(config); - this.axios = axios.create(); - this.headers = {}; + super(config) + this.axios = axios.create() + this.headers = {} this.options = { timeout: 10000, defaultHeaders: {}, endpoint: '', - }; - this.options = Object.assign(this.options, config); - this.headers = { ...this.options.defaultHeaders }; - this.axios.defaults.headers = this.options.defaultHeaders; + } + this.options = Object.assign(this.options, config) + this.headers = { ...this.options.defaultHeaders } + this.axios.defaults.headers = this.options.defaultHeaders } static _checkRequirements() { try { - require('axios'); + require('axios') } catch (e) { - return ['axios']; + return ['axios'] } } static _config() { return [ - { name: 'endpoint', message: 'Endpoint of API you are going to test', default: 'http://localhost:3000/graphql' }, - ]; + { + name: 'endpoint', + message: 'Endpoint of API you are going to test', + default: 'http://localhost:3000/graphql', + }, + ] } /** @@ -71,42 +75,39 @@ class GraphQL extends Helper { * @param {object} request */ async _executeQuery(request) { - this.axios.defaults.timeout = request.timeout || this.options.timeout; + this.axios.defaults.timeout = request.timeout || this.options.timeout if (this.headers && this.headers.auth) { - request.auth = this.headers.auth; + request.auth = this.headers.auth } request.headers = Object.assign(request.headers, { 'Content-Type': 'application/json', - }); + }) - request.headers = { ...this.headers, ...request.headers }; + request.headers = { ...this.headers, ...request.headers } if (this.config.onRequest) { - await this.config.onRequest(request); + await this.config.onRequest(request) } - this.debugSection('Request', JSON.stringify(request)); + this.debugSection('Request', JSON.stringify(request)) - let response; + let response try { - response = await this.axios(request); + response = await this.axios(request) } catch (err) { - if (!err.response) throw err; - this.debugSection( - 'Response', - `Response error. Status code: ${err.response.status}`, - ); - response = err.response; + if (!err.response) throw err + this.debugSection('Response', `Response error. Status code: ${err.response.status}`) + response = err.response } if (this.config.onResponse) { - await this.config.onResponse(response); + await this.config.onResponse(response) } - this.debugSection('Response', JSON.stringify(response.data)); - return response; + this.debugSection('Response', JSON.stringify(response.data)) + return response } /** @@ -122,7 +123,7 @@ class GraphQL extends Helper { method: 'POST', data: operation, headers, - }; + } } /** @@ -148,15 +149,15 @@ class GraphQL extends Helper { */ async sendQuery(query, variables, options = {}, headers = {}) { if (typeof query !== 'string') { - throw new Error(`query expected to be a String, instead received ${typeof query}`); + throw new Error(`query expected to be a String, instead received ${typeof query}`) } const operation = { query, variables, ...options, - }; - const request = this._prepareGraphQLRequest(operation, headers); - return this._executeQuery(request); + } + const request = this._prepareGraphQLRequest(operation, headers) + return this._executeQuery(request) } /** @@ -188,19 +189,19 @@ class GraphQL extends Helper { */ async sendMutation(mutation, variables, options = {}, headers = {}) { if (typeof mutation !== 'string') { - throw new Error(`mutation expected to be a String, instead received ${typeof mutation}`); + throw new Error(`mutation expected to be a String, instead received ${typeof mutation}`) } const operation = { query: mutation, variables, ...options, - }; - const request = this._prepareGraphQLRequest(operation, headers); - return this._executeQuery(request); + } + const request = this._prepareGraphQLRequest(operation, headers) + return this._executeQuery(request) } _setRequestTimeout(newTimeout) { - this.options.timeout = newTimeout; + this.options.timeout = newTimeout } /** @@ -209,7 +210,7 @@ class GraphQL extends Helper { * @param {object} headers headers list */ haveRequestHeaders(headers) { - this.headers = { ...this.headers, ...headers }; + this.headers = { ...this.headers, ...headers } } /** @@ -223,7 +224,7 @@ class GraphQL extends Helper { * @param {string | CodeceptJS.Secret} accessToken Bearer access token */ amBearerAuthenticated(accessToken) { - this.haveRequestHeaders({ Authorization: `Bearer ${accessToken}` }); + this.haveRequestHeaders({ Authorization: `Bearer ${accessToken}` }) } } -module.exports = GraphQL; +module.exports = GraphQL diff --git a/lib/helper/GraphQLDataFactory.js b/lib/helper/GraphQLDataFactory.js index 9a6d802e6..e45f7fe19 100644 --- a/lib/helper/GraphQLDataFactory.js +++ b/lib/helper/GraphQLDataFactory.js @@ -1,7 +1,7 @@ -const path = require('path'); +const path = require('path') -const Helper = require('@codeceptjs/helper'); -const GraphQL = require('./GraphQL'); +const Helper = require('@codeceptjs/helper') +const GraphQL = require('./GraphQL') /** * Helper for managing remote data using GraphQL queries. @@ -151,52 +151,52 @@ const GraphQL = require('./GraphQL'); */ class GraphQLDataFactory extends Helper { constructor(config) { - super(config); + super(config) const defaultConfig = { cleanup: true, GraphQL: {}, factories: {}, - }; - this.config = Object.assign(defaultConfig, this.config); + } + this.config = Object.assign(defaultConfig, this.config) if (this.config.headers) { - this.config.GraphQL.defaultHeaders = this.config.headers; + this.config.GraphQL.defaultHeaders = this.config.headers } if (this.config.onRequest) { - this.config.GraphQL.onRequest = this.config.onRequest; + this.config.GraphQL.onRequest = this.config.onRequest } - this.graphqlHelper = new GraphQL(Object.assign(this.config.GraphQL, { endpoint: this.config.endpoint })); - this.factories = this.config.factories; + this.graphqlHelper = new GraphQL(Object.assign(this.config.GraphQL, { endpoint: this.config.endpoint })) + this.factories = this.config.factories - this.created = {}; - Object.keys(this.factories).forEach(f => (this.created[f] = [])); + this.created = {} + Object.keys(this.factories).forEach((f) => (this.created[f] = [])) } static _checkRequirements() { try { - require('axios'); - require('rosie'); + require('axios') + require('rosie') } catch (e) { - return ['axios', 'rosie']; + return ['axios', 'rosie'] } } _after() { if (!this.config.cleanup) { - return Promise.resolve(); + return Promise.resolve() } - const promises = []; + const promises = [] // clean up all created items for (const mutationName in this.created) { - const createdItems = this.created[mutationName]; - if (!createdItems.length) continue; - this.debug(`Deleting ${createdItems.length} ${mutationName}(s)`); + const createdItems = this.created[mutationName] + if (!createdItems.length) continue + this.debug(`Deleting ${createdItems.length} ${mutationName}(s)`) for (const itemData of createdItems) { - promises.push(this._requestDelete(mutationName, itemData)); + promises.push(this._requestDelete(mutationName, itemData)) } } - return Promise.all(promises); + return Promise.all(promises) } /** @@ -214,9 +214,9 @@ class GraphQLDataFactory extends Helper { * @param {*} params predefined parameters */ mutateData(operation, params) { - const variables = this._createItem(operation, params); - this.debug(`Creating ${operation} ${JSON.stringify(variables)}`); - return this._requestCreate(operation, variables); + const variables = this._createItem(operation, params) + this.debug(`Creating ${operation} ${JSON.stringify(variables)}`) + return this._requestCreate(operation, variables) } /** @@ -235,26 +235,26 @@ class GraphQLDataFactory extends Helper { * @param {*} params */ mutateMultiple(operation, times, params) { - const promises = []; + const promises = [] for (let i = 0; i < times; i++) { - promises.push(this.mutateData(operation, params)); + promises.push(this.mutateData(operation, params)) } - return Promise.all(promises); + return Promise.all(promises) } _createItem(operation, data) { if (!this.factories[operation]) { - throw new Error(`Mutation ${operation} is not defined in config.factories`); + throw new Error(`Mutation ${operation} is not defined in config.factories`) } - let modulePath = this.factories[operation].factory; + let modulePath = this.factories[operation].factory try { try { - require.resolve(modulePath); + require.resolve(modulePath) } catch (e) { - modulePath = path.join(global.codecept_dir, modulePath); + modulePath = path.join(global.codecept_dir, modulePath) } - const builder = require(modulePath); - return builder.build(data); + const builder = require(modulePath) + return builder.build(data) } catch (err) { throw new Error(`Couldn't load factory file from ${modulePath}, check that @@ -265,7 +265,7 @@ class GraphQLDataFactory extends Helper { points to valid factory file. Factory file should export an object with build method. - Current file error: ${err.message}`); + Current file error: ${err.message}`) } } @@ -277,13 +277,13 @@ class GraphQLDataFactory extends Helper { * @param {*} variables to be sent along with the query */ _requestCreate(operation, variables) { - const { query } = this.factories[operation]; + const { query } = this.factories[operation] return this.graphqlHelper.sendMutation(query, variables).then((response) => { - const data = response.data.data[operation]; - this.created[operation].push(data); - this.debugSection('Created', `record: ${data}`); - return data; - }); + const data = response.data.data[operation] + this.created[operation].push(data) + this.debugSection('Created', `record: ${data}`) + return data + }) } /** @@ -294,16 +294,15 @@ class GraphQLDataFactory extends Helper { * @param {*} data of the record to be deleted. */ _requestDelete(operation, data) { - const deleteOperation = this.factories[operation].revert(data); - const { query, variables } = deleteOperation; + const deleteOperation = this.factories[operation].revert(data) + const { query, variables } = deleteOperation - return this.graphqlHelper.sendMutation(query, variables) - .then((response) => { - const idx = this.created[operation].indexOf(data); - this.debugSection('Deleted', `record: ${response.data.data}`); - this.created[operation].splice(idx, 1); - }); + return this.graphqlHelper.sendMutation(query, variables).then((response) => { + const idx = this.created[operation].indexOf(data) + this.debugSection('Deleted', `record: ${response.data.data}`) + this.created[operation].splice(idx, 1) + }) } } -module.exports = GraphQLDataFactory; +module.exports = GraphQLDataFactory diff --git a/lib/helper/JSONResponse.js b/lib/helper/JSONResponse.js index e651a6833..765b37fa9 100644 --- a/lib/helper/JSONResponse.js +++ b/lib/helper/JSONResponse.js @@ -1,13 +1,13 @@ -const Helper = require('@codeceptjs/helper'); +const Helper = require('@codeceptjs/helper') -let expect; +let expect -import('chai').then(chai => { - expect = chai.expect; - chai.use(require('chai-deep-match')); -}); +import('chai').then((chai) => { + expect = chai.expect + chai.use(require('chai-deep-match')) +}) -const joi = require('joi'); +const joi = require('joi') /** * This helper allows performing assertions on JSON responses paired with following helpers: @@ -67,33 +67,35 @@ const joi = require('joi'); */ class JSONResponse extends Helper { constructor(config = {}) { - super(config); + super(config) this.options = { requestHelper: 'REST', - }; - this.options = { ...this.options, ...config }; + } + this.options = { ...this.options, ...config } } _beforeSuite() { - this.response = null; + this.response = null if (!this.helpers[this.options.requestHelper]) { - throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`); + throw new Error( + `Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`, + ) } // connect to REST helper this.helpers[this.options.requestHelper].config.onResponse = (response) => { - this.response = response; - }; + this.response = response + } } _before() { - this.response = null; + this.response = null } static _checkRequirements() { try { - require('joi'); + require('joi') } catch (e) { - return ['joi']; + return ['joi'] } } @@ -107,8 +109,8 @@ class JSONResponse extends Helper { * @param {number} code */ seeResponseCodeIs(code) { - this._checkResponseReady(); - expect(this.response.status).to.eql(code, 'Response code is not the same as expected'); + this._checkResponseReady() + expect(this.response.status).to.eql(code, 'Response code is not the same as expected') } /** @@ -121,35 +123,35 @@ class JSONResponse extends Helper { * @param {number} code */ dontSeeResponseCodeIs(code) { - this._checkResponseReady(); - expect(this.response.status).not.to.eql(code); + this._checkResponseReady() + expect(this.response.status).not.to.eql(code) } /** * Checks that the response code is 4xx */ seeResponseCodeIsClientError() { - this._checkResponseReady(); - expect(this.response.status).to.be.gte(400); - expect(this.response.status).to.be.lt(500); + this._checkResponseReady() + expect(this.response.status).to.be.gte(400) + expect(this.response.status).to.be.lt(500) } /** * Checks that the response code is 3xx */ seeResponseCodeIsRedirection() { - this._checkResponseReady(); - expect(this.response.status).to.be.gte(300); - expect(this.response.status).to.be.lt(400); + this._checkResponseReady() + expect(this.response.status).to.be.gte(300) + expect(this.response.status).to.be.lt(400) } /** * Checks that the response code is 5xx */ seeResponseCodeIsServerError() { - this._checkResponseReady(); - expect(this.response.status).to.be.gte(500); - expect(this.response.status).to.be.lt(600); + this._checkResponseReady() + expect(this.response.status).to.be.gte(500) + expect(this.response.status).to.be.lt(600) } /** @@ -161,9 +163,9 @@ class JSONResponse extends Helper { * ``` */ seeResponseCodeIsSuccessful() { - this._checkResponseReady(); - expect(this.response.status).to.be.gte(200); - expect(this.response.status).to.be.lt(300); + this._checkResponseReady() + expect(this.response.status).to.be.gte(200) + expect(this.response.status).to.be.lt(300) } /** @@ -184,19 +186,19 @@ class JSONResponse extends Helper { * @param {object} json */ seeResponseContainsJson(json = {}) { - this._checkResponseReady(); + this._checkResponseReady() if (Array.isArray(this.response.data)) { - let fails = 0; + let fails = 0 for (const el of this.response.data) { try { - expect(el).to.deep.match(json); + expect(el).to.deep.match(json) } catch (err) { - fails++; + fails++ } } - expect(fails < this.response.data.length, `No elements in array matched ${JSON.stringify(json)}`).to.be.true; + expect(fails < this.response.data.length, `No elements in array matched ${JSON.stringify(json)}`).to.be.true } else { - expect(this.response.data).to.deep.match(json); + expect(this.response.data).to.deep.match(json) } } @@ -218,11 +220,11 @@ class JSONResponse extends Helper { * @param {object} json */ dontSeeResponseContainsJson(json = {}) { - this._checkResponseReady(); + this._checkResponseReady() if (Array.isArray(this.response.data)) { - this.response.data.forEach(data => expect(data).not.to.deep.match(json)); + this.response.data.forEach((data) => expect(data).not.to.deep.match(json)) } else { - expect(this.response.data).not.to.deep.match(json); + expect(this.response.data).not.to.deep.match(json) } } @@ -246,11 +248,11 @@ class JSONResponse extends Helper { * @param {array} keys */ seeResponseContainsKeys(keys = []) { - this._checkResponseReady(); + this._checkResponseReady() if (Array.isArray(this.response.data)) { - this.response.data.forEach(data => expect(data).to.include.keys(keys)); + this.response.data.forEach((data) => expect(data).to.include.keys(keys)) } else { - expect(this.response.data).to.include.keys(keys); + expect(this.response.data).to.include.keys(keys) } } @@ -268,10 +270,10 @@ class JSONResponse extends Helper { * @param {function} fn */ seeResponseValidByCallback(fn) { - this._checkResponseReady(); - fn({ ...this.response, expect }); - const body = fn.toString(); - fn.toString = () => `${body.split('\n')[1]}...`; + this._checkResponseReady() + fn({ ...this.response, expect }) + const body = fn.toString() + fn.toString = () => `${body.split('\n')[1]}...` } /** @@ -285,8 +287,8 @@ class JSONResponse extends Helper { * @param {object} resp */ seeResponseEquals(resp) { - this._checkResponseReady(); - expect(this.response.data).to.deep.equal(resp); + this._checkResponseReady() + expect(this.response.data).to.deep.equal(resp) } /** @@ -317,22 +319,22 @@ class JSONResponse extends Helper { * @param {any} fnOrSchema */ seeResponseMatchesJsonSchema(fnOrSchema) { - this._checkResponseReady(); - let schema = fnOrSchema; + this._checkResponseReady() + let schema = fnOrSchema if (typeof fnOrSchema === 'function') { - schema = fnOrSchema(joi); - const body = fnOrSchema.toString(); - fnOrSchema.toString = () => `${body.split('\n')[1]}...`; + schema = fnOrSchema(joi) + const body = fnOrSchema.toString() + fnOrSchema.toString = () => `${body.split('\n')[1]}...` } - if (!schema) throw new Error('Empty Joi schema provided, see https://joi.dev/ for details'); - if (!joi.isSchema(schema)) throw new Error('Invalid Joi schema provided, see https://joi.dev/ for details'); - schema.toString = () => schema.describe(); - joi.assert(this.response.data, schema); + if (!schema) throw new Error('Empty Joi schema provided, see https://joi.dev/ for details') + if (!joi.isSchema(schema)) throw new Error('Invalid Joi schema provided, see https://joi.dev/ for details') + schema.toString = () => schema.describe() + joi.assert(this.response.data, schema) } _checkResponseReady() { - if (!this.response) throw new Error('Response is not available'); + if (!this.response) throw new Error('Response is not available') } } -module.exports = JSONResponse; +module.exports = JSONResponse diff --git a/lib/helper/Mochawesome.js b/lib/helper/Mochawesome.js index 826b2f232..abc143af4 100644 --- a/lib/helper/Mochawesome.js +++ b/lib/helper/Mochawesome.js @@ -1,71 +1,71 @@ -let addMochawesomeContext; -let currentTest; -let currentSuite; +let addMochawesomeContext +let currentTest +let currentSuite -const Helper = require('@codeceptjs/helper'); -const { clearString } = require('../utils'); +const Helper = require('@codeceptjs/helper') +const { clearString } = require('../utils') class Mochawesome extends Helper { constructor(config) { - super(config); + super(config) // set defaults this.options = { uniqueScreenshotNames: false, disableScreenshots: false, - }; + } - addMochawesomeContext = require('mochawesome/addContext'); - this._createConfig(config); + addMochawesomeContext = require('mochawesome/addContext') + this._createConfig(config) } _createConfig(config) { // override defaults with config - Object.assign(this.options, config); + Object.assign(this.options, config) } _beforeSuite(suite) { - currentSuite = suite; - currentTest = ''; + currentSuite = suite + currentTest = '' } _before() { if (currentSuite && currentSuite.ctx) { - currentTest = { test: currentSuite.ctx.currentTest }; + currentTest = { test: currentSuite.ctx.currentTest } } } _test(test) { - currentTest = { test }; + currentTest = { test } } _failed(test) { - if (this.options.disableScreenshots) return; - let fileName; + if (this.options.disableScreenshots) return + let fileName // Get proper name if we are fail on hook if (test.ctx.test.type === 'hook') { - currentTest = { test: test.ctx.test }; + currentTest = { test: test.ctx.test } // ignore retries if we are in hook - test._retries = -1; - fileName = clearString(`${test.title}_${currentTest.test.title}`); + test._retries = -1 + fileName = clearString(`${test.title}_${currentTest.test.title}`) } else { - currentTest = { test }; - fileName = clearString(test.title); + currentTest = { test } + fileName = clearString(test.title) } if (this.options.uniqueScreenshotNames) { - const uuid = test.uuid || test.ctx.test.uuid; - fileName = `${fileName.substring(0, 10)}_${uuid}`; + const uuid = test.uuid || test.ctx.test.uuid + fileName = `${fileName.substring(0, 10)}_${uuid}` } if (test._retries < 1 || test._retries === test.retryNum) { - fileName = `${fileName}.failed.png`; - return addMochawesomeContext(currentTest, fileName); + fileName = `${fileName}.failed.png` + return addMochawesomeContext(currentTest, fileName) } } addMochawesomeContext(context) { - if (currentTest === '') currentTest = { test: currentSuite.ctx.test }; - return addMochawesomeContext(currentTest, context); + if (currentTest === '') currentTest = { test: currentSuite.ctx.test } + return addMochawesomeContext(currentTest, context) } } -module.exports = Mochawesome; +module.exports = Mochawesome diff --git a/lib/helper/MockServer.js b/lib/helper/MockServer.js index 9cb748808..7278c1021 100644 --- a/lib/helper/MockServer.js +++ b/lib/helper/MockServer.js @@ -1,4 +1,4 @@ -const { mock, settings } = require('pactum'); +const { mock, settings } = require('pactum') /** * ## Configuration @@ -18,7 +18,7 @@ let config = { key: '', cert: '', }, -}; +} /** * MockServer @@ -141,10 +141,10 @@ let config = { */ class MockServer { constructor(passedConfig) { - settings.setLogLevel('SILENT'); - config = { ...passedConfig }; + settings.setLogLevel('SILENT') + config = { ...passedConfig } if (global.debugMode) { - settings.setLogLevel('VERBOSE'); + settings.setLogLevel('VERBOSE') } } @@ -155,10 +155,10 @@ class MockServer { * @returns void */ async startMockServer(port) { - const _config = { ...config }; - if (port) _config.port = port; - await mock.setDefaults(_config); - await mock.start(); + const _config = { ...config } + if (port) _config.port = port + await mock.setDefaults(_config) + await mock.start() } /** @@ -168,7 +168,7 @@ class MockServer { * */ async stopMockServer() { - await mock.stop(); + await mock.stop() } /** @@ -214,8 +214,8 @@ class MockServer { * */ async addInteractionToMockServer(interaction) { - await mock.addInteraction(interaction); + await mock.addInteraction(interaction) } } -module.exports = MockServer; +module.exports = MockServer diff --git a/lib/helper/Nightmare.js b/lib/helper/Nightmare.js index 59947bec6..d89fc520a 100644 --- a/lib/helper/Nightmare.js +++ b/lib/helper/Nightmare.js @@ -1,29 +1,24 @@ -const path = require('path'); - -const urlResolve = require('url').resolve; - -const Helper = require('@codeceptjs/helper'); -const { includes: stringIncludes } = require('../assert/include'); -const { urlEquals } = require('../assert/equal'); -const { equals } = require('../assert/equal'); -const { empty } = require('../assert/empty'); -const { truth } = require('../assert/truth'); -const Locator = require('../locator'); -const ElementNotFound = require('./errors/ElementNotFound'); -const { - xpathLocator, - fileExists, - screenshotOutputFolder, - toCamelCase, -} = require('../utils'); +const path = require('path') + +const urlResolve = require('url').resolve + +const Helper = require('@codeceptjs/helper') +const { includes: stringIncludes } = require('../assert/include') +const { urlEquals } = require('../assert/equal') +const { equals } = require('../assert/equal') +const { empty } = require('../assert/empty') +const { truth } = require('../assert/truth') +const Locator = require('../locator') +const ElementNotFound = require('./errors/ElementNotFound') +const { xpathLocator, fileExists, screenshotOutputFolder, toCamelCase } = require('../utils') const specialKeys = { Backspace: '\u0008', Enter: '\u000d', Delete: '\u007f', -}; +} -let withinStatus = false; +let withinStatus = false /** * Nightmare helper wraps [Nightmare](https://github.com/segmentio/nightmare) library to provide @@ -54,12 +49,12 @@ let withinStatus = false; */ class Nightmare extends Helper { constructor(config) { - super(config); + super(config) - this.isRunning = false; + this.isRunning = false // override defaults with config - this._setConfig(config); + this._setConfig(config) } _validateConfig(config) { @@ -75,235 +70,268 @@ class Nightmare extends Helper { keepCookies: false, js_errors: null, enableHAR: false, - }; + } - return Object.assign(defaults, config); + return Object.assign(defaults, config) } static _config() { return [ { name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' }, { - name: 'show', message: 'Show browser window', default: true, type: 'confirm', + name: 'show', + message: 'Show browser window', + default: true, + type: 'confirm', }, - ]; + ] } static _checkRequirements() { try { - require('nightmare'); + require('nightmare') } catch (e) { - return ['nightmare']; + return ['nightmare'] } } async _init() { - this.Nightmare = require('nightmare'); + this.Nightmare = require('nightmare') if (this.options.enableHAR) { - require('nightmare-har-plugin').install(this.Nightmare); + require('nightmare-har-plugin').install(this.Nightmare) } this.Nightmare.action('findElements', function (locator, contextEl, done) { if (!done) { - done = contextEl; - contextEl = null; + done = contextEl + contextEl = null } - const by = Object.keys(locator)[0]; - const value = locator[by]; + const by = Object.keys(locator)[0] + const value = locator[by] - this.evaluate_now((by, locator, contextEl) => window.codeceptjs.findAndStoreElements(by, locator, contextEl), done, by, value, contextEl); - }); + this.evaluate_now( + (by, locator, contextEl) => window.codeceptjs.findAndStoreElements(by, locator, contextEl), + done, + by, + value, + contextEl, + ) + }) this.Nightmare.action('findElement', function (locator, contextEl, done) { if (!done) { - done = contextEl; - contextEl = null; + done = contextEl + contextEl = null } - const by = Object.keys(locator)[0]; - const value = locator[by]; + const by = Object.keys(locator)[0] + const value = locator[by] - this.evaluate_now((by, locator, contextEl) => { - const res = window.codeceptjs.findAndStoreElement(by, locator, contextEl); - if (res === null) { - throw new Error(`Element ${(new Locator(locator))} couldn't be located by ${by}`); - } - return res; - }, done, by, value, contextEl); - }); + this.evaluate_now( + (by, locator, contextEl) => { + const res = window.codeceptjs.findAndStoreElement(by, locator, contextEl) + if (res === null) { + throw new Error(`Element ${new Locator(locator)} couldn't be located by ${by}`) + } + return res + }, + done, + by, + value, + contextEl, + ) + }) this.Nightmare.action('asyncScript', function () { - let args = Array.prototype.slice.call(arguments); - const done = args.pop(); - args = args.splice(1, 0, done); - this.evaluate_now.apply(this, args); - }); + let args = Array.prototype.slice.call(arguments) + const done = args.pop() + args = args.splice(1, 0, done) + this.evaluate_now.apply(this, args) + }) this.Nightmare.action('enterText', function (el, text, clean, done) { - const child = this.child; - const typeFn = () => child.call('type', text, done); - - this.evaluate_now((el, clean) => { - const element = window.codeceptjs.fetchElement(el); - if (clean) element.value = ''; - element.focus(); - }, () => { - if (clean) return typeFn(); - child.call('pressKey', 'End', typeFn); // type End before - }, el, clean); - }); - - this.Nightmare.action('pressKey', (ns, options, parent, win, renderer, done) => { - parent.respondTo('pressKey', (ch, done) => { - win.webContents.sendInputEvent({ - type: 'keyDown', - keyCode: ch, - }); - - win.webContents.sendInputEvent({ - type: 'char', - keyCode: ch, - }); - - win.webContents.sendInputEvent({ - type: 'keyUp', - keyCode: ch, - }); - done(); - }); - done(); - }, function (key, done) { - this.child.call('pressKey', key, done); - }); - - this.Nightmare.action('triggerMouseEvent', (ns, options, parent, win, renderer, done) => { - parent.respondTo('triggerMouseEvent', (evt, done) => { - win.webContents.sendInputEvent(evt); - done(); - }); - done(); - }, function (event, done) { - this.child.call('triggerMouseEvent', event, done); - }); + const child = this.child + const typeFn = () => child.call('type', text, done) + + this.evaluate_now( + (el, clean) => { + const element = window.codeceptjs.fetchElement(el) + if (clean) element.value = '' + element.focus() + }, + () => { + if (clean) return typeFn() + child.call('pressKey', 'End', typeFn) // type End before + }, + el, + clean, + ) + }) + + this.Nightmare.action( + 'pressKey', + (ns, options, parent, win, renderer, done) => { + parent.respondTo('pressKey', (ch, done) => { + win.webContents.sendInputEvent({ + type: 'keyDown', + keyCode: ch, + }) + + win.webContents.sendInputEvent({ + type: 'char', + keyCode: ch, + }) + + win.webContents.sendInputEvent({ + type: 'keyUp', + keyCode: ch, + }) + done() + }) + done() + }, + function (key, done) { + this.child.call('pressKey', key, done) + }, + ) + + this.Nightmare.action( + 'triggerMouseEvent', + (ns, options, parent, win, renderer, done) => { + parent.respondTo('triggerMouseEvent', (evt, done) => { + win.webContents.sendInputEvent(evt) + done() + }) + done() + }, + function (event, done) { + this.child.call('triggerMouseEvent', event, done) + }, + ) this.Nightmare.action( 'upload', (ns, options, parent, win, renderer, done) => { parent.respondTo('upload', (selector, pathsToUpload, done) => { - parent.emit('log', 'paths', pathsToUpload); + parent.emit('log', 'paths', pathsToUpload) try { - // attach the debugger - // NOTE: this will fail if devtools is open - win.webContents.debugger.attach('1.1'); + // attach the debugger + // NOTE: this will fail if devtools is open + win.webContents.debugger.attach('1.1') } catch (e) { - parent.emit('log', 'problem attaching', e); - return done(e); + parent.emit('log', 'problem attaching', e) + return done(e) } win.webContents.debugger.sendCommand('DOM.getDocument', {}, (err, domDocument) => { - win.webContents.debugger.sendCommand('DOM.querySelector', { - nodeId: domDocument.root.nodeId, - selector, - }, (err, queryResult) => { - // HACK: chromium errors appear to be unpopulated objects? - if (Object.keys(err) - .length > 0) { - parent.emit('log', 'problem selecting', err); - return done(err); - } - win.webContents.debugger.sendCommand('DOM.setFileInputFiles', { - nodeId: queryResult.nodeId, - files: pathsToUpload, - }, (err) => { - if (Object.keys(err) - .length > 0) { - parent.emit('log', 'problem setting input', err); - return done(err); + win.webContents.debugger.sendCommand( + 'DOM.querySelector', + { + nodeId: domDocument.root.nodeId, + selector, + }, + (err, queryResult) => { + // HACK: chromium errors appear to be unpopulated objects? + if (Object.keys(err).length > 0) { + parent.emit('log', 'problem selecting', err) + return done(err) } - win.webContents.debugger.detach(); - done(null, pathsToUpload); - }); - }); - }); - }); - done(); + win.webContents.debugger.sendCommand( + 'DOM.setFileInputFiles', + { + nodeId: queryResult.nodeId, + files: pathsToUpload, + }, + (err) => { + if (Object.keys(err).length > 0) { + parent.emit('log', 'problem setting input', err) + return done(err) + } + win.webContents.debugger.detach() + done(null, pathsToUpload) + }, + ) + }, + ) + }) + }) + done() }, function (selector, pathsToUpload, done) { if (!Array.isArray(pathsToUpload)) { - pathsToUpload = [pathsToUpload]; + pathsToUpload = [pathsToUpload] } this.child.call('upload', selector, pathsToUpload, (err, stuff) => { - done(err, stuff); - }); + done(err, stuff) + }) }, - ); + ) - return Promise.resolve(); + return Promise.resolve() } async _beforeSuite() { if (!this.options.restart && !this.isRunning) { - this.debugSection('Session', 'Starting singleton browser session'); - return this._startBrowser(); + this.debugSection('Session', 'Starting singleton browser session') + return this._startBrowser() } } async _before() { - if (this.options.restart) return this._startBrowser(); - if (!this.isRunning) return this._startBrowser(); - return this.browser; + if (this.options.restart) return this._startBrowser() + if (!this.isRunning) return this._startBrowser() + return this.browser } async _after() { - if (!this.isRunning) return; + if (!this.isRunning) return if (this.options.restart) { - this.isRunning = false; - return this._stopBrowser(); + this.isRunning = false + return this._stopBrowser() } if (this.options.enableHAR) { - await this.browser.resetHAR(); + await this.browser.resetHAR() } - if (this.options.keepBrowserState) return; + if (this.options.keepBrowserState) return if (this.options.keepCookies) { - await this.browser.cookies.clearAll(); + await this.browser.cookies.clearAll() } - this.debugSection('Session', 'cleaning up'); - return this.executeScript(() => localStorage.clear()); + this.debugSection('Session', 'cleaning up') + return this.executeScript(() => localStorage.clear()) } - _afterSuite() { - } + _afterSuite() {} _finishTest() { if (!this.options.restart && this.isRunning) { - this._stopBrowser(); + this._stopBrowser() } } async _startBrowser() { - this.context = this.options.rootElement; + this.context = this.options.rootElement if (this.options.enableHAR) { - this.browser = this.Nightmare(Object.assign(require('nightmare-har-plugin').getDevtoolsOptions(), this.options)); - await this.browser; - await this.browser.waitForDevtools(); + this.browser = this.Nightmare(Object.assign(require('nightmare-har-plugin').getDevtoolsOptions(), this.options)) + await this.browser + await this.browser.waitForDevtools() } else { - this.browser = this.Nightmare(this.options); - await this.browser; + this.browser = this.Nightmare(this.options) + await this.browser } - await this.browser.goto('about:blank'); // Load a blank page so .saveScreenshot (/evaluate) will work - this.isRunning = true; - this.browser.on('dom-ready', () => this._injectClientScripts()); - this.browser.on('did-start-loading', () => this._injectClientScripts()); - this.browser.on('will-navigate', () => this._injectClientScripts()); + await this.browser.goto('about:blank') // Load a blank page so .saveScreenshot (/evaluate) will work + this.isRunning = true + this.browser.on('dom-ready', () => this._injectClientScripts()) + this.browser.on('did-start-loading', () => this._injectClientScripts()) + this.browser.on('will-navigate', () => this._injectClientScripts()) this.browser.on('console', (type, message) => { - this.debug(`${type}: ${message}`); - }); + this.debug(`${type}: ${message}`) + }) if (this.options.windowSize) { - const size = this.options.windowSize.split('x'); - return this.browser.viewport(parseInt(size[0], 10), parseInt(size[1], 10)); + const size = this.options.windowSize.split('x') + return this.browser.viewport(parseInt(size[0], 10), parseInt(size[1], 10)) } } @@ -316,45 +344,49 @@ class Nightmare extends Helper { * ``` */ async grabHAR() { - return this.browser.getHAR(); + return this.browser.getHAR() } async saveHAR(fileName) { - const outputFile = path.join(global.output_dir, fileName); - this.debug(`HAR is saving to ${outputFile}`); + const outputFile = path.join(global.output_dir, fileName) + this.debug(`HAR is saving to ${outputFile}`) await this.browser.getHAR().then((har) => { - require('fs').writeFileSync(outputFile, JSON.stringify({ log: har })); - }); + require('fs').writeFileSync(outputFile, JSON.stringify({ log: har })) + }) } async resetHAR() { - await this.browser.resetHAR(); + await this.browser.resetHAR() } async _stopBrowser() { return this.browser.end().catch((error) => { - this.debugSection('Error on End', error); - }); + this.debugSection('Error on End', error) + }) } async _withinBegin(locator) { - this.context = locator; - locator = new Locator(locator, 'css'); - withinStatus = true; - return this.browser.evaluate((by, locator) => { - const el = window.codeceptjs.findElement(by, locator); - if (!el) throw new Error(`Element by ${by}: ${locator} not found`); - window.codeceptjs.within = el; - }, locator.type, locator.value); + this.context = locator + locator = new Locator(locator, 'css') + withinStatus = true + return this.browser.evaluate( + (by, locator) => { + const el = window.codeceptjs.findElement(by, locator) + if (!el) throw new Error(`Element by ${by}: ${locator} not found`) + window.codeceptjs.within = el + }, + locator.type, + locator.value, + ) } _withinEnd() { - this.context = this.options.rootElement; - withinStatus = false; + this.context = this.options.rootElement + withinStatus = false return this.browser.evaluate(() => { - window.codeceptjs.within = null; - }); + window.codeceptjs.within = null + }) } /** @@ -377,10 +409,14 @@ class Nightmare extends Helper { * ``` */ _locate(locator) { - locator = new Locator(locator, 'css'); - return this.browser.evaluate((by, locator) => { - return window.codeceptjs.findAndStoreElements(by, locator); - }, locator.type, locator.value); + locator = new Locator(locator, 'css') + return this.browser.evaluate( + (by, locator) => { + return window.codeceptjs.findAndStoreElements(by, locator) + }, + locator.type, + locator.value, + ) } /** @@ -392,7 +428,7 @@ class Nightmare extends Helper { * ``` */ haveHeader(header, value) { - return this.browser.header(header, value); + return this.browser.header(header, value) } /** @@ -401,223 +437,237 @@ class Nightmare extends Helper { * */ async amOnPage(url, headers = null) { - if (!(/^\w+\:\/\//.test(url))) { - url = urlResolve(this.options.url, url); + if (!/^\w+\:\/\//.test(url)) { + url = urlResolve(this.options.url, url) } - const currentUrl = await this.browser.url(); + const currentUrl = await this.browser.url() if (url === currentUrl) { // navigating to the same url will cause an error in nightmare, so don't do it - return; + return } return this.browser.goto(url, headers).then((res) => { - this.debugSection('URL', res.url); - this.debugSection('Code', res.code); - this.debugSection('Headers', JSON.stringify(res.headers)); - }); + this.debugSection('URL', res.url) + this.debugSection('Code', res.code) + this.debugSection('Headers', JSON.stringify(res.headers)) + }) } /** * {{> seeInTitle }} */ async seeInTitle(text) { - const title = await this.browser.title(); - stringIncludes('web page title').assert(text, title); + const title = await this.browser.title() + stringIncludes('web page title').assert(text, title) } /** * {{> dontSeeInTitle }} */ async dontSeeInTitle(text) { - const title = await this.browser.title(); - stringIncludes('web page title').negate(text, title); + const title = await this.browser.title() + stringIncludes('web page title').negate(text, title) } /** * {{> grabTitle }} */ async grabTitle() { - return this.browser.title(); + return this.browser.title() } /** * {{> grabCurrentUrl }} */ async grabCurrentUrl() { - return this.browser.url(); + return this.browser.url() } /** * {{> seeInCurrentUrl }} */ async seeInCurrentUrl(url) { - const currentUrl = await this.browser.url(); - stringIncludes('url').assert(url, currentUrl); + const currentUrl = await this.browser.url() + stringIncludes('url').assert(url, currentUrl) } /** * {{> dontSeeInCurrentUrl }} */ async dontSeeInCurrentUrl(url) { - const currentUrl = await this.browser.url(); - stringIncludes('url').negate(url, currentUrl); + const currentUrl = await this.browser.url() + stringIncludes('url').negate(url, currentUrl) } /** * {{> seeCurrentUrlEquals }} */ async seeCurrentUrlEquals(url) { - const currentUrl = await this.browser.url(); - urlEquals(this.options.url).assert(url, currentUrl); + const currentUrl = await this.browser.url() + urlEquals(this.options.url).assert(url, currentUrl) } /** * {{> dontSeeCurrentUrlEquals }} */ async dontSeeCurrentUrlEquals(url) { - const currentUrl = await this.browser.url(); - urlEquals(this.options.url).negate(url, currentUrl); + const currentUrl = await this.browser.url() + urlEquals(this.options.url).negate(url, currentUrl) } /** * {{> see }} */ async see(text, context = null) { - return proceedSee.call(this, 'assert', text, context); + return proceedSee.call(this, 'assert', text, context) } /** * {{> dontSee }} */ dontSee(text, context = null) { - return proceedSee.call(this, 'negate', text, context); + return proceedSee.call(this, 'negate', text, context) } /** * {{> seeElement }} */ async seeElement(locator) { - locator = new Locator(locator, 'css'); - const num = await this.browser.evaluate((by, locator) => { - return window.codeceptjs.findElements(by, locator).filter(e => e.offsetWidth > 0 && e.offsetHeight > 0).length; - }, locator.type, locator.value); - equals('number of elements on a page').negate(0, num); + locator = new Locator(locator, 'css') + const num = await this.browser.evaluate( + (by, locator) => { + return window.codeceptjs.findElements(by, locator).filter((e) => e.offsetWidth > 0 && e.offsetHeight > 0).length + }, + locator.type, + locator.value, + ) + equals('number of elements on a page').negate(0, num) } /** * {{> dontSeeElement }} */ async dontSeeElement(locator) { - locator = new Locator(locator, 'css'); - locator = new Locator(locator, 'css'); - const num = await this.browser.evaluate((by, locator) => { - return window.codeceptjs.findElements(by, locator).filter(e => e.offsetWidth > 0 && e.offsetHeight > 0).length; - }, locator.type, locator.value); - equals('number of elements on a page').assert(0, num); + locator = new Locator(locator, 'css') + locator = new Locator(locator, 'css') + const num = await this.browser.evaluate( + (by, locator) => { + return window.codeceptjs.findElements(by, locator).filter((e) => e.offsetWidth > 0 && e.offsetHeight > 0).length + }, + locator.type, + locator.value, + ) + equals('number of elements on a page').assert(0, num) } /** * {{> seeElementInDOM }} */ async seeElementInDOM(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - empty('elements').negate(els.fill('ELEMENT')); + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + empty('elements').negate(els.fill('ELEMENT')) } /** * {{> dontSeeElementInDOM }} */ async dontSeeElementInDOM(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - empty('elements').assert(els.fill('ELEMENT')); + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + empty('elements').assert(els.fill('ELEMENT')) } /** * {{> seeInSource }} */ async seeInSource(text) { - const source = await this.browser.evaluate(() => document.documentElement.outerHTML); - stringIncludes('HTML source of a page').assert(text, source); + const source = await this.browser.evaluate(() => document.documentElement.outerHTML) + stringIncludes('HTML source of a page').assert(text, source) } /** * {{> dontSeeInSource }} */ async dontSeeInSource(text) { - const source = await this.browser.evaluate(() => document.documentElement.outerHTML); - stringIncludes('HTML source of a page').negate(text, source); + const source = await this.browser.evaluate(() => document.documentElement.outerHTML) + stringIncludes('HTML source of a page').negate(text, source) } /** * {{> seeNumberOfElements }} */ async seeNumberOfElements(locator, num) { - const elements = await this._locate(locator); - return equals(`expected number of elements (${(new Locator(locator))}) is ${num}, but found ${elements.length}`).assert(elements.length, num); + const elements = await this._locate(locator) + return equals( + `expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`, + ).assert(elements.length, num) } /** * {{> seeNumberOfVisibleElements }} */ async seeNumberOfVisibleElements(locator, num) { - const res = await this.grabNumberOfVisibleElements(locator); - return equals(`expected number of visible elements (${(new Locator(locator))}) is ${num}, but found ${res}`).assert(res, num); + const res = await this.grabNumberOfVisibleElements(locator) + return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert( + res, + num, + ) } /** * {{> grabNumberOfVisibleElements }} */ async grabNumberOfVisibleElements(locator) { - locator = new Locator(locator, 'css'); + locator = new Locator(locator, 'css') - const num = await this.browser.evaluate((by, locator) => { - return window.codeceptjs.findElements(by, locator) - .filter(e => e.offsetWidth > 0 && e.offsetHeight > 0).length; - }, locator.type, locator.value); + const num = await this.browser.evaluate( + (by, locator) => { + return window.codeceptjs.findElements(by, locator).filter((e) => e.offsetWidth > 0 && e.offsetHeight > 0).length + }, + locator.type, + locator.value, + ) - return num; + return num } /** * {{> click }} */ async click(locator, context = null) { - const el = await findClickable.call(this, locator, context); - assertElementExists(el, locator, 'Clickable'); - return this.browser.evaluate(el => window.codeceptjs.clickEl(el), el) - .wait(this.options.waitForAction); + const el = await findClickable.call(this, locator, context) + assertElementExists(el, locator, 'Clickable') + return this.browser.evaluate((el) => window.codeceptjs.clickEl(el), el).wait(this.options.waitForAction) } /** * {{> doubleClick }} */ async doubleClick(locator, context = null) { - const el = await findClickable.call(this, locator, context); - assertElementExists(el, locator, 'Clickable'); - return this.browser.evaluate(el => window.codeceptjs.doubleClickEl(el), el) - .wait(this.options.waitForAction); + const el = await findClickable.call(this, locator, context) + assertElementExists(el, locator, 'Clickable') + return this.browser.evaluate((el) => window.codeceptjs.doubleClickEl(el), el).wait(this.options.waitForAction) } /** * {{> rightClick }} */ async rightClick(locator, context = null) { - const el = await findClickable.call(this, locator, context); - assertElementExists(el, locator, 'Clickable'); - return this.browser.evaluate(el => window.codeceptjs.rightClickEl(el), el) - .wait(this.options.waitForAction); + const el = await findClickable.call(this, locator, context) + assertElementExists(el, locator, 'Clickable') + return this.browser.evaluate((el) => window.codeceptjs.rightClickEl(el), el).wait(this.options.waitForAction) } /** * {{> moveCursorTo }} */ async moveCursorTo(locator, offsetX = 0, offsetY = 0) { - locator = new Locator(locator, 'css'); - const el = await this.browser.findElement(locator.toStrict()); - assertElementExists(el, locator); - return this.browser.evaluate((el, x, y) => window.codeceptjs.hoverEl(el, x, y), el, offsetX, offsetY) - .wait(this.options.waitForAction); // wait for hover event to happen + locator = new Locator(locator, 'css') + const el = await this.browser.findElement(locator.toStrict()) + assertElementExists(el, locator) + return this.browser + .evaluate((el, x, y) => window.codeceptjs.hoverEl(el, x, y), el, offsetX, offsetY) + .wait(this.options.waitForAction) // wait for hover event to happen } /** @@ -626,8 +676,7 @@ class Nightmare extends Helper { * Wrapper for synchronous [evaluate](https://github.com/segmentio/nightmare#evaluatefn-arg1-arg2) */ async executeScript(...args) { - return this.browser.evaluate.apply(this.browser, args) - .catch(err => err); // Nightmare's first argument is error :( + return this.browser.evaluate.apply(this.browser, args).catch((err) => err) // Nightmare's first argument is error :( } /** @@ -637,8 +686,7 @@ class Nightmare extends Helper { * Unlike NightmareJS implementation calling `done` will return its first argument. */ async executeAsyncScript(...args) { - return this.browser.evaluate.apply(this.browser, args) - .catch(err => err); // Nightmare's first argument is error :( + return this.browser.evaluate.apply(this.browser, args).catch((err) => err) // Nightmare's first argument is error :( } /** @@ -646,72 +694,68 @@ class Nightmare extends Helper { */ async resizeWindow(width, height) { if (width === 'maximize') { - throw new Error('Nightmare doesn\'t support resizeWindow to maximum!'); + throw new Error("Nightmare doesn't support resizeWindow to maximum!") } - return this.browser.viewport(width, height).wait(this.options.waitForAction); + return this.browser.viewport(width, height).wait(this.options.waitForAction) } /** * {{> checkOption }} */ async checkOption(field, context = null) { - const els = await findCheckable.call(this, field, context); - assertElementExists(els[0], field, 'Checkbox or radio'); - return this.browser.evaluate(els => window.codeceptjs.checkEl(els[0]), els) - .wait(this.options.waitForAction); + const els = await findCheckable.call(this, field, context) + assertElementExists(els[0], field, 'Checkbox or radio') + return this.browser.evaluate((els) => window.codeceptjs.checkEl(els[0]), els).wait(this.options.waitForAction) } /** * {{> uncheckOption }} */ async uncheckOption(field, context = null) { - const els = await findCheckable.call(this, field, context); - assertElementExists(els[0], field, 'Checkbox or radio'); - return this.browser.evaluate(els => window.codeceptjs.unCheckEl(els[0]), els) - .wait(this.options.waitForAction); + const els = await findCheckable.call(this, field, context) + assertElementExists(els[0], field, 'Checkbox or radio') + return this.browser.evaluate((els) => window.codeceptjs.unCheckEl(els[0]), els).wait(this.options.waitForAction) } /** * {{> fillField }} */ async fillField(field, value) { - const el = await findField.call(this, field); - assertElementExists(el, field, 'Field'); - return this.browser.enterText(el, value.toString(), true) - .wait(this.options.waitForAction); + const el = await findField.call(this, field) + assertElementExists(el, field, 'Field') + return this.browser.enterText(el, value.toString(), true).wait(this.options.waitForAction) } /** * {{> clearField }} */ async clearField(field) { - return this.fillField(field, ''); + return this.fillField(field, '') } /** * {{> appendField }} */ async appendField(field, value) { - const el = await findField.call(this, field); - assertElementExists(el, field, 'Field'); - return this.browser.enterText(el, value.toString(), false) - .wait(this.options.waitForAction); + const el = await findField.call(this, field) + assertElementExists(el, field, 'Field') + return this.browser.enterText(el, value.toString(), false).wait(this.options.waitForAction) } /** * {{> seeInField }} */ async seeInField(field, value) { - const _value = (typeof value === 'boolean') ? value : value.toString(); - return proceedSeeInField.call(this, 'assert', field, _value); + const _value = typeof value === 'boolean' ? value : value.toString() + return proceedSeeInField.call(this, 'assert', field, _value) } /** * {{> dontSeeInField }} */ async dontSeeInField(field, value) { - const _value = (typeof value === 'boolean') ? value : value.toString(); - return proceedSeeInField.call(this, 'negate', field, _value); + const _value = typeof value === 'boolean' ? value : value.toString() + return proceedSeeInField.call(this, 'negate', field, _value) } /** @@ -720,12 +764,12 @@ class Nightmare extends Helper { */ async pressKey(key) { if (Array.isArray(key)) { - key = key.join('+'); // should work with accelerators... + key = key.join('+') // should work with accelerators... } if (Object.keys(specialKeys).indexOf(key) >= 0) { - key = specialKeys[key]; + key = specialKeys[key] } - return this.browser.pressKey(key).wait(this.options.waitForAction); + return this.browser.pressKey(key).wait(this.options.waitForAction) } /** @@ -739,21 +783,21 @@ class Nightmare extends Helper { } */ async triggerMouseEvent(event) { - return this.browser.triggerMouseEvent(event).wait(this.options.waitForAction); + return this.browser.triggerMouseEvent(event).wait(this.options.waitForAction) } /** * {{> seeCheckboxIsChecked }} */ async seeCheckboxIsChecked(field) { - return proceedIsChecked.call(this, 'assert', field); + return proceedIsChecked.call(this, 'assert', field) } /** * {{> dontSeeCheckboxIsChecked }} */ async dontSeeCheckboxIsChecked(field) { - return proceedIsChecked.call(this, 'negate', field); + return proceedIsChecked.call(this, 'negate', field) } /** @@ -762,167 +806,171 @@ class Nightmare extends Helper { * Doesn't work if the Chromium DevTools panel is open (as Chromium allows only one attachment to the debugger at a time. [See more](https://github.com/rosshinkley/nightmare-upload#important-note-about-setting-file-upload-inputs)) */ async attachFile(locator, pathToFile) { - const file = path.join(global.codecept_dir, pathToFile); + const file = path.join(global.codecept_dir, pathToFile) - locator = new Locator(locator, 'css'); + locator = new Locator(locator, 'css') if (!locator.isCSS()) { - throw new Error('Only CSS locator allowed for attachFile in Nightmare helper'); + throw new Error('Only CSS locator allowed for attachFile in Nightmare helper') } if (!fileExists(file)) { - throw new Error(`File at ${file} can not be found on local system`); + throw new Error(`File at ${file} can not be found on local system`) } - return this.browser.upload(locator.value, file); + return this.browser.upload(locator.value, file) } /** * {{> grabTextFromAll }} */ async grabTextFromAll(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - const texts = []; - const getText = el => window.codeceptjs.fetchElement(el).innerText; + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + const texts = [] + const getText = (el) => window.codeceptjs.fetchElement(el).innerText for (const el of els) { - texts.push(await this.browser.evaluate(getText, el)); + texts.push(await this.browser.evaluate(getText, el)) } - return texts; + return texts } /** * {{> grabTextFrom }} */ async grabTextFrom(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElement(locator.toStrict()); - assertElementExists(els, locator); - const texts = await this.grabTextFromAll(locator); + locator = new Locator(locator, 'css') + const els = await this.browser.findElement(locator.toStrict()) + assertElementExists(els, locator) + const texts = await this.grabTextFromAll(locator) if (texts.length > 1) { - this.debugSection('GrabText', `Using first element out of ${texts.length}`); + this.debugSection('GrabText', `Using first element out of ${texts.length}`) } - return texts[0]; + return texts[0] } /** * {{> grabValueFromAll }} */ async grabValueFromAll(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - const values = []; - const getValues = el => window.codeceptjs.fetchElement(el).value; + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + const values = [] + const getValues = (el) => window.codeceptjs.fetchElement(el).value for (const el of els) { - values.push(await this.browser.evaluate(getValues, el)); + values.push(await this.browser.evaluate(getValues, el)) } - return values; + return values } /** * {{> grabValueFrom }} */ async grabValueFrom(locator) { - const el = await findField.call(this, locator); - assertElementExists(el, locator, 'Field'); - const values = await this.grabValueFromAll(locator); + const el = await findField.call(this, locator) + assertElementExists(el, locator, 'Field') + const values = await this.grabValueFromAll(locator) if (values.length > 1) { - this.debugSection('GrabValue', `Using first element out of ${values.length}`); + this.debugSection('GrabValue', `Using first element out of ${values.length}`) } - return values[0]; + return values[0] } /** * {{> grabAttributeFromAll }} */ async grabAttributeFromAll(locator, attr) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - const array = []; + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + const array = [] for (let index = 0; index < els.length; index++) { - const el = els[index]; - array.push(await this.browser.evaluate((el, attr) => window.codeceptjs.fetchElement(el).getAttribute(attr), el, attr)); + const el = els[index] + array.push( + await this.browser.evaluate((el, attr) => window.codeceptjs.fetchElement(el).getAttribute(attr), el, attr), + ) } - return array; + return array } /** * {{> grabAttributeFrom }} */ async grabAttributeFrom(locator, attr) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElement(locator.toStrict()); - assertElementExists(els, locator); + locator = new Locator(locator, 'css') + const els = await this.browser.findElement(locator.toStrict()) + assertElementExists(els, locator) - const attrs = await this.grabAttributeFromAll(locator, attr); + const attrs = await this.grabAttributeFromAll(locator, attr) if (attrs.length > 1) { - this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`); + this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`) } - return attrs[0]; + return attrs[0] } /** * {{> grabHTMLFromAll }} */ async grabHTMLFromAll(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - const array = []; + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + const array = [] for (let index = 0; index < els.length; index++) { - const el = els[index]; - array.push(await this.browser.evaluate(el => window.codeceptjs.fetchElement(el).innerHTML, el)); + const el = els[index] + array.push(await this.browser.evaluate((el) => window.codeceptjs.fetchElement(el).innerHTML, el)) } - this.debugSection('GrabHTML', array); + this.debugSection('GrabHTML', array) - return array; + return array } /** * {{> grabHTMLFrom }} */ async grabHTMLFrom(locator) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElement(locator.toStrict()); - assertElementExists(els, locator); - const html = await this.grabHTMLFromAll(locator); + locator = new Locator(locator, 'css') + const els = await this.browser.findElement(locator.toStrict()) + assertElementExists(els, locator) + const html = await this.grabHTMLFromAll(locator) if (html.length > 1) { - this.debugSection('GrabHTML', `Using first element out of ${html.length}`); + this.debugSection('GrabHTML', `Using first element out of ${html.length}`) } - return html[0]; + return html[0] } /** * {{> grabCssPropertyFrom }} */ async grabCssPropertyFrom(locator, cssProperty) { - locator = new Locator(locator, 'css'); - const els = await this.browser.findElements(locator.toStrict()); - const array = []; + locator = new Locator(locator, 'css') + const els = await this.browser.findElements(locator.toStrict()) + const array = [] const getCssPropForElement = async (el, prop) => { - return (await this.browser.evaluate((el) => { - return window.getComputedStyle(window.codeceptjs.fetchElement(el)); - }, el))[toCamelCase(prop)]; - }; + return ( + await this.browser.evaluate((el) => { + return window.getComputedStyle(window.codeceptjs.fetchElement(el)) + }, el) + )[toCamelCase(prop)] + } for (const el of els) { - assertElementExists(el, locator); - const cssValue = await getCssPropForElement(el, cssProperty); - array.push(cssValue); + assertElementExists(el, locator) + const cssValue = await getCssPropForElement(el, cssProperty) + array.push(cssValue) } - this.debugSection('HTML', array); + this.debugSection('HTML', array) - return array.length > 1 ? array : array[0]; + return array.length > 1 ? array : array[0] } _injectClientScripts() { - return this.browser.inject('js', path.join(__dirname, 'clientscripts', 'nightmare.js')); + return this.browser.inject('js', path.join(__dirname, 'clientscripts', 'nightmare.js')) } /** @@ -930,41 +978,41 @@ class Nightmare extends Helper { */ async selectOption(select, option) { const fetchAndCheckOption = function (el, locator) { - el = window.codeceptjs.fetchElement(el); - const found = document.evaluate(locator, el, null, 5, null); - let current = null; - const items = []; - while (current = found.iterateNext()) { - items.push(current); + el = window.codeceptjs.fetchElement(el) + const found = document.evaluate(locator, el, null, 5, null) + let current = null + const items = [] + while ((current = found.iterateNext())) { + items.push(current) } for (let i = 0; i < items.length; i++) { - current = items[i]; + current = items[i] if (current instanceof HTMLOptionElement) { - current.selected = true; - if (!el.multiple) el.value = current.value; + current.selected = true + if (!el.multiple) el.value = current.value } - const event = document.createEvent('HTMLEvents'); - event.initEvent('change', true, true); - el.dispatchEvent(event); + const event = document.createEvent('HTMLEvents') + event.initEvent('change', true, true) + el.dispatchEvent(event) } - return !!current; - }; + return !!current + } - const el = await findField.call(this, select); - assertElementExists(el, select, 'Selectable field'); + const el = await findField.call(this, select) + assertElementExists(el, select, 'Selectable field') if (!Array.isArray(option)) { - option = [option]; + option = [option] } for (const key in option) { - const opt = xpathLocator.literal(option[key]); - const checked = await this.browser.evaluate(fetchAndCheckOption, el, Locator.select.byVisibleText(opt)); + const opt = xpathLocator.literal(option[key]) + const checked = await this.browser.evaluate(fetchAndCheckOption, el, Locator.select.byVisibleText(opt)) if (!checked) { - await this.browser.evaluate(fetchAndCheckOption, el, Locator.select.byValue(opt)); + await this.browser.evaluate(fetchAndCheckOption, el, Locator.select.byValue(opt)) } } - return this.browser.wait(this.options.waitForAction); + return this.browser.wait(this.options.waitForAction) } /** @@ -974,7 +1022,7 @@ class Nightmare extends Helper { * [See more](https://github.com/segmentio/nightmare/blob/master/Readme.md#cookiessetcookie) */ async setCookie(cookie) { - return this.browser.cookies.set(cookie); + return this.browser.cookies.set(cookie) } /** @@ -982,16 +1030,16 @@ class Nightmare extends Helper { * */ async seeCookie(name) { - const res = await this.browser.cookies.get(name); - truth(`cookie ${name}`, 'to be set').assert(res); + const res = await this.browser.cookies.get(name) + truth(`cookie ${name}`, 'to be set').assert(res) } /** * {{> dontSeeCookie }} */ async dontSeeCookie(name) { - const res = await this.browser.cookies.get(name); - truth(`cookie ${name}`, 'to be set').negate(res); + const res = await this.browser.cookies.get(name) + truth(`cookie ${name}`, 'to be set').negate(res) } /** @@ -1002,7 +1050,7 @@ class Nightmare extends Helper { * Multiple cookies can be received by passing query object `I.grabCookie({ secure: true});`. If you'd like get all cookies for all urls, use: `.grabCookie({ url: null }).` */ async grabCookie(name) { - return this.browser.cookies.get(name); + return this.browser.cookies.get(name) } /** @@ -1010,34 +1058,34 @@ class Nightmare extends Helper { */ async clearCookie(cookie) { if (!cookie) { - return this.browser.cookies.clearAll(); + return this.browser.cookies.clearAll() } - return this.browser.cookies.clear(cookie); + return this.browser.cookies.clear(cookie) } /** * {{> waitForFunction }} */ async waitForFunction(fn, argsOrSec = null, sec = null) { - let args = []; + let args = [] if (argsOrSec) { if (Array.isArray(argsOrSec)) { - args = argsOrSec; + args = argsOrSec } else if (typeof argsOrSec === 'number') { - sec = argsOrSec; + sec = argsOrSec } } - this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - return this.browser.wait(fn, ...args); + this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + return this.browser.wait(fn, ...args) } /** * {{> wait }} */ async wait(sec) { - return new Promise(((done) => { - setTimeout(done, sec * 1000); - })); + return new Promise((done) => { + setTimeout(done, sec * 1000) + }) } /** @@ -1045,113 +1093,136 @@ class Nightmare extends Helper { */ async waitForText(text, sec, context = null) { if (!context) { - context = this.context; + context = this.context } - const locator = new Locator(context, 'css'); - this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - return this.browser.wait((by, locator, text) => { - return window.codeceptjs.findElement(by, locator).innerText.indexOf(text) > -1; - }, locator.type, locator.value, text).catch((err) => { - if (err.message.indexOf('Cannot read property') > -1) { - throw new Error(`element (${JSON.stringify(context)}) is not in DOM. Unable to wait text.`); - } else if (err.message && err.message.indexOf('.wait() timed out after') > -1) { - throw new Error(`there is no element(${JSON.stringify(context)}) with text "${text}" after ${sec} sec`); - } else throw err; - }); + const locator = new Locator(context, 'css') + this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + return this.browser + .wait( + (by, locator, text) => { + return window.codeceptjs.findElement(by, locator).innerText.indexOf(text) > -1 + }, + locator.type, + locator.value, + text, + ) + .catch((err) => { + if (err.message.indexOf('Cannot read property') > -1) { + throw new Error(`element (${JSON.stringify(context)}) is not in DOM. Unable to wait text.`) + } else if (err.message && err.message.indexOf('.wait() timed out after') > -1) { + throw new Error(`there is no element(${JSON.stringify(context)}) with text "${text}" after ${sec} sec`) + } else throw err + }) } /** * {{> waitForVisible }} */ waitForVisible(locator, sec) { - this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - locator = new Locator(locator, 'css'); - - return this.browser.wait((by, locator) => { - const el = window.codeceptjs.findElement(by, locator); - if (!el) return false; - return el.offsetWidth > 0 && el.offsetHeight > 0; - }, locator.type, locator.value).catch((err) => { - if (err.message && err.message.indexOf('.wait() timed out after') > -1) { - throw new Error(`element (${JSON.stringify(locator)}) still not visible on page after ${sec} sec`); - } else throw err; - }); + this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + locator = new Locator(locator, 'css') + + return this.browser + .wait( + (by, locator) => { + const el = window.codeceptjs.findElement(by, locator) + if (!el) return false + return el.offsetWidth > 0 && el.offsetHeight > 0 + }, + locator.type, + locator.value, + ) + .catch((err) => { + if (err.message && err.message.indexOf('.wait() timed out after') > -1) { + throw new Error(`element (${JSON.stringify(locator)}) still not visible on page after ${sec} sec`) + } else throw err + }) } /** * {{> waitToHide }} */ async waitToHide(locator, sec = null) { - return this.waitForInvisible(locator, sec); + return this.waitForInvisible(locator, sec) } /** * {{> waitForInvisible }} */ waitForInvisible(locator, sec) { - this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - locator = new Locator(locator, 'css'); - - return this.browser.wait((by, locator) => { - const el = window.codeceptjs.findElement(by, locator); - if (!el) return true; - return !(el.offsetWidth > 0 && el.offsetHeight > 0); - }, locator.type, locator.value).catch((err) => { - if (err.message && err.message.indexOf('.wait() timed out after') > -1) { - throw new Error(`element (${JSON.stringify(locator)}) still visible after ${sec} sec`); - } else throw err; - }); + this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + locator = new Locator(locator, 'css') + + return this.browser + .wait( + (by, locator) => { + const el = window.codeceptjs.findElement(by, locator) + if (!el) return true + return !(el.offsetWidth > 0 && el.offsetHeight > 0) + }, + locator.type, + locator.value, + ) + .catch((err) => { + if (err.message && err.message.indexOf('.wait() timed out after') > -1) { + throw new Error(`element (${JSON.stringify(locator)}) still visible after ${sec} sec`) + } else throw err + }) } /** * {{> waitForElement }} */ async waitForElement(locator, sec) { - this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - locator = new Locator(locator, 'css'); + this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + locator = new Locator(locator, 'css') - return this.browser.wait((by, locator) => window.codeceptjs.findElement(by, locator) !== null, locator.type, locator.value).catch((err) => { - if (err.message && err.message.indexOf('.wait() timed out after') > -1) { - throw new Error(`element (${JSON.stringify(locator)}) still not present on page after ${sec} sec`); - } else throw err; - }); + return this.browser + .wait((by, locator) => window.codeceptjs.findElement(by, locator) !== null, locator.type, locator.value) + .catch((err) => { + if (err.message && err.message.indexOf('.wait() timed out after') > -1) { + throw new Error(`element (${JSON.stringify(locator)}) still not present on page after ${sec} sec`) + } else throw err + }) } async waitUntilExists(locator, sec) { console.log(`waitUntilExists deprecated: * use 'waitForElement' to wait for element to be attached - * use 'waitForDetached to wait for element to be removed'`); - return this.waitForDetached(locator, sec); + * use 'waitForDetached to wait for element to be removed'`) + return this.waitForDetached(locator, sec) } /** * {{> waitForDetached }} */ async waitForDetached(locator, sec) { - this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - sec = this.browser.options.waitForTimeout / 1000; - locator = new Locator(locator, 'css'); + this.browser.options.waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + sec = this.browser.options.waitForTimeout / 1000 + locator = new Locator(locator, 'css') - return this.browser.wait((by, locator) => window.codeceptjs.findElement(by, locator) === null, locator.type, locator.value).catch((err) => { - if (err.message && err.message.indexOf('.wait() timed out after') > -1) { - throw new Error(`element (${JSON.stringify(locator)}) still on page after ${sec} sec`); - } else throw err; - }); + return this.browser + .wait((by, locator) => window.codeceptjs.findElement(by, locator) === null, locator.type, locator.value) + .catch((err) => { + if (err.message && err.message.indexOf('.wait() timed out after') > -1) { + throw new Error(`element (${JSON.stringify(locator)}) still on page after ${sec} sec`) + } else throw err + }) } /** * {{> refreshPage }} */ async refreshPage() { - return this.browser.refresh(); + return this.browser.refresh() } /** * Reload the page */ refresh() { - console.log('Deprecated in favor of refreshPage'); - return this.browser.refresh(); + console.log('Deprecated in favor of refreshPage') + return this.browser.refresh() } /** @@ -1159,70 +1230,74 @@ class Nightmare extends Helper { * */ async saveElementScreenshot(locator, fileName) { - const outputFile = screenshotOutputFolder(fileName); + const outputFile = screenshotOutputFolder(fileName) - const rect = await this.grabElementBoundingRect(locator); + const rect = await this.grabElementBoundingRect(locator) const button_clip = { x: Math.floor(rect.x), y: Math.floor(rect.y), width: Math.floor(rect.width), height: Math.floor(rect.height), - }; + } - this.debug(`Screenshot of ${(new Locator(locator))} element has been saved to ${outputFile}`); + this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`) // take the screenshot - await this.browser.screenshot(outputFile, button_clip); + await this.browser.screenshot(outputFile, button_clip) } /** * {{> grabElementBoundingRect }} */ async grabElementBoundingRect(locator, prop) { - locator = new Locator(locator, 'css'); + locator = new Locator(locator, 'css') - const rect = await this.browser.evaluate(async (by, locator) => { - // store the button in a variable + const rect = await this.browser.evaluate( + async (by, locator) => { + // store the button in a variable - const build_cluster_btn = await window.codeceptjs.findElement(by, locator); + const build_cluster_btn = await window.codeceptjs.findElement(by, locator) - // use the getClientRects() function on the button to determine - // the size and location - const rect = build_cluster_btn.getBoundingClientRect(); + // use the getClientRects() function on the button to determine + // the size and location + const rect = build_cluster_btn.getBoundingClientRect() - // convert the rectangle to a clip object and return it - return { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height, - }; - }, locator.type, locator.value); + // convert the rectangle to a clip object and return it + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height, + } + }, + locator.type, + locator.value, + ) - if (prop) return rect[prop]; - return rect; + if (prop) return rect[prop] + return rect } /** * {{> saveScreenshot }} */ async saveScreenshot(fileName, fullPage = this.options.fullPageScreenshots) { - const outputFile = screenshotOutputFolder(fileName); + const outputFile = screenshotOutputFolder(fileName) - this.debug(`Screenshot is saving to ${outputFile}`); + this.debug(`Screenshot is saving to ${outputFile}`) if (!fullPage) { - return this.browser.screenshot(outputFile); + return this.browser.screenshot(outputFile) } const { height, width } = await this.browser.evaluate(() => { - return { height: document.body.scrollHeight, width: document.body.scrollWidth }; - }); - await this.browser.viewport(width, height); - return this.browser.screenshot(outputFile); + return { height: document.body.scrollHeight, width: document.body.scrollWidth } + }) + await this.browser.viewport(width, height) + return this.browser.screenshot(outputFile) } async _failed() { - if (withinStatus !== false) await this._withinEnd(); + if (withinStatus !== false) await this._withinEnd() } /** @@ -1230,28 +1305,40 @@ class Nightmare extends Helper { */ async scrollTo(locator, offsetX = 0, offsetY = 0) { if (typeof locator === 'number' && typeof offsetX === 'number') { - offsetY = offsetX; - offsetX = locator; - locator = null; + offsetY = offsetX + offsetX = locator + locator = null } if (locator) { - locator = new Locator(locator, 'css'); - return this.browser.evaluate((by, locator, offsetX, offsetY) => { - const el = window.codeceptjs.findElement(by, locator); - if (!el) throw new Error(`Element not found ${by}: ${locator}`); - const rect = el.getBoundingClientRect(); - window.scrollTo(rect.left + offsetX, rect.top + offsetY); - }, locator.type, locator.value, offsetX, offsetY); + locator = new Locator(locator, 'css') + return this.browser.evaluate( + (by, locator, offsetX, offsetY) => { + const el = window.codeceptjs.findElement(by, locator) + if (!el) throw new Error(`Element not found ${by}: ${locator}`) + const rect = el.getBoundingClientRect() + window.scrollTo(rect.left + offsetX, rect.top + offsetY) + }, + locator.type, + locator.value, + offsetX, + offsetY, + ) } // eslint-disable-next-line prefer-arrow-callback - return this.executeScript(function (x, y) { return window.scrollTo(x, y); }, offsetX, offsetY); + return this.executeScript( + function (x, y) { + return window.scrollTo(x, y) + }, + offsetX, + offsetY, + ) } /** * {{> scrollPageToTop }} */ async scrollPageToTop() { - return this.executeScript(() => window.scrollTo(0, 0)); + return this.executeScript(() => window.scrollTo(0, 0)) } /** @@ -1260,16 +1347,13 @@ class Nightmare extends Helper { async scrollPageToBottom() { /* eslint-disable prefer-arrow-callback, comma-dangle */ return this.executeScript(function () { - const body = document.body; - const html = document.documentElement; - window.scrollTo(0, Math.max( - body.scrollHeight, - body.offsetHeight, - html.clientHeight, - html.scrollHeight, - html.offsetHeight - )); - }); + const body = document.body + const html = document.documentElement + window.scrollTo( + 0, + Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), + ) + }) /* eslint-enable */ } @@ -1281,135 +1365,143 @@ class Nightmare extends Helper { function getScrollPosition() { return { x: window.pageXOffset, - y: window.pageYOffset - }; + y: window.pageYOffset, + } } /* eslint-enable comma-dangle */ - return this.executeScript(getScrollPosition); + return this.executeScript(getScrollPosition) } } -module.exports = Nightmare; +module.exports = Nightmare async function proceedSee(assertType, text, context) { - let description; - let locator; + let description + let locator if (!context) { if (this.context === this.options.rootElement) { - locator = new Locator(this.context, 'css'); - description = 'web application'; + locator = new Locator(this.context, 'css') + description = 'web application' } else { - description = `current context ${this.context}`; - locator = new Locator({ xpath: './/*' }); + description = `current context ${this.context}` + locator = new Locator({ xpath: './/*' }) } } else { - locator = new Locator(context, 'css'); - description = `element ${locator.toString()}`; - } - - const texts = await this.browser.evaluate((by, locator) => { - return window.codeceptjs.findElements(by, locator).map(el => el.innerText); - }, locator.type, locator.value); - const allText = texts.join(' | '); - return stringIncludes(description)[assertType](text, allText); + locator = new Locator(context, 'css') + description = `element ${locator.toString()}` + } + + const texts = await this.browser.evaluate( + (by, locator) => { + return window.codeceptjs.findElements(by, locator).map((el) => el.innerText) + }, + locator.type, + locator.value, + ) + const allText = texts.join(' | ') + return stringIncludes(description)[assertType](text, allText) } async function proceedSeeInField(assertType, field, value) { - const el = await findField.call(this, field); - assertElementExists(el, field, 'Field'); - const tag = await this.browser.evaluate(el => window.codeceptjs.fetchElement(el).tagName, el); - const fieldVal = await this.browser.evaluate(el => window.codeceptjs.fetchElement(el).value, el); + const el = await findField.call(this, field) + assertElementExists(el, field, 'Field') + const tag = await this.browser.evaluate((el) => window.codeceptjs.fetchElement(el).tagName, el) + const fieldVal = await this.browser.evaluate((el) => window.codeceptjs.fetchElement(el).value, el) if (tag === 'select') { // locate option by values and check them - const text = await this.browser.evaluate((el, val) => { - return el.querySelector(`option[value="${val}"]`).innerText; - }, el, xpathLocator.literal(fieldVal)); - return equals(`select option by ${field}`)[assertType](value, text); + const text = await this.browser.evaluate( + (el, val) => { + return el.querySelector(`option[value="${val}"]`).innerText + }, + el, + xpathLocator.literal(fieldVal), + ) + return equals(`select option by ${field}`)[assertType](value, text) } - return stringIncludes(`field by ${field}`)[assertType](value, fieldVal); + return stringIncludes(`field by ${field}`)[assertType](value, fieldVal) } async function proceedIsChecked(assertType, option) { - const els = await findCheckable.call(this, option); - assertElementExists(els, option, 'Checkable'); + const els = await findCheckable.call(this, option) + assertElementExists(els, option, 'Checkable') const selected = await this.browser.evaluate((els) => { - return els.map(el => window.codeceptjs.fetchElement(el).checked).reduce((prev, cur) => prev || cur); - }, els); - return truth(`checkable ${option}`, 'to be checked')[assertType](selected); + return els.map((el) => window.codeceptjs.fetchElement(el).checked).reduce((prev, cur) => prev || cur) + }, els) + return truth(`checkable ${option}`, 'to be checked')[assertType](selected) } async function findCheckable(locator, context) { - let contextEl = null; + let contextEl = null if (context) { - contextEl = await this.browser.findElement((new Locator(context, 'css')).toStrict()); + contextEl = await this.browser.findElement(new Locator(context, 'css').toStrict()) } - const matchedLocator = new Locator(locator); + const matchedLocator = new Locator(locator) if (!matchedLocator.isFuzzy()) { - return this.browser.findElements(matchedLocator.toStrict(), contextEl); + return this.browser.findElements(matchedLocator.toStrict(), contextEl) } - const literal = xpathLocator.literal(locator); - let els = await this.browser.findElements({ xpath: Locator.checkable.byText(literal) }, contextEl); + const literal = xpathLocator.literal(locator) + let els = await this.browser.findElements({ xpath: Locator.checkable.byText(literal) }, contextEl) if (els.length) { - return els; + return els } - els = await this.browser.findElements({ xpath: Locator.checkable.byName(literal) }, contextEl); + els = await this.browser.findElements({ xpath: Locator.checkable.byName(literal) }, contextEl) if (els.length) { - return els; + return els } - return this.browser.findElements({ css: locator }, contextEl); + return this.browser.findElements({ css: locator }, contextEl) } async function findClickable(locator, context) { - let contextEl = null; + let contextEl = null if (context) { - contextEl = await this.browser.findElement((new Locator(context, 'css')).toStrict()); + contextEl = await this.browser.findElement(new Locator(context, 'css').toStrict()) } - const matchedLocator = new Locator(locator); + const matchedLocator = new Locator(locator) if (!matchedLocator.isFuzzy()) { - return this.browser.findElement(matchedLocator.toStrict(), contextEl); + return this.browser.findElement(matchedLocator.toStrict(), contextEl) } - const literal = xpathLocator.literal(locator); + const literal = xpathLocator.literal(locator) - let els = await this.browser.findElements({ xpath: Locator.clickable.narrow(literal) }, contextEl); + let els = await this.browser.findElements({ xpath: Locator.clickable.narrow(literal) }, contextEl) if (els.length) { - return els[0]; + return els[0] } - els = await this.browser.findElements({ xpath: Locator.clickable.wide(literal) }, contextEl); + els = await this.browser.findElements({ xpath: Locator.clickable.wide(literal) }, contextEl) if (els.length) { - return els[0]; + return els[0] } - return this.browser.findElement({ css: locator }, contextEl); + return this.browser.findElement({ css: locator }, contextEl) } async function findField(locator) { - const matchedLocator = new Locator(locator); + const matchedLocator = new Locator(locator) if (!matchedLocator.isFuzzy()) { - return this.browser.findElements(matchedLocator.toStrict()); + return this.browser.findElements(matchedLocator.toStrict()) } - const literal = xpathLocator.literal(locator); + const literal = xpathLocator.literal(locator) - let els = await this.browser.findElements({ xpath: Locator.field.labelEquals(literal) }); + let els = await this.browser.findElements({ xpath: Locator.field.labelEquals(literal) }) if (els.length) { - return els[0]; + return els[0] } - els = await this.browser.findElements({ xpath: Locator.field.labelContains(literal) }); + els = await this.browser.findElements({ xpath: Locator.field.labelContains(literal) }) if (els.length) { - return els[0]; + return els[0] } - els = await this.browser.findElements({ xpath: Locator.field.byName(literal) }); + els = await this.browser.findElements({ xpath: Locator.field.byName(literal) }) if (els.length) { - return els[0]; + return els[0] } - return this.browser.findElement({ css: locator }); + return this.browser.findElement({ css: locator }) } function assertElementExists(el, locator, prefix, suffix) { - if (el === null) throw new ElementNotFound(locator, prefix, suffix); + if (el === null) throw new ElementNotFound(locator, prefix, suffix) } diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index d03e3af2b..d505b4d05 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -1,17 +1,17 @@ -const path = require('path'); -const fs = require('fs'); - -const Helper = require('@codeceptjs/helper'); -const { v4: uuidv4 } = require('uuid'); -const assert = require('assert'); -const promiseRetry = require('promise-retry'); -const Locator = require('../locator'); -const recorder = require('../recorder'); -const stringIncludes = require('../assert/include').includes; -const { urlEquals } = require('../assert/equal'); -const { equals } = require('../assert/equal'); -const { empty } = require('../assert/empty'); -const { truth } = require('../assert/truth'); +const path = require('path') +const fs = require('fs') + +const Helper = require('@codeceptjs/helper') +const { v4: uuidv4 } = require('uuid') +const assert = require('assert') +const promiseRetry = require('promise-retry') +const Locator = require('../locator') +const recorder = require('../recorder') +const stringIncludes = require('../assert/include').includes +const { urlEquals } = require('../assert/equal') +const { equals } = require('../assert/equal') +const { empty } = require('../assert/empty') +const { truth } = require('../assert/truth') const { xpathLocator, ucfirst, @@ -24,37 +24,44 @@ const { clearString, requireWithFallback, normalizeSpacesInString, -} = require('../utils'); -const { - isColorProperty, - convertColorToRGBA, -} = require('../colorUtils'); -const ElementNotFound = require('./errors/ElementNotFound'); -const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused'); -const Popup = require('./extras/Popup'); -const Console = require('./extras/Console'); -const { findReact, findVue, findByPlaywrightLocator } = require('./extras/PlaywrightReactVueLocator'); - -let playwright; -let perfTiming; -let defaultSelectorEnginesInitialized = false; - -const popupStore = new Popup(); -const consoleLogStore = new Console(); -const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron']; +} = require('../utils') +const { isColorProperty, convertColorToRGBA } = require('../colorUtils') +const ElementNotFound = require('./errors/ElementNotFound') +const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused') +const Popup = require('./extras/Popup') +const Console = require('./extras/Console') +const { findReact, findVue, findByPlaywrightLocator } = require('./extras/PlaywrightReactVueLocator') + +let playwright +let perfTiming +let defaultSelectorEnginesInitialized = false + +const popupStore = new Popup() +const consoleLogStore = new Console() +const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'] const { - setRestartStrategy, restartsSession, restartsContext, restartsBrowser, -} = require('./extras/PlaywrightRestartOpts'); -const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine'); + setRestartStrategy, + restartsSession, + restartsContext, + restartsBrowser, +} = require('./extras/PlaywrightRestartOpts') +const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine') const { - seeElementError, dontSeeElementError, dontSeeElementInDOMError, seeElementInDOMError, -} = require('./errors/ElementAssertion'); + seeElementError, + dontSeeElementError, + dontSeeElementInDOMError, + seeElementInDOMError, +} = require('./errors/ElementAssertion') const { - dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics, -} = require('./network/actions'); + dontSeeTraffic, + seeTraffic, + grabRecordedNetworkTraffics, + stopRecordingTraffic, + flushNetworkTraffics, +} = require('./network/actions') -const pathSeparator = path.sep; +const pathSeparator = path.sep /** * ## Configuration @@ -103,7 +110,7 @@ const pathSeparator = path.sep; * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har). * @prop {string} [testIdAttribute=data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id). */ -const config = {}; +const config = {} /** * Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside: @@ -325,34 +332,34 @@ const config = {}; */ class Playwright extends Helper { constructor(config) { - super(config); + super(config) - playwright = requireWithFallback('playwright', 'playwright-core'); + playwright = requireWithFallback('playwright', 'playwright-core') // set defaults - this.isRemoteBrowser = false; - this.isRunning = false; - this.isAuthenticated = false; - this.sessionPages = {}; - this.activeSessionName = ''; - this.isElectron = false; - this.isCDPConnection = false; - this.electronSessions = []; - this.storageState = null; + this.isRemoteBrowser = false + this.isRunning = false + this.isAuthenticated = false + this.sessionPages = {} + this.activeSessionName = '' + this.isElectron = false + this.isCDPConnection = false + this.electronSessions = [] + this.storageState = null // for network stuff - this.requests = []; - this.recording = false; - this.recordedAtLeastOnce = false; + this.requests = [] + this.recording = false + this.recordedAtLeastOnce = false // for websocket messages - this.webSocketMessages = []; - this.recordingWebSocketMessages = false; - this.recordedWebSocketMessagesAtLeastOnce = false; - this.cdpSession = null; + this.webSocketMessages = [] + this.recordingWebSocketMessages = false + this.recordedWebSocketMessagesAtLeastOnce = false + this.cdpSession = null // override defaults with config - this._setConfig(config); + this._setConfig(config) } _validateConfig(config) { @@ -379,61 +386,65 @@ class Playwright extends Helper { use: { actionTimeout: 0 }, ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors, highlightElement: false, - }; + } - process.env.testIdAttribute = 'data-testid'; - config = Object.assign(defaults, config); + process.env.testIdAttribute = 'data-testid' + config = Object.assign(defaults, config) if (availableBrowsers.indexOf(config.browser) < 0) { - throw new Error(`Invalid config. Can't use browser "${config.browser}". Accepted values: ${availableBrowsers.join(', ')}`); + throw new Error( + `Invalid config. Can't use browser "${config.browser}". Accepted values: ${availableBrowsers.join(', ')}`, + ) } - return config; + return config } _getOptionsForBrowser(config) { if (config[config.browser]) { if (config[config.browser].browserWSEndpoint && config[config.browser].browserWSEndpoint.wsEndpoint) { - config[config.browser].browserWSEndpoint = config[config.browser].browserWSEndpoint.wsEndpoint; + config[config.browser].browserWSEndpoint = config[config.browser].browserWSEndpoint.wsEndpoint } return { ...config[config.browser], wsEndpoint: config[config.browser].browserWSEndpoint, - }; + } } - return {}; + return {} } _setConfig(config) { - this.options = this._validateConfig(config); - setRestartStrategy(this.options); + this.options = this._validateConfig(config) + setRestartStrategy(this.options) this.playwrightOptions = { headless: !this.options.show, ...this._getOptionsForBrowser(config), - }; + } if (this.options.channel && this.options.browser === 'chromium') { - this.playwrightOptions.channel = this.options.channel; + this.playwrightOptions.channel = this.options.channel } if (this.options.video) { // set the video resolution with window size - let size = parseWindowSize(this.options.windowSize); + let size = parseWindowSize(this.options.windowSize) // if the video resolution is passed, set the record resoultion with that resolution if (this.options.recordVideo && this.options.recordVideo.size) { - size = parseWindowSize(this.options.recordVideo.size); + size = parseWindowSize(this.options.recordVideo.size) } - this.options.recordVideo = { size }; + this.options.recordVideo = { size } } if (this.options.recordVideo && !this.options.recordVideo.dir) { - this.options.recordVideo.dir = `${global.output_dir}/videos/`; + this.options.recordVideo.dir = `${global.output_dir}/videos/` } - this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint; - this.isElectron = this.options.browser === 'electron'; - this.userDataDir = this.playwrightOptions.userDataDir ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` : undefined; - this.isCDPConnection = this.playwrightOptions.cdpConnection; - popupStore.defaultAction = this.options.defaultPopupAction; + this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint + this.isElectron = this.options.browser === 'electron' + this.userDataDir = this.playwrightOptions.userDataDir + ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` + : undefined + this.isCDPConnection = this.playwrightOptions.cdpConnection + popupStore.defaultAction = this.options.defaultPopupAction } static _config() { @@ -456,216 +467,222 @@ class Playwright extends Helper { type: 'confirm', when: (answers) => answers.Playwright_browser !== 'electron', }, - ]; + ] } static _checkRequirements() { try { - requireWithFallback('playwright', 'playwright-core'); + requireWithFallback('playwright', 'playwright-core') } catch (e) { - return ['playwright@^1.18']; + return ['playwright@^1.18'] } } async _init() { // register an internal selector engine for reading value property of elements in a selector - if (defaultSelectorEnginesInitialized) return; - defaultSelectorEnginesInitialized = true; + if (defaultSelectorEnginesInitialized) return + defaultSelectorEnginesInitialized = true try { - await playwright.selectors.register('__value', createValueEngine); - await playwright.selectors.register('__disabled', createDisabledEngine); - if (process.env.testIdAttribute) await playwright.selectors.setTestIdAttribute(process.env.testIdAttribute); + await playwright.selectors.register('__value', createValueEngine) + await playwright.selectors.register('__disabled', createDisabledEngine) + if (process.env.testIdAttribute) await playwright.selectors.setTestIdAttribute(process.env.testIdAttribute) } catch (e) { - console.warn(e); + console.warn(e) } } _beforeSuite() { if ((restartsSession() || restartsContext()) && !this.options.manualStart && !this.isRunning) { - this.debugSection('Session', 'Starting singleton browser session'); - return this._startBrowser(); + this.debugSection('Session', 'Starting singleton browser session') + return this._startBrowser() } } async _before(test) { - this.currentRunningTest = test; + this.currentRunningTest = test recorder.retry({ retries: process.env.FAILED_STEP_RETRIES || 3, - when: err => { - if (!err || typeof (err.message) !== 'string') { - return false; + when: (err) => { + if (!err || typeof err.message !== 'string') { + return false } // ignore context errors - return err.message.includes('context'); + return err.message.includes('context') }, - }); + }) - if (restartsBrowser() && !this.options.manualStart) await this._startBrowser(); - if (!this.isRunning && !this.options.manualStart) await this._startBrowser(); + if (restartsBrowser() && !this.options.manualStart) await this._startBrowser() + if (!this.isRunning && !this.options.manualStart) await this._startBrowser() - this.isAuthenticated = false; + this.isAuthenticated = false if (this.isElectron) { - this.browserContext = this.browser.context(); + this.browserContext = this.browser.context() } else if (this.playwrightOptions.userDataDir) { - this.browserContext = this.browser; + this.browserContext = this.browser } else { const contextOptions = { ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate, - }; + } if (this.options.basicAuth) { - contextOptions.httpCredentials = this.options.basicAuth; - this.isAuthenticated = true; + contextOptions.httpCredentials = this.options.basicAuth + this.isAuthenticated = true } - if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP; - if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo; + if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP + if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo if (this.options.recordHar) { - const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har'; - const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`; - const dir = path.dirname(fileName); - if (!fileExists(dir)) fs.mkdirSync(dir); - this.options.recordHar.path = fileName; - this.currentRunningTest.artifacts.har = fileName; - contextOptions.recordHar = this.options.recordHar; + const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har' + const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}` + const dir = path.dirname(fileName) + if (!fileExists(dir)) fs.mkdirSync(dir) + this.options.recordHar.path = fileName + this.currentRunningTest.artifacts.har = fileName + contextOptions.recordHar = this.options.recordHar } - if (this.storageState) contextOptions.storageState = this.storageState; - if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent; - if (this.options.locale) contextOptions.locale = this.options.locale; - if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme; - this.contextOptions = contextOptions; + if (this.storageState) contextOptions.storageState = this.storageState + if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent + if (this.options.locale) contextOptions.locale = this.options.locale + if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme + this.contextOptions = contextOptions if (!this.browserContext || !restartsSession()) { - this.browserContext = await this.browser.newContext(this.contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors + this.browserContext = await this.browser.newContext(this.contextOptions) // Adding the HTTPSError ignore in the context so that we can ignore those errors } } - let mainPage; + let mainPage if (this.isElectron) { - mainPage = await this.browser.firstWindow(); + mainPage = await this.browser.firstWindow() } else { try { - const existingPages = await this.browserContext.pages(); - mainPage = existingPages[0] || await this.browserContext.newPage(); + const existingPages = await this.browserContext.pages() + mainPage = existingPages[0] || (await this.browserContext.newPage()) } catch (e) { if (this.playwrightOptions.userDataDir) { - this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions); - this.browserContext = this.browser; - const existingPages = await this.browserContext.pages(); - mainPage = existingPages[0]; + this.browser = await playwright[this.options.browser].launchPersistentContext( + this.userDataDir, + this.playwrightOptions, + ) + this.browserContext = this.browser + const existingPages = await this.browserContext.pages() + mainPage = existingPages[0] } } } - await targetCreatedHandler.call(this, mainPage); + await targetCreatedHandler.call(this, mainPage) - await this._setPage(mainPage); + await this._setPage(mainPage) - if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true }); + if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true }) - return this.browser; + return this.browser } async _after() { - if (!this.isRunning) return; + if (!this.isRunning) return if (this.isElectron) { - this.browser.close(); - this.electronSessions.forEach(session => session.close()); - return; + this.browser.close() + this.electronSessions.forEach((session) => session.close()) + return } if (restartsSession()) { - return refreshContextSession.bind(this)(); + return refreshContextSession.bind(this)() } if (restartsBrowser()) { - this.isRunning = false; - return this._stopBrowser(); + this.isRunning = false + return this._stopBrowser() } // close other sessions try { if ((await this.browser)._type === 'Browser') { - const contexts = await this.browser.contexts(); - const currentContext = contexts[0]; + const contexts = await this.browser.contexts() + const currentContext = contexts[0] if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) { - this.storageState = await currentContext.storageState(); + this.storageState = await currentContext.storageState() } - await Promise.all(contexts.map(c => c.close())); + await Promise.all(contexts.map((c) => c.close())) } } catch (e) { - console.log(e); + console.log(e) } // await this.closeOtherTabs(); - return this.browser; + return this.browser } _afterSuite() {} async _finishTest() { - if ((restartsSession() || restartsContext()) && this.isRunning) return this._stopBrowser(); + if ((restartsSession() || restartsContext()) && this.isRunning) return this._stopBrowser() } _session() { - const defaultContext = this.browserContext; + const defaultContext = this.browserContext return { start: async (sessionName = '', config) => { - this.debugSection('New Context', config ? JSON.stringify(config) : 'opened'); - this.activeSessionName = sessionName; + this.debugSection('New Context', config ? JSON.stringify(config) : 'opened') + this.activeSessionName = sessionName - let browserContext; - let page; + let browserContext + let page if (this.isElectron) { - const browser = await playwright._electron.launch(this.playwrightOptions); - this.electronSessions.push(browser); - browserContext = browser.context(); - page = await browser.firstWindow(); + const browser = await playwright._electron.launch(this.playwrightOptions) + this.electronSessions.push(browser) + browserContext = browser.context() + page = await browser.firstWindow() } else { try { - browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config)); - page = await browserContext.newPage(); + browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config)) + page = await browserContext.newPage() } catch (e) { if (this.playwrightOptions.userDataDir) { - browserContext = await playwright[this.options.browser].launchPersistentContext(`${this.userDataDir}_${this.activeSessionName}`, this.playwrightOptions); - this.browser = browserContext; - page = await browserContext.pages()[0]; + browserContext = await playwright[this.options.browser].launchPersistentContext( + `${this.userDataDir}_${this.activeSessionName}`, + this.playwrightOptions, + ) + this.browser = browserContext + page = await browserContext.pages()[0] } } } - if (this.options.trace) await browserContext.tracing.start({ screenshots: true, snapshots: true }); - await targetCreatedHandler.call(this, page); - await this._setPage(page); + if (this.options.trace) await browserContext.tracing.start({ screenshots: true, snapshots: true }) + await targetCreatedHandler.call(this, page) + await this._setPage(page) // Create a new page inside context. - return browserContext; + return browserContext }, stop: async () => { // is closed by _after }, loadVars: async (context) => { if (context) { - this.browserContext = context; - const existingPages = await context.pages(); - this.sessionPages[this.activeSessionName] = existingPages[0]; - return this._setPage(this.sessionPages[this.activeSessionName]); + this.browserContext = context + const existingPages = await context.pages() + this.sessionPages[this.activeSessionName] = existingPages[0] + return this._setPage(this.sessionPages[this.activeSessionName]) } }, restoreVars: async (session) => { - this.withinLocator = null; - this.browserContext = defaultContext; + this.withinLocator = null + this.browserContext = defaultContext if (!session) { - this.activeSessionName = ''; + this.activeSessionName = '' } else { - this.activeSessionName = session; + this.activeSessionName = session } - const existingPages = await this.browserContext.pages(); - await this._setPage(existingPages[0]); + const existingPages = await this.browserContext.pages() + await this._setPage(existingPages[0]) - return this._waitForAction(); + return this._waitForAction() }, - }; + } } /** @@ -686,7 +703,7 @@ class Playwright extends Helper { * @param {function} fn async function that executed with Playwright helper as arguments */ usePlaywrightTo(description, fn) { - return this._useTo(...arguments); + return this._useTo(...arguments) } /** @@ -700,7 +717,7 @@ class Playwright extends Helper { * ``` */ amAcceptingPopups() { - popupStore.actionType = 'accept'; + popupStore.actionType = 'accept' } /** @@ -709,7 +726,7 @@ class Playwright extends Helper { * libraries](http://jster.net/category/windows-modals-popups). */ acceptPopup() { - popupStore.assertPopupActionType('accept'); + popupStore.assertPopupActionType('accept') } /** @@ -723,23 +740,23 @@ class Playwright extends Helper { * ``` */ amCancellingPopups() { - popupStore.actionType = 'cancel'; + popupStore.actionType = 'cancel' } /** * Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt. */ cancelPopup() { - popupStore.assertPopupActionType('cancel'); + popupStore.assertPopupActionType('cancel') } /** * {{> seeInPopup }} */ async seeInPopup(text) { - popupStore.assertPopupVisible(); - const popupText = await popupStore.popup.message(); - stringIncludes('text in popup').assert(text, popupText); + popupStore.assertPopupVisible() + const popupText = await popupStore.popup.message() + stringIncludes('text in popup').assert(text, popupText) } /** @@ -747,21 +764,21 @@ class Playwright extends Helper { * @param {object} page page to set */ async _setPage(page) { - page = await page; - this._addPopupListener(page); - this.page = page; - if (!page) return; - this.browserContext.setDefaultTimeout(0); - page.setDefaultNavigationTimeout(this.options.getPageTimeout); - page.setDefaultTimeout(this.options.timeout); + page = await page + this._addPopupListener(page) + this.page = page + if (!page) return + this.browserContext.setDefaultTimeout(0) + page.setDefaultNavigationTimeout(this.options.getPageTimeout) + page.setDefaultTimeout(this.options.timeout) page.on('crash', async () => { - console.log('ERROR: Page has crashed, closing page!'); - await page.close(); - }); - this.context = await this.page; - this.contextLocator = null; - await page.bringToFront(); + console.log('ERROR: Page has crashed, closing page!') + await page.close() + }) + this.context = await this.page + this.contextLocator = null + await page.bringToFront() } /** @@ -773,33 +790,33 @@ class Playwright extends Helper { */ _addPopupListener(page) { if (!page) { - return; + return } - page.removeAllListeners('dialog'); + page.removeAllListeners('dialog') page.on('dialog', async (dialog) => { - popupStore.popup = dialog; - const action = popupStore.actionType || this.options.defaultPopupAction; - await this._waitForAction(); + popupStore.popup = dialog + const action = popupStore.actionType || this.options.defaultPopupAction + await this._waitForAction() switch (action) { case 'accept': - return dialog.accept(); + return dialog.accept() case 'cancel': - return dialog.dismiss(); + return dialog.dismiss() default: { - throw new Error('Unknown popup action type. Only "accept" or "cancel" are accepted'); + throw new Error('Unknown popup action type. Only "accept" or "cancel" are accepted') } } - }); + }) } /** * Gets page URL including hash. */ async _getPageUrl() { - return this.executeScript(() => window.location.href); + return this.executeScript(() => window.location.href) } /** @@ -812,45 +829,48 @@ class Playwright extends Helper { */ async grabPopupText() { if (popupStore.popup) { - return popupStore.popup.message(); + return popupStore.popup.message() } - return null; + return null } async _startBrowser() { if (this.isElectron) { - this.browser = await playwright._electron.launch(this.playwrightOptions); + this.browser = await playwright._electron.launch(this.playwrightOptions) } else if (this.isRemoteBrowser && this.isCDPConnection) { try { - this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions); + this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions) } catch (err) { if (err.toString().indexOf('ECONNREFUSED')) { - throw new RemoteBrowserConnectionRefused(err); + throw new RemoteBrowserConnectionRefused(err) } - throw err; + throw err } } else if (this.isRemoteBrowser) { try { - this.browser = await playwright[this.options.browser].connect(this.playwrightOptions); + this.browser = await playwright[this.options.browser].connect(this.playwrightOptions) } catch (err) { if (err.toString().indexOf('ECONNREFUSED')) { - throw new RemoteBrowserConnectionRefused(err); + throw new RemoteBrowserConnectionRefused(err) } - throw err; + throw err } } else if (this.playwrightOptions.userDataDir) { - this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions); + this.browser = await playwright[this.options.browser].launchPersistentContext( + this.userDataDir, + this.playwrightOptions, + ) } else { - this.browser = await playwright[this.options.browser].launch(this.playwrightOptions); + this.browser = await playwright[this.options.browser].launch(this.playwrightOptions) } // works only for Chromium this.browser.on('targetchanged', (target) => { - this.debugSection('Url', target.url()); - }); + this.debugSection('Url', target.url()) + }) - this.isRunning = true; - return this.browser; + this.isRunning = true + return this.browser } /** @@ -859,72 +879,72 @@ class Playwright extends Helper { * @param {object} [contextOptions] See https://playwright.dev/docs/api/class-browser#browser-new-context */ async _createContextPage(contextOptions) { - this.browserContext = await this.browser.newContext(contextOptions); - const page = await this.browserContext.newPage(); - targetCreatedHandler.call(this, page); - await this._setPage(page); + this.browserContext = await this.browser.newContext(contextOptions) + const page = await this.browserContext.newPage() + targetCreatedHandler.call(this, page) + await this._setPage(page) } _getType() { - return this.browser._type; + return this.browser._type } async _stopBrowser() { - this.withinLocator = null; - await this._setPage(null); - this.context = null; - this.frame = null; - popupStore.clear(); - if (this.options.recordHar) await this.browserContext.close(); - await this.browser.close(); + this.withinLocator = null + await this._setPage(null) + this.context = null + this.frame = null + popupStore.clear() + if (this.options.recordHar) await this.browserContext.close() + await this.browser.close() } async _evaluateHandeInContext(...args) { - const context = await this._getContext(); - return context.evaluateHandle(...args); + const context = await this._getContext() + return context.evaluateHandle(...args) } async _withinBegin(locator) { if (this.withinLocator) { - throw new Error('Can\'t start within block inside another within block'); + throw new Error("Can't start within block inside another within block") } - const frame = isFrameLocator(locator); + const frame = isFrameLocator(locator) if (frame) { if (Array.isArray(frame)) { - await this.switchTo(null); - return frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()); + await this.switchTo(null) + return frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()) } - await this.switchTo(frame); - this.withinLocator = new Locator(frame); - return; + await this.switchTo(frame) + this.withinLocator = new Locator(frame) + return } - const el = await this._locateElement(locator); - assertElementExists(el, locator); - this.context = el; - this.contextLocator = locator; + const el = await this._locateElement(locator) + assertElementExists(el, locator) + this.context = el + this.contextLocator = locator - this.withinLocator = new Locator(locator); + this.withinLocator = new Locator(locator) } async _withinEnd() { - this.withinLocator = null; - this.context = await this.page; - this.contextLocator = null; - this.frame = null; + this.withinLocator = null + this.context = await this.page + this.contextLocator = null + this.frame = null } _extractDataFromPerformanceTiming(timing, ...dataNames) { - const navigationStart = timing.navigationStart; + const navigationStart = timing.navigationStart - const extractedData = {}; + const extractedData = {} dataNames.forEach((name) => { - extractedData[name] = timing[name] - navigationStart; - }); + extractedData[name] = timing[name] - navigationStart + }) - return extractedData; + return extractedData } /** @@ -932,22 +952,22 @@ class Playwright extends Helper { */ async amOnPage(url) { if (this.isElectron) { - throw new Error('Cannot open pages inside an Electron container'); + throw new Error('Cannot open pages inside an Electron container') } - if (!(/^\w+\:(\/\/|.+)/.test(url))) { - url = this.options.url + (url.startsWith('/') ? url : `/${url}`); + if (!/^\w+\:(\/\/|.+)/.test(url)) { + url = this.options.url + (url.startsWith('/') ? url : `/${url}`) } - if (this.options.basicAuth && (this.isAuthenticated !== true)) { + if (this.options.basicAuth && this.isAuthenticated !== true) { if (url.includes(this.options.url)) { - await this.browserContext.setHTTPCredentials(this.options.basicAuth); - this.isAuthenticated = true; + await this.browserContext.setHTTPCredentials(this.options.basicAuth) + this.isAuthenticated = true } } - await this.page.goto(url, { waitUntil: this.options.waitForNavigation }); + await this.page.goto(url, { waitUntil: this.options.waitForNavigation }) - const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing))); + const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing))) perfTiming = this._extractDataFromPerformanceTiming( performanceTiming, @@ -955,9 +975,9 @@ class Playwright extends Helper { 'domInteractive', 'domContentLoadedEventEnd', 'loadEventEnd', - ); + ) - return this._waitForAction(); + return this._waitForAction() } /** @@ -978,11 +998,11 @@ class Playwright extends Helper { */ async resizeWindow(width, height) { if (width === 'maximize') { - throw new Error('Playwright can\'t control windows, so it can\'t maximize it'); + throw new Error("Playwright can't control windows, so it can't maximize it") } - await this.page.setViewportSize({ width, height }); - return this._waitForAction(); + await this.page.setViewportSize({ width, height }) + return this._waitForAction() } /** @@ -998,9 +1018,9 @@ class Playwright extends Helper { */ async setPlaywrightRequestHeaders(customHeaders) { if (!customHeaders) { - throw new Error('Cannot send empty headers.'); + throw new Error('Cannot send empty headers.') } - return this.browserContext.setExtraHTTPHeaders(customHeaders); + return this.browserContext.setExtraHTTPHeaders(customHeaders) } /** @@ -1008,13 +1028,13 @@ class Playwright extends Helper { * */ async moveCursorTo(locator, offsetX = 0, offsetY = 0) { - const el = await this._locateElement(locator); - assertElementExists(el, locator); + const el = await this._locateElement(locator) + assertElementExists(el, locator) // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates - const { x, y } = await clickablePoint(el); - await this.page.mouse.move(x + offsetX, y + offsetY); - return this._waitForAction(); + const { x, y } = await clickablePoint(el) + await this.page.mouse.move(x + offsetX, y + offsetY) + return this._waitForAction() } /** @@ -1022,11 +1042,11 @@ class Playwright extends Helper { * */ async focus(locator, options = {}) { - const el = await this._locateElement(locator); - assertElementExists(el, locator, 'Element to focus'); + const el = await this._locateElement(locator) + assertElementExists(el, locator, 'Element to focus') - await el.focus(options); - return this._waitForAction(); + await el.focus(options) + return this._waitForAction() } /** @@ -1034,11 +1054,11 @@ class Playwright extends Helper { * */ async blur(locator, options = {}) { - const el = await this._locateElement(locator); - assertElementExists(el, locator, 'Element to blur'); + const el = await this._locateElement(locator) + assertElementExists(el, locator, 'Element to blur') - await el.blur(options); - return this._waitForAction(); + await el.blur(options) + return this._waitForAction() } /** * Return the checked status of given element. @@ -1050,14 +1070,14 @@ class Playwright extends Helper { */ async grabCheckedElementStatus(locator, options = {}) { - const supportedTypes = ['checkbox', 'radio']; - const el = await this._locateElement(locator); - const type = await el.getAttribute('type'); + const supportedTypes = ['checkbox', 'radio'] + const el = await this._locateElement(locator) + const type = await el.getAttribute('type') if (supportedTypes.includes(type)) { - return el.isChecked(options); + return el.isChecked(options) } - throw new Error(`Element is not a ${supportedTypes.join(' or ')} input`); + throw new Error(`Element is not a ${supportedTypes.join(' or ')} input`) } /** * Return the disabled status of given element. @@ -1069,8 +1089,8 @@ class Playwright extends Helper { */ async grabDisabledElementStatus(locator, options = {}) { - const el = await this._locateElement(locator); - return el.isDisabled(options); + const el = await this._locateElement(locator) + return el.isDisabled(options) } /** @@ -1087,24 +1107,24 @@ class Playwright extends Helper { * */ async dragAndDrop(srcElement, destElement, options) { - const src = new Locator(srcElement); - const dst = new Locator(destElement); + const src = new Locator(srcElement) + const dst = new Locator(destElement) if (options) { - return this.page.dragAndDrop(buildLocatorString(src), buildLocatorString(dst), options); + return this.page.dragAndDrop(buildLocatorString(src), buildLocatorString(dst), options) } - const _smallWaitInMs = 600; - await this.page.locator(buildLocatorString(src)).hover(); - await this.page.mouse.down(); - await this.page.waitForTimeout(_smallWaitInMs); + const _smallWaitInMs = 600 + await this.page.locator(buildLocatorString(src)).hover() + await this.page.mouse.down() + await this.page.waitForTimeout(_smallWaitInMs) - const destElBox = await this.page.locator(buildLocatorString(dst)).boundingBox(); + const destElBox = await this.page.locator(buildLocatorString(dst)).boundingBox() - await this.page.mouse.move(destElBox.x + destElBox.width / 2, destElBox.y + destElBox.height / 2); - await this.page.locator(buildLocatorString(dst)).hover({ position: { x: 10, y: 10 } }); - await this.page.waitForTimeout(_smallWaitInMs); - await this.page.mouse.up(); + await this.page.mouse.move(destElBox.x + destElBox.width / 2, destElBox.y + destElBox.height / 2) + await this.page.locator(buildLocatorString(dst)).hover({ position: { x: 10, y: 10 } }) + await this.page.waitForTimeout(_smallWaitInMs) + await this.page.mouse.up() } /** @@ -1122,16 +1142,16 @@ class Playwright extends Helper { * @param {object} [contextOptions] [Options for browser context](https://playwright.dev/docs/api/class-browser#browser-new-context) when starting new browser */ async restartBrowser(contextOptions) { - await this._stopBrowser(); - await this._startBrowser(); - await this._createContextPage(contextOptions); + await this._stopBrowser() + await this._startBrowser() + await this._createContextPage(contextOptions) } /** * {{> refreshPage }} */ async refreshPage() { - return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation }); + return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation }) } /** @@ -1152,13 +1172,13 @@ class Playwright extends Helper { * @returns Promise */ async replayFromHar(harFilePath, opts) { - const file = path.join(global.codecept_dir, harFilePath); + const file = path.join(global.codecept_dir, harFilePath) if (!fileExists(file)) { - throw new Error(`File at ${file} cannot be found on local system`); + throw new Error(`File at ${file} cannot be found on local system`) } - await this.page.routeFromHAR(harFilePath, opts); + await this.page.routeFromHAR(harFilePath, opts) } /** @@ -1166,8 +1186,8 @@ class Playwright extends Helper { */ scrollPageToTop() { return this.executeScript(() => { - window.scrollTo(0, 0); - }); + window.scrollTo(0, 0) + }) } /** @@ -1175,16 +1195,13 @@ class Playwright extends Helper { */ async scrollPageToBottom() { return this.executeScript(() => { - const body = document.body; - const html = document.documentElement; - window.scrollTo(0, Math.max( - body.scrollHeight, - body.offsetHeight, - html.clientHeight, - html.scrollHeight, - html.offsetHeight, - )); - }); + const body = document.body + const html = document.documentElement + window.scrollTo( + 0, + Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), + ) + }) } /** @@ -1192,29 +1209,32 @@ class Playwright extends Helper { */ async scrollTo(locator, offsetX = 0, offsetY = 0) { if (typeof locator === 'number' && typeof offsetX === 'number') { - offsetY = offsetX; - offsetX = locator; - locator = null; + offsetY = offsetX + offsetX = locator + locator = null } if (locator) { - const el = await this._locateElement(locator); - assertElementExists(el, locator, 'Element'); - await el.scrollIntoViewIfNeeded(); - const elementCoordinates = await clickablePoint(el); - await this.executeScript((offsetX, offsetY) => window.scrollBy(offsetX, offsetY), { offsetX: elementCoordinates.x + offsetX, offsetY: elementCoordinates.y + offsetY }); + const el = await this._locateElement(locator) + assertElementExists(el, locator, 'Element') + await el.scrollIntoViewIfNeeded() + const elementCoordinates = await clickablePoint(el) + await this.executeScript((offsetX, offsetY) => window.scrollBy(offsetX, offsetY), { + offsetX: elementCoordinates.x + offsetX, + offsetY: elementCoordinates.y + offsetY, + }) } else { - await this.executeScript(({ offsetX, offsetY }) => window.scrollTo(offsetX, offsetY), { offsetX, offsetY }); + await this.executeScript(({ offsetX, offsetY }) => window.scrollTo(offsetX, offsetY), { offsetX, offsetY }) } - return this._waitForAction(); + return this._waitForAction() } /** * {{> seeInTitle }} */ async seeInTitle(text) { - const title = await this.page.title(); - stringIncludes('web page title').assert(text, title); + const title = await this.page.title() + stringIncludes('web page title').assert(text, title) } /** @@ -1225,34 +1245,34 @@ class Playwright extends Helper { function getScrollPosition() { return { x: window.pageXOffset, - y: window.pageYOffset - }; + y: window.pageYOffset, + } } /* eslint-enable comma-dangle */ - return this.executeScript(getScrollPosition); + return this.executeScript(getScrollPosition) } /** * {{> seeTitleEquals }} */ async seeTitleEquals(text) { - const title = await this.page.title(); - return equals('web page title').assert(title, text); + const title = await this.page.title() + return equals('web page title').assert(title, text) } /** * {{> dontSeeInTitle }} */ async dontSeeInTitle(text) { - const title = await this.page.title(); - stringIncludes('web page title').negate(text, title); + const title = await this.page.title() + stringIncludes('web page title').negate(text, title) } /** * {{> grabTitle }} */ async grabTitle() { - return this.page.title(); + return this.page.title() } /** @@ -1264,11 +1284,11 @@ class Playwright extends Helper { * ``` */ async _locate(locator) { - const context = await this.context || await this._getContext(); + const context = (await this.context) || (await this._getContext()) - if (this.frame) return findElements(this.frame, locator); + if (this.frame) return findElements(this.frame, locator) - return findElements(context, locator); + return findElements(context, locator) } /** @@ -1280,8 +1300,8 @@ class Playwright extends Helper { * ``` */ async _locateElement(locator) { - const context = await this.context || await this._getContext(); - return findElement(context, locator); + const context = (await this.context) || (await this._getContext()) + return findElement(context, locator) } /** @@ -1293,10 +1313,10 @@ class Playwright extends Helper { * ``` */ async _locateCheckable(locator, providedContext = null) { - const context = providedContext || await this._getContext(); - const els = await findCheckable.call(this, locator, context); - assertElementExists(els[0], locator, 'Checkbox or radio'); - return els[0]; + const context = providedContext || (await this._getContext()) + const els = await findCheckable.call(this, locator, context) + assertElementExists(els[0], locator, 'Checkbox or radio') + return els[0] } /** @@ -1307,8 +1327,8 @@ class Playwright extends Helper { * ``` */ async _locateClickable(locator) { - const context = await this._getContext(); - return findClickable.call(this, context, locator); + const context = await this._getContext() + return findClickable.call(this, context, locator) } /** @@ -1319,7 +1339,7 @@ class Playwright extends Helper { * ``` */ async _locateFields(locator) { - return findFields.call(this, locator); + return findFields.call(this, locator) } /** @@ -1327,7 +1347,7 @@ class Playwright extends Helper { * */ async grabWebElements(locator) { - return this._locate(locator); + return this._locate(locator) } /** @@ -1335,7 +1355,7 @@ class Playwright extends Helper { * */ async grabWebElement(locator) { - return this._locateElement(locator); + return this._locateElement(locator) } /** @@ -1350,20 +1370,20 @@ class Playwright extends Helper { */ async switchToNextTab(num = 1) { if (this.isElectron) { - throw new Error('Cannot switch tabs inside an Electron container'); + throw new Error('Cannot switch tabs inside an Electron container') } - const pages = await this.browserContext.pages(); + const pages = await this.browserContext.pages() - const index = pages.indexOf(this.page); - this.withinLocator = null; - const page = pages[index + num]; + const index = pages.indexOf(this.page) + this.withinLocator = null + const page = pages[index + num] if (!page) { - throw new Error(`There is no ability to switch to next tab with offset ${num}`); + throw new Error(`There is no ability to switch to next tab with offset ${num}`) } - await targetCreatedHandler.call(this, page); - await this._setPage(page); - return this._waitForAction(); + await targetCreatedHandler.call(this, page) + await this._setPage(page) + return this._waitForAction() } /** @@ -1377,19 +1397,19 @@ class Playwright extends Helper { */ async switchToPreviousTab(num = 1) { if (this.isElectron) { - throw new Error('Cannot switch tabs inside an Electron container'); + throw new Error('Cannot switch tabs inside an Electron container') } - const pages = await this.browserContext.pages(); - const index = pages.indexOf(this.page); - this.withinLocator = null; - const page = pages[index - num]; + const pages = await this.browserContext.pages() + const index = pages.indexOf(this.page) + this.withinLocator = null + const page = pages[index - num] if (!page) { - throw new Error(`There is no ability to switch to previous tab with offset ${num}`); + throw new Error(`There is no ability to switch to previous tab with offset ${num}`) } - await this._setPage(page); - return this._waitForAction(); + await this._setPage(page) + return this._waitForAction() } /** @@ -1401,12 +1421,12 @@ class Playwright extends Helper { */ async closeCurrentTab() { if (this.isElectron) { - throw new Error('Cannot close current tab inside an Electron container'); + throw new Error('Cannot close current tab inside an Electron container') } - const oldPage = this.page; - await this.switchToPreviousTab(); - await oldPage.close(); - return this._waitForAction(); + const oldPage = this.page + await this.switchToPreviousTab() + await oldPage.close() + return this._waitForAction() } /** @@ -1417,13 +1437,13 @@ class Playwright extends Helper { * ``` */ async closeOtherTabs() { - const pages = await this.browserContext.pages(); - const otherPages = pages.filter(page => page !== this.page); + const pages = await this.browserContext.pages() + const otherPages = pages.filter((page) => page !== this.page) if (otherPages.length) { - this.debug(`Closing ${otherPages.length} tabs`); - return Promise.all(otherPages.map(p => p.close())); + this.debug(`Closing ${otherPages.length} tabs`) + return Promise.all(otherPages.map((p) => p.close())) } - return Promise.resolve(); + return Promise.resolve() } /** @@ -1442,20 +1462,20 @@ class Playwright extends Helper { */ async openNewTab(options) { if (this.isElectron) { - throw new Error('Cannot open new tabs inside an Electron container'); + throw new Error('Cannot open new tabs inside an Electron container') } - const page = await this.browserContext.newPage(options); - await targetCreatedHandler.call(this, page); - await this._setPage(page); - return this._waitForAction(); + const page = await this.browserContext.newPage(options) + await targetCreatedHandler.call(this, page) + await this._setPage(page) + return this._waitForAction() } /** * {{> grabNumberOfOpenTabs }} */ async grabNumberOfOpenTabs() { - const pages = await this.browserContext.pages(); - return pages.length; + const pages = await this.browserContext.pages() + return pages.length } /** @@ -1463,12 +1483,12 @@ class Playwright extends Helper { * */ async seeElement(locator) { - let els = await this._locate(locator); - els = await Promise.all(els.map(el => el.isVisible())); + let els = await this._locate(locator) + els = await Promise.all(els.map((el) => el.isVisible())) try { - return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT')); + return empty('visible elements').negate(els.filter((v) => v).fill('ELEMENT')) } catch (e) { - dontSeeElementError(locator); + dontSeeElementError(locator) } } @@ -1477,12 +1497,12 @@ class Playwright extends Helper { * */ async dontSeeElement(locator) { - let els = await this._locate(locator); - els = await Promise.all(els.map(el => el.isVisible())); + let els = await this._locate(locator) + els = await Promise.all(els.map((el) => el.isVisible())) try { - return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT')); + return empty('visible elements').assert(els.filter((v) => v).fill('ELEMENT')) } catch (e) { - seeElementError(locator); + seeElementError(locator) } } @@ -1490,11 +1510,11 @@ class Playwright extends Helper { * {{> seeElementInDOM }} */ async seeElementInDOM(locator) { - const els = await this._locate(locator); + const els = await this._locate(locator) try { - return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT')); + return empty('elements on page').negate(els.filter((v) => v).fill('ELEMENT')) } catch (e) { - dontSeeElementInDOMError(locator); + dontSeeElementInDOMError(locator) } } @@ -1502,11 +1522,11 @@ class Playwright extends Helper { * {{> dontSeeElementInDOM }} */ async dontSeeElementInDOM(locator) { - const els = await this._locate(locator); + const els = await this._locate(locator) try { - return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT')); + return empty('elements on a page').assert(els.filter((v) => v).fill('ELEMENT')) } catch (e) { - seeElementInDOMError(locator); + seeElementInDOMError(locator) } } @@ -1529,18 +1549,18 @@ class Playwright extends Helper { */ async handleDownloads(fileName) { this.page.waitForEvent('download').then(async (download) => { - const filePath = await download.path(); - fileName = fileName || `downloads/${path.basename(filePath)}`; + const filePath = await download.path() + fileName = fileName || `downloads/${path.basename(filePath)}` - const downloadPath = path.join(global.output_dir, fileName); + const downloadPath = path.join(global.output_dir, fileName) if (!fs.existsSync(path.dirname(downloadPath))) { - fs.mkdirSync(path.dirname(downloadPath), '0777'); + fs.mkdirSync(path.dirname(downloadPath), '0777') } - fs.copyFileSync(filePath, downloadPath); - this.debug('Download completed'); - this.debugSection('Downloaded From', await download.url()); - this.debugSection('Downloaded To', downloadPath); - }); + fs.copyFileSync(filePath, downloadPath) + this.debug('Download completed') + this.debugSection('Downloaded From', await download.url()) + this.debugSection('Downloaded To', downloadPath) + }) } /** @@ -1560,37 +1580,37 @@ class Playwright extends Helper { * */ async click(locator, context = null, options = {}) { - return proceedClick.call(this, locator, context, options); + return proceedClick.call(this, locator, context, options) } /** * Clicks link and waits for navigation (deprecated) */ async clickLink(locator, context = null) { - console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.'); - console.log('Replace I.clickLink with I.click'); - return this.click(locator, context); + console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.') + console.log('Replace I.clickLink with I.click') + return this.click(locator, context) } /** * {{> forceClick }} */ async forceClick(locator, context = null) { - return proceedClick.call(this, locator, context, { force: true }); + return proceedClick.call(this, locator, context, { force: true }) } /** * {{> doubleClick }} */ async doubleClick(locator, context = null) { - return proceedClick.call(this, locator, context, { clickCount: 2 }); + return proceedClick.call(this, locator, context, { clickCount: 2 }) } /** * {{> rightClick }} */ async rightClick(locator, context = null) { - return proceedClick.call(this, locator, context, { button: 'right' }); + return proceedClick.call(this, locator, context, { button: 'right' }) } /** @@ -1609,9 +1629,9 @@ class Playwright extends Helper { * */ async checkOption(field, context = null, options = { force: true }) { - const elm = await this._locateCheckable(field, context); - await elm.check(options); - return this._waitForAction(); + const elm = await this._locateCheckable(field, context) + await elm.check(options) + return this._waitForAction() } /** @@ -1629,41 +1649,41 @@ class Playwright extends Helper { * {{> uncheckOption }} */ async uncheckOption(field, context = null, options = { force: true }) { - const elm = await this._locateCheckable(field, context); - await elm.uncheck(options); - return this._waitForAction(); + const elm = await this._locateCheckable(field, context) + await elm.uncheck(options) + return this._waitForAction() } /** * {{> seeCheckboxIsChecked }} */ async seeCheckboxIsChecked(field) { - return proceedIsChecked.call(this, 'assert', field); + return proceedIsChecked.call(this, 'assert', field) } /** * {{> dontSeeCheckboxIsChecked }} */ async dontSeeCheckboxIsChecked(field) { - return proceedIsChecked.call(this, 'negate', field); + return proceedIsChecked.call(this, 'negate', field) } /** * {{> pressKeyDown }} */ async pressKeyDown(key) { - key = getNormalizedKey.call(this, key); - await this.page.keyboard.down(key); - return this._waitForAction(); + key = getNormalizedKey.call(this, key) + await this.page.keyboard.down(key) + return this._waitForAction() } /** * {{> pressKeyUp }} */ async pressKeyUp(key) { - key = getNormalizedKey.call(this, key); - await this.page.keyboard.up(key); - return this._waitForAction(); + key = getNormalizedKey.call(this, key) + await this.page.keyboard.up(key) + return this._waitForAction() } /** @@ -1673,28 +1693,28 @@ class Playwright extends Helper { * {{> pressKeyWithKeyNormalization }} */ async pressKey(key) { - const modifiers = []; + const modifiers = [] if (Array.isArray(key)) { for (let k of key) { - k = getNormalizedKey.call(this, k); + k = getNormalizedKey.call(this, k) if (isModifierKey(k)) { - modifiers.push(k); + modifiers.push(k) } else { - key = k; - break; + key = k + break } } } else { - key = getNormalizedKey.call(this, key); + key = getNormalizedKey.call(this, key) } for (const modifier of modifiers) { - await this.page.keyboard.down(modifier); + await this.page.keyboard.down(modifier) } - await this.page.keyboard.press(key); + await this.page.keyboard.press(key) for (const modifier of modifiers) { - await this.page.keyboard.up(modifier); + await this.page.keyboard.up(modifier) } - return this._waitForAction(); + return this._waitForAction() } /** @@ -1702,13 +1722,13 @@ class Playwright extends Helper { */ async type(keys, delay = null) { if (!Array.isArray(keys)) { - keys = keys.toString(); - keys = keys.split(''); + keys = keys.toString() + keys = keys.split('') } for (const key of keys) { - await this.page.keyboard.press(key); - if (delay) await this.wait(delay / 1000); + await this.page.keyboard.press(key) + if (delay) await this.wait(delay / 1000) } } @@ -1717,75 +1737,75 @@ class Playwright extends Helper { * */ async fillField(field, value) { - const els = await findFields.call(this, field); - assertElementExists(els, field, 'Field'); - const el = els[0]; + const els = await findFields.call(this, field) + assertElementExists(els, field, 'Field') + const el = els[0] - await el.clear(); + await el.clear() - await highlightActiveElement.call(this, el); + await highlightActiveElement.call(this, el) - await el.type(value.toString(), { delay: this.options.pressKeyDelay }); + await el.type(value.toString(), { delay: this.options.pressKeyDelay }) - return this._waitForAction(); + return this._waitForAction() } /** * Clears the text input element: ``, `