diff --git a/.eslintrc b/.eslintrc index e44ffd7427..6b7091c1ac 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,10 @@ { + "root": true, "extends": [ "eslint:recommended" ], "parserOptions": { - "ecmaVersion": 2020, + "ecmaVersion": 13, "sourceType": "module", "ecmaFeatures": { "jsx": false @@ -13,6 +14,33 @@ "mocha": true, "node": true }, + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "excludedFiles": [ + "test/**/*.ts" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module", + "ecmaFeatures": { + "jsx": false + }, + "project": "./tsconfig.json" + } + } + ], "rules": { "eqeqeq": [ "error", @@ -43,13 +71,13 @@ "omitLastInOneLineBlock": true } ], - "no-trailing-spaces": 0, + "no-trailing-spaces": 1, "no-else-return": 2, "no-extra-bind": 0, "no-implicit-coercion": 0, "no-useless-call": 0, "no-return-assign": 0, - "eol-last": 0, + "eol-last": 1, "no-unused-vars": 0, "no-extra-semi": 0, "comma-dangle": 2, @@ -86,6 +114,7 @@ "after": true } ], + "space-infix-ops": 1, "padding-line-between-statements": [ "error", { diff --git a/.github/workflows/build-node.yaml b/.github/workflows/build-node.yaml index 1a732e528c..da45e9f946 100644 --- a/.github/workflows/build-node.yaml +++ b/.github/workflows/build-node.yaml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [14.x, 16.x, 18.x] + node-version: [16.x, 18.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: diff --git a/api/index.js b/api/index.js index 2ca0a30a00..e2a0b033a0 100644 --- a/api/index.js +++ b/api/index.js @@ -16,7 +16,7 @@ const exportedCommands = [ const basePath = '../dist/api'; const Commands = {}; const props = exportedCommands.reduce((prev, fileName) => { - const commandName = fileName.substring(fileName.lastIndexOf('/')+1).replace('.js', ''); + const commandName = fileName.substring(fileName.lastIndexOf('/') + 1).replace('.js', ''); prev[commandName] = { configurable: true, get: function() { @@ -29,4 +29,4 @@ const props = exportedCommands.reduce((prev, fileName) => { Object.defineProperties(Commands, props); -module.exports = Commands; \ No newline at end of file +module.exports = Commands; diff --git a/examples/cucumber-js/features/step_definitions/nightwatch.js b/examples/cucumber-js/features/step_definitions/nightwatch.js index cbfffe3872..789cf23ad7 100644 --- a/examples/cucumber-js/features/step_definitions/nightwatch.js +++ b/examples/cucumber-js/features/step_definitions/nightwatch.js @@ -30,4 +30,4 @@ Then(/^the title is "([^"]*)"$/, function(title) { Then(/^Body contains "([^"]*)"$/, function(contains) { return browser.assert.textContains('.search-results', contains); -}); \ No newline at end of file +}); diff --git a/examples/custom-commands/strictClick.js b/examples/custom-commands/strictClick.js index 99afe91ffb..e5686811fd 100644 --- a/examples/custom-commands/strictClick.js +++ b/examples/custom-commands/strictClick.js @@ -3,4 +3,4 @@ module.exports = { return this.waitForElementVisible(selector) .click(selector); } -}; \ No newline at end of file +}; diff --git a/examples/pages/google/search.js b/examples/pages/google/search.js index 04df86b323..430d8dc1f0 100644 --- a/examples/pages/google/search.js +++ b/examples/pages/google/search.js @@ -2,7 +2,7 @@ const searchCommands = { submit() { this.waitForElementVisible('@submitButton', 1000) .click('@submitButton'); - + this.pause(1000); return this; // Return page object for chaining diff --git a/examples/pages/google/searchResults.js b/examples/pages/google/searchResults.js index 72684c1533..35061c08cf 100644 --- a/examples/pages/google/searchResults.js +++ b/examples/pages/google/searchResults.js @@ -6,7 +6,7 @@ const menuCommands = { var self = this; return this.getAttribute(product, 'class', function (result) { - let isSelected = result.value.indexOf('hdtb-msel') > -1; + const isSelected = result.value.indexOf('hdtb-msel') > -1; callback.call(self, isSelected); }); } @@ -44,4 +44,4 @@ module.exports = { } } } -}; \ No newline at end of file +}; diff --git a/examples/tests/chromeCDP_example.js b/examples/tests/chromeCDP_example.js index ea08a33063..4f6709faa2 100644 --- a/examples/tests/chromeCDP_example.js +++ b/examples/tests/chromeCDP_example.js @@ -1,7 +1,7 @@ describe('Chrome DevTools Example', function() { this.disabled = this.argv.env !== 'chrome'; - + it ('using CDP DOM Snapshot', async function(browser) { await browser.navigateTo('https://nightwatchjs.org'); @@ -11,4 +11,4 @@ describe('Chrome DevTools Example', function() { browser.assert.deepStrictEqual(Object.keys(dom), ['documents', 'strings']); }); -}); \ No newline at end of file +}); diff --git a/examples/tests/duckDuckGo.js b/examples/tests/duckDuckGo.js index 24863a7167..4c5c1c38c2 100644 --- a/examples/tests/duckDuckGo.js +++ b/examples/tests/duckDuckGo.js @@ -11,5 +11,5 @@ describe('duckduckgo example', function() { .assert.visible('button[type=submit]') .click('button[type=submit]') .assert.textContains('.react-results--main', 'Nightwatch.js'); - }); + }); }); diff --git a/examples/tests/element/elementapi-tests.js b/examples/tests/element/elementapi-tests.js index 99b37861b3..eba4b3652b 100644 --- a/examples/tests/element/elementapi-tests.js +++ b/examples/tests/element/elementapi-tests.js @@ -79,4 +79,4 @@ describe('queries tests', function() { }); -}); \ No newline at end of file +}); diff --git a/examples/tests/sample-with-relative-locators.js b/examples/tests/sample-with-relative-locators.js index b8f9d4f301..65d8f82a4c 100644 --- a/examples/tests/sample-with-relative-locators.js +++ b/examples/tests/sample-with-relative-locators.js @@ -22,4 +22,4 @@ describe('sample with relative locators', function () { }); after(browser => browser.end()); -}); \ No newline at end of file +}); diff --git a/examples/tests/selectElement.js b/examples/tests/selectElement.js index 0d7a0176ad..c026cd5465 100644 --- a/examples/tests/selectElement.js +++ b/examples/tests/selectElement.js @@ -13,4 +13,4 @@ module.exports = { }) .assert.selected(await selectElement.findElement('option[value=four]'), 'Forth option is selected'); } -}; \ No newline at end of file +}; diff --git a/examples/tests/shadowRootExample.js b/examples/tests/shadowRootExample.js index abe7d6dfce..462c875c34 100644 --- a/examples/tests/shadowRootExample.js +++ b/examples/tests/shadowRootExample.js @@ -50,4 +50,4 @@ describe('Shadow Root example test', function() { // await expect.element(firstElement).to.be.an('img'); }); -}); \ No newline at end of file +}); diff --git a/examples/unittests/testUtils.js b/examples/unittests/testUtils.js index bff4a13f2f..c091100170 100644 --- a/examples/unittests/testUtils.js +++ b/examples/unittests/testUtils.js @@ -19,4 +19,4 @@ module.exports = { assert.strictEqual(Utils.getTestSuiteName('test.case.one'), 'Test Case One'); assert.strictEqual(Utils.getTestSuiteName('testCaseOne'), 'Test Case One'); } -}; \ No newline at end of file +}; diff --git a/examples/unittests/testUtilsWithChai.js b/examples/unittests/testUtilsWithChai.js index 4e6adc8108..6f8d701800 100644 --- a/examples/unittests/testUtilsWithChai.js +++ b/examples/unittests/testUtilsWithChai.js @@ -16,4 +16,4 @@ module.exports = { var resultMs = Utils.formatElapsedTime(999); expect(resultMs).to.equal('999ms'); } -}; \ No newline at end of file +}; diff --git a/lib/api/_loaders/_base-loader.js b/lib/api/_loaders/_base-loader.js index 0942aba134..14c2a4c7d1 100644 --- a/lib/api/_loaders/_base-loader.js +++ b/lib/api/_loaders/_base-loader.js @@ -399,20 +399,20 @@ class BaseLoader extends EventEmitter { const options = this.getCommandOptions(); - if (args && args.length > 0 && Utils.isFunction(args[args.length-1])) { + if (args && args.length > 0 && Utils.isFunction(args[args.length - 1])) { const callback = args.pop(); - + const userCallbackWrapper = (function(context) { - + const proxyFn = new Proxy(callback, { apply: function(target, thisArg, argumentsList) { context.addedInsideCallback = true; - + return target.apply(thisArg, argumentsList); } }); proxyFn.originalTarget = callback; - + return proxyFn; })(this); diff --git a/lib/api/_loaders/command.js b/lib/api/_loaders/command.js index 2843a9cc2f..3afd576d5b 100644 --- a/lib/api/_loaders/command.js +++ b/lib/api/_loaders/command.js @@ -42,7 +42,7 @@ class CommandLoader extends BaseCommandLoader { return function(...args) { let callback; let method; - const isLastArgFunction = isFunction(args[args.length-1]); + const isLastArgFunction = isFunction(args[args.length - 1]); if (isLastArgFunction) { callback = args.pop(); @@ -74,8 +74,8 @@ class CommandLoader extends BaseCommandLoader { reportProtocolErrors(result) { if (opts.isUserDefined) { return true; - } - + } + return super.reportProtocolErrors(result); } @@ -85,11 +85,11 @@ class CommandLoader extends BaseCommandLoader { get reuseBrowser() { return nightwatchInstance.argv['reuse-browser'] || (nightwatchInstance.settings.globals && nightwatchInstance.settings.globals.reuseBrowserSession); - } - + } + get isES6AsyncCommand() { return isES6AsyncFn( - CommandLoader.isDeprecatedCommandStyle(CommandModule) ? CommandModule.command: this.command + CommandLoader.isDeprecatedCommandStyle(CommandModule) ? CommandModule.command : this.command ); } @@ -210,7 +210,7 @@ class CommandLoader extends BaseCommandLoader { if (result.stack) { err.stack = result.stack; } - + if (result.error instanceof Error) { result.error.registered = true; } else { diff --git a/lib/api/_loaders/element-global.js b/lib/api/_loaders/element-global.js index ebed36155d..30f7a53024 100644 --- a/lib/api/_loaders/element-global.js +++ b/lib/api/_loaders/element-global.js @@ -161,7 +161,7 @@ class ElementGlobal { value = { value: locator, using: this.client.locateStrategy - }; + }; } else { value = locator; } @@ -267,7 +267,7 @@ class ElementGlobal { }); } - const lastArg = args[args.length-1]; + const lastArg = args[args.length - 1]; if (isFunction(lastArg)) { if (error) { return lastArg.call(this.api, { diff --git a/lib/api/_loaders/expect.js b/lib/api/_loaders/expect.js index 24ee01f385..a186f083ad 100644 --- a/lib/api/_loaders/expect.js +++ b/lib/api/_loaders/expect.js @@ -115,7 +115,7 @@ class ExpectLoader extends BaseCommandLoader { deferred.reject(err); }); } - + return expectCommand.instance; }.bind(this)); } diff --git a/lib/api/_loaders/static.js b/lib/api/_loaders/static.js index 913a9dd14d..dc75f485ba 100644 --- a/lib/api/_loaders/static.js +++ b/lib/api/_loaders/static.js @@ -182,7 +182,7 @@ module.exports = class StaticAssert { Object.assign(node.deferred.promise, apiToReturn || this.api); - //prevent unhandled rejection + //prevent unhandled rejection node.deferred.promise.catch(err => { return StaticAssert.lastDeferred.reject(err); }); diff --git a/lib/api/assertions/_assertionInstance.js b/lib/api/assertions/_assertionInstance.js index 273671ee0c..7bca65cbab 100644 --- a/lib/api/assertions/_assertionInstance.js +++ b/lib/api/assertions/_assertionInstance.js @@ -21,7 +21,7 @@ class AssertionInstance { } static init({nightwatchInstance, args, fileName, options}) { - if (Utils.isFunction(args[args.length-1])) { + if (Utils.isFunction(args[args.length - 1])) { this.__doneCallback = args.pop(); } else { this.__doneCallback = function(result) { diff --git a/lib/api/assertions/domPropertyMatches.js b/lib/api/assertions/domPropertyMatches.js index be80463240..ff03f08e77 100644 --- a/lib/api/assertions/domPropertyMatches.js +++ b/lib/api/assertions/domPropertyMatches.js @@ -1,12 +1,12 @@ /** * Check if specified DOM property value of a given element matches a regex. For all the available DOM element properties, consult the [Element doc at MDN](https://developer.mozilla.org/en-US/docs/Web/API/element). - * - * + * + * * @example * this.demoTest = function (browser) { * browser.assert.domPropertyMatches('#main', 'tagName', /^frame/); * } - * + * * @method assert.domPropertyMatches * @param {string|object} definition The selector (CSS/Xpath) used to locate the element. Can either be a string or an object which specifies [element properties](https://nightwatchjs.org/guide#element-properties). * @param {string} domProperty The DOM property name. @@ -41,10 +41,10 @@ exports.assertion = function (definition, domProperty, regexExpression, msg) { if (!Array.isArray(value)) { return regex.test(value); - } + } return false; - }; + }; this.value = function(result = {}) { return result.value || ''; diff --git a/lib/api/assertions/elementsCount.js b/lib/api/assertions/elementsCount.js index 68d0e244a7..c8b5285817 100644 --- a/lib/api/assertions/elementsCount.js +++ b/lib/api/assertions/elementsCount.js @@ -50,7 +50,7 @@ exports.assertion = function (definition, count, msg) { this.command = async function(callback) { - + await this.api.findElements(definition, callback); }; }; diff --git a/lib/api/assertions/enabled.js b/lib/api/assertions/enabled.js index 686542abf5..9e42334314 100644 --- a/lib/api/assertions/enabled.js +++ b/lib/api/assertions/enabled.js @@ -21,7 +21,7 @@ exports.assertion = function(definition, msg) { this.formatMessage = function() { const message = msg || `Testing if element %s ${this.negate ? 'is not enabled' : 'is enabled'}`; - + return { message, args: [this.elementSelector] @@ -43,5 +43,5 @@ exports.assertion = function(definition, msg) { this.command = function(callback) { this.api.isEnabled(definition, callback); }; - + }; diff --git a/lib/api/assertions/hasAttribute.js b/lib/api/assertions/hasAttribute.js index c05de41cd0..8143b300d4 100644 --- a/lib/api/assertions/hasAttribute.js +++ b/lib/api/assertions/hasAttribute.js @@ -3,12 +3,12 @@ * Checks if the given element contains the specified DOM attribute. * * Equivalent of: https://developer.mozilla.org/en-US/docs/Web/API/Element/hasAttribute - * + * * @example * this.demoTest = function (browser) { * browser.assert.hasAttribute('#main', 'data-track'); * }; - * + * * @method assert.hasAttribute * @param {string|object} definition The selector (CSS/Xpath) used to locate the element. Can either be a string or an object which specifies [element properties](https://nightwatchjs.org/guide#element-properties). * @param {string} expectedAttribute The DOM attribute to look for. @@ -26,15 +26,15 @@ exports.assertion = function(definition, expectedAttribute, msg) { this.expected = function() { return this.negate ? `has not ${expectedAttribute}` : `has ${expectedAttribute}`; }; - + this.formatMessage = function() { if (!isString(expectedAttribute)) { throw new Error('Expected attribute must be a string'); } - let message = msg || `Testing if element %s ${this.negate ? 'doesn\'t have attribute %s' : 'has attribute %s'}`; - + const message = msg || `Testing if element %s ${this.negate ? 'doesn\'t have attribute %s' : 'has attribute %s'}`; + return { message, args: [this.elementSelector, `'${expectedAttribute}'`] @@ -47,7 +47,7 @@ exports.assertion = function(definition, expectedAttribute, msg) { if (!result || !result.value) { return false; } - + return true; }; diff --git a/lib/api/assertions/hasClass.js b/lib/api/assertions/hasClass.js index 077ff18ab6..fa115dd5b3 100644 --- a/lib/api/assertions/hasClass.js +++ b/lib/api/assertions/hasClass.js @@ -1,6 +1,6 @@ /** * Checks if the given element has the specified CSS class. - * + * * @example * this.demoTest = function (browser) { * browser.assert.hasClass('#main', 'container'); @@ -18,7 +18,7 @@ const classListRegexp = /\s/; const classNameRegexp = /\w/; const {containsMultiple, setElementSelectorProps} = require('../../utils'); - + exports.assertion = function(definition, expected, msg) { this.options = { elementSelector: true @@ -27,41 +27,40 @@ exports.assertion = function(definition, expected, msg) { this.expected = function() { return this.negate ? `has not ${expected}` : `has ${expected}`; }; - + this.formatMessage = function() { - let message = msg || `Testing if element %s ${this.negate ? 'doesn\'t have css class %s' : 'has css class %s'}`; - + const message = msg || `Testing if element %s ${this.negate ? 'doesn\'t have css class %s' : 'has css class %s'}`; + return { message, args: [this.elementSelector, `'${Array.isArray(expected) ? expected.join(' ') : expected}'`] }; }; - - + + this.evaluate = function() { if (!this.classList) { return false; } - + return containsMultiple(this.classList, expected, ' '); }; - + this.value = function(result) { if (!result || !result.value) { return ''; } - + this.classList = result.value .split(classListRegexp) .filter(item => classNameRegexp.test(item)); - + return result.value; }; - + this.command = function(callback) { this.api.getAttribute(setElementSelectorProps(definition, { suppressNotFoundErrors: true }), 'class', callback); }; }; - \ No newline at end of file diff --git a/lib/api/assertions/selected.js b/lib/api/assertions/selected.js index 296c01549d..90528945a4 100644 --- a/lib/api/assertions/selected.js +++ b/lib/api/assertions/selected.js @@ -44,4 +44,4 @@ exports.assertion = function(definition, msg) { this.api.isSelected(definition, callback); }; -}; \ No newline at end of file +}; diff --git a/lib/api/assertions/textEquals.js b/lib/api/assertions/textEquals.js index 17fe92fdc5..4d83a80eef 100644 --- a/lib/api/assertions/textEquals.js +++ b/lib/api/assertions/textEquals.js @@ -5,7 +5,7 @@ * this.demoTest = function (browser) { * browser.assert.textEquals('#main', 'The Night Watch'); * }; - * + * * @method assert.textEquals * @param {string|object} definition The selector (CSS/Xpath) used to locate the element. Can either be a string or an object which specifies [element properties](https://nightwatchjs.org/guide#element-properties). * @param {string} expected text to match text. @@ -19,20 +19,20 @@ exports.assertion = function(definition, expected, msg) { this.options = { elementSelector: true }; - + this.formatMessage = function() { const message = msg || `Testing if element's %s inner text ${this.negate ? 'doesn\'t equal %s' : 'equals %s'}`; - + return { message, args: [this.elementSelector, `'${expected}'`] }; }; - + this.expected = function() { return this.negate ? `doesn't equal '${expected}'` : `equals '${expected}'`; }; - + this.evaluate = function(value) { if (typeof value != 'string') { return false; @@ -67,4 +67,4 @@ exports.assertion = function(definition, expected, msg) { suppressNotFoundErrors: true }), callback); }; -}; \ No newline at end of file +}; diff --git a/lib/api/assertions/textMatches.js b/lib/api/assertions/textMatches.js index 9adb84e3f0..d09e14a790 100644 --- a/lib/api/assertions/textMatches.js +++ b/lib/api/assertions/textMatches.js @@ -5,7 +5,7 @@ * this.demoTest = function (browser) { * browser.assert.textMatches('#main', '^Nightwatch'); * }; - * + * * @method assert.textMatches * @param {string|object} definition The selector (CSS/Xpath) used to locate the element. Can either be a string or an object which specifies [element properties](https://nightwatchjs.org/guide#element-properties). * @param {string|RegExp} regexExpression Regex expression to match text. diff --git a/lib/api/assertions/titleEquals.js b/lib/api/assertions/titleEquals.js index 2897d6ed58..7ac6865b22 100644 --- a/lib/api/assertions/titleEquals.js +++ b/lib/api/assertions/titleEquals.js @@ -1,6 +1,6 @@ /** * Checks if the page title equals the given value. - * + * * @example * this.demoTest = function (client) { * browser.assert.titleEquals('https://www.google.com'); diff --git a/lib/api/assertions/titleMatches.js b/lib/api/assertions/titleMatches.js index 445f414993..35a89d8bd5 100644 --- a/lib/api/assertions/titleMatches.js +++ b/lib/api/assertions/titleMatches.js @@ -5,7 +5,7 @@ * this.demoTest = function (client) { * browser.assert.titleMatches('^Nightwatch'); * }; - * + * * @method assert.titleMatches * @param {string|RegExp} regexExpression Regex expression to match current title of a page * @param {string} [msg] Optional log message to display in the output. If missing, one is displayed by default. diff --git a/lib/api/assertions/urlMatches.js b/lib/api/assertions/urlMatches.js index 5b5d05a20a..a33910fc5c 100644 --- a/lib/api/assertions/urlMatches.js +++ b/lib/api/assertions/urlMatches.js @@ -5,7 +5,7 @@ * this.demoTest = function (client) { * browser.assert.urlMatches('^https'); * }; - * + * * @method assert.urlMatches * @param {string|RegExp} regexExpression Regex expression to match URL * @param {string} [msg] Optional log message to display in the output. If missing, one is displayed by default. diff --git a/lib/api/assertions/valueEquals.js b/lib/api/assertions/valueEquals.js index 011070c457..14a3d291e5 100644 --- a/lib/api/assertions/valueEquals.js +++ b/lib/api/assertions/valueEquals.js @@ -1,10 +1,10 @@ /** * Checks if the given form element's value equals the expected value. - * + * * The existing .assert.value() command. * - * + * * @example * this.demoTest = function (browser) { * browser.assert.valueEquals("form.login input[type=text]", "username"); diff --git a/lib/api/client-commands/_base-command.js b/lib/api/client-commands/_base-command.js index d190d231a7..e98a24b3eb 100644 --- a/lib/api/client-commands/_base-command.js +++ b/lib/api/client-commands/_base-command.js @@ -35,6 +35,10 @@ module.exports = class ClientCommand { promise = Promise.resolve(promise); } + // the final result returned in the test would be same irrespective of + // the value of fullPromiseResolve, thanks to `getResult` method in + // lib/core/treenode.js but the result value everywhere else in Nightwatch + // would appear to be the value of resolveValue below. const resolveValue = fullPromiseResolve ? result : result.value; promise.then(_ => resolve(resolveValue)).catch(err => reject(err)); } catch (e) { diff --git a/lib/api/client-commands/alerts/setText.js b/lib/api/client-commands/alerts/setText.js index 5b2e06b792..4f817d8f8d 100644 --- a/lib/api/client-commands/alerts/setText.js +++ b/lib/api/client-commands/alerts/setText.js @@ -44,7 +44,7 @@ class SetAlertText extends ClientCommand { } this.alertText = value; - + return super.command(callback); } } diff --git a/lib/api/client-commands/axeInject.js b/lib/api/client-commands/axeInject.js index 4a72d758db..99332b6147 100644 --- a/lib/api/client-commands/axeInject.js +++ b/lib/api/client-commands/axeInject.js @@ -25,4 +25,4 @@ module.exports = class AxeInjectAbstract { static get allowOverride() { return true; } -}; \ No newline at end of file +}; diff --git a/lib/api/client-commands/debug.js b/lib/api/client-commands/debug.js index 76e1477b56..f2c313017a 100644 --- a/lib/api/client-commands/debug.js +++ b/lib/api/client-commands/debug.js @@ -7,7 +7,7 @@ const Debuggability = require('../../utils/debuggability.js'); * This command halts the test execution and provides users with a REPL interface where they can type * any of the available Nightwatch commands and the command will be executed in the running browser * in real-time. - * + * * This can be used to debug why a certain command in not working as expected, find the correct * locators for your assertions or just play around with the available Nightwatch commands. * @@ -16,10 +16,10 @@ const Debuggability = require('../../utils/debuggability.js'); * // command to get the correct result as output. * this.demoTest = async function (browser) { * browser.debug(); - * + * * // with no auto-complete * browser.debug({preview: false}); - * + * * // with a timeout of 6000 ms (time for which the interface * // would wait for a result). * browser.debug({timeout: 6000}) diff --git a/lib/api/client-commands/document/executeAsync.js b/lib/api/client-commands/document/executeAsyncScript.js similarity index 95% rename from lib/api/client-commands/document/executeAsync.js rename to lib/api/client-commands/document/executeAsyncScript.js index 2719ae29c6..935a3e902b 100644 --- a/lib/api/client-commands/document/executeAsync.js +++ b/lib/api/client-commands/document/executeAsyncScript.js @@ -1,5 +1,5 @@ const ClientCommand = require('../_base-command.js'); -const {isFunction} = require('../../../utils'); +const {isFunction} = require('../../../utils/index.js'); /** * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. The executed script is assumed to be asynchronous. @@ -52,7 +52,7 @@ const {isFunction} = require('../../../utils'); */ class ExecuteAsyncScript extends ClientCommand { static get namespacedAliases() { - return ['document.executeAsyncScript', 'executeAsync', 'executeAsyncScript']; + return ['document.executeAsync', 'executeAsync', 'executeAsyncScript']; } static get isTraceable() { diff --git a/lib/api/client-commands/document/execute.js b/lib/api/client-commands/document/executeScript.js similarity index 96% rename from lib/api/client-commands/document/execute.js rename to lib/api/client-commands/document/executeScript.js index f3af809ddf..165d8358ae 100644 --- a/lib/api/client-commands/document/execute.js +++ b/lib/api/client-commands/document/executeScript.js @@ -1,5 +1,5 @@ const ClientCommand = require('../_base-command.js'); -const {isFunction} = require('../../../utils'); +const {isFunction} = require('../../../utils/index.js'); /** * Inject a snippet of JavaScript into the page for execution in the context of the currently selected frame. The executed script is assumed to be synchronous. @@ -53,7 +53,7 @@ const {isFunction} = require('../../../utils'); */ class ExecuteScript extends ClientCommand { static get namespacedAliases() { - return ['document.executeScript', 'execute', 'executeScript']; + return ['document.execute', 'execute', 'executeScript']; } static get isTraceable() { diff --git a/lib/api/client-commands/document/injectScript.js b/lib/api/client-commands/document/injectScript.js index 9954cf28ed..790dc88339 100644 --- a/lib/api/client-commands/document/injectScript.js +++ b/lib/api/client-commands/document/injectScript.js @@ -50,7 +50,7 @@ class InjectScript extends ClientCommand { this.scriptFn = 'var passedArgs = Array.prototype.slice.call(arguments,0); return (' + script.toString() + ').apply(window, passedArgs);'; - this.scriptArgs = args; + this.scriptArgs = args; return super.command(callback); } diff --git a/lib/api/client-commands/enablePerformanceMetrics.js b/lib/api/client-commands/enablePerformanceMetrics.js index 1494188522..3439346cb1 100644 --- a/lib/api/client-commands/enablePerformanceMetrics.js +++ b/lib/api/client-commands/enablePerformanceMetrics.js @@ -40,7 +40,7 @@ class EnablePerformanceMetrics extends ClientCommand { } const {enable = true} = this; - + this.transportActions.enablePerformanceMetrics(enable, callback); } diff --git a/lib/api/client-commands/getLog.js b/lib/api/client-commands/getLog.js index 849e23d932..acd263afbf 100644 --- a/lib/api/client-commands/getLog.js +++ b/lib/api/client-commands/getLog.js @@ -20,6 +20,7 @@ const ClientCommand = require('./_base-command.js'); * @param {function} callback Callback function which is called with the result value. * @api protocol.sessions * @see getLogTypes + * @deprecated In favour of `.logs.getSessionLog()`. */ class GetLog extends ClientCommand { get returnsFullResultObject() { diff --git a/lib/api/client-commands/getLogTypes.js b/lib/api/client-commands/getLogTypes.js index 7ce75ed067..f45eab1717 100644 --- a/lib/api/client-commands/getLogTypes.js +++ b/lib/api/client-commands/getLogTypes.js @@ -17,6 +17,7 @@ const ClientCommand = require('./_base-command.js'); * @returns {Array} Available log types * @api protocol.sessions * @see sessionLogTypes + * @deprecated In favour of `.logs.getSessionLogTypes()`. */ class GetLogTypes extends ClientCommand { get returnsFullResultObject() { diff --git a/lib/api/client-commands/getPerformanceMetrics.js b/lib/api/client-commands/getPerformanceMetrics.js index be396c9684..9ed95aeefb 100644 --- a/lib/api/client-commands/getPerformanceMetrics.js +++ b/lib/api/client-commands/getPerformanceMetrics.js @@ -38,7 +38,7 @@ class GetPerformanceMetrics extends ClientCommand { return callback(error); } - + this.transportActions.getPerformanceMetrics(callback); } diff --git a/lib/api/client-commands/isLogAvailable.js b/lib/api/client-commands/isLogAvailable.js index ab4668e2bf..664c4a026c 100644 --- a/lib/api/client-commands/isLogAvailable.js +++ b/lib/api/client-commands/isLogAvailable.js @@ -17,6 +17,7 @@ const ClientCommand = require('./_base-command.js'); * @param {function} callback Callback function which is called with the result value. * @api protocol.sessions * @see getLogTypes + * @deprecated In favour of `.logs.isSessionLogAvailable()`. */ class IsLogAvailable extends ClientCommand { performAction(actionCallback) { diff --git a/lib/api/client-commands/captureBrowserConsoleLogs.js b/lib/api/client-commands/logs/captureBrowserConsoleLogs.js similarity index 90% rename from lib/api/client-commands/captureBrowserConsoleLogs.js rename to lib/api/client-commands/logs/captureBrowserConsoleLogs.js index df9344b999..e7756ae636 100644 --- a/lib/api/client-commands/captureBrowserConsoleLogs.js +++ b/lib/api/client-commands/logs/captureBrowserConsoleLogs.js @@ -1,5 +1,5 @@ -const ClientCommand = require('./_base-command.js'); -const {Logger} = require('../../utils'); +const ClientCommand = require('../_base-command.js'); +const {Logger} = require('../../../utils'); /** * Listen to the `console` events (ex. `console.log` event) and register callback to process the same. @@ -26,9 +26,11 @@ const {Logger} = require('../../utils'); * @moreinfo nightwatchjs.org/guide/running-tests/capture-console-messages.html */ class StartCapturingLogs extends ClientCommand { + static get namespacedAliases() { + return 'captureBrowserConsoleLogs'; + } performAction(callback) { - if (!this.api.isChrome() && !this.api.isEdge()) { const error = new Error('The command .captureBrowserConsoleLogs() is only supported in Chrome and Edge drivers'); Logger.error(error); @@ -43,7 +45,7 @@ class StartCapturingLogs extends ClientCommand { return callback(error); } - + this.transportActions.startLogsCapture(userCallback, callback); } diff --git a/lib/api/client-commands/captureBrowserExceptions.js b/lib/api/client-commands/logs/captureBrowserExceptions.js similarity index 89% rename from lib/api/client-commands/captureBrowserExceptions.js rename to lib/api/client-commands/logs/captureBrowserExceptions.js index f7f6a2c556..43bfeafebd 100644 --- a/lib/api/client-commands/captureBrowserExceptions.js +++ b/lib/api/client-commands/logs/captureBrowserExceptions.js @@ -1,5 +1,5 @@ -const ClientCommand = require('./_base-command.js'); -const {Logger} = require('../../utils'); +const ClientCommand = require('../_base-command.js'); +const {Logger} = require('../../../utils'); /** * Catch the JavaScript exceptions thrown in the browser. @@ -30,9 +30,11 @@ const {Logger} = require('../../utils'); * @moreinfo nightwatchjs.org/guide/running-tests/catch-js-exceptions.html */ class CatchJsExceptions extends ClientCommand { + static get namespacedAliases() { + return 'captureBrowserExceptions'; + } performAction(callback) { - if (!this.api.isChrome() && !this.api.isEdge()) { const error = new Error('The command .captureBrowserExceptions() is only supported in Chrome and Edge drivers'); Logger.error(error); @@ -58,4 +60,4 @@ class CatchJsExceptions extends ClientCommand { } } -module.exports = CatchJsExceptions; \ No newline at end of file +module.exports = CatchJsExceptions; diff --git a/lib/api/client-commands/logs/getSessionLog.js b/lib/api/client-commands/logs/getSessionLog.js new file mode 100644 index 0000000000..a22f9f94ec --- /dev/null +++ b/lib/api/client-commands/logs/getSessionLog.js @@ -0,0 +1,54 @@ +const ClientCommand = require('../_base-command.js'); + +/** + * Gets a log from Selenium. + * + * @example + * describe('get log from Selenium', function() { + * it('get browser log (default)', function(browser) { + * browser.logs.getSessionLog(function(result) { + * const logEntriesArray = result.value; + * console.log('Log length: ' + logEntriesArray.length); + * logEntriesArray.forEach(function(log) { + * console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); + * }); + * }); + * }); + * + * it('get driver log with ES6 async/await', async function(browser) { + * const driverLogAvailable = await browser.logs.isSessionLogAvailable('driver'); + * if (driverLogAvailable) { + * const logEntriesArray = await browser.logs.getSessionLog('driver'); + * logEntriesArray.forEach(function(log) { + * console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); + * }); + * } + * }); + * }); + * + * @syntax .logs.getSessionLog([typeString], [callback]) + * @method logs.getSessionLog + * @param {string} typeString Log type to request. Default: 'browser'. Use `.logs.getLogTypes()` command to get all available log types. + * @param {function} [callback] Callback function which is called with the result value. + * @returns {Array} An array of log Entry objects with properties as defined [here](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/logging_exports_Entry.html) (see Instance Properties). + * @see logs.getSessionLogTypes + * @api protocol.sessions + */ +class GetSessionLog extends ClientCommand { + performAction(callback) { + this.transportActions.getLogContents(this.typeString, callback); + } + + command(typeString = 'browser', callback) { + if (arguments.length === 1 && typeof arguments[0] == 'function') { + callback = arguments[0]; + typeString = 'browser'; + } + + this.typeString = typeString; + + return super.command(callback); + } +} + +module.exports = GetSessionLog; diff --git a/lib/api/client-commands/logs/getSessionLogTypes.js b/lib/api/client-commands/logs/getSessionLogTypes.js new file mode 100644 index 0000000000..13cda02d8f --- /dev/null +++ b/lib/api/client-commands/logs/getSessionLogTypes.js @@ -0,0 +1,34 @@ +const ClientCommand = require('../_base-command.js'); + +/** + * Gets the available log types. More info about log types in WebDriver can be found here: https://github.com/SeleniumHQ/selenium/wiki/Logging + * + * @example + * describe('get available log types', function() { + * it('get log types', function(browser) { + * browser.logs.getSessionLogTypes(function(result) { + * const logTypes = result.value; + * console.log('Log types available:', logTypes); + * }); + * }); + * + * it('get log types with ES6 async/await', async function(browser) { + * const logTypes = await browser.logs.getSessionLogTypes(); + * console.log('Log types available:', logTypes); + * }); + * }); + * + * @syntax .logs.getSessionLogTypes([callback]) + * @method logs.getSessionLogTypes + * @param {function} [callback] Callback function which is called with the result value. + * @returns {Array} Available log types. + * @see logs.getSessionLog + * @api protocol.sessions + */ +class GetSessionLogTypes extends ClientCommand { + performAction(callback) { + this.transportActions.getSessionLogTypes(callback); + } +} + +module.exports = GetSessionLogTypes; diff --git a/lib/api/client-commands/logs/isSessionLogAvailable.js b/lib/api/client-commands/logs/isSessionLogAvailable.js new file mode 100644 index 0000000000..14231b5123 --- /dev/null +++ b/lib/api/client-commands/logs/isSessionLogAvailable.js @@ -0,0 +1,67 @@ +const ClientCommand = require('../_base-command.js'); + +/** + * Utility command to test if the log type is available. + * + * @example + * describe('test if the log type is available', function() { + * it('test browser log type', function(browser) { + * browser.logs.isSessionLogAvailable('browser', function(result) { + * const isAvailable = result.value; + * if (isAvailable) { + * // do something more in here + * } + * }); + * }); + * + * it('test driver log type with ES6 async/await', async function(browser) { + * const isAvailable = await browser.logs.isSessionLogAvailable('driver'); + * if (isAvailable) { + * // do something more in here + * } + * }); + * }); + * + * @syntax .logs.isSessionLogAvailable([typeString], [callback]) + * @method logs.isSessionLogAvailable + * @param {string} typeString Type of log to test. Default: 'browser'. + * @param {function} [callback] Callback function which is called with the result value. + * @returns {boolean} True if log type is available. + * @see logs.getSessionLogTypes + * @api protocol.sessions + */ +class IsSessionLogAvailable extends ClientCommand { + performAction(callback) { + const {typeString} = this; + + this.transportActions.getSessionLogTypes(function(result) { + if (result.status === 0) { + const types = result.value; + let isAvailable; + + try { + isAvailable = Array.isArray(types) && types.indexOf(typeString) >= 0; + } catch (err) { + isAvailable = false; + } + + result.value = isAvailable; + } + + callback.call(this, result); + }); + } + + command(typeString = 'browser', callback) { + if (arguments.length === 1 && typeof arguments[0] == 'function') { + callback = arguments[0]; + typeString = 'browser'; + } + + this.typeString = typeString; + + return super.command(callback); + } +} + +module.exports = IsSessionLogAvailable; diff --git a/lib/api/client-commands/captureNetworkRequests.js b/lib/api/client-commands/network/captureRequests.js similarity index 82% rename from lib/api/client-commands/captureNetworkRequests.js rename to lib/api/client-commands/network/captureRequests.js index d71794cdb2..77aa01312f 100644 --- a/lib/api/client-commands/captureNetworkRequests.js +++ b/lib/api/client-commands/network/captureRequests.js @@ -1,5 +1,5 @@ -const ClientCommand = require('./_base-command.js'); -const {Logger} = require('../../utils'); +const ClientCommand = require('../_base-command.js'); +const {Logger} = require('../../../utils'); /** * Capture outgoing network calls from the browser. @@ -9,7 +9,7 @@ const {Logger} = require('../../utils'); * it('captures and logs network requests as they occur', function(this: ExtendDescribeThis<{requestCount: number}>) { * this.requestCount = 1; * browser - * .captureNetworkRequests((requestParams) => { + * .network.captureRequests((requestParams) => { * console.log('Request Number:', this.requestCount!++); * console.log('Request URL:', requestParams.request.url); * console.log('Request method:', requestParams.request.method); @@ -19,8 +19,9 @@ const {Logger} = require('../../utils'); * }); * }); * - * @method captureNetworkRequests + * @method network.captureRequests * @syntax .captureNetworkRequests(onRequestCallback) + * @syntax .network.captureRequests(onRequestCallback) * @param {function} onRequestCallback Callback function called whenever a new outgoing network request is made. * @api protocol.cdp * @since 2.2.0 @@ -28,6 +29,10 @@ const {Logger} = require('../../utils'); */ class CaptureNetworkCalls extends ClientCommand { + static get namespacedAliases() { + return 'captureNetworkRequests'; + } + performAction(callback) { if (!this.api.isChrome() && !this.api.isEdge()) { @@ -44,7 +49,7 @@ class CaptureNetworkCalls extends ClientCommand { return callback(error); } - + this.transportActions.interceptNetworkCalls(userCallback, callback); } @@ -55,4 +60,4 @@ class CaptureNetworkCalls extends ClientCommand { } } -module.exports = CaptureNetworkCalls; \ No newline at end of file +module.exports = CaptureNetworkCalls; diff --git a/lib/api/client-commands/mockNetworkResponse.js b/lib/api/client-commands/network/mockResponse.js similarity index 84% rename from lib/api/client-commands/mockNetworkResponse.js rename to lib/api/client-commands/network/mockResponse.js index e564b46a7e..0ce0977cad 100644 --- a/lib/api/client-commands/mockNetworkResponse.js +++ b/lib/api/client-commands/network/mockResponse.js @@ -1,5 +1,5 @@ -const ClientCommand = require('./_base-command.js'); -const {Logger} = require('../../utils'); +const ClientCommand = require('../_base-command.js'); +const {Logger} = require('../../../utils'); /** * Intercept the request made on a particular URL and mock the response. @@ -8,7 +8,7 @@ const {Logger} = require('../../utils'); * describe('mock network response', function() { * it('intercepts the request made to Google search and mocks its response', function() { * browser - * .mockNetworkResponse('https://www.google.com/', { + * .network.mockResponse('https://www.google.com/', { * status: 200, * headers: { * 'Content-Type': 'UTF-8' @@ -20,8 +20,9 @@ const {Logger} = require('../../utils'); * }); * }); * - * @method mockNetworkResponse + * @method network.mockResponse * @syntax .mockNetworkResponse(urlToIntercept, {status, headers, body}, [callback]) + * @syntax .network.mockResponse(urlToIntercept, {status, headers, body}, [callback]) * @param {string} urlToIntercept URL to intercept and mock the response from. * @param {object} response Response to return. Defaults: `{status: 200, headers: {}, body: ''}`. * @param {function} [callback] Callback function to be called when the command finishes. @@ -31,6 +32,10 @@ const {Logger} = require('../../utils'); */ class MockNetworkResponse extends ClientCommand { + static get namespacedAliases() { + return 'mockNetworkResponse'; + } + performAction(callback) { if (!this.api.isChrome() && !this.api.isEdge()) { @@ -49,7 +54,7 @@ class MockNetworkResponse extends ClientCommand { const slashDel = needsSlash ? '/' : ''; urlToIntercept = `${launchUrl}${slashDel}${urlToIntercept}`; } - + this.transportActions.mockNetworkResponse(urlToIntercept, response, callback); } diff --git a/lib/api/client-commands/setNetworkConditions.js b/lib/api/client-commands/network/setConditions.js similarity index 53% rename from lib/api/client-commands/setNetworkConditions.js rename to lib/api/client-commands/network/setConditions.js index 6a932c70c7..64e80df09f 100644 --- a/lib/api/client-commands/setNetworkConditions.js +++ b/lib/api/client-commands/network/setConditions.js @@ -1,31 +1,39 @@ -const ClientCommand = require('./_base-command.js'); -const {Logger} = require('../../utils'); +const ClientCommand = require('../_base-command.js'); +const {Logger} = require('../../../utils'); /** - * + * * Command to set Chrome network emulation settings. * * @example - * this.demoTest = function (browser) { - * browser.setNetworkConditions({ + * describe('set network conditions', function() { + * it('sets the network conditions',function() { + * browser + * .network.setConditions({ * offline: false, - * latency: 50000, - * download_throughput: 450 * 1024, - * upload_throughput: 150 * 1024 + * latency: 3000, + * download_throughput: 500 * 1024, + * upload_throughput: 500 * 1024 * }); - * }; - * + * }); + * }); * - * @method setNetworkConditions + * @method network.setConditions * @syntax .setNetworkConditions(spec, [callback]) + * @syntax .network.setConditions(spec, [callback]) * @param {object} spec * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.sessions */ class SetNetworkConditions extends ClientCommand { + + static get namespacedAliases() { + return 'setNetworkConditions'; + } + performAction(callback) { if (!this.api.isChrome() && !this.api.isEdge()) { - const error = new Error('SetNetworkConditions is not supported while using this driver'); + const error = new Error('The command .setNetworkConditions() is only supported in Chromium based drivers'); Logger.error(error); return callback(error); diff --git a/lib/api/client-commands/pause.js b/lib/api/client-commands/pause.js index 6accd2dadc..695d8a4a18 100644 --- a/lib/api/client-commands/pause.js +++ b/lib/api/client-commands/pause.js @@ -9,11 +9,11 @@ const Debuggability = require('../../utils/debuggability.js'); * - Pause the test execution for the given time in milliseconds. * - Pause the test execution indefinitely, until resumed by pressing a key in terminal. * - Pause the test execution, and then step over to the next test command (execute the next test command) and pause again. - * + * * This command will allow you to pause the test execution in between, hop on to the browser to check the state of your * application (or use DevTools to debug), and once satisfied, either resume the test execution from where it was left * off or step over to the next test command (execute the next test command) and pause again. - * + * * Stepping over to the next test command would allow you to see what exactly changed in your application when the next * test command was executed. You can also use DevTools to monitor those changes, like the network calls that were made * during the execution of that command, etc. @@ -84,7 +84,7 @@ Pause.prototype.command = function(ms, cb) { if (cb) { cb.call(this.client.api); } - + this.emit('complete'); }, ms); } diff --git a/lib/api/client-commands/registerBasicAuth.js b/lib/api/client-commands/registerBasicAuth.js index 1b15fadb54..1b660128d2 100644 --- a/lib/api/client-commands/registerBasicAuth.js +++ b/lib/api/client-commands/registerBasicAuth.js @@ -28,14 +28,14 @@ class RegisterBasicAuth extends ClientCommand { performAction(callback) { if (!this.api.isChrome() && !this.api.isEdge()) { - const error = new Error('RegisterBasicAuth is not supported while using this driver'); + const error = new Error('The command .registerBasicAuth() is only supported in Chromium based drivers'); Logger.error(error); return callback(error); } const {username, password} = this; - + this.transportActions .registerAuth(username, password, callback) .catch(err => { diff --git a/lib/api/client-commands/saveSnapshot.js b/lib/api/client-commands/saveSnapshot.js index dbc37e2920..2c3fceb909 100644 --- a/lib/api/client-commands/saveSnapshot.js +++ b/lib/api/client-commands/saveSnapshot.js @@ -84,4 +84,4 @@ class SaveSnapshot extends ClientCommand { } } -module.exports = SaveSnapshot; \ No newline at end of file +module.exports = SaveSnapshot; diff --git a/lib/api/client-commands/setDeviceDimensions.js b/lib/api/client-commands/setDeviceDimensions.js index e5f16c1583..ddfc94ce89 100644 --- a/lib/api/client-commands/setDeviceDimensions.js +++ b/lib/api/client-commands/setDeviceDimensions.js @@ -59,4 +59,4 @@ class SetDeviceDimensions extends ClientCommand { } } -module.exports = SetDeviceDimensions; \ No newline at end of file +module.exports = SetDeviceDimensions; diff --git a/lib/api/client-commands/takeHeapSnapshot.js b/lib/api/client-commands/takeHeapSnapshot.js index a5e6348d2a..11724d1e88 100644 --- a/lib/api/client-commands/takeHeapSnapshot.js +++ b/lib/api/client-commands/takeHeapSnapshot.js @@ -38,7 +38,7 @@ class TakeHeapSnapshot extends ClientCommand { } const {heapSnapshotLocation} = this; - + this.transportActions.takeHeapSnapshot(heapSnapshotLocation, callback); } diff --git a/lib/api/client-commands/within.js b/lib/api/client-commands/within.js index 872ecc5fda..60931fe6bc 100644 --- a/lib/api/client-commands/within.js +++ b/lib/api/client-commands/within.js @@ -20,4 +20,4 @@ module.exports = class WithinAbstract { static get allowOverride() { return true; } -}; \ No newline at end of file +}; diff --git a/lib/api/element-commands/click.js b/lib/api/element-commands/click.js index 96763ce5ac..7a7a6006a0 100644 --- a/lib/api/element-commands/click.js +++ b/lib/api/element-commands/click.js @@ -66,4 +66,4 @@ class ClickElement extends BaseElementCommand { } } -module.exports = ClickElement; \ No newline at end of file +module.exports = ClickElement; diff --git a/lib/api/element-commands/getFirstElementChild.js b/lib/api/element-commands/getFirstElementChild.js index 35e0445b02..6aff20a5f4 100644 --- a/lib/api/element-commands/getFirstElementChild.js +++ b/lib/api/element-commands/getFirstElementChild.js @@ -23,7 +23,7 @@ const BaseElementCommand = require('./_baseElementCommand.js'); * @api protocol.elements */ class GetFirstElementChild extends BaseElementCommand { - + get extraArgsCount() { return 0; } @@ -35,4 +35,4 @@ class GetFirstElementChild extends BaseElementCommand { } } -module.exports = GetFirstElementChild; \ No newline at end of file +module.exports = GetFirstElementChild; diff --git a/lib/api/element-commands/getLastElementChild.js b/lib/api/element-commands/getLastElementChild.js index b3640414bf..a73cbcf4b3 100644 --- a/lib/api/element-commands/getLastElementChild.js +++ b/lib/api/element-commands/getLastElementChild.js @@ -28,10 +28,10 @@ class GetLastElementChild extends BaseElementCommand { get extraArgsCount() { return 0; } - + async protocolAction() { return this.executeProtocolAction('getLastElementChild'); } } -module.exports = GetLastElementChild; \ No newline at end of file +module.exports = GetLastElementChild; diff --git a/lib/api/element-commands/getNextSibling.js b/lib/api/element-commands/getNextSibling.js index cf54871c85..4f89748939 100644 --- a/lib/api/element-commands/getNextSibling.js +++ b/lib/api/element-commands/getNextSibling.js @@ -23,14 +23,14 @@ const BaseElementCommand = require('./_baseElementCommand.js'); * @exampleLink /api/getNextSibling.js */ class GetNextSibling extends BaseElementCommand { - + get extraArgsCount() { return 0; } - + async protocolAction() { return await this.executeProtocolAction('getNextSibling'); } } -module.exports = GetNextSibling; \ No newline at end of file +module.exports = GetNextSibling; diff --git a/lib/api/element-commands/getPreviousSibling.js b/lib/api/element-commands/getPreviousSibling.js index e0daef3556..953d4d84b6 100644 --- a/lib/api/element-commands/getPreviousSibling.js +++ b/lib/api/element-commands/getPreviousSibling.js @@ -43,14 +43,14 @@ const BaseElementCommand = require('./_baseElementCommand.js'); * @api protocol.elements */ class GetPreviousSibling extends BaseElementCommand { - + get extraArgsCount() { return 0; } - + async protocolAction() { return this.executeProtocolAction('getPreviousSibling'); } } -module.exports = GetPreviousSibling; \ No newline at end of file +module.exports = GetPreviousSibling; diff --git a/lib/api/element-commands/isEnabled.js b/lib/api/element-commands/isEnabled.js index e270d852f1..d0a03a8ebd 100644 --- a/lib/api/element-commands/isEnabled.js +++ b/lib/api/element-commands/isEnabled.js @@ -54,4 +54,4 @@ class IsEnabled extends BaseElementCommand { } -module.exports = IsEnabled; \ No newline at end of file +module.exports = IsEnabled; diff --git a/lib/api/element-commands/isPresent.js b/lib/api/element-commands/isPresent.js index 011127bda9..8bac2f5041 100644 --- a/lib/api/element-commands/isPresent.js +++ b/lib/api/element-commands/isPresent.js @@ -2,7 +2,7 @@ const BaseElementCommand = require('./_baseElementCommand.js'); const {Logger, filterStackTrace} = require('../../utils'); /** * Determines if an element is present in the DOM. - * + * * @example * module.exports = { * demoTest(browser) { @@ -70,4 +70,4 @@ class isPresent extends BaseElementCommand { } -module.exports = isPresent; \ No newline at end of file +module.exports = isPresent; diff --git a/lib/api/element-commands/isSelected.js b/lib/api/element-commands/isSelected.js index 23514aac99..48c97880ca 100644 --- a/lib/api/element-commands/isSelected.js +++ b/lib/api/element-commands/isSelected.js @@ -54,4 +54,4 @@ class IsSelected extends BaseElementCommand { } -module.exports = IsSelected; \ No newline at end of file +module.exports = IsSelected; diff --git a/lib/api/element-commands/setPassword.js b/lib/api/element-commands/setPassword.js index 43dbcd6716..c71a9d0193 100644 --- a/lib/api/element-commands/setPassword.js +++ b/lib/api/element-commands/setPassword.js @@ -47,4 +47,4 @@ class SetPassword extends BaseElementCommand { } } -module.exports = SetPassword; \ No newline at end of file +module.exports = SetPassword; diff --git a/lib/api/element-commands/uploadFile.js b/lib/api/element-commands/uploadFile.js index 2eaa39bc72..ec715406f5 100644 --- a/lib/api/element-commands/uploadFile.js +++ b/lib/api/element-commands/uploadFile.js @@ -32,4 +32,4 @@ class UploadFile extends BaseElementCommand { } -module.exports = UploadFile; \ No newline at end of file +module.exports = UploadFile; diff --git a/lib/api/expect/assertions/_baseAssertion.js b/lib/api/expect/assertions/_baseAssertion.js index 756b1dd7cf..bc492b0926 100644 --- a/lib/api/expect/assertions/_baseAssertion.js +++ b/lib/api/expect/assertions/_baseAssertion.js @@ -147,7 +147,7 @@ class BaseAssertion { init() { const {waitForConditionTimeout, waitForConditionPollInterval, abortOnAssertionFailure} = this.client.api.globals; - let assertions = this.flag('assertions') || 0; + const assertions = this.flag('assertions') || 0; this.flag('assertions', assertions + 1); this.promise = this.flag('promise'); @@ -340,7 +340,7 @@ class BaseAssertion { } getResult(value, fn) { - let result = fn.call(this); + const result = fn.call(this); this.setNegate(); return this.negate ? !result : result; @@ -357,7 +357,7 @@ class BaseAssertion { } getElapsedTime() { - let timeNow = new Date().getTime(); + const timeNow = new Date().getTime(); return timeNow - this.emitter.startTime; } @@ -467,8 +467,8 @@ class BaseAssertion { } '@matchesFlag'(re) { - let adverb = this.hasFlag('that') || this.hasFlag('which'); - let verb = adverb ? 'matches' : 'match'; + const adverb = this.hasFlag('that') || this.hasFlag('which'); + const verb = adverb ? 'matches' : 'match'; this.conditionFlag(re, function() { return re.test(this.resultValue); @@ -483,7 +483,7 @@ class BaseAssertion { conditionFlag(value, conditionFn, arrverb) { this.passed = this.getResult(value, conditionFn); - let verb = this.negate ? arrverb[0]: arrverb[1]; + let verb = this.negate ? arrverb[0] : arrverb[1]; this.expected = `${verb} '${value}'`; this.actual = this.resultValue; @@ -491,7 +491,7 @@ class BaseAssertion { return; } - let needsSpace = this.messageParts.length === 0 ? true : this.messageParts[this.messageParts.length-1].slice(-1) !== ' '; + const needsSpace = this.messageParts.length === 0 ? true : this.messageParts[this.messageParts.length - 1].slice(-1) !== ' '; if (needsSpace) { verb = ' ' + verb; } diff --git a/lib/api/expect/assertions/element/type.js b/lib/api/expect/assertions/element/type.js index f7ec379fab..b02d4f02f2 100644 --- a/lib/api/expect/assertions/element/type.js +++ b/lib/api/expect/assertions/element/type.js @@ -34,7 +34,7 @@ class TypeAssertion extends BaseAssertion { this.article = ['a', 'e', 'i', 'o'].indexOf(type.toString().substring(0, 1)) > -1 ? 'an' : 'a'; this.hasCustomMessage = typeof msg != 'undefined'; this.flag('typeFlag', true); - this.message = msg || 'Expected element %s to ' + (this.negate ? 'not be' : 'be') + ' ' + this.article +' ' + type; + this.message = msg || 'Expected element %s to ' + (this.negate ? 'not be' : 'be') + ' ' + this.article + ' ' + type; this.start(); } diff --git a/lib/api/protocol/elementIdDoubleClick.js b/lib/api/protocol/elementIdDoubleClick.js index ccfbc91b35..3649bfeba3 100644 --- a/lib/api/protocol/elementIdDoubleClick.js +++ b/lib/api/protocol/elementIdDoubleClick.js @@ -5,7 +5,7 @@ const {isFunction} = require('../../utils'); * Move to the element and performs a double-click in the middle of the given element if element is given else double-clicks at the current mouse coordinates (set by `.moveTo()`). * * @method elementIdDoubleClick - * @param {string} webElementId The [Web Element ID](https://www.w3.org/TR/webdriver1/#dfn-web-elements) of the element to route the command to. + * @param {string} webElementId The [Web Element ID](https://www.w3.org/TR/webdriver1/#dfn-web-elements) of the element to route the command to. * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.useractions */ @@ -13,7 +13,7 @@ module.exports = class elementIdDoubleClick extends ProtocolAction { static get isTraceable() { return true; } - + command(webElementId, callback) { if (isFunction(webElementId)) { diff --git a/lib/api/protocol/forward.js b/lib/api/protocol/forward.js index ca22bf4859..facb4c8462 100644 --- a/lib/api/protocol/forward.js +++ b/lib/api/protocol/forward.js @@ -3,6 +3,7 @@ const ProtocolAction = require('./_base-action.js'); /** * Navigate forward in the browser history, if possible (the equivalent of hitting the browser forward button). * + * @method forward * @link /#back * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.navigation diff --git a/lib/api/protocol/frame.js b/lib/api/protocol/frame.js index 85ae2f915d..00a542c939 100644 --- a/lib/api/protocol/frame.js +++ b/lib/api/protocol/frame.js @@ -21,13 +21,14 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method frame * @link /#switch-to-frame * @param {string|number|null} [frameId] Identifier for the frame to change focus to. * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.frames */ const findElement = function(selector) { - + return new Promise((resolve, reject) => { this.api.findElement({selector, suppressNotFoundErrors: true}, function(res) { if (res.status === -1 && res.error) { diff --git a/lib/api/protocol/frameParent.js b/lib/api/protocol/frameParent.js index a95b9be6f9..ddf85cf3a2 100644 --- a/lib/api/protocol/frameParent.js +++ b/lib/api/protocol/frameParent.js @@ -10,6 +10,7 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method frameParent * @link /#switch-to-parent-frame * @param {function} [callback] Optional callback function to be called when the command finishes. * @since v0.4.8 diff --git a/lib/api/protocol/moveTo.js b/lib/api/protocol/moveTo.js index 11ca2a01e5..3131b1730a 100644 --- a/lib/api/protocol/moveTo.js +++ b/lib/api/protocol/moveTo.js @@ -48,7 +48,7 @@ module.exports = class Command extends ProtocolAction { ProtocolAction.validateElementId(webElementId); } } - + if (args.length === 1 && isNumber(args[0]) && !isNaN(args[0])) { xoffset = args[0]; } else if (args.length === 2 && isNumber(args[0]) && !isNaN(args[0]) && isNumber(args[1]) && !isNaN(args[1])) { @@ -63,6 +63,6 @@ module.exports = class Command extends ProtocolAction { } return this.transportActions.moveTo(webElementId, xoffset, yoffset, callback); - + } }; diff --git a/lib/api/protocol/refresh.js b/lib/api/protocol/refresh.js index e5a599d0de..e0ff600cb7 100644 --- a/lib/api/protocol/refresh.js +++ b/lib/api/protocol/refresh.js @@ -3,6 +3,7 @@ const ProtocolAction = require('./_base-action.js'); /** * Refresh the current page. * + * @method refresh * @link /#refresh * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.navigation diff --git a/lib/api/protocol/releaseMouseButton.js b/lib/api/protocol/releaseMouseButton.js index 2ef6a66120..96e72ba36d 100644 --- a/lib/api/protocol/releaseMouseButton.js +++ b/lib/api/protocol/releaseMouseButton.js @@ -1,7 +1,7 @@ const ProtocolAction = require('./_base-action.js'); /** * Release the depressed left mouse button at the current mouse coordinates (set by `.moveTo()`). - * + * * * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.useractions diff --git a/lib/api/protocol/screenshot.js b/lib/api/protocol/screenshot.js index 697e9840f8..6be05ae812 100644 --- a/lib/api/protocol/screenshot.js +++ b/lib/api/protocol/screenshot.js @@ -3,6 +3,7 @@ const ProtocolAction = require('./_base-action.js'); /** * Take a screenshot of the current page. * + * @method screenshot * @link /#take-screenshot * @param {boolean} log_screenshot_data Whether or not the screenshot data should appear in the logs when running with --verbose * @param {function} callback Callback function which is called with the result value. diff --git a/lib/api/protocol/session.js b/lib/api/protocol/session.js index 5f32100b0c..eeac7afdfe 100644 --- a/lib/api/protocol/session.js +++ b/lib/api/protocol/session.js @@ -19,6 +19,7 @@ const ProtocolAction = require('./_base-action.js'); * } * * + * @method session * @link /#new-session * @editline L141 * @syntax .session([action], [sessionId], [callback]) diff --git a/lib/api/protocol/sessionLog.js b/lib/api/protocol/sessionLog.js index 503e245242..b283f356c2 100644 --- a/lib/api/protocol/sessionLog.js +++ b/lib/api/protocol/sessionLog.js @@ -17,6 +17,7 @@ const ProtocolAction = require('./_base-action.js'); * @param {function} callback Callback function which is called with the result value. * @returns {Array} Array of the text entries of the log. * @api protocol.sessions + * @deprecated In favour of `.logs.getSessionLog()`. */ module.exports = class Action extends ProtocolAction { command(typeString, callback) { diff --git a/lib/api/protocol/sessionLogTypes.js b/lib/api/protocol/sessionLogTypes.js index f2826f582b..7ce1aa6a9b 100644 --- a/lib/api/protocol/sessionLogTypes.js +++ b/lib/api/protocol/sessionLogTypes.js @@ -12,6 +12,7 @@ const ProtocolAction = require('./_base-action.js'); * * @param {function} callback Callback function which is called with the result value. * @api protocol.sessions + * @deprecated In favour of `.logs.getSessionLogTypes()`. */ module.exports = class Action extends ProtocolAction { command(callback) { diff --git a/lib/api/protocol/sessions.js b/lib/api/protocol/sessions.js index 83b1bb629b..b548eda4f4 100644 --- a/lib/api/protocol/sessions.js +++ b/lib/api/protocol/sessions.js @@ -10,6 +10,7 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method sessions * @editline L166 * @section sessions * @syntax .sessions(callback) diff --git a/lib/api/protocol/status.js b/lib/api/protocol/status.js index 58d952b6e9..8d75e9b314 100644 --- a/lib/api/protocol/status.js +++ b/lib/api/protocol/status.js @@ -3,6 +3,7 @@ const ProtocolAction = require('./_base-action.js'); /** * Query the server's current status. * + * @method status * @link /#status * @syntax .status([callback]) * @param {function} callback Callback function which is called with the result value. diff --git a/lib/api/protocol/submit.js b/lib/api/protocol/submit.js index ab2ff0be58..69c8d9706b 100644 --- a/lib/api/protocol/submit.js +++ b/lib/api/protocol/submit.js @@ -3,6 +3,7 @@ const ProtocolAction = require('./_base-action.js'); /** * Submit a FORM element. The submit command may also be applied to any element that is a descendant of a FORM element. * + * @method submit * @param {string} webElementId The [Web Element ID](https://www.w3.org/TR/webdriver1/#dfn-web-elements) of the element to route the command to. * @param {function} [callback] Optional callback function to be called when the command finishes. * @api protocol.elementinternal diff --git a/lib/api/protocol/timeouts.js b/lib/api/protocol/timeouts.js index 4af7b999af..caabdfb453 100644 --- a/lib/api/protocol/timeouts.js +++ b/lib/api/protocol/timeouts.js @@ -17,6 +17,7 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method timeouts * @link /#set-timeout * @editline L188Í * @syntax .timeouts([callback]) diff --git a/lib/api/protocol/timeoutsAsyncScript.js b/lib/api/protocol/timeoutsAsyncScript.js index 7fbe73d460..a79d00055c 100644 --- a/lib/api/protocol/timeoutsAsyncScript.js +++ b/lib/api/protocol/timeoutsAsyncScript.js @@ -10,6 +10,7 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method timeoutsAsyncScript * @syntax .timeoutsAsyncScript(ms, [callback]) * @jsonwire * @param {number} ms The amount of time, in milliseconds, that time-limited commands are permitted to run. diff --git a/lib/api/protocol/timeoutsImplicitWait.js b/lib/api/protocol/timeoutsImplicitWait.js index af37b02c97..bd6e015898 100644 --- a/lib/api/protocol/timeoutsImplicitWait.js +++ b/lib/api/protocol/timeoutsImplicitWait.js @@ -10,6 +10,7 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method timeoutsImplicitWait * @jsonwire * @syntax .timeoutsImplicitWait(ms, [callback]) * @param {number} ms The amount of time, in milliseconds, that time-limited commands are permitted to run. diff --git a/lib/api/protocol/url.js b/lib/api/protocol/url.js index bcee9b7e48..875914c969 100644 --- a/lib/api/protocol/url.js +++ b/lib/api/protocol/url.js @@ -22,6 +22,7 @@ const ora = require('ora'); * } * } * + * @method url * @link /#navigate-to * @syntax .url([url], [callback]) * @syntax .url(callback) @@ -36,7 +37,7 @@ module.exports = class Action extends ProtocolAction { command(url, callback = function(r) {return r}) { if (typeof url == 'string') { - let startTime = new Date(); + const startTime = new Date(); let spinner; if (this.settings.output) { spinner = ora({ diff --git a/lib/api/protocol/waitUntil.js b/lib/api/protocol/waitUntil.js index 3af89b24a4..97f84bd972 100644 --- a/lib/api/protocol/waitUntil.js +++ b/lib/api/protocol/waitUntil.js @@ -21,6 +21,7 @@ const ProtocolAction = require('./_base-action.js'); * }); * } * + * @method waitUntil * @link https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebDriver.html#wait * @selenium_webdriver * @syntax .waitUntil(conditionFn, [callback]) diff --git a/lib/api/web-element/assert/element-assertions.js b/lib/api/web-element/assert/element-assertions.js index 138b39d5bb..59360c48bc 100644 --- a/lib/api/web-element/assert/element-assertions.js +++ b/lib/api/web-element/assert/element-assertions.js @@ -11,7 +11,7 @@ class ScopedElementAssertions { const assert = this.nightwatchInstance.api.assert; await callback(this.negated ? assert.not : assert, this.scopedElement.webElement); - + return this.scopedElement; } diff --git a/lib/api/web-element/commands/getAccessibleName.js b/lib/api/web-element/commands/getAccessibleName.js index 361180f918..202e2a3d26 100644 --- a/lib/api/web-element/commands/getAccessibleName.js +++ b/lib/api/web-element/commands/getAccessibleName.js @@ -26,4 +26,4 @@ */ module.exports.command = function() { return this.runQueuedCommandScoped('getElementAccessibleName'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getAriaRole.js b/lib/api/web-element/commands/getAriaRole.js index e670d425c8..0f32df12a7 100644 --- a/lib/api/web-element/commands/getAriaRole.js +++ b/lib/api/web-element/commands/getAriaRole.js @@ -25,4 +25,4 @@ */ module.exports.command = function() { return this.runQueuedCommandScoped('getElementAriaRole'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getAttribute.js b/lib/api/web-element/commands/getAttribute.js index 3dcd4a952b..2d47d223cd 100644 --- a/lib/api/web-element/commands/getAttribute.js +++ b/lib/api/web-element/commands/getAttribute.js @@ -29,4 +29,4 @@ */ module.exports.command = function(name) { return this.runQueuedCommandScoped('getElementValue', name); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getCssProperty.js b/lib/api/web-element/commands/getCssProperty.js index 254bc13d5b..cbdb1d83cd 100644 --- a/lib/api/web-element/commands/getCssProperty.js +++ b/lib/api/web-element/commands/getCssProperty.js @@ -40,4 +40,4 @@ */ module.exports.command = function(cssProperty) { return this.runQueuedCommandScoped('getElementCSSValue', cssProperty); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getId.js b/lib/api/web-element/commands/getId.js index c756229a5e..a73b53923e 100644 --- a/lib/api/web-element/commands/getId.js +++ b/lib/api/web-element/commands/getId.js @@ -25,4 +25,4 @@ module.exports.command = function() { return this.runQueuedCommandScoped(function (webElement) { return webElement.getId(); }); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getProperty.js b/lib/api/web-element/commands/getProperty.js index 0c3f2773b2..934f7b158d 100644 --- a/lib/api/web-element/commands/getProperty.js +++ b/lib/api/web-element/commands/getProperty.js @@ -42,4 +42,4 @@ */ module.exports.command = function(name) { return this.runQueuedCommandScoped('getElementProperty', name); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getRect.js b/lib/api/web-element/commands/getRect.js index ab4be35e54..9ecdaaf5ab 100644 --- a/lib/api/web-element/commands/getRect.js +++ b/lib/api/web-element/commands/getRect.js @@ -32,4 +32,4 @@ */ module.exports.command = function() { return this.runQueuedCommandScoped('getElementRect'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getTagName.js b/lib/api/web-element/commands/getTagName.js index 2d6a62c408..f2a06e56cd 100644 --- a/lib/api/web-element/commands/getTagName.js +++ b/lib/api/web-element/commands/getTagName.js @@ -26,4 +26,4 @@ */ module.exports.command = function() { return this.runQueuedCommandScoped('getElementTagName'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getText.js b/lib/api/web-element/commands/getText.js index 78cac96931..f4d56d9420 100644 --- a/lib/api/web-element/commands/getText.js +++ b/lib/api/web-element/commands/getText.js @@ -26,4 +26,4 @@ */ module.exports.command = function() { return this.runQueuedCommandScoped('getElementText'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/getValue.js b/lib/api/web-element/commands/getValue.js index da0c4a9fe9..a1ea155c71 100644 --- a/lib/api/web-element/commands/getValue.js +++ b/lib/api/web-element/commands/getValue.js @@ -26,4 +26,4 @@ */ module.exports.command = function () { return this.getProperty('value'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/rightClick.js b/lib/api/web-element/commands/rightClick.js index d9b0eb9a7b..a31f0e14b4 100644 --- a/lib/api/web-element/commands/rightClick.js +++ b/lib/api/web-element/commands/rightClick.js @@ -24,4 +24,4 @@ */ module.exports.command = function() { return this.runQueuedCommand('contextClick'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/setAttribute.js b/lib/api/web-element/commands/setAttribute.js index 01aeeddfd7..c5464a8a07 100644 --- a/lib/api/web-element/commands/setAttribute.js +++ b/lib/api/web-element/commands/setAttribute.js @@ -27,4 +27,4 @@ module.exports.command = function(name, value) { return this.runQueuedCommand('setElementAttribute', { args: [name, value] }); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/setProperty.js b/lib/api/web-element/commands/setProperty.js index b21153d373..27339bfaf6 100644 --- a/lib/api/web-element/commands/setProperty.js +++ b/lib/api/web-element/commands/setProperty.js @@ -25,4 +25,4 @@ module.exports.command = function(name, value) { return this.runQueuedCommand('setElementProperty', { args: [name, value] }); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/setValue.js b/lib/api/web-element/commands/setValue.js index d27c86ae3a..44cd424991 100644 --- a/lib/api/web-element/commands/setValue.js +++ b/lib/api/web-element/commands/setValue.js @@ -1,3 +1,32 @@ +/** + * Sends some text to an element. Can be used to set the value of a form element or to send a sequence of key strokes to an element. Any UTF-8 character may be specified. + * + *
From Nightwatch v2, setValue also clears the existing value of the element by calling the clearValue() beforehand.
+ * + * An object map with available keys and their respective UTF-8 characters, as defined on [W3C WebDriver draft spec](https://www.w3.org/TR/webdriver/#character-types), is loaded onto the main Nightwatch instance as `browser.Keys`. + * + * For more info on working with DOM elements in Nightwatch, refer to the Finding & interacting with DOM Elements guide page. + * @example + * // send some simple text to an input + * this.demoTest = function (browser) { + * const result = await browser.element('input[type=text]').setValue('nightwatch'); + * }; + * + * // send some text to an input and hit enter. + * this.demoTest = function (browser) { + * const result = await browser.element('input[type=text]').setValue(['nightwatch', browser.Keys.ENTER]); + * }; + * + * + * @link /session/:sessionId/element/:id/value + * @method setValue + * @memberof ScopedWebElement + * @instance + * @syntax browser.element(selector).setValue(inputValue) + * @param {string|array} inputValue The text to send to the element or key strokes. + * @param {function} [callback] Optional callback function to be called when the command finishes. + * @link https://www.w3.org/TR/webdriver#element-send-keys + */ module.exports.command = function(...args) { const keys = args.reduce((prev, key) => { const keyList = Array.isArray(key) ? key : [key]; diff --git a/lib/api/web-element/commands/submit.js b/lib/api/web-element/commands/submit.js index 8348b70c3a..0582d00112 100644 --- a/lib/api/web-element/commands/submit.js +++ b/lib/api/web-element/commands/submit.js @@ -25,4 +25,4 @@ */ module.exports.command = function() { return this.runQueuedCommand('elementSubmit'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/takeScreenshot.js b/lib/api/web-element/commands/takeScreenshot.js index 5512e909d5..9f4ab1b200 100644 --- a/lib/api/web-element/commands/takeScreenshot.js +++ b/lib/api/web-element/commands/takeScreenshot.js @@ -26,4 +26,4 @@ */ module.exports.command = function() { return this.runQueuedCommandScoped('takeElementScreenshot'); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/commands/upload.js b/lib/api/web-element/commands/upload.js index 63cd51871d..f4ec3b1b95 100644 --- a/lib/api/web-element/commands/upload.js +++ b/lib/api/web-element/commands/upload.js @@ -27,4 +27,4 @@ module.exports.command = function(file) { return this.runQueuedCommand('uploadFile', { args: [file] }); -}; \ No newline at end of file +}; diff --git a/lib/api/web-element/factory.js b/lib/api/web-element/factory.js index a67a728737..b7e557831d 100644 --- a/lib/api/web-element/factory.js +++ b/lib/api/web-element/factory.js @@ -60,11 +60,11 @@ const createScopedWebElement = function(selector, parentElement, nightwatchInsta nightwatchInstance, ...args }); - + return waitUntil.wait().then(element => resolve(element)); }.bind(instance)), parentElement, nightwatchInstance); } - + }); Object.defineProperty(exported, 'then', { diff --git a/lib/api/web-element/index.js b/lib/api/web-element/index.js index 7a71bb6d1e..4f39928242 100644 --- a/lib/api/web-element/index.js +++ b/lib/api/web-element/index.js @@ -17,4 +17,4 @@ module.exports.root = function(nightwatchInstance) { }; module.exports.createScopedWebElements = Factory.createScopedWebElements; -module.exports.create = Factory.create; \ No newline at end of file +module.exports.create = Factory.create; diff --git a/lib/api/web-element/waitUntil.js b/lib/api/web-element/waitUntil.js index 6b1f151013..527dfa1307 100644 --- a/lib/api/web-element/waitUntil.js +++ b/lib/api/web-element/waitUntil.js @@ -1,6 +1,8 @@ -const until = require('selenium-webdriver/lib/until'); +const {until, Condition} = require('selenium-webdriver'); const {AssertionRunner} = require('../../assertion'); const {isDefined, format} = require('../../utils'); +const {ScopedElementLocator} = require('./element-locator'); +const {NoSuchElementError} = require('../../element/locator'); const mapToSeleniumFunction = { 'selected': until.elementIsSelected, @@ -9,9 +11,61 @@ const mapToSeleniumFunction = { 'not.visible': until.elementIsNotVisible, 'enabled': until.elementIsEnabled, 'not.enabled': until.elementIsDisabled, - 'disabled': until.elementIsDisabled + 'disabled': until.elementIsDisabled, + 'present': (webElement, scopedElementLocator) => until.elementLocated(scopedElementLocator.condition), + 'not.present': (webElement, scopedElementLocator) => new Condition('until elmenet is not present', (driver) => driver.findElements(scopedElementLocator.condition).then(elements => elements.length ? false : true)) }; +/** + * Waits a given time in milliseconds (default 5000ms) for an element to be in the action state provided before performing any other commands or assertions. + * If the element fails to be in the action state withing the given time, the test fails. You can change this by setting `abortOnFailure` to `false`. + * + * You can change the polling interval by defining a `waitForConditionPollInterval` property (in milliseconds) in as a global property in your `nightwatch.conf.js` or in your external globals file. + * Similarly, the default timeout can be specified as a global `waitForConditionTimeout` property (in milliseconds). + * + * @example + * describe('demo Test', function() { + * it ('wait for container', async function(browser){ + * // with default implicit timeout of 5000ms (can be overwritten in settings under 'globals.waitForConditionTimeout') + * await browser.element.find('#index-container').waitUntil('visible'); + * + * // with explicit timeout (in milliseconds) + * await browser.element.find('#index-container').waitUntil('visible', {timeout: 1000}); + * + * // continue if failed + * await browser.element.find('#index-container').waitUntil('visible', {timeout: 1000, abortOnFailure: false}); + * + * // with negative assertion + * await browser.element.find('#index-container').waitUntil('not.visible'); + * + * // with xpath as the locate strategy + * await browser.element.find(by.xpath('//*[@id="index-container"]')).waitUntil('visible', {message: 'The index container is found.'}); + * + * // with custom message + * await browser.element.find('#index-container').waitUntil('visible', {message: 'The index container is found.'}); + * }); + * + * + * it('page object demo Test', async function (browser) { + * const nightwatchPage = browser.page.nightwatch(); + * + * nightwatchPage + * .navigate() + * .assert.titleContains('Nightwatch.js'); + * + * await nightwatchPage.element.find('@featuresList').waitUntil('visible'); + * }); + * }); + * + * @method waitUntil + * @syntax .waitUntil(action, {timeout, retryInterval, message, abortOnFailure}); + * @param {string} action The action state. Should be one of the following: selected, not.selected, visible, not.visible, enabled, disabled, present, not.present + * @param {number} [timeout] The total number of milliseconds to wait before failing. Can also be set using 'globals.waitForConditionTimeout' under settings. + * @param {number} [retryInterval] The number of milliseconds to wait between retries. You can use this only if you also specify the time parameter. Can also be set using 'globals.waitForConditionPollInterval' under settings. + * @param {string} [message] Optional message to be shown in the output. The message supports two placeholders: %s for current selector and %d for the time (e.g. Element %s was not in the page for %d ms). + * @param {boolean} [abortOnFailure=abortOnAssertionFailure] By the default if the element is not found the test will fail. Set this to false if you wish for the test to continue even if the assertion fails. To set this globally you can define a property `abortOnAssertionFailure` in your globals. + */ + class WaitUntil { constructor(scopedElement, {action, timeout, retryInterval, message, nightwatchInstance, selector, abortOnFailure}) { this.scopedElement = scopedElement; @@ -23,33 +77,41 @@ class WaitUntil { this.timeout = timeout || this.nightwatchInstance.settings.globals.waitForConditionTimeout; this.retryInterval = retryInterval || this.nightwatchInstance.settings.globals.waitForConditionPollInterval; this.abortOnFailure = isDefined(abortOnFailure) ? abortOnFailure : this.nightwatchInstance.settings.globals.abortOnAssertionFailure; + this.scopedElementLocator = ScopedElementLocator.create(selector, nightwatchInstance); } createNode() { - const createAction = (actions, webElement) => function() { - if (!this.conditionFn) { - throw new Error(`Invalid action ${this.action} for element.waitUntil command. Possible actions: ${Object.keys(mapToSeleniumFunction).toString()}`); - } + const createAction = (actions, webElementPromise) => async function() { + + let result; + try { - return actions.wait(this.conditionFn(webElement), this.timeout, this.retryInterval, this.message, ()=>{}) - .then(result => { - const elapsedTime = new Date().getTime() - node.startTime; - if (result.error) { - const actual = result.error.name === 'NightwatchElementError' ? 'not found' : null; + if (!this.conditionFn) { + throw new Error(`Invalid action ${this.action} for element.waitUntil command. Possible actions: ${Object.keys(mapToSeleniumFunction).toString()}`); + } - return this.fail(result, actual, elapsedTime); - } + const webElement = await webElementPromise; - return this.pass(result, elapsedTime); - }) - .catch(err => err) - .then(result => { - node.deferred.resolve(result); + if (!webElement && !['present', 'not.present'].includes(this.action)) { + throw new NoSuchElementError({element: this.scopedElementLocator, abortOnFailure: this.abortOnFailure}); + } - return result; - }); + const elapsedTime = new Date().getTime() - node.startTime; + const commandResult = await this.scopedElement.driver.wait(this.conditionFn(webElementPromise, this.scopedElementLocator), this.timeout, this.message, this.retryInterval, ()=>{}); + result = await this.pass(commandResult, elapsedTime).catch(err => err); + + return result; + } catch (err) { + const elapsedTime = new Date().getTime() - node.startTime; + const actual = err instanceof NoSuchElementError ? 'not present' : null; + result = await this.fail(err, actual, elapsedTime).catch(err => err); + + return result; + } finally { + node.deferred.resolve(result); + } }.bind(this); - + const node = this.scopedElement.queueAction({name: 'waitUntil', createAction}); return node; @@ -61,7 +123,7 @@ class WaitUntil { try { const node = this.createNode(); - + return this.scopedElement.waitFor(node.deferred.promise); } catch (err) { assertApi.ok(false, err.message); @@ -70,13 +132,13 @@ class WaitUntil { formatMsg(message, timeMs) { const defaultMsg = this.message || message; - + return format(defaultMsg, this.selector, timeMs); } assert({result, passed, err, message, elapsedTime}) { const {reporter} = this.scopedElement; - + const runner = new AssertionRunner({abortOnFailure: this.abortOnFailure, passed, err, message, reporter, elapsedTime}); return runner.run(result); @@ -100,7 +162,7 @@ class WaitUntil { fail(result, actual, elapsedTime) { const sliceAction = this.action.split('.'); - + actual = actual ? actual : sliceAction[0] === 'not' ? sliceAction[1] : `not ${this.action}`; const expected = sliceAction[0] === 'not' ? `not ${sliceAction[1]}` : this.action; const message = this.formatMsg(`Timed out while waiting for element <%s> to be ${expected} for %d milliseconds`, this.timeout); @@ -118,4 +180,4 @@ class WaitUntil { } } -module.exports = WaitUntil; \ No newline at end of file +module.exports = WaitUntil; diff --git a/lib/core/asynctree.js b/lib/core/asynctree.js index 317310c494..72152e56b9 100644 --- a/lib/core/asynctree.js +++ b/lib/core/asynctree.js @@ -92,7 +92,7 @@ class AsyncTree extends EventEmitter{ } shouldRejectNodePromise(err, node = this.currentNode) { - if ((err.isExpect|| node.namespace === 'assert') && this.currentNode.isES6Async) { + if ((err.isExpect || node.namespace === 'assert') && this.currentNode.isES6Async) { return true; } diff --git a/lib/core/client.js b/lib/core/client.js index 3be3fd1271..c25cd030f6 100644 --- a/lib/core/client.js +++ b/lib/core/client.js @@ -1,6 +1,7 @@ const EventEmitter = require('events'); const {Key, Capabilities, Browser} = require('selenium-webdriver'); +const lodashMerge = require('lodash.merge'); const HttpOptions = require('../http/options.js'); const HttpRequest = require('../http/request.js'); const Utils = require('../utils'); @@ -67,7 +68,13 @@ class NightwatchAPI { const browserNames = [this.browserName, lowerCaseBrowserName]; if (alternateName) { - return [browser, alternateName].some(name => browserNames.includes(name)); + alternateName = Array.isArray(alternateName) + ? alternateName + : [alternateName]; + + return [browser, ...alternateName].some(name => + browserNames.includes(name) + ); } return browserNames.includes(browser); @@ -78,7 +85,7 @@ class NightwatchAPI { return false; } - return this.platformName.toLowerCase() === platform.toLowerCase(); + return this.platformName.toLowerCase() === platform.toLowerCase(); } isIOS() { @@ -106,7 +113,7 @@ class NightwatchAPI { } isEdge() { - return this.__isBrowserName(Browser.EDGE, 'edge'); + return this.__isBrowserName(Browser.EDGE, ['edge', 'msedge']); } isInternetExplorer() { @@ -270,7 +277,7 @@ class NightwatchClient extends EventEmitter { let callback; let method; let sessionId = api.sessionId; - const lastArg = args[args.length-1]; + const lastArg = args[args.length - 1]; const isLastArgFunction = Utils.isFunction(lastArg); if (isLastArgFunction) { @@ -521,7 +528,7 @@ class NightwatchClient extends EventEmitter { let value = this.settings.baseUrl || this.settings.launchUrl || this.settings.launch_url || null; // For e2e and component testing on android emulator - if (value && !this.settings.desiredCapabilities.real_mobile && this.settings.desiredCapabilities.avd) { + if (value && !this.settings.desiredCapabilities.real_mobile && this.settings.desiredCapabilities.avd) { value = value.replace('localhost', '10.0.2.2').replace('127.0.0.1', '10.0.2.2'); } @@ -591,7 +598,7 @@ class NightwatchClient extends EventEmitter { return; } - Object.assign(this.initialCapabilities, props); + lodashMerge(this.initialCapabilities, props); this.setSessionOptions(); } diff --git a/lib/core/queue.js b/lib/core/queue.js index d16386353f..3e8b3a71bb 100644 --- a/lib/core/queue.js +++ b/lib/core/queue.js @@ -34,6 +34,10 @@ class CommandQueue extends EventEmitter { const {compatMode} = this; const parentContext = this.currentNode.context; + if (!this.deferred) { + this.deferred = Utils.createPromise(); + } + const node = new Node({ name: commandName, parent: this.currentNode, @@ -57,7 +61,7 @@ class CommandQueue extends EventEmitter { if (context && context.module && context.module.autoInvoke) { return node; } - + this.tree.addNode(node); if (this.currentNode.done || !this.currentNode.started || initialChildNode) { @@ -115,7 +119,7 @@ class CommandQueue extends EventEmitter { get inProgress() { return this.tree.rootNode.childNodes.length > 0; } - + done(err) { if (this.tree.rootNode.childNodes.length > 0) { return this; @@ -133,12 +137,22 @@ class CommandQueue extends EventEmitter { } } + waitForCompletion() { + if (this.deferred) { + return this.deferred.promise; + } + + return Promise.resolve(); + } + run() { if (this.tree.started) { return this; } - this.deferred = Utils.createPromise(); + if (!this.deferred) { + this.deferred = Utils.createPromise(); + } this.scheduleTraverse(); return this.deferred.promise; diff --git a/lib/element/command.js b/lib/element/command.js index 8fda88135b..5a71187be5 100644 --- a/lib/element/command.js +++ b/lib/element/command.js @@ -274,7 +274,7 @@ class ElementCommand extends EventEmitter { } if (passedArgs < expectedArgs - 1 || passedArgs > expectedArgs) { - const error = new Error(`${this.commandName} method expects ${(expectedArgs - 1)} `+ + const error = new Error(`${this.commandName} method expects ${(expectedArgs - 1)} ` + `(or ${expectedArgs} if using implicit "css selector" strategy) arguments - ${passedArgs} given.`); error.rejectPromise = rejectPromise; @@ -339,7 +339,7 @@ class ElementCommand extends EventEmitter { this.__retryOnFailure = retryAction; } - let {WebdriverElementId} = this.selector; + const {WebdriverElementId} = this.selector; if (WebdriverElementId) { this.WebdriverElementId = WebdriverElementId; } diff --git a/lib/http/formatter.js b/lib/http/formatter.js index fe720e6efd..3d9013d6f2 100644 --- a/lib/http/formatter.js +++ b/lib/http/formatter.js @@ -12,10 +12,10 @@ class ResponseFormatter { */ static jsonStringify(s) { try { - let json = JSON.stringify(s); + const json = JSON.stringify(s); if (json) { return json.replace(jsonRegex, function jsonRegexReplace(c) { - return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4); + return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); }); } @@ -29,9 +29,9 @@ class ResponseFormatter { } static stripUnknownChars(str) { - let x = []; + const x = []; let i = 0; - let length = str.length; + const length = str.length; for (i; i < length; i++) { if (str.charCodeAt(i)) { @@ -43,9 +43,9 @@ class ResponseFormatter { } static formatHostname(hostname, port, useSSL) { - let isLocalHost = ['127.0.0.1', 'localhost'].indexOf(hostname) > -1; - let protocol = useSSL ? 'https://' : 'http://'; - let isPortDefault = [80, 443].indexOf(port) > -1; + const isLocalHost = ['127.0.0.1', 'localhost'].indexOf(hostname) > -1; + const protocol = useSSL ? 'https://' : 'http://'; + const isPortDefault = [80, 443].indexOf(port) > -1; if (isLocalHost) { return ''; diff --git a/lib/http/request.js b/lib/http/request.js index e0e7bed3c4..1b5f6ba999 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -219,7 +219,7 @@ class HttpRequest extends EventEmitter { createHttpRequest() { try { - const req = (this.use_ssl ? https: http).request(this.reqOptions, response => { + const req = (this.use_ssl ? https : http).request(this.reqOptions, response => { this.httpResponse = new HttpResponse(response, this); this.httpResponse.on('complete', this.onRequestComplete.bind(this)); this.proxyEvents(this.httpResponse, ['response', 'error', 'success']); @@ -250,7 +250,7 @@ class HttpRequest extends EventEmitter { }); } - const content = ` Request ${Logger.colors.light_cyan([this.reqOptions.method, this.hostname + this.reqOptions.path, retryStr + ' '].join(' '))}`; + const content = ` Request ${[this.reqOptions.method, this.hostname + this.reqOptions.path, retryStr + ' '].join(' ')}`; const paramsStr = this.redact ? '' : params; Logger.request(content, paramsStr); diff --git a/lib/index.js b/lib/index.js index 783ae769de..f2b4e74145 100644 --- a/lib/index.js +++ b/lib/index.js @@ -44,6 +44,7 @@ module.exports.createClient = function({ debug = false, enable_global_apis = false, config = './nightwatch.json', + disable_process_listener = false, test_settings } = {}) { if (browserName && !env) { @@ -74,7 +75,8 @@ module.exports.createClient = function({ timeout, devtools, debug, - parallel + parallel, + disable_process_listener }); const settings = arguments[0] || {}; @@ -106,10 +108,10 @@ module.exports.createClient = function({ }); cliRunner.test_settings.disable_global_apis = cliRunner.test_settings.disable_global_apis || !enable_global_apis; - - //merge settings recieved from testsuite to cli runner settings (this might have changed in hooks) + + //merge settings recieved from testsuite to cli runner settings (this might have changed in hooks) lodashMerge(cliRunner.test_settings, test_settings); - + const client = Nightwatch.client(cliRunner.test_settings, reporter, cliRunner.argv, true); // TODO: Do we need this (global `element` api will only be available on @@ -130,6 +132,14 @@ module.exports.createClient = function({ return client.mergeCapabilities(value); }, + runGlobalBeforeHook() { + return cliRunner.globals.runGlobalHook('before'); + }, + + runGlobalAfterHook() { + return cliRunner.globals.runGlobalHook('after'); + }, + launchBrowser() { const {argv} = cliRunner; @@ -140,7 +150,7 @@ module.exports.createClient = function({ .then((data) => { if (this.settings.test_runner?.type === 'cucumber' && NightwatchEventHub.isAvailable) { const NightwatchFormatter = require('./runner/test-runners/cucumber/nightwatch-format'); - + NightwatchFormatter.setCapabilities(data); } @@ -149,14 +159,22 @@ module.exports.createClient = function({ .catch((error) => { if (this.settings.test_runner?.type === 'cucumber' && NightwatchEventHub.isAvailable) { const NightwatchFormatter = require('./runner/test-runners/cucumber/nightwatch-format'); - + NightwatchFormatter.setCapabilities({ error: error }); } - + throw error; }); + }, + + async cleanup() { + await client.queue.waitForCompletion(); + client.queue.empty(); + client.queue.reset(); + client.queue.removeAllListeners(); + Logger.reset(); } }; diff --git a/lib/page-object/command-wrapper.js b/lib/page-object/command-wrapper.js index 9e640538af..0b40a54bcf 100644 --- a/lib/page-object/command-wrapper.js +++ b/lib/page-object/command-wrapper.js @@ -392,7 +392,7 @@ class CommandLoader { } return originalFn.apply(targetApi, args); - }; + }; } /** diff --git a/lib/reporter/axe-report.js b/lib/reporter/axe-report.js index a3842c56dc..8b5af97479 100644 --- a/lib/reporter/axe-report.js +++ b/lib/reporter/axe-report.js @@ -185,4 +185,4 @@ module.exports = class AxeReport { // eslint-disable-next-line no-console console.log(table.toString()); } -}; \ No newline at end of file +}; diff --git a/lib/reporter/base-reporter.js b/lib/reporter/base-reporter.js index bcc6322f42..618572f1a1 100644 --- a/lib/reporter/base-reporter.js +++ b/lib/reporter/base-reporter.js @@ -39,7 +39,7 @@ module.exports = class BaseReporter { }, {}); module.completedSections = Object.keys(module.completedSections).reduce(function(prev, item) { - if ((module.skippedAtRuntime && module.skippedAtRuntime.includes(item)) || + if ((module.skippedAtRuntime && module.skippedAtRuntime.includes(item)) || (module.skippedByUser && module.skippedByUser.includes(item)) ) { return prev; diff --git a/lib/reporter/global-reporter.js b/lib/reporter/global-reporter.js index 8e79fef661..f45f4e8265 100644 --- a/lib/reporter/global-reporter.js +++ b/lib/reporter/global-reporter.js @@ -5,7 +5,7 @@ const stripAnsi = require('strip-ansi'); const Concurrency = require('../runner/concurrency'); const DefaultSettings = require('../settings/defaults.js'); const Utils = require('../utils'); -const {Logger, beautifyStackTrace} = Utils; +const {Logger} = Utils; const Results = require('./results.js'); const AxeReport = require('./axe-report.js'); const Summary = require('./summary.js'); @@ -98,9 +98,9 @@ module.exports = class GlobalReporter { const endTime = new Date().getTime(); this.elapsedTime = endTime - startTime; - + this.globalResults = Results.createGlobalReport(this.suiteResults, initialReport); - this.globalResults.elapsedTime = (this.elapsedTime/1000).toPrecision(4); + this.globalResults.elapsedTime = (this.elapsedTime / 1000).toPrecision(4); this.globalResults.startTimestamp = new Date(startTime).toUTCString(); this.globalResults.endTimestamp = new Date(endTime).toUTCString(); @@ -300,7 +300,7 @@ module.exports = class GlobalReporter { if (!hasCount) { const passedCount = Math.max(0, totalTests - this.globalResults.failed - this.globalResults.errors); // testcases passed - passedMsg = '\n - '+ colors.green(passedCount + '/' + (totalTests > 0 ? totalTests : 'NA')) + ' tests passed'; + passedMsg = '\n - ' + colors.green(passedCount + '/' + (totalTests > 0 ? totalTests : 'NA')) + ' tests passed'; } let skipped = ''; diff --git a/lib/reporter/index.js b/lib/reporter/index.js index b4047fd3b9..2af49178f5 100644 --- a/lib/reporter/index.js +++ b/lib/reporter/index.js @@ -3,6 +3,7 @@ const TestResults = require('./results.js'); const SimplifiedReporter = require('./simplified.js'); const {Logger, Screenshots} = Utils; const {colors} = Logger; +const analyticsCollector = require('../utils/analytics.js'); class Reporter extends SimplifiedReporter { static printAssertions(testcase) { @@ -48,7 +49,7 @@ class Reporter extends SimplifiedReporter { get currentTest() { return this.testResults.currentTest; } - + get currentSection() { return this.testResults.currentSection; } @@ -167,11 +168,11 @@ class Reporter extends SimplifiedReporter { logTestCase(testName) { if (this.settings.live_output || !this.settings.parallel_mode) { // eslint-disable-next-line no-console - console.log(`${(!this.settings.silent?'\n\n':'')}\n Running ${colors.green(testName)}${colors.stack_trace(':')}`); + console.log(`${(!this.settings.silent ? '\n\n' : '')}\n Running ${colors.green(testName)}${colors.stack_trace(':')}`); const {columns = 100} = process.stdout; // eslint-disable-next-line no-console - console.log(colors.stack_trace(new Array(Math.max(100, Math.floor(columns/2))).join('─'))); + console.log(colors.stack_trace(new Array(Math.max(100, Math.floor(columns / 2))).join('─'))); //} } else { // eslint-disable-next-line no-console @@ -203,10 +204,11 @@ class Reporter extends SimplifiedReporter { result.status === -1 ); - // Use only necessary values + // Use only necessary values if (result) { const {status, message, showDiff, name, abortOnFailure, stack, beautifiedStack} = result; - commandResult ={ + + commandResult = { status, message, showDiff, @@ -215,8 +217,8 @@ class Reporter extends SimplifiedReporter { stack, beautifiedStack }; - } - + } + if (this.shouldLogCommand(node)) { this.testResults.logCommand({ name: node.fullName, @@ -246,7 +248,7 @@ class Reporter extends SimplifiedReporter { if (!node.parent) { //root node return false; } - + if (node.parent.isRootNode) { return true; } @@ -276,6 +278,8 @@ class Reporter extends SimplifiedReporter { return; } + analyticsCollector.collectErrorEvent(err); + super.registerTestError(err); const incrementTotal = this.shouldIncrementTotalCount(err); @@ -331,10 +335,10 @@ class Reporter extends SimplifiedReporter { printSimplifiedTestResult(ok, elapsedTime, isWorker) { const {currentTest} = this; - const result = [colors[ok ? 'green': 'red'](Utils.symbols[ok ? 'ok' : 'fail'])]; + const result = [colors[ok ? 'green' : 'red'](Utils.symbols[ok ? 'ok' : 'fail'])]; if (!this.unitTestsMode) { if (isWorker) { - result.push(colors.white(this.settings.testEnv, colors.background.black)); + result.push(colors.bgBlack.white.bold(this.settings.testEnv)); } result.push(colors.cyan('[' + this.suiteName + ']')); @@ -344,7 +348,7 @@ class Reporter extends SimplifiedReporter { result.push(ok ? testName : colors.red(testName)); if (elapsedTime > 20) { - result.push(colors.yellow('(' + Utils.formatElapsedTime(elapsedTime, true) + ')')); + result.push(colors.yellow.bold('(' + Utils.formatElapsedTime(elapsedTime, true) + ')')); } // eslint-disable-next-line no-console diff --git a/lib/reporter/reporters/html.js b/lib/reporter/reporters/html.js index c611699251..d168ed3ae4 100644 --- a/lib/reporter/reporters/html.js +++ b/lib/reporter/reporters/html.js @@ -41,7 +41,7 @@ class HtmlReporter extends BaseReporter { getFileName() { let fileName = 'index'; const {filename_format} = this.options; - if (filename_format) { + if (filename_format) { if (typeof filename_format === 'function') { fileName = filename_format(this.results); } else if (typeof filename_format === 'string') { diff --git a/lib/reporter/results.js b/lib/reporter/results.js index ed1772bf31..ddf1772b57 100644 --- a/lib/reporter/results.js +++ b/lib/reporter/results.js @@ -16,7 +16,7 @@ module.exports = class Results { return 'skip'; } - constructor(tests = [], opts, settings, skippedTests=[], allScreenedTests=[]) { + constructor(tests = [], opts, settings, skippedTests = [], allScreenedTests = []) { this.skippedByUser = skippedTests; this.skippedAtRuntime = tests.slice(0); this.testcases = {}; @@ -73,7 +73,7 @@ module.exports = class Results { if (!this.currentHookName) { throw new Error('Hook run not started yet'); } - + const currentSection = this.getTestSection(this.currentSectionName); const hookdata = this.testSectionHook; @@ -82,7 +82,7 @@ module.exports = class Results { const elapsedTime = endTime - startTime; hookdata.endTimestamp = endTime; - hookdata.time = (elapsedTime/1000).toPrecision(4); + hookdata.time = (elapsedTime / 1000).toPrecision(4); hookdata.timeMs = elapsedTime; hookdata.httpOutput = Logger.collectTestHooksOutput(); @@ -323,7 +323,7 @@ module.exports = class Results { get retryTest() { return this.__retryTest; } - + /** * @param {TestCase} testcase * @return {Object} @@ -451,7 +451,7 @@ module.exports = class Results { return this; } - + initCount(allScreenedTests) { this.__passedCount = 0; this.__failedCount = 0; @@ -473,7 +473,7 @@ module.exports = class Results { this.time += elapsedTime; if (currentTest) { - this.currentTestResult.time = (elapsedTime/1000).toPrecision(4); + this.currentTestResult.time = (elapsedTime / 1000).toPrecision(4); this.currentTestResult.timeMs = elapsedTime; this.currentTestResult.startTimestamp = new Date(startTime).toUTCString(); this.currentTestResult.endTimestamp = new Date(endTime).toUTCString(); @@ -493,7 +493,7 @@ module.exports = class Results { this.time += elapsedTime; if (currentSection) { - currentSection.time = (elapsedTime/1000).toPrecision(4); + currentSection.time = (elapsedTime / 1000).toPrecision(4); currentSection.timeMs = elapsedTime; currentSection.startTimestamp = startTime; currentSection.endTimestamp = endTime; @@ -507,7 +507,7 @@ module.exports = class Results { const currentTest = this.getCurrentTest(); const currentSection = this.getTestSection(this.currentSectionName); if (!currentTest) { - return; + return; } if (this.hasFailures()) { @@ -530,7 +530,7 @@ module.exports = class Results { setTotalElapsedTime() { this.timeMs = this.time; - this.time = (this.time/1000).toPrecision(4); + this.time = (this.time / 1000).toPrecision(4); return this; } @@ -585,7 +585,7 @@ module.exports = class Results { getTestStatus() { if (this.failedCount > 0 || this.errorsCount > 0) { return Results.TEST_FAIL; - } + } if (this.passedCount > 0) { return Results.TEST_PASS; @@ -597,8 +597,8 @@ module.exports = class Results { /** * Appends data in current testSections data. - * - * @param {Object} data + * + * @param {Object} data */ appendTestResult(data) { lodashMerge(this.testSections, data); @@ -642,8 +642,8 @@ module.exports = class Results { } get eventDataToEmit() { - const {testEnv, sessionCapabilities, sessionId, tags, modulePath, name, host} = this; - + const {testEnv, sessionCapabilities, sessionId, tags, modulePath, name, host} = this; + return { envelope: this.testSections, metadata: { diff --git a/lib/reporter/summary.js b/lib/reporter/summary.js index a0aef38ef0..fdf022f35a 100644 --- a/lib/reporter/summary.js +++ b/lib/reporter/summary.js @@ -1,7 +1,6 @@ const Utils = require('../utils'); -const {Logger, beautifyStackTrace} = Utils; +const {Logger} = Utils; const {colors} = Logger; -const stripAnsi = require('strip-ansi'); module.exports = class Summary { static failed(test) { @@ -27,7 +26,7 @@ module.exports = class Summary { */ static getFailedSuiteContent({testSuite, testSuiteName, index = 0, startSessionEnabled = true}) { const testcases = Object.keys(testSuite.completed); - const initial = [colors.red(` ${Utils.symbols.fail} ${index+1}) ${testSuiteName}`)]; + const initial = [colors.red(` ${Utils.symbols.fail} ${index + 1}) ${testSuiteName}`)]; return testcases.reduce((prev, name) => { const testcase = testSuite.completed[name]; diff --git a/lib/runner/androidEmulator.js b/lib/runner/androidEmulator.js index 4379cca285..e1fe8be1a3 100644 --- a/lib/runner/androidEmulator.js +++ b/lib/runner/androidEmulator.js @@ -13,7 +13,7 @@ module.exports = class AndroidServer { async killEmulator() { return killEmulatorWithoutWait(this.sdkRoot, getPlatformName(), this.emulatorId); } - + async launchEmulator() { try { const emulatorId = await getAlreadyRunningAvd(this.sdkRoot, getPlatformName(), this.avd); diff --git a/lib/runner/cli/argv-setup.js b/lib/runner/cli/argv-setup.js index 36effbf52e..5acad8f487 100644 --- a/lib/runner/cli/argv-setup.js +++ b/lib/runner/cli/argv-setup.js @@ -213,7 +213,7 @@ module.exports = new (function () { : null, this.options.default[key] !== undefined ? '[default: ' + JSON.stringify(this.options.default[key]) + ']' - : null + : null ].filter(Boolean).join(' '); const body = [desc, extra].filter(Boolean).join(' '); @@ -399,7 +399,7 @@ module.exports = new (function () { group: 'Reporting', descriptions: 'Record trace information during nightwatch execution.' }); - + // $ nightwatch --open this.option('open', { group: 'Reporting', diff --git a/lib/runner/cli/cli.js b/lib/runner/cli/cli.js index ff670b6e63..261a254ea3 100644 --- a/lib/runner/cli/cli.js +++ b/lib/runner/cli/cli.js @@ -27,7 +27,7 @@ class CliRunner { static get CONFIG_FILE_TS() { return './nightwatch.conf.ts'; } - + static createDefaultConfig(destFileName) { // eslint-disable-next-line no-console console.log(Logger.colors.cyan('No config file found in the current working directory, creating nightwatch.conf.js in the current folder...')); @@ -112,7 +112,10 @@ class CliRunner { this.globals = null; this.testEnv = null; this.testEnvArray = []; - this.processListener = new ProcessListener(); + + if (!argv.disable_process_listener) { + this.processListener = new ProcessListener(); + } } initTestSettings(userSettings = {}, baseSettings = null, argv = null, testEnv = '', asyncLoading) { @@ -149,7 +152,7 @@ class CliRunner { } } } - + setMobileOptions(argv) { const {desiredCapabilities, selenium = {}} = this.test_settings; @@ -194,7 +197,7 @@ class CliRunner { if (!this.baseSettings) { return this; - } + } this.availableTestEnvs = Object.keys(this.baseSettings.test_settings).filter(key => { return Utils.isObject(this.baseSettings.test_settings[key]); @@ -256,12 +259,12 @@ class CliRunner { analyticsCollector.updateSettings(this.test_settings); analyticsCollector.updateLogger(Logger); - analyticsCollector.event('nw_test_run', { + analyticsCollector.collectEvent('nw_test_run', { arg_parallel: this.argv.parallel, - browserName: this.test_settings.desiredCapabilities.browserName, - testWorkersEnabled: this.test_settings.testWorkersEnabled, + browser_name: this.test_settings.desiredCapabilities.browserName, + test_workers_enabled: this.test_settings.testWorkersEnabled, use_xpath: this.test_settings.use_xpath, - isBstack: this.test_settings.desiredCapabilities['bstack:options'] !== undefined, + is_bstack: this.test_settings.desiredCapabilities['bstack:options'] !== undefined, test_runner: this.test_settings.test_runner ? this.test_settings.test_runner.type : null }); @@ -271,7 +274,7 @@ class CliRunner { isRegisterEventHandlersCallbackExistsInGlobal() { const {globals} = this.test_settings; const {plugins = []} = this.globals; - + return Utils.isFunction(globals.registerEventHandlers) || plugins.some(plugin => plugin.globals && Utils.isFunction(plugin.globals.registerEventHandlers)); } @@ -279,15 +282,15 @@ class CliRunner { if (this.isRegisterEventHandlersCallbackExistsInGlobal() && !NightwatchEventHub.isAvailable) { NightwatchEventHub.runner = this.testRunner.type; NightwatchEventHub.isAvailable = true; - + const {globals, output_folder} = this.test_settings; NightwatchEventHub.output_folder = output_folder; const {plugins} = this.globals; - + if (Utils.isFunction(globals.registerEventHandlers)) { globals.registerEventHandlers(NightwatchEventHub); } - + if (plugins.length > 0) { plugins.forEach((plugin) => { if (plugin.globals && Utils.isFunction(plugin.globals.registerEventHandlers)){ @@ -329,7 +332,7 @@ class CliRunner { if (usingTS) { return path.resolve(CliRunner.CONFIG_FILE_TS); } - + return path.resolve(CliRunner.CONFIG_FILE_JS); } @@ -402,7 +405,7 @@ class CliRunner { start_time: start_time }); } - + promise = this.globals.runGlobalHook(key, args); return promise.finally(() => { @@ -414,7 +417,7 @@ class CliRunner { } }); } - + return Promise.resolve(); } @@ -454,13 +457,13 @@ class CliRunner { for (const env of this.testEnvArray) { const {webdriver = {}} = this.testEnvSettings[env]; const desiredCapabilities = this.testEnvSettings[env].capabilities || this.testEnvSettings[env].desiredCapabilities; - + if (isMobile(desiredCapabilities)) { - + if (Concurrency.isWorker()) { Logger.info('Disabling parallelism while running tests on mobile platform'); } - + return false; } @@ -484,6 +487,11 @@ class CliRunner { } setupConcurrency() { + + if (this.testRunner?.type === Runner.MOCHA_RUNNER) { + this.test_settings.use_child_process = true; + } + this.concurrency = new Concurrency(this.test_settings, this.argv, this.isTestWorkersEnabled()); return this; @@ -507,7 +515,9 @@ class CliRunner { globalsInstance: this.globals }); - this.processListener.setTestRunner(this.testRunner); + if (this.processListener) { + this.processListener.setTestRunner(this.testRunner); + } return this; } @@ -542,7 +552,7 @@ class CliRunner { return this.runGlobalHook('before', [this.test_settings]) .then(_ => { return this.runGlobalHook('beforeChildProcess', [this.test_settings], true); - }) + }) .then(_ => { return this.getTestsFiles(); }) @@ -677,6 +687,8 @@ class CliRunner { killSimulator(this.test_settings.deviceUDID); } + analyticsCollector.__flush(); + return errorOrFailed; }); } diff --git a/lib/runner/concurrency/child-process.js b/lib/runner/concurrency/child-process.js index 5b231ca6ca..1eddf4edfa 100644 --- a/lib/runner/concurrency/child-process.js +++ b/lib/runner/concurrency/child-process.js @@ -37,7 +37,7 @@ class ChildProcess extends EventEmitter { setLabel(label) { this.env_itemKey = label; - this.env_label = this.settings.disable_colors ? ` ${label} ` : Logger.colors.yellow(` ${label} `, Logger.colors.background.black); + this.env_label = this.settings.disable_colors ? ` ${label} ` : Logger.colors.bgBlack.yellow.bold(` ${label} `); return this; } @@ -81,7 +81,7 @@ class ChildProcess extends EventEmitter { writeToStdout(data) { data = data.toString().trim(); - const color_pair = this.availColors[this.index%4]; + const color_pair = this.availColors[this.index % 4]; let output = ''; if (ChildProcess.prevIndex !== this.index) { @@ -97,7 +97,7 @@ class ChildProcess extends EventEmitter { childProcessLabel = ' ' + this.environment + ' '; } else { childProcessLabel = ''; - this.env_label = Logger.colors[color_pair[1]](` ${this.environment} `, Logger.colors.background[color_pair[0]]); + this.env_label = Logger.colors[color_pair[0]][color_pair[1]](` ${this.environment} `); } const lines = data.split('\n').map(line => { @@ -148,9 +148,9 @@ class ChildProcess extends EventEmitter { })); } - const color_pair = this.availColors[this.index%4]; + const color_pair = this.availColors[this.index % 4]; - this.printLog(' Running: ' + Logger.colors[color_pair[1]](` ${this.env_itemKey} `, Logger.colors.background[color_pair[0]])); + this.printLog(' Running: ' + Logger.colors[color_pair[0]][color_pair[1]](` ${this.env_itemKey} `)); this.child.stdout.on('data', data => { this.writeToStdout(data); diff --git a/lib/runner/concurrency/index.js b/lib/runner/concurrency/index.js index 1eb6890eb4..fa64d842c7 100644 --- a/lib/runner/concurrency/index.js +++ b/lib/runner/concurrency/index.js @@ -15,7 +15,7 @@ class Concurrency extends EventEmitter { this.useChildProcess = settings.use_child_process; this.childProcessOutput = {}; this.globalExitCode = 0; - this.testWorkersEnabled = typeof isTestWorkerEnabled !== undefined ? isTestWorkerEnabled : settings.testWorkersEnabled; + this.testWorkersEnabled = typeof isTestWorkerEnabled !== 'undefined' ? isTestWorkerEnabled : settings.testWorkersEnabled; this.isSafariEnvPresent = isSafariEnvPresent; } @@ -47,12 +47,12 @@ class Concurrency extends EventEmitter { createChildProcess(label, args = [], extraArgs = [], index = 0) { this.childProcessOutput[label] = []; const childArgs = args.slice().concat(extraArgs); - + const childProcess = new ChildProcess(label, index, this.childProcessOutput[label], this.settings, childArgs); childProcess.on('message', data => { this.emit('message', data); }); - + return childProcess; } @@ -84,7 +84,7 @@ class Concurrency extends EventEmitter { runChildProcesses(envs, modules) { const availColors = Concurrency.getAvailableColors(); const args = Concurrency.getChildProcessArgs(envs); - + if (this.testWorkersEnabled) { const jobs = []; if (envs.length > 0) { @@ -106,10 +106,10 @@ class Concurrency extends EventEmitter { }); jobs.push(...jobList); } - + return this.runMultipleTestProcess(jobs, args, availColors); } - + return this.runProcessTestEnvironment(envs, args, availColors); } @@ -185,7 +185,7 @@ class Concurrency extends EventEmitter { if (this.isSafariEnvPresent) { extraArgs.push('--serial'); } - let childProcess = this.createChildProcess(environment, args, extraArgs, index); + const childProcess = this.createChildProcess(environment, args, extraArgs, index); return this.runChildProcess(childProcess, environment + ' environment', availColors, 'envs'); })); @@ -216,8 +216,14 @@ class Concurrency extends EventEmitter { }); }); - return Promise.all(workerPool.tasks); + return new Promise((resolve, reject) => { + Promise.allSettled(workerPool.tasks) + .then(values => { + values.some(({status}) => status === 'rejected') ? reject() : resolve(); + }); + }); } + /** * * @param {Array} modules @@ -249,7 +255,7 @@ class Concurrency extends EventEmitter { return this.runChildProcess(childProcess, outputLabel, availColors, 'workers') .then(_ => { - remaining -=1; + remaining -= 1; if (remaining > 0) { next(); @@ -281,7 +287,7 @@ class Concurrency extends EventEmitter { modules.forEach(({module, env}) => { let outputLabel = Utils.getModuleKey(module, this.settings.src_folders, modules); outputLabel = env ? `${env}: ${outputLabel}` : outputLabel; - + const workerArgv = {...this.argv}; workerArgv._source = [module]; workerArgv.env = env; @@ -295,16 +301,21 @@ class Concurrency extends EventEmitter { }); }); - - return Promise.all(workerPool.tasks); + + return new Promise((resolve, reject) => { + Promise.allSettled(workerPool.tasks) + .then(values => { + values.some(({status}) => status === 'rejected') ? reject() : resolve(); + }); + }); } /** - * - * @param {Array} args - * @param {Object} settings - * @param {Number} maxWorkerCount + * + * @param {Array} args + * @param {Object} settings + * @param {Number} maxWorkerCount */ setupWorkerPool(args, settings, maxWorkerCount) { const workerPool = new WorkerPool(args, settings, maxWorkerCount); @@ -396,10 +407,10 @@ class Concurrency extends EventEmitter { static getAvailableColors() { const availColorPairs = [ - ['red', 'light_gray'], - ['green', 'black'], - ['blue', 'light_gray'], - ['magenta', 'light_gray'] + ['bgRed', 'white'], + ['bgGreen', 'black'], + ['bgBlue', 'white'], + ['bgMagenta', 'white'] ]; let currentIndex = availColorPairs.length; let temporaryValue; diff --git a/lib/runner/concurrency/task.js b/lib/runner/concurrency/task.js index 25b89aaf44..f1dc8b0b27 100644 --- a/lib/runner/concurrency/task.js +++ b/lib/runner/concurrency/task.js @@ -16,8 +16,8 @@ function runWorkerTask({argv, port1}) { //send reports to main thread using message port process.port = port1; - + return Nightwatch.runTests(argv, {}); } -module.exports = runWorkerTask; \ No newline at end of file +module.exports = runWorkerTask; diff --git a/lib/runner/concurrency/worker-process.js b/lib/runner/concurrency/worker-process.js index bb6bc9e148..389d6887ff 100644 --- a/lib/runner/concurrency/worker-process.js +++ b/lib/runner/concurrency/worker-process.js @@ -1,5 +1,5 @@ const path = require('path'); -const Piscina= require('piscina'); +const Piscina = require('piscina'); const {isWorkerThread} = Piscina; const EventEmitter = require('events'); const WorkerTask = require('./worker-task'); @@ -20,7 +20,7 @@ class WorkerPool extends EventEmitter { set tasks(tasks) { this.__tasks = tasks; } - + constructor(args, settings, maxWorkerCount) { super(); @@ -44,14 +44,14 @@ class WorkerPool extends EventEmitter { */ addTask({label, argv, colors} = {}) { const workerTask = new WorkerTask({piscina: this.piscina, index: this.index, label, argv, settings: this.settings}); - + workerTask.on('message', (data) => { this.emit('message', data); }); this.index++; this.__tasks.push(workerTask.runWorkerTask(colors)); - + } } diff --git a/lib/runner/concurrency/worker-task.js b/lib/runner/concurrency/worker-task.js index 427a99c641..f41a293ffd 100644 --- a/lib/runner/concurrency/worker-task.js +++ b/lib/runner/concurrency/worker-task.js @@ -2,6 +2,7 @@ const boxen = require('boxen'); const {MessageChannel} = require('worker_threads'); const {Logger, symbols} = require('../../utils'); const EventEmitter = require('events'); +const {SafeJSON, isString} = require('../../utils'); let prevIndex = 0; @@ -36,14 +37,14 @@ class WorkerTask extends EventEmitter { } setlabel(colorPair) { - this.task_label = this.settings.disable_colors ? ` ${this.label} ` : Logger.colors[colorPair[1]](` ${this.label} `, Logger.colors.background[colorPair[0]]); + this.task_label = this.settings.disable_colors ? ` ${this.label} ` : Logger.colors[colorPair[0]][colorPair[1]](` ${this.label} `); } writeToStdOut(data) { let output = ''; if (WorkerTask.prevIndex !== this.index) { - WorkerTask.prevIndex = this.index; + WorkerTask.prevIndex = this.index; if (this.settings.live_output) { output += '\n'; } @@ -65,20 +66,27 @@ class WorkerTask extends EventEmitter { async runWorkerTask(colors, type) { this.availColors = colors; - const colorPair = this.availColors[this.index%4]; + const colorPair = this.availColors[this.index % 4]; this.setlabel(colorPair); - - this.printLog('Running '+ Logger.colors[colorPair[1]](` ${this.task_label} `, Logger.colors.background[colorPair[0]])); - + + this.printLog('Running ' + Logger.colors[colorPair[0]][colorPair[1]](` ${this.task_label} `)); + const {port1, port2} = new MessageChannel(); port2.onmessage = ({data: result}) => { + if (isString(result)) { + try { + result = JSON.parse(result); + // eslint-disable-next-line no-empty + } catch (e) {} + } + switch (result.type) { case 'testsuite_finished': result.itemKey = this.label, this.emit('message', result); break; - case 'stdout': + case 'stdout': this.writeToStdOut(result.data); break; } @@ -86,6 +94,7 @@ class WorkerTask extends EventEmitter { port2.unref(); return this.piscina.run({argv: this.argv, port1}, {transferList: [port1]}) + .catch(err => err) .then(failures => { if (this.settings.disable_output_boxes){ // eslint-disable-next-line no-console @@ -94,8 +103,13 @@ class WorkerTask extends EventEmitter { // eslint-disable-next-line no-console console.log(boxen(this.task_output.join('\n'), {title: `────────────────── ${failures ? symbols.fail : symbols.ok} ${this.task_label}`, padding: 1, borderColor: 'cyan'})); } + + //throw error to mark exit-code of the process + if (failures) { + throw new Error(); + } }); } } -module.exports = WorkerTask; \ No newline at end of file +module.exports = WorkerTask; diff --git a/lib/runner/folder-walk.js b/lib/runner/folder-walk.js index 59548515cb..d55d046d70 100644 --- a/lib/runner/folder-walk.js +++ b/lib/runner/folder-walk.js @@ -129,6 +129,10 @@ class Walker { } async applyTagFilter(list) { + if (!Array.isArray(list)) { + return; + } + if (!this.tags.anyTagsDefined() || this.usingCucumber) { return list; } @@ -169,7 +173,7 @@ class Walker { if (stat && stat.isFile()) { return fullPath; } - + if (stat && !stat.isDirectory()) { return null; } @@ -269,7 +273,7 @@ class Walker { if (this.disableTs && Utils.isTsFile(resource)) { return null; } - + if (stat && stat.isFile() && Utils.isFileNameValid(resource)) { return path.join(folderPath, resource); } diff --git a/lib/runner/matchers/tags.js b/lib/runner/matchers/tags.js index 3556614a48..cd75571edd 100644 --- a/lib/runner/matchers/tags.js +++ b/lib/runner/matchers/tags.js @@ -44,7 +44,7 @@ class TagsMatcher { const context = new Context({modulePath, settings}); context.setReloadModuleCache(); - + // Defining global browser object to make it available before nightwatch client created. // To avoid errors like browser is not defined if testsuits has tags Object.defineProperty(global, 'browser', { @@ -147,7 +147,7 @@ class TagsMatcher { // when multiple --tag arguments are passed (e.g.: --tag a --tag b) // the resulting array is [a, b], otherwise it is [a] - let tagsArray = Array.isArray(tags) ? tags : [tags]; + const tagsArray = Array.isArray(tags) ? tags : [tags]; // now parse each --tag argument return tagsArray.map(t => TagsMatcher.convertTags(t)); diff --git a/lib/runner/process-listener.js b/lib/runner/process-listener.js index 07c0fcb676..af5784144e 100644 --- a/lib/runner/process-listener.js +++ b/lib/runner/process-listener.js @@ -84,7 +84,7 @@ module.exports = class { Logger.enable(); Logger.error(`${type}: ${err.message}\n${err.stack}`); - analyticsCollector.exception(err); + analyticsCollector.collectErrorEvent(err, true); if (['TimeoutError', 'NoSuchElementError'].includes(err.name) && this.testRunner.type !== 'cucumber') { this.closeProcess(err); @@ -111,7 +111,7 @@ module.exports = class { exit() { this.process.exit && this.process.exit(this.exitCode); - + return this; } }; diff --git a/lib/runner/test-runners/cucumber.js b/lib/runner/test-runners/cucumber.js index 54a39fda43..3d4ca86150 100644 --- a/lib/runner/test-runners/cucumber.js +++ b/lib/runner/test-runners/cucumber.js @@ -92,8 +92,8 @@ class CucumberSuite extends TestSuite { } shouldUseNightwatchFormatter(options) { - return (NightwatchEventHub.isAvailable || - options.format === 'nightwatch-format' || + return (NightwatchEventHub.isAvailable || + options.format === 'nightwatch-format' || this.argv.format === 'nightwatch-format' ); } @@ -115,7 +115,7 @@ class CucumberSuite extends TestSuite { } const {feature_path = ''} = options; - const parallelArgs = this.usingCucumberWorkers ? ['--parallel', this.usingCucumberWorkers]: []; + const parallelArgs = this.usingCucumberWorkers ? ['--parallel', this.usingCucumberWorkers] : []; const additionalOptions = this.buildArgvValue(['tags', 'retry-tag-filter', 'profile', 'format', 'format-options', 'dry-run', 'fail-fast', ['retry', 'retries'], 'no-strict', 'name']); const extraParams = ['--world-parameters', JSON.stringify({...this.argv, settings: this.settings})]; @@ -126,14 +126,21 @@ class CucumberSuite extends TestSuite { } createInitialRequires() { + const {options} = this.settings.test_runner; + const isESMEnable = options.enable_esm || this.argv['enable-esm']; + const importTypeArgument = isESMEnable ? '--import' : '--require'; const initialRequires = [ - '--require', CucumberSuite.cucumberSetupFile + importTypeArgument, CucumberSuite.cucumberSetupFile ]; - initialRequires.push(...this.buildArgvValue(['require', 'require-module'])); + if (isESMEnable){ + initialRequires.push(...this.buildArgvValue(['import'])); + } else { + initialRequires.push(...this.buildArgvValue(['require', 'require-module'])); + } return this.allModulePaths.reduce((prev, spec) => { - prev.push('--require', spec); + prev.push(importTypeArgument, spec); return prev; }, initialRequires); @@ -173,7 +180,7 @@ class CucumberSuite extends TestSuite { import: this.mergeCliConfigValues('import') }; - if ((isDefined(allArgv[argName]) && allArgv[argName] !== '') || + if ((isDefined(allArgv[argName]) && allArgv[argName] !== '') || (isDefined(allArgv[key]) && allArgv[key] !== '') ) { let argValues = allArgv[argName] || allArgv[key]; diff --git a/lib/runner/test-runners/mocha.js b/lib/runner/test-runners/mocha.js index d601b02e4a..3af11a28be 100644 --- a/lib/runner/test-runners/mocha.js +++ b/lib/runner/test-runners/mocha.js @@ -118,7 +118,7 @@ class MochaRunner { if (this.isTestWorker()) { const filePath = this.argv.test; - const reportFilename = filePath.substring(filePath.lastIndexOf('/')+1).split('.')[0]; + const reportFilename = filePath.substring(filePath.lastIndexOf('/') + 1).split('.')[0]; this.mochaOpts.isWorker = true; if (argv.reporter.includes('mocha-junit-reporter')) { @@ -194,7 +194,7 @@ class MochaRunner { const client = mochaSuite && mochaSuite.client; if (client && client.sessionId) { - let request = client.transport.createHttpRequest({ + const request = client.transport.createHttpRequest({ path: `/session/${client.sessionId}` }); diff --git a/lib/runner/test-runners/mocha/custom-runnable.js b/lib/runner/test-runners/mocha/custom-runnable.js index 1deb22eea0..6ee53db00b 100644 --- a/lib/runner/test-runners/mocha/custom-runnable.js +++ b/lib/runner/test-runners/mocha/custom-runnable.js @@ -1,7 +1,7 @@ module.exports = async function(fn, isHook = false) { - let self = this; - let start = new Date(); - let ctx = this.ctx; + const self = this; + const start = new Date(); + const ctx = this.ctx; let finished; if (this.isPending()) { @@ -24,7 +24,7 @@ module.exports = async function(fn, isHook = false) { return; } emitted = true; - let msg = 'done() called multiple times'; + const msg = 'done() called multiple times'; if (err && err.message) { err.message += ` (and Mocha's ${msg})`; self.emit('error', err); @@ -35,7 +35,7 @@ module.exports = async function(fn, isHook = false) { // finished function done(err) { - let ms = self.timeout(); + const ms = self.timeout(); if (self.timedOut) { return; } @@ -125,4 +125,4 @@ module.exports = async function(fn, isHook = false) { done(err); emitted = true; } -}; \ No newline at end of file +}; diff --git a/lib/runner/test-runners/mocha/custom-runner.js b/lib/runner/test-runners/mocha/custom-runner.js index b9efc84732..f2c546c054 100644 --- a/lib/runner/test-runners/mocha/custom-runner.js +++ b/lib/runner/test-runners/mocha/custom-runner.js @@ -76,4 +76,4 @@ module.exports = class CustomRunner extends Mocha.Runner { super.run(fn); } -}; \ No newline at end of file +}; diff --git a/lib/runner/test-runners/mocha/extensions.js b/lib/runner/test-runners/mocha/extensions.js index 882b65cdba..62deab2134 100644 --- a/lib/runner/test-runners/mocha/extensions.js +++ b/lib/runner/test-runners/mocha/extensions.js @@ -100,4 +100,4 @@ module.exports = class Extensions { suite.timeout(30000); } } -}; \ No newline at end of file +}; diff --git a/lib/runner/test-runners/mocha/nightwatchSuite.js b/lib/runner/test-runners/mocha/nightwatchSuite.js index a7a7573777..53d65422e8 100644 --- a/lib/runner/test-runners/mocha/nightwatchSuite.js +++ b/lib/runner/test-runners/mocha/nightwatchSuite.js @@ -121,4 +121,4 @@ module.exports.init = function({suite, nightwatchSuite}) { console.error(err); process.exit(1); }); -}; \ No newline at end of file +}; diff --git a/lib/runner/test-source.js b/lib/runner/test-source.js index 642afeaa39..524373ee41 100644 --- a/lib/runner/test-source.js +++ b/lib/runner/test-source.js @@ -48,6 +48,61 @@ class TestSource { return testsource; } + getRerunFailedFile(minimal_report_file_path) { + const jsonFile = path.resolve(process.env.NIGHTWATCH_RERUN_REPORT_FILE || minimal_report_file_path || ''); + + if (!Utils.fileExistsSync(jsonFile)) { + const err = new Error('Unable to find the Json reporter file to rerun failed tests'); + + err.showTrace = false; + err.detailedErr = 'Configure the environment variable NIGHTWATCH_RERUN_REPORT_FILE with Json reporter file path'; + err.help = [ + `Try setting ${Logger.colors.cyan('minimal_report_file_path: "JSON-REPORTER-PATH"')} in nightwatch configuration`, + `Or, try running: ${Logger.colors.cyan('export NIGHTWATCH_RERUN_REPORT_FILE="JSON-REPORTER-PATH"')}` + ]; + + throw err; + } + + return jsonFile; + } + + getTestSourceForRerunFailed() { + const {reporter_options: {minimal_report_file_path}} = this.settings; + const minimalJsonFile = this.getRerunFailedFile(minimal_report_file_path); + + try { + const {modules = {}} = require(minimalJsonFile); + const testsource = []; + + Object.keys(modules).forEach(moduleKey => { + if (modules[moduleKey] && modules[moduleKey].status === 'fail') { + testsource.push(modules[moduleKey].modulePath); + } + }); + + if (testsource.length === 0) { + const err = new Error('Rerun Failed Tests: No failed tests found to rerun.'); + err.noFailedTestFound = true; + err.showTrace = false; + err.detailedErr = 'Run nightwatch with --help to display usage info.'; + + throw err; + } + + return testsource; + } catch (err) { + if (err.noFailedTestFound) { + err.message = 'Rerun Failed Tests: Invalid Json reporter.'; + + err.showTrace = false; + err.detailedErr = 'Please set env variable NIGHTWATCH_RERUN_REPORT_FILE with valid Json reporter path.'; + } + + throw err; + } + } + /** * Returns the path where the tests are located * @returns {*} @@ -62,7 +117,7 @@ class TestSource { if (this.src_folders.length === 0 && test_runner?.src_folders && test_runner?.type === 'cucumber') { this.src_folders = test_runner?.src_folders; } - + if (this.argv['test-worker'] || singleSourceFile(this.argv)) { return this.getTestSourceForSingle(this.argv.test || this.argv._source); } diff --git a/lib/settings/defaults.js b/lib/settings/defaults.js index d7aed546c0..7eeb82b2fe 100644 --- a/lib/settings/defaults.js +++ b/lib/settings/defaults.js @@ -60,7 +60,7 @@ module.exports = { // An object which will be made available on the main test api, throughout the test execution globals: { - + // this controls whether to abort the test execution when an assertion failed and skip the rest // it's being used in waitFor commands and expect assertions abortOnAssertionFailure: true, diff --git a/lib/settings/settings.js b/lib/settings/settings.js index cbaaef6ea4..de6901211d 100644 --- a/lib/settings/settings.js +++ b/lib/settings/settings.js @@ -398,7 +398,7 @@ class Settings { } inheritFromSuperEnv(testEnvSettings) { - if (testEnvSettings.extends) { + if (testEnvSettings.extends) { const superEnv = this.baseSettings.test_settings[testEnvSettings.extends] || {}; delete testEnvSettings.extends; defaultsDeep(testEnvSettings, superEnv); diff --git a/lib/testsuite/hooks/afterAll.js b/lib/testsuite/hooks/afterAll.js index 9e5a54804d..1b58eed586 100644 --- a/lib/testsuite/hooks/afterAll.js +++ b/lib/testsuite/hooks/afterAll.js @@ -6,4 +6,4 @@ class AfterAll extends BaseHook { } } -module.exports = AfterAll; \ No newline at end of file +module.exports = AfterAll; diff --git a/lib/testsuite/hooks/afterEach.js b/lib/testsuite/hooks/afterEach.js index 077d513828..bcb49c490d 100644 --- a/lib/testsuite/hooks/afterEach.js +++ b/lib/testsuite/hooks/afterEach.js @@ -6,4 +6,4 @@ class AfterEach extends BaseHook { } } -module.exports = AfterEach; \ No newline at end of file +module.exports = AfterEach; diff --git a/lib/testsuite/hooks/beforeAll.js b/lib/testsuite/hooks/beforeAll.js index 2a1d766b61..b97945ce06 100644 --- a/lib/testsuite/hooks/beforeAll.js +++ b/lib/testsuite/hooks/beforeAll.js @@ -10,4 +10,4 @@ class BeforeAll extends BaseHook { } } -module.exports = BeforeAll; \ No newline at end of file +module.exports = BeforeAll; diff --git a/lib/testsuite/hooks/beforeEach.js b/lib/testsuite/hooks/beforeEach.js index 3b2b0c62a3..1a301c9576 100644 --- a/lib/testsuite/hooks/beforeEach.js +++ b/lib/testsuite/hooks/beforeEach.js @@ -10,4 +10,4 @@ class BeforeAll extends BaseHook { } } -module.exports = BeforeAll; \ No newline at end of file +module.exports = BeforeAll; diff --git a/lib/testsuite/index.js b/lib/testsuite/index.js index 4a85989e5b..b20e88b422 100644 --- a/lib/testsuite/index.js +++ b/lib/testsuite/index.js @@ -147,8 +147,8 @@ class TestSuite { validateNightwatchInspectorCriteria() { return ( - this.argv.debug && - Concurrency.isMasterProcess() && + this.argv.debug && + Concurrency.isMasterProcess() && this.client.api.isChrome() ); } @@ -458,7 +458,7 @@ class TestSuite { if (this.context.hasHook(hookName)) { NightwatchEventHub.emit(TestSuiteHook[hookName].started, this.reporter.testResults.eventDataToEmit); } - + if (hookName === 'beforeEach' || hookName === 'afterEach') { this.client.reporter.markHookRun(hookName); } @@ -496,7 +496,7 @@ class TestSuite { } this.client.reporter.setCurrentSection({testName: `__global_${hookName}_hook`}); - + return this.handleRunnable(hookName, async () => { if (this.globalsInstance) { await this.globalsInstance.runPluginHook(hookName, [this.settings]); @@ -507,7 +507,7 @@ class TestSuite { if (globals[hookName]) { NightwatchEventHub.emit(GlobalHook[hookName].finished, this.reporter.testResults.eventDataToEmit); } - + this.onTestSectionFinished(); return result; @@ -578,7 +578,7 @@ class TestSuite { .then(() => this.runHook('before')) .then(result => { this.onTestSectionFinished(); - + if (result instanceof Error) { Logger.error(result); } @@ -699,11 +699,11 @@ class TestSuite { httpOutput: Logger.collectOutput() })); } else if (process.port && typeof process.port.postMessage === 'function') { - process.port.postMessage({ + process.port.postMessage(SafeJSON.stringify({ type: 'testsuite_finished', results: this.reporter.exportResults(), httpOutput: Logger.collectOutput() - }); + })); } } @@ -714,15 +714,15 @@ class TestSuite { if (Concurrency.isWorker()) { this.sendReportToParentWorker(); } - + NightwatchEventHub.emit(TestSuiteHook.finished, this.reporter.testResults.eventDataToEmit); return failures; } - + async sessionFinished(reason) { let lastError = null; - + if (reason === 'FAILED' || reason === 'RETRY_SUITE') { lastError = this.reporter.testResults.lastError; } @@ -752,7 +752,7 @@ class TestSuite { return potentialError; } - + if (endSession) { const runnable = new Runnable('terminate', _ => { if (this.api && isFunction(this.api.end)) { @@ -852,7 +852,7 @@ class TestSuite { if (this.transport.driverService && this.reporter.testResults) { this.reporter.testResults.setSeleniumLogFile(this.transport.driverService.getSeleniumOutputFilePath()); } - + // if there was an error in the testcase and skip_testcases_on_fail, we must send it forward, but after we run afterEach and after hooks return this.runHook('afterEach') .then(() => this.testCaseFinished()) @@ -923,10 +923,10 @@ class TestSuite { if (!err && this.transport.isResultSuccess(result)) { const assertions = this.api.currentTest.results.assertions || []; const commands = this.reporter.currentSection.commands || []; - + if (assertions.length > 0) { - const currentAssertion = assertions[assertions.length-1]; - const currentCommand = commands[commands.length-1]; + const currentAssertion = assertions[assertions.length - 1]; + const currentCommand = commands[commands.length - 1]; if (currentAssertion) { currentAssertion.screenshots = currentAssertion.screenshots || []; @@ -950,7 +950,7 @@ class TestSuite { takeSnapshot(node) { const commands = this.reporter.currentSection.commands || []; - const currentCommand = commands[commands.length-1]; + const currentCommand = commands[commands.length - 1]; if (this.settings.trace.enabled && node.isTraceable) { const snapShotPath = Snapshots.getFileName({ @@ -1094,11 +1094,11 @@ class TestSuite { // eslint-disable-next-line no-console (this.context.isDisabled() ? Logger.info : console.log)(Logger.colors.cyan('[' + this.context.moduleKey + ']')); } else { - Logger[this.context.isDisabled() ? 'info': 'logDetailedMessage'](`\n${Logger.colors.cyan(testSuiteDisplay)}`); + Logger[this.context.isDisabled() ? 'info' : 'logDetailedMessage'](`\n${Logger.colors.cyan(testSuiteDisplay)}`); } if (!this.context.unitTestingMode) { - Logger[this.context.isDisabled() ? 'info': 'logDetailedMessage'](Logger.colors.purple(new Array(Math.min(testSuiteDisplay.length*2 + 1, 80)).join('─'))); + Logger[this.context.isDisabled() ? 'info' : 'logDetailedMessage'](Logger.colors.purple(new Array(Math.min(testSuiteDisplay.length * 2 + 1, 80)).join('─'))); } } diff --git a/lib/testsuite/nightwatch-inspector/index.js b/lib/testsuite/nightwatch-inspector/index.js index 5c977a5ac6..379f343b80 100644 --- a/lib/testsuite/nightwatch-inspector/index.js +++ b/lib/testsuite/nightwatch-inspector/index.js @@ -21,7 +21,7 @@ module.exports = class NightwatchInspectorServer extends WebSocket { const {desiredCapabilities} = this.client.settings; const chromeOptions = desiredCapabilities['goog:chromeOptions']; const {args = []} = chromeOptions || {}; - + desiredCapabilities['goog:chromeOptions'] = { ...chromeOptions, extensions: [crxfile], @@ -55,13 +55,13 @@ module.exports = class NightwatchInspectorServer extends WebSocket { getNightwatchCommands() { const {api} = this.client; const {assert, expect, verify, ensure} = api; - + return [ 'browser', ...[api, assert, expect, verify, ensure].reduce((keys, obj) => keys.concat(Object.keys(obj)), []) ]; } - + async executeCommands(data) { let result; const context = {browser: this.client.api}; diff --git a/lib/testsuite/nightwatch-inspector/websocket-server.js b/lib/testsuite/nightwatch-inspector/websocket-server.js index eaff5583a6..b814c70094 100644 --- a/lib/testsuite/nightwatch-inspector/websocket-server.js +++ b/lib/testsuite/nightwatch-inspector/websocket-server.js @@ -21,7 +21,7 @@ module.exports = class Websocket { this._wss.on('error', (error) => { this.handleSocketError(error, cb); }); - + this._wss.on('listening', () => { Logger.log(`WebSocket server is listening on port ${this.portNumber}`); }); diff --git a/lib/transport/selenium-webdriver/browserstack/appAutomate.js b/lib/transport/selenium-webdriver/browserstack/appAutomate.js index 988a57e766..dcb44e6a8a 100644 --- a/lib/transport/selenium-webdriver/browserstack/appAutomate.js +++ b/lib/transport/selenium-webdriver/browserstack/appAutomate.js @@ -74,7 +74,7 @@ class AppAutomate extends BrowserStack { // eslint-disable-next-line no-console console.log(Logger.colors.green(Utils.symbols.ok), Logger.colors.stack_trace('App upload successful!'), '\n'); - + if (!response.custom_id) { // custom_id not being used options['appium:app'] = response.app_url; diff --git a/lib/transport/selenium-webdriver/browserstack/browserstack.js b/lib/transport/selenium-webdriver/browserstack/browserstack.js index d757c790c9..f20eae6ace 100644 --- a/lib/transport/selenium-webdriver/browserstack/browserstack.js +++ b/lib/transport/selenium-webdriver/browserstack/browserstack.js @@ -48,8 +48,8 @@ class Browserstack extends AppiumBaseServer { // checking for legacy-ways for providing config this.settings.desiredCapabilities['bstack:options'] = defaultsDeep(this.settings.desiredCapabilities['bstack:options'], { - userName: desiredCapabilities['browserstack.user'], - accessKey: desiredCapabilities['browserstack.key'], + userName: desiredCapabilities['browserstack.user'], + accessKey: desiredCapabilities['browserstack.key'], buildName: desiredCapabilities.build || desiredCapabilities.buildName, local: desiredCapabilities['browserstack.local'], sessionName: desiredCapabilities['name'] @@ -141,7 +141,7 @@ class Browserstack extends AppiumBaseServer { super.sessionFinished(reason); await this.testSuiteFinished(err); } - + async testSuiteFinished(err) { try { if (this.sessionId) { diff --git a/lib/transport/selenium-webdriver/cdp.js b/lib/transport/selenium-webdriver/cdp.js index d48a8a42e9..06bf9c20a9 100644 --- a/lib/transport/selenium-webdriver/cdp.js +++ b/lib/transport/selenium-webdriver/cdp.js @@ -1,5 +1,5 @@ class Cdp { - async getConnection(driver, reset=false) { + async getConnection(driver, reset = false) { if (!reset && this._connection) { return this._connection; } diff --git a/lib/transport/selenium-webdriver/httpclient.js b/lib/transport/selenium-webdriver/httpclient.js index d4084ecdfa..c7c9f0129b 100644 --- a/lib/transport/selenium-webdriver/httpclient.js +++ b/lib/transport/selenium-webdriver/httpclient.js @@ -18,12 +18,12 @@ module.exports = function(settings, HttpResponse) { const {log_screenshot_data} = settings; let {port} = options; if (port) { - port = Number(port); + port = Number(port); HttpRequest.updateGlobalSettings({port}); } else { - port = protocol === 'https' ? 443 : 80; + port = protocol === 'https' ? 443 : 80; } - + this.options = { host, port, @@ -34,7 +34,7 @@ module.exports = function(settings, HttpResponse) { use_ssl: protocol === 'https:' }; this.errorTimeoutId = null; - + } /** @override */ diff --git a/lib/transport/selenium-webdriver/index.js b/lib/transport/selenium-webdriver/index.js index 8eb9177437..cb493119e4 100644 --- a/lib/transport/selenium-webdriver/index.js +++ b/lib/transport/selenium-webdriver/index.js @@ -64,7 +64,7 @@ class Transport extends BaseTransport { static set driverService(value) { _driverService = value; } - + /** * @override @@ -222,9 +222,9 @@ class Transport extends BaseTransport { if (value) { return Transport.driver; } - + Transport.driver = await this.createDriver({options}); - + return Transport.driver; } @@ -235,7 +235,7 @@ class Transport extends BaseTransport { } try { await Transport.driver.getSession(); - + return true; } catch (err) { return false; @@ -293,18 +293,18 @@ class Transport extends BaseTransport { const session = new Session(this.driver); const sessionExports = await session.exported(); const {sessionInfo, sessionId, capabilities, elementKey} = sessionExports; - + this.__elementKey = elementKey; await this.showConnectInfo({startTime, host, port, start_process, sessionInfo}); return { sessionId, capabilities, - host, + host, port }; } catch (err) { - const error = this.handleConnectError(err); + const error = this.handleConnectError(err, host, port); this.showConnectSpinner(colors.red(`Failed to connect to ${this.serviceName} on ${host} with ${colors.stack_trace(portStr)}.`), 'warn'); throw error; @@ -418,13 +418,13 @@ class Transport extends BaseTransport { return result.value && result.value.message; } - handleConnectError(err) { + handleConnectError(err, host, port) { const errMsg = `An error occurred while creating a new ${this.serviceName} session:`; switch (err.code) { case 'ECONNREFUSED': err.sessionCreate = true; - err.message = `${errMsg} ${err.message.replace('ECONNREFUSED connect ECONNREFUSED', 'Connection refused to')}. If the Webdriver/Selenium service is managed by Nightwatch, check if "start_process" is set to "true".`; + err.message = `${errMsg} Connection refused to ${host}:${port}. If the Webdriver/Selenium service is managed by Nightwatch, check if "start_process" is set to "true".`; break; default: err.message = `${errMsg} [${err.name}] ${err.message}`; diff --git a/lib/transport/selenium-webdriver/method-mappings.js b/lib/transport/selenium-webdriver/method-mappings.js index eef70c1b3c..33a6a1dff5 100644 --- a/lib/transport/selenium-webdriver/method-mappings.js +++ b/lib/transport/selenium-webdriver/method-mappings.js @@ -647,7 +647,7 @@ module.exports = class MethodMappings { async clickElement(webElementOrId) { const element = this.getWebElement(webElementOrId); await element.click(); - + return null; }, @@ -881,7 +881,7 @@ module.exports = class MethodMappings { return null; }, - async contextClick(webElementOrId) { + async contextClick(webElementOrId) { const element = this.getWebElement(webElementOrId); await this.driver.actions({async: true}).contextClick(element).perform(); @@ -889,17 +889,17 @@ module.exports = class MethodMappings { }, async pressAndHold(webElementOrId) { - + const element = this.getWebElement(webElementOrId); await this.driver.actions({async: true}).move({origin: element}).press().perform(); - + return null; }, async release(webElementOrId) { await this.driver.actions({async: true}).release().perform(); - - return null; + + return null; }, /////////////////////////////////////////////////////////// // User Prompts @@ -1155,7 +1155,7 @@ module.exports = class MethodMappings { async mockNetworkResponse(urlToIntercept, response) { const cdpConnection = await cdp.getConnection(this.driver); - const {status=200, headers: headersObject, body: bodyPlain=''} = response; + const {status = 200, headers: headersObject, body: bodyPlain = ''} = response; const headers = []; if (headersObject) { for (const [name, value] of Object.entries(headersObject)) { @@ -1209,29 +1209,29 @@ module.exports = class MethodMappings { async takeHeapSnapshot(heapSnapshotLocation) { const cdpConnection = await cdp.getConnection(this.driver); - + const chunks = []; cdpConnection._wsConnection.on('message', (message) => { const params = JSON.parse(message); if (params.method === 'HeapProfiler.addHeapSnapshotChunk') { const chunk = params['params']['chunk']; - + chunks.push(chunk); } }); - + await cdpConnection.execute( 'HeapProfiler.enable', {} ); - + await cdpConnection.execute( 'HeapProfiler.takeHeapSnapshot', {} ); - + let prevChunks = []; - + return new Promise((resolve) => { const intervalId = setInterval(() => { if (prevChunks.length !== 0 && prevChunks.length === chunks.length) { @@ -1239,15 +1239,15 @@ module.exports = class MethodMappings { } prevChunks = [...chunks]; }, 100); - + const resolveAndClearInterval = () => { clearInterval(intervalId); - + const heapSnapshot = chunks.join(''); if (heapSnapshotLocation) { fs.writeFileSync(heapSnapshotLocation, heapSnapshot); } - + resolve({value: heapSnapshot}); }; }); diff --git a/lib/transport/selenium-webdriver/service-builders/base-service.js b/lib/transport/selenium-webdriver/service-builders/base-service.js index fee27a5520..fca9a27829 100644 --- a/lib/transport/selenium-webdriver/service-builders/base-service.js +++ b/lib/transport/selenium-webdriver/service-builders/base-service.js @@ -38,7 +38,7 @@ class BaseService { let binaryMissing = `${this.serviceName} cannot be found in the current project.`; if (this.npmPackageName) { - binaryMissing += '\n\n ' + Logger.colors.yellow(`You can either install ${this.npmPackageName} from NPM with: \n\n npm install ${this.npmPackageName} --save-dev\n\n`) + ' or '; + binaryMissing += '\n\n ' + Logger.colors.yellow.bold(`You can either install ${this.npmPackageName} from NPM with: \n\n npm install ${this.npmPackageName} --save-dev\n\n`) + ' or '; } else { binaryMissing += '\n\n Please '; } @@ -51,7 +51,7 @@ class BaseService { } get errorOutput() { - let errorOut = this.error_out.split('\n'); + const errorOut = this.error_out.split('\n'); return errorOut.reduce(function(prev, message) { if (prev.indexOf(message) < 0) { @@ -148,7 +148,7 @@ class BaseService { this.processExited = true; if (code > 0) { - let err = this.createError(null, code); + const err = this.createError(null, code); err.detailedErr = this.error_out || this.output; } } @@ -182,7 +182,7 @@ class BaseService { message = this.createErrorMessage(code); } - let err = new Error(message); + const err = new Error(message); err.code = code; err.errorOut = this.errorOutput; @@ -308,7 +308,7 @@ class BaseService { this.process.kill(); } catch (err) { Logger.error(err); - + return Promise.reject(err); } } @@ -324,7 +324,7 @@ class BaseService { } getLogPath() { - let {log_path = 'logs'} = this.settings.webdriver; + const {log_path = 'logs'} = this.settings.webdriver; if (log_path === false) { return null; } diff --git a/lib/transport/selenium-webdriver/session.js b/lib/transport/selenium-webdriver/session.js index 02d1dab6ed..9062246be6 100644 --- a/lib/transport/selenium-webdriver/session.js +++ b/lib/transport/selenium-webdriver/session.js @@ -4,9 +4,9 @@ module.exports = class Session { } static serializeCapabilities(caps) { - let ret = {}; - for (let key of caps.keys()) { - let cap = caps.get(key); + const ret = {}; + for (const key of caps.keys()) { + const cap = caps.get(key); if (cap !== undefined && cap !== null) { ret[key] = cap; } @@ -45,4 +45,4 @@ module.exports = class Session { capabilities: Session.serializeCapabilities(sessionCapabilities) }; } -}; \ No newline at end of file +}; diff --git a/lib/utils/addDetailedError.js b/lib/utils/addDetailedError.ts similarity index 85% rename from lib/utils/addDetailedError.js rename to lib/utils/addDetailedError.ts index d31983ccac..dbaee47a4d 100644 --- a/lib/utils/addDetailedError.js +++ b/lib/utils/addDetailedError.ts @@ -1,9 +1,10 @@ +import {NightwatchError} from './types'; + /** * @method addDetailedError - * @param {Error} err */ -module.exports = function(err) { - let detailedErr; +export = function(err: NightwatchError) { + let detailedErr: string | undefined; if (err instanceof TypeError) { if (err.detailedErr && /browser\..+ is not a function$/.test(err.detailedErr)) { @@ -25,14 +26,14 @@ module.exports = function(err) { detailedErr = ' - writing an ES6 async test case? - keep in mind that commands return a Promise; \n - writing unit tests? - make sure to specify "unit_tests_mode=true" in your config.'; } } else if (err instanceof SyntaxError) { - const stackParts = err.stack.split('SyntaxError:'); - detailedErr = stackParts[0]; - let modulePath = err.stack.split('\n')[0]; - if (modulePath.includes(':')) { + const stackParts = err.stack?.split('SyntaxError:'); + detailedErr = stackParts?.[0]; + let modulePath = err.stack?.split('\n')[0]; + if (modulePath?.includes(':')) { modulePath = modulePath.split(':')[0]; } - if (stackParts[1]) { + if (stackParts?.[1]) { if (detailedErr) { err.stack = ''; } @@ -46,7 +47,7 @@ module.exports = function(err) { detailedErr = header + detailedErr; } - if (modulePath.endsWith('.jsx') || modulePath.endsWith('.tsx')) { + if (modulePath?.endsWith('.jsx') || modulePath?.endsWith('.tsx')) { detailedErr = `\n In order to be able to load JSX files, one of these plugins is needed: - @nightwatch/react - @nightwatch/storybook (only if using Storybook in your project) @@ -57,4 +58,4 @@ module.exports = function(err) { if (detailedErr) { err.detailedErr = detailedErr; } -}; \ No newline at end of file +}; diff --git a/lib/utils/alwaysDisplayError.js b/lib/utils/alwaysDisplayError.ts similarity index 77% rename from lib/utils/alwaysDisplayError.js rename to lib/utils/alwaysDisplayError.ts index 5d14565524..0f292b24c4 100644 --- a/lib/utils/alwaysDisplayError.js +++ b/lib/utils/alwaysDisplayError.ts @@ -1,5 +1,5 @@ -module.exports = function(err) { +export = function(err: unknown) { return (err instanceof Error) && [ 'TypeError', 'SyntaxError', 'ReferenceError', 'RangeError' ].includes(err.name); -}; \ No newline at end of file +}; diff --git a/lib/utils/analytics.js b/lib/utils/analytics.js index 974bd6079f..890b4b87fc 100644 --- a/lib/utils/analytics.js +++ b/lib/utils/analytics.js @@ -4,7 +4,7 @@ */ const os = require('os'); -const HttpRequest = require('../http/request.js'); +const https = require('https'); const uuid = require('uuid'); const fs = require('fs').promises; @@ -28,7 +28,8 @@ const RESERVED_EVENT_NAMES = ['ad_activeview', 'ad_click', 'ad_exposure', 'ad_im 'notification_dismiss', 'notification_foreground', 'notification_open', 'notification_receive', 'os_update', 'screen_view', 'session_start', 'user_engagement']; -const ALLOWED_ERRORS = ['Error', 'SyntaxError', 'TypeError', 'ReferenceError']; +const ALLOWED_ERRORS = ['Error', 'SyntaxError', 'TypeError', 'ReferenceError', 'WebDriverError', 'TimeoutError', 'NotFoundError', 'NoSuchElementError', + 'IosSessionNotCreatedError', 'AndroidConnectionError', 'AndroidBinaryError', 'RealIosDeviceIdError', 'AndroidHomeError']; const RESERVED_EVENT_PARAMETERS = ['firebase_conversion']; @@ -39,11 +40,13 @@ const SYSTEM_LANGUAGE = getLanguage(); /** * See: https://developers.google.com/analytics/devguides/collection/protocol/v1/devguide */ -class AnalyticsCollector { +class AnalyticsCollector { constructor() { this.queueLength = 0; this.parameters = {}; this.logger = Logger; + this.userAgentString = buildUserAgentString(); + this.runId = uuid.v4(); // This identifies unique users and helps us separate out sessions. this.parameters['client_id'] = uuid.v4(); @@ -78,6 +81,7 @@ class AnalyticsCollector { this.settings.usage_analytics = defaultSettings; } + this.testEnv = settings.testEnv; // update client_id this.parameters['client_id'] = this.settings.usage_analytics.client_id; } @@ -86,7 +90,7 @@ class AnalyticsCollector { this.logger = logger; } - async event(name, parameters = {}) { + async collectEvent(name, parameters = {}) { if (!this.settings.usage_analytics.enabled) { return; } @@ -100,39 +104,46 @@ class AnalyticsCollector { parameters['event_time'] = new Date().getTime() * 1000; // Add environment information. - parameters['env_os'] = buildUserAgentString(); + parameters['env_os'] = this.userAgentString; parameters['env_lang'] = SYSTEM_LANGUAGE; parameters['env_nw_version'] = VERSION.full; parameters['env_node_version'] = `node ${process.version}`; - - // Add call location. - parameters.callLocation = getCallLocation(); + parameters['test_env'] = this.testEnv; + parameters['run_id'] = this.runId; return await this.__addToQueue(name, parameters); } - - async exception(error) { - await this.initialize(); + async collectErrorEvent(error, isUncaught = false) { if (!error) { return; } - if (!ALLOWED_ERRORS.includes(error.name)) { - error.name = 'CustomError'; + await this.initialize(); + + let errorName = error.name; + if (!ALLOWED_ERRORS.includes(errorName)) { + errorName = 'UserGeneratedError'; } try { const parameters = { + env_os: this.userAgentString, env_nw_version: VERSION.full, env_node_version: `node ${process.version}`, - errorName: error.name, - errorMessage: error.message + test_env: this.testEnv, + err_name: errorName, + is_uncaught: isUncaught, + run_id: this.runId }; - - this.__validateEvent('nw_exception', parameters); - - return await this.__addToQueue('nw_exception', parameters); + + this.__validateEvent('nw_test_err', parameters); + + await this.__addToQueue('nw_test_err', parameters); + + if (isUncaught) { + await this.__flush(); + } } catch (e) { // Ignore } @@ -143,9 +154,7 @@ class AnalyticsCollector { return; } - const pending = this.queueLength > 0; - - if (!pending) { + if (this.queueLength === 0) { return; } @@ -177,15 +186,15 @@ class AnalyticsCollector { this.logger.info('Analytics flush error:', error.message); } } - + async __addToQueue(eventType, parameters) { if (!this.settings.usage_analytics.enabled) { return; } + this.queueLength++; const writeLogPromise = await this.__logAnalyticsEvents({name: eventType, params: parameters}); - this.queueLength++; // periodically flush if (this.queueLength > 5) { await this.__flush(); @@ -206,32 +215,38 @@ class AnalyticsCollector { const serverUrl = new URL(this.settings.usage_analytics.serverUrl || GA_SERVER_URL) ; const serverPort = serverUrl.port || this.settings.usage_analytics.serverPort || GA_SERVER_PORT; - const request = new HttpRequest({ - host: serverUrl.hostname, + const payload = JSON.stringify(data); + + const request = new https.request({ + hostname: serverUrl.hostname, port: serverPort, method: 'POST', - use_ssl: true, path, - data + headers: { + 'Content-Type': 'application/json', + 'Content-Length': payload.length + } }); - return new Promise((resolve, reject) => { - request - .on('success', (result, response) => { - if (response.statusCode !== 204) { - reject( - new Error(`Analytics reporting failed with status code: ${response.statusCode}.`) - ); - } else { - resolve(result); - } - }) - .on('error', (result) => { - new Error(`Failed to send usage metric: ${result.code}.`); - reject(result); - }) - .send(); + request.write(payload); + + request.once('response', (response) => { + if (response.statusCode !== 204) { + reject( + new Error(`Analytics reporting failed with status code: ${response.statusCode}.`) + ); + } else { + resolve(response); + } + }); + + request.on('error', (result) => { + new Error(`Failed to send usage metric: ${result.code}.`); + reject(result); + }); + + request.end(); }); } @@ -241,21 +256,28 @@ class AnalyticsCollector { return `/mp/collect?api_secret=${apiKey}&measurement_id=${trackingId}`; } - + async __logAnalyticsEvents(data) { const logfile = this.__getLogFileLocation(); const hasAnalyticsLog = await fileExists(logfile); - + if (!hasAnalyticsLog) { + data.params = data.params ? data.params : {}; + data.params['first_run'] = true; + await fs.mkdir(path.dirname(logfile), {recursive: true}); - } + } const writeFn = hasAnalyticsLog ? fs.appendFile : fs.writeFile; try { await writeFn(logfile, JSON.stringify(data) + '\n'); + // Always send data for first runs, this helps us detect CI builds also. + if (!hasAnalyticsLog) { + await this.__flush(); + } } catch (err) { this.logger.warn('Failed to log usage data:', err.message); } @@ -278,7 +300,7 @@ class AnalyticsCollector { if (RESERVED_EVENT_NAMES.includes(name)) { throw Error(`Analytics event name ${name} is reserved.`); } - + Object.keys(parameters).forEach(key => { if (RESERVED_EVENT_PARAMETERS.includes(key)) { throw Error(`Parameter name ${key} is reserved.`); @@ -306,13 +328,6 @@ function getLanguage() { '??'); } -function getCallLocation() { - const stackTrace = {}; - Error.captureStackTrace(stackTrace); - - return stackTrace.stack.split('\n')[3]; -} - /** * Attempt to get the Windows Language Code string. */ @@ -341,4 +356,4 @@ function buildUserAgentString() { } // export singleton instance -module.exports = new AnalyticsCollector();; \ No newline at end of file +module.exports = new AnalyticsCollector();; diff --git a/lib/utils/beautifyStackTrace.js b/lib/utils/beautifyStackTrace.js index d7ef4f1859..98d5f64596 100644 --- a/lib/utils/beautifyStackTrace.js +++ b/lib/utils/beautifyStackTrace.js @@ -2,13 +2,13 @@ const fs = require('fs'); const stackTraceParser = require('stacktrace-parser'); const AssertionError = require('assertion-error'); const {filterStackTrace} = require('./stackTrace.js'); -const alwaysDisplayError = require('./alwaysDisplayError.js'); -const {colors} = require('./colors.js'); +const alwaysDisplayError = require('./alwaysDisplayError'); +const {colors} = require('./chalkColors.js'); /** * Read the User file from the stackTrace and create a string with highlighting the line with error * @param {Error} err - */ + */ function beautifyStackTrace(err, errStackPassed = false, modulepath, cli = true) { if ((err instanceof AssertionError) || alwaysDisplayError(err) || errStackPassed) { try { @@ -20,7 +20,7 @@ function beautifyStackTrace(err, errStackPassed = false, modulepath, cli = true) if (!parsedStack) { parsedStack = parsedStacks[0]; } - + const file = fs.readFileSync(parsedStack.file, 'utf-8'); const errorLinesofFile = file.split(/\r?\n/); @@ -32,11 +32,11 @@ function beautifyStackTrace(err, errStackPassed = false, modulepath, cli = true) const desiredLines = formattedStack.codeSnippet.reduce(function(lines, newLine) { const currentLine = newLine.line_number; if (currentLine === formattedStack.error_line_number) { - lines += '\n ' + colors.light_gray(` ${currentLine} | ${newLine.code} `, colors.background.red); + lines += '\n ' + colors.bgRed.white(` ${currentLine} | ${newLine.code} `); } else if (currentLine <= (formattedStack.error_line_number + 2) && currentLine >= (formattedStack.error_line_number - 2)) { lines += `\n ${currentLine} | ${newLine.code}`; } - + return lines; }, ''); @@ -54,7 +54,7 @@ function beautifyStackTrace(err, errStackPassed = false, modulepath, cli = true) /** * Read the User file from the stackTrace and create a string with highlighting the line with error * @param {Error} err - */ + */ function formatStackTrace(errorLinesofFile, parsedStack) { const result = { @@ -74,7 +74,7 @@ function formatStackTrace(errorLinesofFile, parsedStack) { return result; - + } module.exports = beautifyStackTrace; diff --git a/lib/utils/browsername.js b/lib/utils/browsername.ts similarity index 73% rename from lib/utils/browsername.js rename to lib/utils/browsername.ts index eda7ab1537..c313aea2ee 100644 --- a/lib/utils/browsername.js +++ b/lib/utils/browsername.ts @@ -1,27 +1,27 @@ -const BrowserName = module.exports = { +class BrowserName { get CHROME() { return 'chrome'; - }, + } get FIREFOX() { return 'firefox'; - }, + } get SAFARI() { return 'safari'; - }, + } get EDGE() { return 'MicrosoftEdge'; - }, + } get INTERNET_EXPLORER() { return 'internet explorer'; - }, + } get OPERA() { return 'opera'; } -}; +} -Object.freeze(BrowserName); +export = new BrowserName(); diff --git a/lib/utils/chalkColors.js b/lib/utils/chalkColors.js new file mode 100644 index 0000000000..94ab50bfe8 --- /dev/null +++ b/lib/utils/chalkColors.js @@ -0,0 +1,54 @@ +const chalk = require('chalk'); + +class ChalkColors { + constructor() { + this.instance = new chalk.Instance(); + this.origLevel = this.instance.level; + + this.loadCustomColors(); // for backward compatibility + + if (process.env.COLORS === '0') { + this.disable(); + } + } + + loadCustomColors() { + const colorsInstance = this.instance; + + // foreground colors + colorsInstance.dark_gray = colorsInstance.black.bold; + colorsInstance.light_blue = colorsInstance.blue.bold; + colorsInstance.light_green = colorsInstance.green.bold; + colorsInstance.light_cyan = colorsInstance.cyan.bold; + colorsInstance.light_red = colorsInstance.red.bold; + colorsInstance.light_purple = colorsInstance.magenta.bold; + colorsInstance.light_gray = colorsInstance.white; + + colorsInstance.purple = colorsInstance.magenta; + colorsInstance.brown = colorsInstance.yellow; + colorsInstance.stack_trace = colorsInstance.gray; + } + + get colors() { + return this.instance; + } + + get colorsEnabled() { + return this.instance.level !== 0; + } + + disable() { + this.prevLevel = this.instance.level; + this.instance.level = 0; + } + + enable() { + this.instance.level = this.prevLevel; + } + + reset() { + this.instance.level = this.origLevel; + } +} + +module.exports = new ChalkColors(); diff --git a/lib/utils/colors.js b/lib/utils/colors.js deleted file mode 100644 index 73670f1903..0000000000 --- a/lib/utils/colors.js +++ /dev/null @@ -1,133 +0,0 @@ -const Settings = { - enabled: process.env.COLORS !== '0' -}; - -function Background() { - return this; -} - -function ConsoleColor() { - this.background = new Background(); - - const foregroundColors = { - black: '0;30', - dark_gray: '1;30', - blue: '0;34', - light_blue: '1;34', - green: '0;32', - light_green: '1;32', - cyan: '0;36', - light_cyan: '1;36', - red: '0;31', - light_red: '1;31', - purple: '0;35', - light_purple: '1;35', - brown: '0;33', - yellow: '1;33', - light_gray: '0;37', - white: '1;37', - stack_trace: '0;90' - }; - - const backgroundColors = { - black: '40', - red: '41', - green: '42', - yellow: '43', - blue: '44', - magenta: '45', - cyan: '46', - light_gray: '47' - }; - - Object.keys(foregroundColors).forEach(k => { - ConsoleColor.prototype[k.toLowerCase()] = (text, background) => { - if (!Settings.enabled) { - return text; - } - - let string = `\u{1b}[${foregroundColors[k.toLowerCase()]}m`; - if (background !== undefined) { - string += background(); - } - - string += `${text}\u{1b}[0m`; - - return string; - }; - }); - - Object.keys(backgroundColors).forEach(k => { - Background.prototype[k.toLowerCase()] = (text) => { - return `\u{1b}[${backgroundColors[k.toLowerCase()]}m`; - }; - }); - - return this; -} - -module.exports = new (function() { - let instance; - - const disable = function() { - Settings.enabled = false; - - Object.keys(ConsoleColor.prototype).forEach(function (color) { - ConsoleColor.prototype[color] = function (text) { - return text; - }; - }); - }; - - const enable = function() { - Settings.enabled = true; - }; - - const setup = function() { - instance = new ConsoleColor(); - }; - - const initialize = function() { - if (!Settings.enabled) { - disable(); - } - - setup(); - }; - - //////////////////////////////////////////////////////////// - // Public methods - //////////////////////////////////////////////////////////// - const exported = { - disableColors() { - disable(); - setup(); - }, - disable() { - disable(); - setup(); - }, - enable() { - enable(); - setup(); - } - }; - - Object.defineProperty(exported, 'colors', { - configurable: true, - get: function() { - return instance; - } - }); - - Object.defineProperty(exported, 'colorsEnabled', { - configurable: true, - get: function() { - return Settings.enabled; - } - }); - - initialize(); - - return exported; -}); diff --git a/lib/utils/createPromise.js b/lib/utils/createPromise.js deleted file mode 100644 index 3ec3afaf8c..0000000000 --- a/lib/utils/createPromise.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @return {{resolve, reject, promise}} - */ -module.exports = function createPromise() { - const deferred = { - resolve: null, - reject: null, - promise: null - }; - - deferred.promise = new Promise((resolve, reject) => { - deferred.resolve = resolve; - deferred.reject = reject; - }); - - return deferred; -}; diff --git a/lib/utils/createPromise.ts b/lib/utils/createPromise.ts new file mode 100644 index 0000000000..5a8d5ec4cc --- /dev/null +++ b/lib/utils/createPromise.ts @@ -0,0 +1,16 @@ +interface Deferred { + promise: Promise; + resolve: ((value: T | PromiseLike) => void); + reject: ((reason?: unknown) => void); +} + +export = function createPromise(): Deferred { + const deferred = > {}; + + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve; + deferred.reject = reject; + }); + + return deferred; +}; diff --git a/lib/utils/debuggability.js b/lib/utils/debuggability.js index aa9455bfc4..ed291e2640 100644 --- a/lib/utils/debuggability.js +++ b/lib/utils/debuggability.js @@ -6,7 +6,7 @@ class Debuggability { static set stepOverAndPause(value) { this._stepOverAndPause = value; } - + static reset() { this._stepOverAndPause = false; } diff --git a/lib/utils/getAllClassMethodNames.js b/lib/utils/getAllClassMethodNames.js index 1cf79b0a95..127bb49dba 100644 --- a/lib/utils/getAllClassMethodNames.js +++ b/lib/utils/getAllClassMethodNames.js @@ -24,4 +24,4 @@ module.exports = function(instance, additionalReserved = []) { } return [...result]; -}; \ No newline at end of file +}; diff --git a/lib/utils/getFreePort.js b/lib/utils/getFreePort.ts similarity index 56% rename from lib/utils/getFreePort.js rename to lib/utils/getFreePort.ts index deb3878eff..485b70db6e 100644 --- a/lib/utils/getFreePort.js +++ b/lib/utils/getFreePort.ts @@ -1,20 +1,25 @@ +import * as net from 'net'; + /** * @method getFreePort - * @param host - * @returns {Promise} */ -module.exports = function(host = 'localhost') { - const net = require('net'); - +export = function(host = 'localhost'): Promise { return new Promise((resolve, reject) => { const server = net.createServer(); server.on('listening', function () { - resolve(server.address().port); + const serverAddress = server.address(); + + if (!serverAddress || typeof serverAddress === 'string') { + reject(new Error('Unable to get port from server address.')); + } else { + resolve(serverAddress.port); + } + server.close(); }); - server.on('error', (e) => { + server.on('error', (e: NodeJS.ErrnoException) => { let err; if (e.code === 'EADDRINUSE' || e.code === 'EACCES') { err = new Error('Unable to find a free port'); @@ -28,4 +33,4 @@ module.exports = function(host = 'localhost') { // By providing 0 we let the operative system find an arbitrary port server.listen(0, host); }); -}; \ No newline at end of file +}; diff --git a/lib/utils/index.js b/lib/utils/index.js index 2d1613ee32..3b5d031d42 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -6,16 +6,16 @@ const {By, Capabilities} = require('selenium-webdriver'); const {inspect} = require('util'); const Logger = require('./logger'); -const BrowserName = require('./browsername.js'); +const BrowserName = require('./browsername'); const LocateStrategy = require('./locatestrategy.js'); const PeriodicPromise = require('./periodic-promise.js'); -const createPromise = require('./createPromise.js'); -const isErrorObject = require('./isErrorObject.js'); -const alwaysDisplayError = require('./alwaysDisplayError.js'); +const createPromise = require('./createPromise'); +const isErrorObject = require('./isErrorObject'); +const alwaysDisplayError = require('./alwaysDisplayError'); const Screenshots = require('./screenshots.js'); const Snapshots = require('./snapshots.js'); const TimedCallback = require('./timed-callback.js'); -const getFreePort = require('./getFreePort.js'); +const getFreePort = require('./getFreePort'); const requireModule = require('./requireModule.js'); const getAllClassMethodNames = require('./getAllClassMethodNames.js'); const VERSION = require('./version.js'); @@ -151,11 +151,11 @@ class Utils { } static formatElapsedTime(timeMs, includeMs = false) { - const seconds = timeMs/1000; + const seconds = timeMs / 1000; return (seconds < 1 && timeMs + 'ms') || (seconds > 1 && seconds < 60 && (seconds + 's')) || - (Math.floor(seconds/60) + 'm' + ' ' + Math.floor(seconds%60) + 's' + (includeMs ? (' / ' + timeMs + 'ms') : '')); + (Math.floor(seconds / 60) + 'm' + ' ' + Math.floor(seconds % 60) + 's' + (includeMs ? (' / ' + timeMs + 'ms') : '')); } /** @@ -200,7 +200,7 @@ class Utils { return ''; } - return (offset > 0 && (string.charAt(offset-1) !== ' ') ? ' ':'') + $1; + return (offset > 0 && (string.charAt(offset - 1) !== ' ') ? ' ' : '') + $1; }); const words = moduleName.split(nameSeparatorRegxp).map(function(word, index, matches) { @@ -261,7 +261,7 @@ class Utils { } } parentFolder = this.isFileNameValid(parentFolder) ? '' : parentFolder; - + return path.join(parentFolder, diffInFolder, moduleName); } @@ -526,7 +526,7 @@ class Utils { /** * Writes content to file. Creates parent folders if the folders do not exist. * @param {string} filePath - * @param {string} data + * @param {string} data */ static writeToFile(filePath, data, encoding = null) { const dir = path.resolve(filePath, '..'); @@ -578,7 +578,7 @@ class Utils { if (Utils.fileExistsSync(projectTsFileLocation1)) { return projectTsFileLocation1; - } + } if (Utils.fileExistsSync(projectTsFileLocation2)) { return projectTsFileLocation2; } @@ -590,6 +590,7 @@ class Utils { try { require('ts-node').register({ esm: false, + transpileOnly: true, project: projectTsFile }); } catch (err) { @@ -629,7 +630,7 @@ class Utils { static isLocalhost(webdriver = {}) { const {host} = webdriver; - + return ['127.0.0.1', 'localhost'].indexOf(host) > -1; } @@ -643,7 +644,7 @@ class Utils { return fn; } - + static stringifyObject(objects) { const objectString = Utils.isObject(objects) ? inspect(objects) : objects; @@ -693,8 +694,6 @@ class Utils { } } - - lodashMerge(Utils, { PrimitiveTypes, BrowserName, @@ -716,7 +715,6 @@ lodashMerge(Utils, { Screenshots, Snapshots, TimedCallback, - getFreePort, VERSION, diff --git a/lib/utils/isErrorObject.js b/lib/utils/isErrorObject.ts similarity index 71% rename from lib/utils/isErrorObject.js rename to lib/utils/isErrorObject.ts index bb2955ff71..7b3a827cb5 100644 --- a/lib/utils/isErrorObject.js +++ b/lib/utils/isErrorObject.ts @@ -1,3 +1,3 @@ -module.exports = function(err) { +export = function(err: unknown) { return err instanceof Error || Object.prototype.toString.call(err) === '[object Error]'; -}; \ No newline at end of file +}; diff --git a/lib/utils/locatestrategy.js b/lib/utils/locatestrategy.js index 65e8862a85..fdb88d16d5 100644 --- a/lib/utils/locatestrategy.js +++ b/lib/utils/locatestrategy.js @@ -46,4 +46,4 @@ class LocateStrategy { } } -module.exports = LocateStrategy; \ No newline at end of file +module.exports = LocateStrategy; diff --git a/lib/utils/logger/index.js b/lib/utils/logger/index.js index 32ff0e196c..329b765ea0 100644 --- a/lib/utils/logger/index.js +++ b/lib/utils/logger/index.js @@ -5,14 +5,14 @@ const AssertionError = require('assertion-error'); const lodashEscape = require('lodash.escape'); const LogSettings = require('./log_settings.js'); -const {colors, colorsEnabled, disableColors} = require('../colors.js'); +const chalkColors = require('../chalkColors.js'); -const addDetailedError = require('../addDetailedError.js'); +const addDetailedError = require('../addDetailedError'); const Errors = require('../../transport/errors'); const beautifyStackTrace = require('../beautifyStackTrace'); -const alwaysDisplayError = require('../alwaysDisplayError.js'); +const alwaysDisplayError = require('../alwaysDisplayError'); const Severity = { LOG: 'LOG', @@ -49,7 +49,7 @@ function inspectObject(obj) { return util.inspect(obj, { showHidden: false, depth: 4, - colors: colorsEnabled + colors: chalkColors.colorsEnabled }) .replace(/^\s{2}/gm, ' ') .replace(/^}$/m, ' }'); @@ -62,7 +62,7 @@ function logObject(obj) { function logTimestamp(d) { if (LogSettings.isLogTimestamp()) { - return colors.white(timestamp(d, LogSettings.timestampFormat)); + return chalkColors.colors.white.bold(timestamp(d, LogSettings.timestampFormat)); } return ''; @@ -73,6 +73,7 @@ function logMessage(type, message, args, alwaysShow) { return; } + const colors = chalkColors.colors; let messageStr = ''; let logMethod = 'log'; let prefix; @@ -82,24 +83,28 @@ function logMessage(type, message, args, alwaysShow) { switch (type) { case Severity.ERROR: - prefix = colors.yellow(type, colors.background.black); - messageStr = colors.light_red(message); + prefix = colors.bgBlack.yellow.bold(type); + // eslint-disable-next-line no-control-regex + messageStr = message.match(/\u001b\[.*?m/) ? message : colors.red.bold(message); logMethod = 'error'; break; case Severity.INFO: - prefix = colors.light_purple(type, colors.background.black); - messageStr = colors.light_cyan(message); + prefix = colors.bgBlack.magenta.bold(type); + // eslint-disable-next-line no-control-regex + messageStr = message.match(/\u001b\[.*?m/) ? message : colors.cyan.bold(message); break; case Severity.LOG: - prefix = colors.white(type + ' ', colors.background.black); - messageStr = colors.white(message); + prefix = colors.bgBlack.white.bold(type + ' '); + // eslint-disable-next-line no-control-regex + messageStr = message.match(/\u001b\[.*?m/) ? message : colors.white.bold(message); break; case Severity.WARN: - prefix = colors.light_green(type, colors.background.black); - messageStr = colors.light_green(message); + prefix = colors.bgBlack.green.bold(type); + // eslint-disable-next-line no-control-regex + messageStr = message.match(/\u001b\[.*?m/) ? message : colors.green.bold(message); logMethod = 'warn'; break; } @@ -184,7 +189,8 @@ class Logger { } constructor() { - this.colors = colors; + this.colors = chalkColors.colors; + this.output = []; this.commandOutput = []; this.testSectionOutput = []; @@ -252,8 +258,7 @@ class Logger { } disableColors() { - disableColors(); - this.colors = colors; + chalkColors.disable(); } setHtmlReporterEnabled(value) { @@ -332,7 +337,7 @@ class Logger { getFailedAssertions(assertions, modulepath) { return assertions.reduce((prev, a) => { if (a.failure !== false) { - prev.push(' ' + colors.light_red(`→${this.getErrorContent(a, modulepath)}`)); + prev.push(` ${this.colors.red.bold('→') + this.getErrorContent(a, modulepath)}`); } return prev; @@ -355,20 +360,23 @@ class Logger { const content = []; - let errorTitle = ` ${colors.light_red(errorObj.name)}`; + let errorTitle = ` ${this.colors.light_red(errorObj.name)}`; if (this.isAssertionError(error) || alwaysDisplayError(error)) { - errorTitle = ` ✖${errorTitle}`; + errorTitle = ` ${this.colors.red.bold('✖') + errorTitle}`; } const message = []; if (errorObj.message || errorObj.fullMsg) { - message.push(colors.red(errorObj.message)); + let errorObjMessage = errorObj.message; + // eslint-disable-next-line no-control-regex + errorObjMessage = errorObjMessage.match(/\u001b\[.*?m/) ? errorObjMessage : this.colors.red(errorObjMessage); + message.push(errorObjMessage); if (errorObj.detailedErr) { - message.push(colors.light_green(errorObj.detailedErr)); + message.push(this.colors.light_green(errorObj.detailedErr)); if (errorObj.extraDetail) { - message.push(colors.light_green(errorObj.extraDetail)); + message.push(this.colors.light_green(errorObj.extraDetail)); } } } @@ -379,7 +387,7 @@ class Logger { if (!showStack && errorObj.reportShown) { return ' ' + message.join('\n '); } - + if (!showStack && !this.isAssertionError(error) && !error.help) { return '\n' + boxen(`${message.join('\n')}\n`, {padding: 1, borderColor: 'red'}) + '\n'; } @@ -394,14 +402,14 @@ class Logger { } if (errorObj.help) { - content.push(colors.brown('\n Try fixing by :')); + content.push(this.colors.brown('\n Try fixing by :')); errorObj.help.forEach((step, index) => { - content.push(` ${colors.blue(index+1)}. ${step}`); + content.push(` ${this.colors.blue(index + 1)}. ${step}`); }); } - + if (errorObj.link){ - content.push(`\n ${colors.brown('Read More')} : \n ${colors.cyan(errorObj.link)} `); + content.push(`\n ${this.colors.brown('Read More')} : \n ${this.colors.cyan(errorObj.link)} `); } let stack = error.stack || error.stackTrace; @@ -410,20 +418,20 @@ class Logger { const beautified = beautifyStackTrace(stack, true, modulepath); if (beautified) { - content.push(colors.brown('\n Error location:')); + content.push(this.colors.brown('\n Error location:')); content.push(beautified); } - + if (alwaysDisplayError(errorObj)) { - content.push(colors.brown(' Stack Trace :')); - const coloredStack = stack.split('\n').map((line) => colors.stack_trace(line)).join('\n'); + content.push(this.colors.brown(' Stack Trace :')); + const coloredStack = stack.split('\n').map((line) => this.colors.stack_trace(line)).join('\n'); content.push(`${coloredStack}\n`); } } if (content.length === 2) { if (content[0].includes('WebDriverError')) { - return content.join(colors.light_red(':')); + return content.join(this.colors.light_red(':')); } if (content[0].includes('Error') && content[1].includes('Error while')) { @@ -438,7 +446,7 @@ class Logger { formatMessage(msg, ...args) { args = args.map(val => { - return colors.brown(val); + return this.colors.brown(val); }); return util.format(msg, ...args); @@ -512,3 +520,14 @@ module.exports.collectTestHooksOutput = function() { return testHooksOutput; }; + +module.exports.reset = function() { + const instance = Logger.getInstance(); + + if (!instance) { + return; + } + + instance.testSectionOutput = []; + instance.output = []; +}; diff --git a/lib/utils/logger/log_settings.js b/lib/utils/logger/log_settings.js index ae38580660..7906ced82c 100644 --- a/lib/utils/logger/log_settings.js +++ b/lib/utils/logger/log_settings.js @@ -1,103 +1,106 @@ -const lodashClone = require('lodash.clone'); - -const Settings = { - outputEnabled: true, - showResponseHeaders: false, - showRequestData: { - enabled: true, - trimLongScripts: true - }, - detailedOutput: true, - disableErrorLog: false, - log_timestamp: false, - timestamp_format: null, - enabled: true -}; - -module.exports = new (function() { - const logSettings = lodashClone(Settings, true); - - class LogSettings { - get outputEnabled() { - return logSettings.outputEnabled; - } - - get detailedOutput() { - return logSettings.detailedOutput; - } +class LogSettings { + #outputEnabled; + #showResponseHeaders; + #showRequestData; + #detailedOutput; + #disableErrorLog; + #log_timestamp; + #timestamp_format; + #enabled; + #htmlReporterEnabled; + + constructor() { + this.#outputEnabled = true; + this.#showResponseHeaders = false; + this.#showRequestData = { + enabled: true, + trimLongScripts: true + }, + this.#detailedOutput = true; + this.#disableErrorLog = false; + this.#log_timestamp = false; + this.#timestamp_format = null; + this.#enabled = true; + this.#htmlReporterEnabled = false; + } - get showRequestData() { - return logSettings.showRequestData; - } + get outputEnabled() { + return this.#outputEnabled; + } - get enabled() { - return logSettings.enabled; - } + get detailedOutput() { + return this.#detailedOutput; + } - get showResponseHeaders() { - return logSettings.showResponseHeaders; - } + get showRequestData() { + return this.#showRequestData; + } - get timestampFormat() { - return logSettings.timestamp_format; - } + get enabled() { + return this.#enabled; + } - set outputEnabled(value) { - if (typeof value == 'undefined') { - value = true; - } + get showResponseHeaders() { + return this.#showResponseHeaders; + } - logSettings.outputEnabled = value; - } + get timestampFormat() { + return this.#timestamp_format; + } - set detailedOutput(value) { - logSettings.detailedOutput = value; + set outputEnabled(value) { + if (typeof value === 'undefined') { + value = true; } - set disableErrorLog(value) { - if (typeof value == 'undefined') { - value = true; - } + this.#outputEnabled = value; + } - logSettings.disableErrorLog = value; - } + set detailedOutput(value) { + this.#detailedOutput = value; + } - set htmlReporterEnabled(value) { - logSettings.htmlReporterEnabled = value; + set disableErrorLog(value) { + if (typeof value === 'undefined') { + value = true; } - get htmlReporterEnabled() { - return logSettings.htmlReporterEnabled; - } + this.#disableErrorLog = value; + } - isLogTimestamp() { - return logSettings.log_timestamp; - } + set htmlReporterEnabled(value) { + this.#htmlReporterEnabled = value; + } - isErrorLogEnabled() { - return !logSettings.disableErrorLog; - } + get htmlReporterEnabled() { + return this.#htmlReporterEnabled; + } - disable() { - logSettings.enabled = false; - } + isLogTimestamp() { + return this.#log_timestamp; + } - enable() { - logSettings.enabled = true; - } + isErrorLogEnabled() { + return !this.#disableErrorLog; + } - setLogTimestamp(val, format) { - logSettings.log_timestamp = val; - logSettings.timestamp_format = format; - } + disable() { + this.#enabled = false; + } - setHttpLogOptions({showRequestData, showResponseHeaders}) { - logSettings.showRequestData = showRequestData; - logSettings.showResponseHeaders = showResponseHeaders; - } + enable() { + this.#enabled = true; + } + setLogTimestamp(val, format) { + this.#log_timestamp = val; + this.#timestamp_format = format; + } + setHttpLogOptions({showRequestData, showResponseHeaders}) { + this.#showRequestData = showRequestData; + this.#showResponseHeaders = showResponseHeaders; } +} - return new LogSettings(); -})(); \ No newline at end of file +module.exports = new LogSettings(); diff --git a/lib/utils/mobile.js b/lib/utils/mobile.js index 9d74c08cd4..2799b3b298 100644 --- a/lib/utils/mobile.js +++ b/lib/utils/mobile.js @@ -16,7 +16,7 @@ function requireMobileHelper() { } else if (!semver.satisfies(process.version, '>=14.0.0')) { err.message = 'You are using Node ' + process.version + ', but @nightwatch/mobile-helper requires Node >= v14.0.0.\nPlease upgrade your Node version.'; } - + err.showTrace = false; err.displayed = false; @@ -26,7 +26,7 @@ function requireMobileHelper() { /** * check if target is Android - * @param {Object} desiredCapabilities + * @param {Object} desiredCapabilities * @returns {Boolean} */ function isAndroid(desiredCapabilities = {}){ @@ -47,7 +47,7 @@ function isAndroid(desiredCapabilities = {}){ /** * check if target is iOS Device - * @param {Object} desiredCapabilities + * @param {Object} desiredCapabilities * @returns {Boolean} */ function isIos(desiredCapabilities = {}) { @@ -62,7 +62,7 @@ function isIos(desiredCapabilities = {}) { /** * check if target is Simulator - * @param {Object} desiredCapabilities + * @param {Object} desiredCapabilities * @returns {Boolean} */ function isSimulator(desiredCapabilities){ @@ -75,7 +75,7 @@ function isSimulator(desiredCapabilities){ /** * check if target is Real iOS Device - * @param {Object} desiredCapabilities + * @param {Object} desiredCapabilities * @returns {Boolean} */ function isRealIos(desiredCapabilities) { @@ -88,7 +88,7 @@ function isRealIos(desiredCapabilities) { /** * check if the target is a mobile platform - * @param {Object} desiredCapabilities + * @param {Object} desiredCapabilities * @returns {Boolean} */ function isMobile(desiredCapabilities){ @@ -101,14 +101,14 @@ function isMobile(desiredCapabilities){ /** * Check if Real iOS device UDID is correct - * @param {String} udid + * @param {String} udid * @returns {String} */ function iosRealDeviceUDID(udid){ if (udid.length > 25) { return udid; - } - + } + if (udid.length < 24) { throw new Error('Incorrect UDID provided for real iOS device'); } @@ -117,7 +117,7 @@ function iosRealDeviceUDID(udid){ }; /** - * Function to kill iOS Simulator + * Function to kill iOS Simulator * @param {String} udid */ function killSimulator(udid) { @@ -233,7 +233,7 @@ class AndroidConnectionError extends Error { help = [ `${Logger.colors.cyan('adb')} binary not found. Run command ${Logger.colors.cyan('npx @nightwatch/mobile-helper android')} to setup the missing requirements.` ]; - } else { + } else { if (binaryLocation === 'PATH') { binaryLocation = 'adb'; } @@ -287,7 +287,7 @@ class IosSessionNotCreatedError extends Error { `If it doesn't work, try running: ${Logger.colors.cyan('npx run @nightwatch/mobile-helper ios')}` ]; } else { - help= [ + help = [ `Make sure you have passed correct ${Logger.colors.green('deviceId')} in the command (for e.g : ${Logger.colors.cyan('--deviceId 00008030-00024C2C3453402E')})`, `Or pass ${Logger.colors.green('safari:deviceUDID')} capability in config`, `To verify the deviceId run, ${Logger.colors.cyan('system_profiler SPUSBDataType | sed -n \'/iPhone/,/Serial/p\' | grep \'Serial Number:\' | awk -F \': \' \'{print $2}')}`, diff --git a/lib/utils/periodic-promise.js b/lib/utils/periodic-promise.js index 685fa02943..d77cc2492b 100644 --- a/lib/utils/periodic-promise.js +++ b/lib/utils/periodic-promise.js @@ -1,5 +1,5 @@ const EventEmitter = require('events'); -const createPromise = require('./createPromise.js'); +const createPromise = require('./createPromise'); class PeriodicPromise extends EventEmitter { get rescheduleInterval() { diff --git a/lib/utils/printVersionInfo.js b/lib/utils/printVersionInfo.js index 44b5c33368..0322f67a5a 100644 --- a/lib/utils/printVersionInfo.js +++ b/lib/utils/printVersionInfo.js @@ -8,4 +8,4 @@ module.exports = function() { console.log(' version: ' + VERSION.full); // eslint-disable-next-line no-console console.log(' changelog: https://github.com/nightwatchjs/nightwatch/releases/tag/v' + VERSION.full + '\n'); -}; \ No newline at end of file +}; diff --git a/lib/utils/safeStringify.js b/lib/utils/safeStringify.js index 7d19ff2e19..86a33b6007 100644 --- a/lib/utils/safeStringify.js +++ b/lib/utils/safeStringify.js @@ -28,17 +28,17 @@ class SafeStringify { const result = Object.keys(obj).reduce((result, prop) => { result[prop] = this.visit(obj[prop], seen); - + return result; }, {}); seen.pop(); - + return result; } static safeJSON(obj) { - const seen = []; - + const seen = []; + return this.visit(obj, seen); } @@ -47,4 +47,4 @@ class SafeStringify { } } -module.exports = SafeStringify; \ No newline at end of file +module.exports = SafeStringify; diff --git a/lib/utils/snapshots.js b/lib/utils/snapshots.js index b1226deb6f..ec718eb1af 100644 --- a/lib/utils/snapshots.js +++ b/lib/utils/snapshots.js @@ -15,7 +15,7 @@ class Snapshots { if (typeof traceSettings.filename_format == 'function') { filename_format = traceSettings.filename_format.bind(traceSettings); } else { - filename_format = Defaults.snapshots.filename_format; + filename_format = Defaults.trace.filename_format; } const base_path = traceSettings.path || `${output_folder}/snapshots`; diff --git a/lib/utils/stackTrace.js b/lib/utils/stackTrace.js index a1314123f5..ba60877744 100644 --- a/lib/utils/stackTrace.js +++ b/lib/utils/stackTrace.js @@ -1,11 +1,11 @@ const boxen = require('boxen'); -const {colors} = require('./colors.js'); -const isErrorObject = require('./isErrorObject.js'); -const addDetailedError = require('./addDetailedError.js'); +const {colors} = require('./chalkColors.js'); +const isErrorObject = require('./isErrorObject'); +const addDetailedError = require('./addDetailedError'); const indentRegex = /^/gm; const stackTraceFilter = function (parts) { - let stack = parts.reduce(function(list, line) { + const stack = parts.reduce(function(list, line) { if (contains(line, [ 'node_modules', '(node.js:', @@ -61,13 +61,13 @@ const filterStackTrace = function(stackTrace = '') { }; const showStackTrace = function (stack) { - let parts = stack.split('\n'); - let headline = parts.shift(); + const parts = stack.split('\n'); + const headline = parts.shift(); console.error(colors.red(headline.replace(indentRegex, ' '))); if (parts.length > 0) { - let result = stackTraceFilter(parts); + const result = stackTraceFilter(parts); console.error(colors.stack_trace(result.replace(indentRegex, ' '))); } }; @@ -116,4 +116,4 @@ module.exports = { filterStack, filterStackTrace, showStackTrace -}; \ No newline at end of file +}; diff --git a/lib/utils/timed-callback.js b/lib/utils/timed-callback.js index 084094613a..2d9fe34586 100644 --- a/lib/utils/timed-callback.js +++ b/lib/utils/timed-callback.js @@ -49,7 +49,7 @@ class TimedCallback { createTimeout() { this.timeoutId = setTimeout(() => { - let err = new TimeoutError(`done() callback timeout of ${this.timeoutMs}ms was reached while executing "${this.name}".` + + const err = new TimeoutError(`done() callback timeout of ${this.timeoutMs}ms was reached while executing "${this.name}".` + ' Make sure to call the done() callback when the operation finishes.'); this.onTimeoutExpired(err, this.name, this.timeoutMs); }, this.timeoutMs); @@ -58,4 +58,4 @@ class TimedCallback { } } -module.exports = TimedCallback; \ No newline at end of file +module.exports = TimedCallback; diff --git a/lib/utils/types/index.ts b/lib/utils/types/index.ts new file mode 100644 index 0000000000..9ed3824b82 --- /dev/null +++ b/lib/utils/types/index.ts @@ -0,0 +1,5 @@ +export interface NightwatchError extends Error { + detailedErr: string; + link: string; + help: string[]; +} diff --git a/lib/utils/version.js b/lib/utils/version.js index 7641f27ba0..7c7bf9e384 100644 --- a/lib/utils/version.js +++ b/lib/utils/version.js @@ -6,4 +6,4 @@ module.exports = { major: fullVersion.split('.')[0], minor: fullVersion.split('.')[1], patch: fullVersion.split('.').slice(2).join('.') -}; \ No newline at end of file +}; diff --git a/package-lock.json b/package-lock.json index d537ba94be..a11201d58d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nightwatch", - "version": "3.0.1", + "version": "3.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nightwatch", - "version": "3.0.1", + "version": "3.1.3", "license": "MIT", "dependencies": { "@nightwatch/chai": "5.0.2", @@ -19,6 +19,7 @@ "assertion-error": "1.1.0", "boxen": "5.1.2", "chai-nightwatch": "0.5.3", + "chalk": "^4.1.2", "ci-info": "3.3.0", "cli-table3": "^0.6.3", "devtools-protocol": "^0.0.1140464", @@ -36,13 +37,13 @@ "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", - "mocha": "9.2.2", + "mocha": "10.2.0", "nightwatch-axe-verbose": "^2.2.2", "open": "8.4.0", "ora": "5.4.1", "piscina": "^3.2.0", - "selenium-webdriver": "~4.10.0", - "semver": "7.3.5", + "selenium-webdriver": "~4.11.0", + "semver": "7.5.2", "stacktrace-parser": "0.1.10", "strip-ansi": "6.0.1", "untildify": "^4.0.0", @@ -54,10 +55,12 @@ "devDependencies": { "@cucumber/cucumber": "^8.2.1", "@swc/core": "^1.3.67", - "@types/node": "^18.11.7", + "@types/node": "^18.17.3", + "@typescript-eslint/eslint-plugin": "^6.3.0", + "@typescript-eslint/parser": "^6.3.0", "copyfiles": "^2.4.1", - "coveralls": "^3.1.1", - "eslint": "^8.9.0", + "cross-env": "^7.0.3", + "eslint": "^8.46.0", "husky": "^8.0.0", "is-ci": "^3.0.1", "js-yaml": "^3.13.1", @@ -80,7 +83,7 @@ "wait-on": "^7.0.1" }, "engines": { - "node": ">= 14.20.0" + "node": ">= 16" }, "peerDependencies": { "@cucumber/cucumber": "*" @@ -788,16 +791,40 @@ "integrity": "sha512-N43uWud8ZXuVjza423T9ZCIJsaZhFekmakt7S9bvogTxqdVGbRobjR663s0+uW0Rz9e+Pa8I6jUuWtoBLQD2Mw==", "dev": true }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -829,6 +856,15 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/js": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -845,19 +881,32 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -1340,9 +1389,9 @@ "dev": true }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "node_modules/@types/lodash": { @@ -1358,9 +1407,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", - "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==" + "version": "18.17.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.3.tgz", + "integrity": "sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==" }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -1376,6 +1425,12 @@ "@types/ws": "*" } }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, "node_modules/@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -1390,10 +1445,240 @@ "@types/node": "*" } }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.3.0.tgz", + "integrity": "sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.3.0", + "@typescript-eslint/type-utils": "6.3.0", + "@typescript-eslint/utils": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.3.0.tgz", + "integrity": "sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.3.0", + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/typescript-estree": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz", + "integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.3.0.tgz", + "integrity": "sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.3.0", + "@typescript-eslint/utils": "6.3.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.3.0.tgz", + "integrity": "sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.3.0.tgz", + "integrity": "sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.3.0.tgz", + "integrity": "sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.3.0", + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/typescript-estree": "6.3.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz", + "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.3.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, "node_modules/@zeit/schemas": { "version": "2.29.0", @@ -1747,15 +2032,6 @@ "node": ">=0.10.0" } }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -1823,21 +2099,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, "node_modules/axe-core": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", @@ -1870,15 +2131,6 @@ } ] }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2116,12 +2368,6 @@ "upper-case-first": "^2.0.2" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "node_modules/chai-nightwatch": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz", @@ -2536,25 +2782,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - }, - "bin": { - "coveralls": "bin/coveralls.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -2584,6 +2811,24 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2618,18 +2863,6 @@ "node": ">=14" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/data-urls": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", @@ -2898,16 +3131,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "node_modules/ejs": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", @@ -3052,46 +3275,48 @@ } }, "node_modules/eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.1.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -3132,9 +3357,9 @@ "dev": true }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -3142,42 +3367,21 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/argparse": { @@ -3223,14 +3427,14 @@ } }, "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3252,9 +3456,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -3319,12 +3523,6 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "node_modules/extsprintf": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", @@ -3549,29 +3747,6 @@ "node": ">=8.0.0" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -3639,12 +3814,6 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, "node_modules/functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -3712,15 +3881,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -3776,9 +3936,9 @@ } }, "node_modules/globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3826,36 +3986,11 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "engines": { - "node": ">=4.x" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/hard-rejection": { "version": "2.1.0", @@ -4040,21 +4175,6 @@ "node": ">= 6" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -4122,9 +4242,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, "engines": { "node": ">= 4" @@ -4632,12 +4752,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "node_modules/istanbul-lib-coverage": { @@ -4816,12 +4931,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, "node_modules/jsdom": { "version": "21.1.2", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.2.tgz", @@ -4919,12 +5028,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4966,44 +5069,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/jsprim/node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/jsprim/node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -5088,15 +5153,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true, - "bin": { - "lcov-parse": "bin/cli.js" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -5560,15 +5616,6 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" }, - "node_modules/log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true, - "engines": { - "node": ">=0.8.6" - } - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -5898,41 +5945,38 @@ } }, "node_modules/mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dependencies": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "bin": { "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14.0.0" }, "funding": { "type": "opencollective", @@ -5984,27 +6028,6 @@ "concat-map": "0.0.1" } }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6058,16 +6081,24 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" } }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6353,9 +6384,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -6369,6 +6400,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6678,15 +6715,6 @@ "node": ">=6" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7041,12 +7069,6 @@ "node": "*" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7259,15 +7281,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -7572,18 +7585,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/registry-auth-token": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", @@ -7627,48 +7628,6 @@ "node": ">=0.10" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7851,9 +7810,9 @@ "dev": true }, "node_modules/selenium-webdriver": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", - "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.11.1.tgz", + "integrity": "sha512-bvrnr3UZlLScErOmn8gV6cqc+1PYDHn0575CxUR2U14fMWt7OKxSy0lAThhZq4sq4d1HqP8ebz11oiHSlAQ2WA==", "dependencies": { "jszip": "^3.10.1", "tmp": "^0.2.1", @@ -7864,9 +7823,9 @@ } }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -8345,31 +8304,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -8696,19 +8630,6 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", "dev": true }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/tr46": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", @@ -8729,6 +8650,18 @@ "node": ">=8" } }, + "node_modules/ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -8808,24 +8741,6 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8977,12 +8892,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -9145,6 +9054,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9221,9 +9131,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -10002,16 +9912,31 @@ "integrity": "sha512-N43uWud8ZXuVjza423T9ZCIJsaZhFekmakt7S9bvogTxqdVGbRobjR663s0+uW0Rz9e+Pa8I6jUuWtoBLQD2Mw==", "dev": true }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -10036,6 +9961,12 @@ } } }, + "@eslint/js": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "dev": true + }, "@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -10052,16 +9983,22 @@ } }, "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" } }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, "@humanwhocodes/object-schema": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", @@ -10394,9 +10331,9 @@ "dev": true }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "@types/lodash": { @@ -10412,9 +10349,9 @@ "dev": true }, "@types/node": { - "version": "18.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", - "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==" + "version": "18.17.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.3.tgz", + "integrity": "sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==" }, "@types/normalize-package-data": { "version": "2.4.1", @@ -10430,6 +10367,12 @@ "@types/ws": "*" } }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, "@types/uuid": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", @@ -10444,10 +10387,139 @@ "@types/node": "*" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==" + "@typescript-eslint/eslint-plugin": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.3.0.tgz", + "integrity": "sha512-IZYjYZ0ifGSLZbwMqIip/nOamFiWJ9AH+T/GYNZBWkVcyNQOFGtSMoWV7RvY4poYCMZ/4lHzNl796WOSNxmk8A==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.3.0", + "@typescript-eslint/type-utils": "6.3.0", + "@typescript-eslint/utils": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.3.0.tgz", + "integrity": "sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "6.3.0", + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/typescript-estree": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz", + "integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.3.0.tgz", + "integrity": "sha512-7Oj+1ox1T2Yc8PKpBvOKWhoI/4rWFd1j7FA/rPE0lbBPXTKjdbtC+7Ev0SeBjEKkIhKWVeZSP+mR7y1Db1CdfQ==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "6.3.0", + "@typescript-eslint/utils": "6.3.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/types": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.3.0.tgz", + "integrity": "sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.3.0.tgz", + "integrity": "sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/visitor-keys": "6.3.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.3.0.tgz", + "integrity": "sha512-hLLg3BZE07XHnpzglNBG8P/IXq/ZVXraEbgY7FM0Cnc1ehM8RMdn9mat3LubJ3KBeYXXPxV1nugWbQPjGeJk6Q==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.3.0", + "@typescript-eslint/types": "6.3.0", + "@typescript-eslint/typescript-estree": "6.3.0", + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz", + "integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.3.0", + "eslint-visitor-keys": "^3.4.1" + } }, "@zeit/schemas": { "version": "2.29.0", @@ -10718,15 +10790,6 @@ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -10778,18 +10841,6 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, "axe-core": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", @@ -10805,15 +10856,6 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -10973,12 +11015,6 @@ "upper-case-first": "^2.0.2" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "chai-nightwatch": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/chai-nightwatch/-/chai-nightwatch-0.5.3.tgz", @@ -11283,19 +11319,6 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - } - }, "crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -11316,6 +11339,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -11341,15 +11373,6 @@ "rrweb-cssom": "^0.6.0" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "data-urls": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", @@ -11548,16 +11571,6 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, "ejs": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", @@ -11671,46 +11684,48 @@ } }, "eslint": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", - "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.1.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.2", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "dependencies": { "argparse": { @@ -11768,47 +11783,30 @@ "dev": true }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", "dev": true }, "espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -11817,9 +11815,9 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -11866,12 +11864,6 @@ "strip-final-newline": "^2.0.0" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, "extsprintf": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", @@ -12044,23 +12036,6 @@ "signal-exit": "^3.0.2" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "fromentries": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", @@ -12104,12 +12079,6 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, "functions-have-names": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", @@ -12153,15 +12122,6 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -12201,9 +12161,9 @@ } }, "globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -12236,27 +12196,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -12390,17 +12335,6 @@ "debug": "4" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -12436,9 +12370,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, "immediate": { @@ -12780,12 +12714,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, "istanbul-lib-coverage": { @@ -12927,12 +12856,6 @@ "esprima": "^4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, "jsdom": { "version": "21.1.2", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-21.1.2.tgz", @@ -13006,12 +12929,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -13045,37 +12962,6 @@ "universalify": "^2.0.0" } }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "dependencies": { - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - } - } - }, "jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", @@ -13158,12 +13044,6 @@ } } }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -13530,12 +13410,6 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -13788,31 +13662,28 @@ } }, "mocha": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.2.2.tgz", - "integrity": "sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", "chokidar": "3.5.3", - "debug": "4.3.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.2.0", - "growl": "1.10.5", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "4.2.1", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.3.1", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.2.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -13832,21 +13703,6 @@ "concat-map": "0.0.1" } }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -13884,11 +13740,21 @@ } }, "minimatch": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-4.2.1.tgz", - "integrity": "sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + } } }, "ms": { @@ -14161,9 +14027,9 @@ "dev": true }, "nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==" }, "natural-compare": { "version": "1.4.0", @@ -14171,6 +14037,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14433,12 +14305,6 @@ } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -14686,12 +14552,6 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -14852,12 +14712,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, "querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -15096,12 +14950,6 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "registry-auth-token": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", @@ -15136,42 +14984,6 @@ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", "dev": true }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -15306,9 +15118,9 @@ "dev": true }, "selenium-webdriver": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", - "integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.11.1.tgz", + "integrity": "sha512-bvrnr3UZlLScErOmn8gV6cqc+1PYDHn0575CxUR2U14fMWt7OKxSy0lAThhZq4sq4d1HqP8ebz11oiHSlAQ2WA==", "requires": { "jszip": "^3.10.1", "tmp": "^0.2.1", @@ -15316,9 +15128,9 @@ } }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", "requires": { "lru-cache": "^6.0.0" } @@ -15670,23 +15482,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, "stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -15942,16 +15737,6 @@ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", "dev": true }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, "tr46": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", @@ -15966,6 +15751,13 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "requires": {} + }, "ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -16016,21 +15808,6 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "dev": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -16138,12 +15915,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, "v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -16272,6 +16043,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -16327,9 +16099,9 @@ } }, "workerpool": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", - "integrity": "sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==" }, "wrap-ansi": { "version": "7.0.0", diff --git a/package.json b/package.json index f94144ef02..33ece1719c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nightwatch", "description": "Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.", - "version": "3.0.1", + "version": "3.1.3", "author": "Andrei Rusu", "homepage": "https://nightwatchjs.org", "main": "./dist/index.js", @@ -25,6 +25,7 @@ "assertion-error": "1.1.0", "boxen": "5.1.2", "chai-nightwatch": "0.5.3", + "chalk": "^4.1.2", "ci-info": "3.3.0", "cli-table3": "^0.6.3", "devtools-protocol": "^0.0.1140464", @@ -42,13 +43,13 @@ "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", - "mocha": "9.2.2", + "mocha": "10.2.0", "nightwatch-axe-verbose": "^2.2.2", "open": "8.4.0", "ora": "5.4.1", "piscina": "^3.2.0", - "selenium-webdriver": "~4.10.0", - "semver": "7.3.5", + "selenium-webdriver": "~4.11.0", + "semver": "7.5.2", "stacktrace-parser": "0.1.10", "strip-ansi": "6.0.1", "untildify": "^4.0.0", @@ -59,9 +60,12 @@ }, "devDependencies": { "@swc/core": "^1.3.67", - "@types/node": "^18.11.7", - "coveralls": "^3.1.1", - "eslint": "^8.9.0", + "@types/node": "^18.17.3", + "@typescript-eslint/eslint-plugin": "^6.3.0", + "@typescript-eslint/parser": "^6.3.0", + "copyfiles": "^2.4.1", + "cross-env": "^7.0.3", + "eslint": "^8.46.0", "husky": "^8.0.0", "is-ci": "^3.0.1", "js-yaml": "^3.13.1", @@ -101,12 +105,12 @@ }, "man": "", "scripts": { - "build": "rm -rf dist && tsc", + "build": "rimraf dist && tsc", "postbuild": "copyfiles -u 1 lib/**/*.ejs dist", "postinstall": "npm run build", "prepublishOnly": "npm run build", - "dev": "NIGHTWATCH_TS_NODE_DEV=true npx ts-node --swc ./bin/nightwatch", - "eslint": "eslint index.js lib bin api examples test --quiet", + "dev": "cross-env NIGHTWATCH_TS_NODE_DEV=true npx ts-node --swc ./bin/nightwatch", + "eslint": "eslint index.js lib bin api examples cucumber-js test --quiet", "mocha": "npm run build && mocha", "mocha-coverage": "nyc --reporter=html mocha test/src/ --recursive", "test": "npm run build && mocha test/src/ --recursive --timeout 20000", @@ -135,7 +139,7 @@ } }, "engines": { - "node": ">= 14.20.0" + "node": ">= 16" }, "keywords": [ "nightwatch", diff --git a/test/apidemos/custom-commands-parallel/testUsingES6AsyncCustomCommands.js b/test/apidemos/custom-commands-parallel/testUsingES6AsyncCustomCommands.js new file mode 100644 index 0000000000..3b60eefe69 --- /dev/null +++ b/test/apidemos/custom-commands-parallel/testUsingES6AsyncCustomCommands.js @@ -0,0 +1,11 @@ +describe('Test Using Sync Custom Command returning NightwatchAPI', function() { + before(browser => { + browser.url('http://localhost'); + }); + + it('sampleTest', browser => { + browser + .otherCommand() + .end(); + }); +}); diff --git a/test/apidemos/web-elements/waitUntilElementNotPresent.js b/test/apidemos/web-elements/waitUntilElementNotPresent.js new file mode 100644 index 0000000000..f19ea5ecc6 --- /dev/null +++ b/test/apidemos/web-elements/waitUntilElementNotPresent.js @@ -0,0 +1,5 @@ +describe('demo of failure of waitUntil element commands', function() { + it('waitUntil element is visible - element not present', function({element}) { + element.find('#badElement').waitUntil('visible'); + }); +}); \ No newline at end of file diff --git a/test/apidemos/web-elements/waitUntilTest.js b/test/apidemos/web-elements/waitUntilTest.js index c56d45200e..4abd1e3c4d 100644 --- a/test/apidemos/web-elements/waitUntilTest.js +++ b/test/apidemos/web-elements/waitUntilTest.js @@ -4,11 +4,15 @@ describe('demo tests using waitUntil element APIs', function() { }); it('wait until element is selected', function({element, state}) { - element('#weblogin').waitUntil('selected'); + element('#weblogin').waitUntil('present').waitUntil('selected'); }); it('wait until element is enabled', function({element}) { element('#weblogin').waitUntil('enabled'); }); + + it('wait until with custom message', function({element}) { + element('#weblogin').waitUntil('enabled', {message: 'elemento %s no era presente en %d ms'}); + }); }); diff --git a/test/extra/commands/other/otherCommand.js b/test/extra/commands/other/otherCommand.js index 1de68f562d..b49cef30e4 100644 --- a/test/extra/commands/other/otherCommand.js +++ b/test/extra/commands/other/otherCommand.js @@ -1,5 +1,5 @@ -module.exports = { - command: function() { - return this; +module.exports = class OtherCommand { + command() { + return this.api.pause(10); } }; diff --git a/test/extra/pageobjects/commands/workingCommandsClass.js b/test/extra/pageobjects/commands/workingCommandsClass.js index a0074cc355..cd12a7e53b 100644 --- a/test/extra/pageobjects/commands/workingCommandsClass.js +++ b/test/extra/pageobjects/commands/workingCommandsClass.js @@ -5,7 +5,7 @@ module.exports = class RealCommands { selector, suppressNotFoundErrors: true }, function(result) { - return callback(result ? result.value: []); + return callback(result ? result.value : []); }); } diff --git a/test/extra/parallelism-customCommandsSync.json b/test/extra/parallelism-customCommandsSync.json new file mode 100644 index 0000000000..a80ae994a8 --- /dev/null +++ b/test/extra/parallelism-customCommandsSync.json @@ -0,0 +1,20 @@ +{ + "src_folders" : ["./test/apidemos/custom-commands-parallel"], + "test_workers": true, + "custom_commands_path": ["./test/extra/commands/other"], + "persist_globals": true, + "output_folder" : false, + "output" : false, + "silent": false, + "selenium" : { + "start_process" : false, + "port": 10195 + }, + "test_settings": { + "default" : { + "desiredCapabilities" : { + "browserName" : "firefox" + } + } + } +} diff --git a/test/lib/command-mocks.js b/test/lib/command-mocks.js index 8c08da65b3..4f79dcc93a 100644 --- a/test/lib/command-mocks.js +++ b/test/lib/command-mocks.js @@ -269,7 +269,7 @@ module.exports = { }, true); }, - w3cSelected(elementId ='5cc459b8-36a8-3042-8b4a-258883ea642b', value = true) { + w3cSelected(elementId = '5cc459b8-36a8-3042-8b4a-258883ea642b', value = true) { MockServer.addMock({ url: `/session/13521-10219-202/element/${elementId}/selected`, method: 'GET', @@ -279,7 +279,7 @@ module.exports = { }, true); }, - w3cEnabled(elementId ='5cc459b8-36a8-3042-8b4a-258883ea642b', value = true) { + w3cEnabled(elementId = '5cc459b8-36a8-3042-8b4a-258883ea642b', value = true) { MockServer.addMock({ url: `/session/13521-10219-202/element/${elementId}/enabled`, method: 'GET', diff --git a/test/lib/nightwatch.js b/test/lib/nightwatch.js index db72dfc1a7..85d8e218ee 100644 --- a/test/lib/nightwatch.js +++ b/test/lib/nightwatch.js @@ -46,7 +46,7 @@ module.exports = new function () { } this.createClient = function(options = {}, reporter = null, argv = {}) { - let opts = { + const opts = { selenium: { port: 10195, host: 'localhost', @@ -118,7 +118,7 @@ module.exports = new function () { }); }; - this.initW3CClient = function(opts = {}, argv={}) { + this.initW3CClient = function(opts = {}, argv = {}) { const settings = Object.assign({ selenium: { version2: false, diff --git a/test/lib/nocks.js b/test/lib/nocks.js index e40d4f5195..34820bf6e3 100644 --- a/test/lib/nocks.js +++ b/test/lib/nocks.js @@ -191,7 +191,7 @@ module.exports = { return this; }, - childElementsNotFound(selector='#badElement') { + childElementsNotFound(selector = '#badElement') { nock('http://localhost:10195') .post('/wd/hub/session/1352110219202/element/0/elements', {'using': 'css selector', 'value': selector}) .reply(200, { @@ -203,14 +203,14 @@ module.exports = { return this; }, - childElementsFound(selector='#weblogin') { + childElementsFound(selector = '#weblogin') { nock('http://localhost:10195') .post('/wd/hub/session/1352110219202/element/0/elements', {'using': 'css selector', 'value': selector}) .reply(200, { status: 0, state: 'success', value: [{'element-6066-11e4-a52e-4f735466cecf': '0'}] - }) + }); return this; }, @@ -589,7 +589,7 @@ module.exports = { .reply(200, { status: 0, state: 'success', - value : { + value: { domain: 'cookie-domain', name: name, value: value diff --git a/test/src/analytics/testAnalytics.js b/test/src/analytics/testAnalytics.js index 0bbb297edb..fa5d869f6f 100644 --- a/test/src/analytics/testAnalytics.js +++ b/test/src/analytics/testAnalytics.js @@ -37,7 +37,7 @@ describe('test analytics utility', function() { it('should throw error if event parameters contain objects or arrays', function() { assert.rejects(async function() { - await analytics.event('test', { + await analytics.collectEvent('test', { foo: { bar: 'bas' } @@ -45,28 +45,43 @@ describe('test analytics utility', function() { }); assert.rejects(async function() { - await analytics.event('test', { + await analytics.collectEvent('test', { foo: [1, 2, 3] }); }); }); it('should write events to a log file', async function() { - await analytics.event('test', { + const flushFn = analytics.__flush; + let flushCalled = false; + analytics.__flush = function() { + flushCalled = true; + }; + + await analytics.collectEvent('test', { foo: 'bar' }); const logFile = analytics.__getLogFileLocation(); - + try { const stats = await fs.stat(logFile); assert.ok(stats.size > 0); + assert.ok(flushCalled); } catch (e) { assert.fail(e); } + + analytics.__flush = flushFn; }); it('should have default parameters', async function() { - await analytics.event('test', { + const flushFn = analytics.__flush; + let flushCalled = false; + analytics.__flush = function() { + flushCalled = true; + }; + + await analytics.collectEvent('test', { foo: 'bar' }); @@ -79,12 +94,15 @@ describe('test analytics utility', function() { assert.ok(logFileJson.params.env_os); assert.ok(logFileJson.params.env_nw_version); assert.ok(logFileJson.params.env_node_version); + assert.ok(flushCalled); + + analytics.__flush = flushFn; }); it('should send analytics data to GA', async function() { const analyticsNock = Nocks.analyticsCollector(analytics.__getGoogleAnalyticsPath()); - await analytics.event('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); await analytics.__flush().then((res) => { analyticsNock.done(); @@ -103,12 +121,12 @@ describe('test analytics utility', function() { called = true; }; - await analytics.event('test', {log: 'log'}); - await analytics.event('test', {log: 'log'}); - await analytics.event('test', {log: 'log'}); - await analytics.event('test', {log: 'log'}); - await analytics.event('test', {log: 'log'}); - await analytics.event('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); assert.ok(called); analytics.__flush = flushBack; @@ -137,7 +155,7 @@ describe('test analytics utility', function() { called = true; }; analytics.updateSettings(settings); - await analytics.event('test', {log: 'log'}); + await analytics.collectEvent('test', {log: 'log'}); } assert.ok(called); @@ -147,34 +165,42 @@ describe('test analytics utility', function() { }); it('should report only allowed exceptions', async function() { + const flushFn = analytics.__flush; + let flushCalled = false; + analytics.__flush = function() { + flushCalled = true; + }; + const analyticsNock = Nocks.analyticsCollector(analytics.__getGoogleAnalyticsPath()); const err = new Error('test'); - err.name ='UserGeneratedError'; + err.name = 'UserGeneratedError'; - await analytics.exception(err); + await analytics.collectErrorEvent(err); + + assert.ok(flushCalled); + analytics.__flush = flushFn; const logFile = analytics.__getLogFileLocation(); let logFileContent = await fs.readFile(logFile, 'utf8'); let logFileJson = JSON.parse(logFileContent); - assert.ok(logFileJson.params.errorName === 'CustomError'); + assert.equal(logFileJson.params.err_name, 'UserGeneratedError'); await analytics.__flush().then((res) => { analyticsNock.done(); }).catch((err) => { assert.strictEqual(err, undefined); }); - const analyticsNock2 = Nocks.analyticsCollector(analytics.__getGoogleAnalyticsPath()); err.name = 'SyntaxError'; - await analytics.exception(err); - + await analytics.collectErrorEvent(err); + logFileContent = await fs.readFile(logFile, 'utf8'); logFileJson = JSON.parse(logFileContent); - - assert.ok(logFileJson.params.errorName === 'SyntaxError'); + + assert.equal(logFileJson.params.err_name, 'SyntaxError'); await analytics.__flush().then((res) => { analyticsNock2.done(); diff --git a/test/src/api/commands/client/testCaptureNetworkRequests.js b/test/src/api/commands/client/testCaptureNetworkRequests.js index 90920f1f63..fea08563b2 100644 --- a/test/src/api/commands/client/testCaptureNetworkRequests.js +++ b/test/src/api/commands/client/testCaptureNetworkRequests.js @@ -85,6 +85,73 @@ describe('.captureNetworkRequests()', function () { }); }); + it('browser.network.captureRequests()', function (done) { + + MockServer.addMock({ + url: '/session', + response: { + value: { + sessionId: '13521-10219-202', + capabilities: { + browserName: 'chrome', + browserVersion: '92.0' + } + } + }, + method: 'POST', + statusCode: 201 + }, true); + + Nightwatch.initW3CClient({ + desiredCapabilities: { + browserName: 'chrome', + 'goog:chromeOptions': {} + }, + output: process.env.VERBOSE === '1', + silent: false + }).then(client => { + const expected = {}; + + const cdpNetworkEvent = JSON.stringify({ + method: 'Network.requestWillBeSent', + params: { + request: { + url: 'https://www.google.com', + method: 'GET', + headers: [] + } + } + }); + + cdp.resetConnection(); + client.transport.driver.createCDPConnection = function() { + return Promise.resolve({ + _wsConnection: { + on: (event, callback) => { + expected['wsEvent'] = event; + callback(cdpNetworkEvent); + } + }, + execute: function(command, params) { + expected['cdpCommand'] = command; + expected['cdpParams'] = params; + } + }); + }; + + const userCallback = (requestParams) => { + expected['requestParams'] = requestParams; + }; + client.api.network.captureRequests(userCallback, function () { + assert.deepEqual(expected.cdpCommand, 'Network.enable'); + assert.deepEqual(expected.cdpParams, {}); + assert.strictEqual(expected.wsEvent, 'message'); + assert.deepEqual(expected.requestParams, JSON.parse(cdpNetworkEvent).params); + }); + client.start(done); + }); + }); + it('throws error without callback', function (done) { MockServer.addMock({ diff --git a/test/src/api/commands/client/testMockNetworkResponse.js b/test/src/api/commands/client/testMockNetworkResponse.js index 3d9f812edb..c5cf2b8c97 100644 --- a/test/src/api/commands/client/testMockNetworkResponse.js +++ b/test/src/api/commands/client/testMockNetworkResponse.js @@ -98,6 +98,86 @@ describe('.mockNetworkResponse()', function () { }); }); + it('browser.network.mockResponse(urlToIntercept, {status, headers, body}) with url match', function (done) { + MockServer.addMock({ + url: '/session', + response: { + value: { + sessionId: '13521-10219-202', + capabilities: { + browserName: 'chrome', + browserVersion: '92.0' + } + } + }, + method: 'POST', + statusCode: 201 + }, true); + + Nightwatch.initW3CClient({ + desiredCapabilities: { + browserName: 'chrome', + 'goog:chromeOptions': {} + }, + output: process.env.VERBOSE === '1', + silent: false + }).then(client => { + const expected = { + cdpCommands: [] + }; + + // Parameters of actual request made by browser + const cdpFetchRequestPauseEvent = JSON.stringify({ + method: 'Fetch.requestPaused', + params: { + requestId: '123', + request: { + url: 'https://www.google.com/' + } + } + }); + + cdp.resetConnection(); + client.transport.driver.createCDPConnection = function() { + return Promise.resolve({ + _wsConnection: { + on: (event, callback) => { + expected['wsEvent'] = event; + callback(cdpFetchRequestPauseEvent); + } + }, + execute: function(command, params) { + expected.cdpCommands.push(command); + if (command === 'Fetch.fulfillRequest') { + expected['requestId'] = params.requestId; + expected['responseCode'] = params.responseCode; + expected['responseHeaders'] = params.responseHeaders; + expected['responseBody'] = params.body; + } + } + }); + }; + + const response = { + status: 200, + headers: {'Content-Type': 'UTF-8'}, + body: 'Hey there!' + }; + client.api.network.mockResponse('https://www.google.com/', response, function () { + // Assert final response with response passed + assert.strictEqual(expected.responseCode, response.status); + assert.deepEqual(expected.responseHeaders, [{name: 'Content-Type', value: 'UTF-8'}]); + assert.strictEqual(expected.responseBody, Buffer.from(response.body, 'utf-8').toString('base64')); + + assert.strictEqual(expected.requestId, JSON.parse(cdpFetchRequestPauseEvent).params.requestId); + assert.strictEqual(expected.wsEvent, 'message'); + assert.deepEqual(expected.cdpCommands, ['Fetch.fulfillRequest', 'Fetch.enable', 'Network.setCacheDisabled']); + }); + + client.start(done); + }); + }); + it('browser.mockNetworkResponse(urlToIntercept, {status, headers, body}) with multiple mocks', function (done) { MockServer.addMock({ url: '/session', diff --git a/test/src/api/commands/client/testRegisterBasicAuth.js b/test/src/api/commands/client/testRegisterBasicAuth.js index 7f4c758e7a..d5ed4e253f 100644 --- a/test/src/api/commands/client/testRegisterBasicAuth.js +++ b/test/src/api/commands/client/testRegisterBasicAuth.js @@ -65,7 +65,7 @@ describe('.registerBasicAuth()', function () { }).then(client => { client.api.registerBasicAuth('admin', 'admin', function(result){ assert.strictEqual(result.status, -1); - assert.strictEqual(result.error, 'RegisterBasicAuth is not supported while using this driver'); + assert.strictEqual(result.error, 'The command .registerBasicAuth() is only supported in Chromium based drivers'); }); client.start(done); diff --git a/test/src/api/commands/client/testSetNetworkConditions.js b/test/src/api/commands/client/testSetNetworkConditions.js index 9087db3531..3e5e102a0b 100644 --- a/test/src/api/commands/client/testSetNetworkConditions.js +++ b/test/src/api/commands/client/testSetNetworkConditions.js @@ -5,7 +5,11 @@ const Nightwatch = require('../../../../lib/nightwatch.js'); describe('.setNetworkConditions()', function () { beforeEach(function (done) { - CommandGlobals.beforeEach.call(this, done); + this.server = MockServer.init(); + + this.server.on('listening', () => { + done(); + }); }); afterEach(function (done) { @@ -59,6 +63,70 @@ describe('.setNetworkConditions()', function () { }); }); + + it('browser.network.setConditions()', function (done) { + MockServer.addMock( + { + url: '/session', + response: { + value: { + sessionId: '13521-10219-202', + capabilities: { + browserName: 'chrome', + browserVersion: '92.0' + } + } + }, + method: 'POST', + statusCode: 201 + }, + true + ); + + Nightwatch.initW3CClient({ + desiredCapabilities: { + browserName: 'chrome', + 'goog:chromeOptions': {} + }, + output: process.env.VERBOSE === '1', + silent: false + }).then((client) => { + const expected = {}; + client.transport.driver.setNetworkConditions = function (spec) { + expected['download_throughput'] = spec.download_throughput; + expected['latency'] = spec.latency; + expected['offline'] = spec.offline; + expected['upload_throughput'] = spec.upload_throughput; + + return Promise.resolve(); + }; + + client.api.network.setConditions({ + offline: false, + latency: 50000, + download_throughput: 450 * 1024, + upload_throughput: 150 * 1024 + }, + function (result) { + expected['callback_result'] = result.value; + }); + + client.start(function (err) { + try { + assert.strictEqual(err, undefined); + assert.strictEqual(expected.callback_result, null); + assert.strictEqual(expected.download_throughput, 460800); + assert.strictEqual(expected.latency, 50000); + assert.strictEqual(expected.offline, false); + assert.strictEqual(expected.upload_throughput, 153600); + done(); + } catch (e){ + done(e); + } + }); + }); + }); + it('browser.setNetworkConditions - driver not supported', function (done) { Nightwatch.initW3CClient({ desiredCapabilities: { @@ -73,7 +141,7 @@ describe('.setNetworkConditions()', function () { }, function (result) { assert.strictEqual(result.status, -1); - assert.strictEqual(result.error, 'SetNetworkConditions is not supported while using this driver'); + assert.strictEqual(result.error, 'The command .setNetworkConditions() is only supported in Chromium based drivers'); } ); client.start(done); diff --git a/test/src/api/commands/client/testWaitUntil.js b/test/src/api/commands/client/testWaitUntil.js index d04108b2ad..f0873298a4 100644 --- a/test/src/api/commands/client/testWaitUntil.js +++ b/test/src/api/commands/client/testWaitUntil.js @@ -105,7 +105,7 @@ describe('.waitUntil()', function () { it('client.waitUntil() function failure with custom timeout', function (done) { let tries = 0; - let startTime = new Date().valueOf(); + const startTime = new Date().valueOf(); let timeDiff; const maxTimeout = 100; const client = this.client.api; @@ -123,7 +123,7 @@ describe('.waitUntil()', function () { this.client.start(err => { try { - assert.ok(timeDiff <= maxTimeout+100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); + assert.ok(timeDiff <= maxTimeout + 100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); assert.strictEqual(result.status, -1); assert.strictEqual(tries, 3); assert.ok(err instanceof Error); @@ -136,7 +136,7 @@ describe('.waitUntil()', function () { it('client.waitUntil() function failure with custom timeout, interval, message, and callback', function (done) { let tries = 0; - let startTime = new Date().valueOf(); + const startTime = new Date().valueOf(); let timeDiff; const maxTimeout = 100; const client = this.client.api; @@ -154,7 +154,7 @@ describe('.waitUntil()', function () { this.client.start(err => { try { - assert.ok(timeDiff <= maxTimeout+100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); + assert.ok(timeDiff <= maxTimeout + 100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); assert.strictEqual(result.status, -1); assert.ok(err instanceof Error); const messageParts = err.message.split('\n'); @@ -196,7 +196,7 @@ describe('.waitUntil()', function () { it('client.waitUntil() function failure with custom timeout and default interval', function (done) { let tries = 0; - let startTime = new Date().valueOf(); + const startTime = new Date().valueOf(); let timeDiff; const maxTimeout = 100; const client = this.client.api; @@ -215,7 +215,7 @@ describe('.waitUntil()', function () { this.client.start(err => { try { assert.ok(err instanceof Error); - assert.ok(timeDiff <= maxTimeout+100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); + assert.ok(timeDiff <= maxTimeout + 100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); assert.strictEqual(result.status, -1); assert.strictEqual(tries, 3); done(); @@ -255,7 +255,7 @@ describe('.waitUntil()', function () { it('client.waitUntil() function failure with custom waitForConditionPollInterval', function (done) { let tries = 0; - let startTime = new Date().valueOf(); + const startTime = new Date().valueOf(); let timeDiff; const maxTimeout = 100; const client = this.client.api; @@ -276,7 +276,7 @@ describe('.waitUntil()', function () { this.client.start(err => { try { assert.ok(err instanceof Error); - assert.ok(timeDiff <= maxTimeout+100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); + assert.ok(timeDiff <= maxTimeout + 100, `Expected lower than ${maxTimeout}, but got ${timeDiff}`); assert.strictEqual(result.status, -1); assert.ok(tries > 5); done(); diff --git a/test/src/api/commands/element/testWaitForElementPresent.js b/test/src/api/commands/element/testWaitForElementPresent.js index 0b5b031388..c1d80f88b1 100644 --- a/test/src/api/commands/element/testWaitForElementPresent.js +++ b/test/src/api/commands/element/testWaitForElementPresent.js @@ -55,7 +55,7 @@ describe('waitForElementPresent', function() { }); it('client.waitForElementPresent() failure with custom time message', function(done){ - this.client.api.globals.waitForConditionPollInterval=10; + this.client.api.globals.waitForConditionPollInterval = 10; this.client.api.waitForElementPresent('.weblogin', 15, function callback(result, instance){ assert.strictEqual(instance.message, 'Element .weblogin found in 15 milliseconds'); }, 'Element %s found in %d milliseconds'); diff --git a/test/src/api/commands/client/testCaptureConsoleLogs.js b/test/src/api/commands/logs/testCaptureBrowserConsoleLogs.js similarity index 72% rename from test/src/api/commands/client/testCaptureConsoleLogs.js rename to test/src/api/commands/logs/testCaptureBrowserConsoleLogs.js index 90173e4b15..434181ac39 100644 --- a/test/src/api/commands/client/testCaptureConsoleLogs.js +++ b/test/src/api/commands/logs/testCaptureBrowserConsoleLogs.js @@ -19,7 +19,6 @@ describe('.captureBrowserConsoleLogs()', function () { }); it('browser.captureBrowserConsoleLogs()', function (done) { - MockServer.addMock({ url: '/session', response: { @@ -67,8 +66,54 @@ describe('.captureBrowserConsoleLogs()', function () { }); }); - it('throws error without callback', function (done) { + it('browser.logs.captureBrowserConsoleLogs()', function (done) { + MockServer.addMock({ + url: '/session', + response: { + value: { + sessionId: '13521-10219-202', + capabilities: { + browserName: 'chrome', + browserVersion: '92.0' + } + } + }, + method: 'POST', + statusCode: 201 + }, true); + + Nightwatch.initW3CClient({ + desiredCapabilities: { + browserName: 'chrome', + 'goog:chromeOptions': {} + }, + output: process.env.VERBOSE === '1', + silent: false + }).then(client => { + let expectedCdpConnection; + let expectedUserCallback; + + cdp.resetConnection(); + client.transport.driver.createCDPConnection = function() { + return Promise.resolve(); + }; + client.transport.driver.onLogEvent = (cdpConnection, userCallback) => { + expectedCdpConnection = cdpConnection; + expectedUserCallback = userCallback; + }; + //eslint-disable-next-line + const userCallback = (event) => {console.log(event)}; + client.api.logs.captureBrowserConsoleLogs(userCallback, function () { + assert.strictEqual(expectedCdpConnection, undefined); // cdpConnection is mocked + assert.strictEqual(expectedUserCallback, userCallback); + }); + + client.start(done); + }); + }); + + it('throws error without callback', function (done) { MockServer.addMock({ url: '/session', response: { @@ -119,5 +164,4 @@ describe('.captureBrowserConsoleLogs()', function () { client.start(done); }); }); - }); diff --git a/test/src/api/commands/client/testCaptureBrowserExceptions.js b/test/src/api/commands/logs/testCaptureBrowserExceptions.js similarity index 71% rename from test/src/api/commands/client/testCaptureBrowserExceptions.js rename to test/src/api/commands/logs/testCaptureBrowserExceptions.js index 44de217440..35aea3b03c 100644 --- a/test/src/api/commands/client/testCaptureBrowserExceptions.js +++ b/test/src/api/commands/logs/testCaptureBrowserExceptions.js @@ -19,7 +19,6 @@ describe('.captureBrowserExceptions()', function () { }); it('browser.captureBrowserExceptions(callback)', function (done) { - MockServer.addMock({ url: '/session', response: { @@ -67,8 +66,54 @@ describe('.captureBrowserExceptions()', function () { }); }); - it('throws error without callback', function (done) { + it('browser.logs.captureBrowserExceptions(callback)', function (done) { + MockServer.addMock({ + url: '/session', + response: { + value: { + sessionId: '13521-10219-202', + capabilities: { + browserName: 'chrome', + browserVersion: '92.0' + } + } + }, + method: 'POST', + statusCode: 201 + }, true); + + Nightwatch.initW3CClient({ + desiredCapabilities: { + browserName: 'chrome', + 'goog:chromeOptions': {} + }, + output: process.env.VERBOSE === '1', + silent: false + }).then(client => { + let expectedCdpConnection; + let expectedUserCallback; + + cdp.resetConnection(); + client.transport.driver.createCDPConnection = function() { + return Promise.resolve(); + }; + client.transport.driver.onLogException = (cdpConnection, userCallback) => { + expectedCdpConnection = cdpConnection; + expectedUserCallback = userCallback; + }; + //eslint-disable-next-line + const userCallback = (event) => {console.log(event)}; + client.api.logs.captureBrowserExceptions(userCallback, function () { + assert.strictEqual(expectedCdpConnection, undefined); // cdpConnection is mocked + assert.strictEqual(expectedUserCallback, userCallback); + }); + + client.start(done); + }); + }); + + it('throws error without callback', function (done) { MockServer.addMock({ url: '/session', response: { @@ -119,5 +164,4 @@ describe('.captureBrowserExceptions()', function () { client.start(done); }); }); - }); \ No newline at end of file diff --git a/test/src/api/commands/logs/testGetLog.js b/test/src/api/commands/logs/testGetLog.js index 88cb153cab..d36df50e7e 100644 --- a/test/src/api/commands/logs/testGetLog.js +++ b/test/src/api/commands/logs/testGetLog.js @@ -1,4 +1,5 @@ const assert = require('assert'); +const MockServer = require('../../../../lib/mockserver.js'); const CommandGlobals = require('../../../../lib/globals/commands.js'); describe('getLog', function() { @@ -10,6 +11,100 @@ describe('getLog', function() { CommandGlobals.afterEach.call(this, done); }); + it('client.logs.getSessionLog()', function(done) { + MockServer.addMock({ + url: '/wd/hub/session/1352110219202/se/log', + postdata: { + type: 'driver' + }, + method: 'POST', + response: JSON.stringify({ + status: 0, + value: [ + {level: 'INFO', timestamp: 534557932, message: 'Driver log 1'}, + {level: 650, timestamp: 534563132, message: 'Driver log 2'} + ] + }) + }, true); + + let logsReceived; + const api = this.client.api; + + this.client.api.logs.getSessionLog('driver', function(result) { + logsReceived = result.value; + + assert.strictEqual(this, api); + }); + + this.client.start(function(err) { + try { + assert.strictEqual(err, undefined); + + assert.strictEqual(Array.isArray(logsReceived), true, 'result is array'); + assert.strictEqual(logsReceived.length, 2); + assert.strictEqual(logsReceived[0].level.name, 'INFO'); + assert.strictEqual(logsReceived[0].level.value, 800); + assert.strictEqual(logsReceived[1].level.name, 'FINE'); + assert.strictEqual(logsReceived[1].level.value, 500); + assert.strictEqual(logsReceived[0].message, 'Driver log 1'); + assert.strictEqual(logsReceived[1].message, 'Driver log 2'); + + done(); + } catch (e) { + done(e); + } + }); + }); + + it('client.logs.getSessionLog() implicit', function(done) { + let logsReceived; + this.client.api.logs.getSessionLog(function(result) { + logsReceived = result.value; + }); + + this.client.start(function(err) { + try { + assert.strictEqual(err, undefined); + assert.strictEqual(logsReceived.length, 2); + assert.strictEqual(Array.isArray(logsReceived), true, 'result is array'); + assert.strictEqual(logsReceived[0].message, 'Test log'); + + done(); + } catch (e) { + done(e); + } + }); + }); + + it('client.logs.getSessionLog() with callback rejecting a Promise', function() { + this.client.api.logs.getSessionLog('browser', function(result) { + return new Promise((resolve, reject) => { + reject(new Error('test error')); + }); + }); + + return this.client.start(function(err) { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Error while running "logs.getSessionLog" command: test error'); + }); + }); + + it('client.logs.getSessionLog() with callback returning a Promise', function() { + let logsResult; + this.client.api.logs.getSessionLog('browser', function(result) { + logsResult = result.value; + + return new Promise((resolve) => { + resolve(); + }); + }); + + return this.client.start(function() { + assert.strictEqual(Array.isArray(logsResult), true, 'result is array'); + assert.strictEqual(logsResult.length, 2); + }); + }); + it('client.getLog()', function(done) { const api = this.client.api; this.client.api.getLog('browser', function(result) { diff --git a/test/src/api/commands/element/testGetLogTypes.js b/test/src/api/commands/logs/testGetLogTypes.js similarity index 50% rename from test/src/api/commands/element/testGetLogTypes.js rename to test/src/api/commands/logs/testGetLogTypes.js index 3e17ce0265..1ccc2bc917 100644 --- a/test/src/api/commands/element/testGetLogTypes.js +++ b/test/src/api/commands/logs/testGetLogTypes.js @@ -20,16 +20,27 @@ describe('getLogTypes', function () { status: 0, value: ['browser', 'har'] }) - }); + }, true, true); const api = this.client.api; - this.client.api.getLogTypes(function callback(result) { - assert.strictEqual(this, api); - assert.ok(Array.isArray(result)); - assert.strictEqual(result.length, 2); - assert.strictEqual(result[0], 'browser'); - assert.strictEqual(result[1], 'har'); - }); + + this.client.api + .getLogTypes(function callback(result) { + assert.strictEqual(this, api); + assert.ok(Array.isArray(result)); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0], 'browser'); + assert.strictEqual(result[1], 'har'); + }) + .logs.getSessionLogTypes(function callback(result) { + const availableLogTypes = result.value; + + assert.strictEqual(this, api); + assert.ok(Array.isArray(availableLogTypes)); + assert.strictEqual(availableLogTypes.length, 2); + assert.strictEqual(availableLogTypes[0], 'browser'); + assert.strictEqual(availableLogTypes[1], 'har'); + }); this.client.start(done); }); diff --git a/test/src/api/commands/logs/testIsLogAvailable.js b/test/src/api/commands/logs/testIsLogAvailable.js index b6eb1adda9..f95c584f56 100644 --- a/test/src/api/commands/logs/testIsLogAvailable.js +++ b/test/src/api/commands/logs/testIsLogAvailable.js @@ -18,6 +18,64 @@ describe('isLogAvailable', function () { CommandGlobals.afterEach.call(this, done); }); + it('client.logs.isSessionLogAvailable()', function (done) { + MockServer.addMock({ + url: '/wd/hub/session/1352110219202/se/log/types', + method: 'GET', + response: JSON.stringify({ + sessionId: '1352110219202', + status: 0, + value: ['browser', 'har'] + }) + }); + + const api = this.client.api; + + this.client.api + .logs.isSessionLogAvailable('unknown', function callback(result) { + const isAvailable = result.value; + + assert.strictEqual(this, api); + assert.strictEqual(isAvailable, false); + }) + .logs.isSessionLogAvailable('browser', function callback(result) { + const isAvailable = result.value; + + assert.strictEqual(typeof isAvailable, 'boolean'); + assert.strictEqual(isAvailable, true); + }); + + this.client.start(done); + }); + + it('client.logs.isSessionLogAvailable() failure', function (done) { + MockServer.addMock({ + url: '/wd/hub/session/1352110219202/se/log/types', + method: 'GET', + response: JSON.stringify({ + sessionId: '1352110219202', + status: 0, + value: {'message': 'Session not started or terminated'} + }) + }); + + this.client.api + .logs.isSessionLogAvailable('unknown', function callback(result) { + const isAvailable = result.value; + + assert.strictEqual(typeof isAvailable === 'boolean', true); + assert.strictEqual(isAvailable, false); + }) + .logs.isSessionLogAvailable('browser', function callback(result) { + const isAvailable = result.value; + + assert.strictEqual(typeof isAvailable === 'boolean', true); + assert.strictEqual(isAvailable, false); + }); + + this.client.start(done); + }); + it('client.isLogAvailable()', function (done) { MockServer.addMock({ url: '/wd/hub/session/1352110219202/se/log/types', diff --git a/test/src/apidemos/custom-commands/testCustomCommandSync.js b/test/src/apidemos/custom-commands/testCustomCommandSync.js new file mode 100644 index 0000000000..2b2208ff06 --- /dev/null +++ b/test/src/apidemos/custom-commands/testCustomCommandSync.js @@ -0,0 +1,49 @@ +const path = require('path'); +const assert = require('assert'); +const MockServer = require('../../../lib/mockserver.js'); +const Mocks = require('../../../lib/command-mocks.js'); +const common = require('../../../common.js'); +const {settings} = common; +const NightwatchClient = common.require('index.js'); + +describe('sync custom commands', function() { + beforeEach(function(done) { + this.server = MockServer.init(); + this.server.on('listening', () => { + done(); + }); + }); + + afterEach(function(done) { + this.server.close(function() { + done(); + }); + }); + + it('test sync custom command returning NightwatchAPI as result', function() { + const testsPath = path.join(__dirname, '../../../apidemos/custom-commands-parallel'); + Mocks.createNewW3CSession({ + testName: 'Test Sync Custom Commands returning NightwatchAPI' + }); + + const globals = { + waitForConditionPollInterval: 50, + waitForConditionTimeout: 120, + retryAssertionTimeout: 1000, + + reporter(results) { + assert.strictEqual(Object.keys(results.modules).length, 1); + if (results.lastError) { + throw results.lastError; + } + } + }; + + return NightwatchClient.runTests({ + env: 'default', + config: 'test/extra/parallelism-customCommandsSync.json' + }, Object.assign({}, { + globals + })); + }); +}); diff --git a/test/src/apidemos/web-elements/testWaitUntil.js b/test/src/apidemos/web-elements/testWaitUntil.js index f833d6b599..9fb74d4f3a 100644 --- a/test/src/apidemos/web-elements/testWaitUntil.js +++ b/test/src/apidemos/web-elements/testWaitUntil.js @@ -83,4 +83,37 @@ describe('waitUntil element command', function() { globals })); }); + + it('test waitUntil element command - element not present', function() { + const testsPath = path.join(__dirname, '../../../apidemos/web-elements/waitUntilElementNotPresent.js'); + + + const globals = { + waitForConditionPollInterval: 50, + waitForConditionTimeout: 120, + retryAssertionTimeout: 100, + + reporter(results) { + if (!results.lastError) { + assert.fail('Should result into failure'); + } + assert.ok(results.lastError.message.includes('not present'), 'err message should be element not present'); + } + }; + + return NightwatchClient.runTests(testsPath, settings({ + selenium: { + port: null, + start_process: false + }, + selenium_host: null, + webdriver: { + port: 10195, + start_process: false + }, + output: false, + skip_testcases_on_fail: false, + globals + })); + }); }); diff --git a/test/src/cli/testCliRunnerMocha.js b/test/src/cli/testCliRunnerMocha.js index 07c7a8f56f..0f0244c2fd 100644 --- a/test/src/cli/testCliRunnerMocha.js +++ b/test/src/cli/testCliRunnerMocha.js @@ -26,6 +26,7 @@ describe('test CLI Runner Mocha', function() { resolve: function(a) { return a; }, + extname: path.extname, join: path.join }); @@ -89,6 +90,49 @@ describe('test CLI Runner Mocha', function() { }); }); + it('testRunWithMocha - parallelism', function() { + + const ChildProcess = common.require('runner/concurrency/child-process.js'); + let childProcessCreated = false; + class ChildProcessMock extends ChildProcess { + run(colors, type) { + childProcessCreated = true; + assert.strictEqual(colors.length, 4); + assert.strictEqual(type, 'workers'); + assert.deepStrictEqual(Object.keys(this._events), ['message']); + + return Promise.resolve(0); + } + } + + mockery.registerMock('./child-process.js', ChildProcessMock); + + mockery.registerMock('./withmocha.json', { + src_folders: ['tests'], + output_folder: false, + use_child_process: false, + test_settings: { + 'default': { + silent: true + } + }, + test_runner: 'mocha' + }); + + + const CliRunner = common.require('runner/cli/cli.js'); + const runner = new CliRunner({ + config: './withmocha.json', + env: 'default', + reporter: 'junit', + parallel: true + }).setup(); + + return runner.runTests().then(function() { + assert.ok(childProcessCreated, 'mocha runner with parallel threads should use child process'); + }); + }); + it('testRunWithMochaPerEnvironment', function() { const testFiles = []; const defaultOptions = {timeout: 20000, reporterOptions: {}}; diff --git a/test/src/cli/testParallelExecution.js b/test/src/cli/testParallelExecution.js index a4b899a093..7039476279 100644 --- a/test/src/cli/testParallelExecution.js +++ b/test/src/cli/testParallelExecution.js @@ -7,7 +7,7 @@ const Nightwatch = require('../../lib/nightwatch.js'); describe('test Parallel Execution', function() { const workerPoolArgv = []; - const taskArgv= []; + const taskArgv = []; const allArgs = []; const allOpts = []; @@ -88,9 +88,9 @@ describe('test Parallel Execution', function() { it('testParallelExecution - child-process', function() { const CliRunner = common.require('runner/cli/cli.js'); - let originalCwd = process.cwd(); + const originalCwd = process.cwd(); process.chdir(path.join(__dirname, '../../extra/')); - let runner = new CliRunner({ + const runner = new CliRunner({ config: './nightwatch.json', env: 'default,mixed', reporter: 'junit' @@ -167,7 +167,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers defaults -- worker thread', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism.json') }); @@ -187,10 +187,10 @@ describe('test Parallel Execution', function() { }); it('testParallelExecutionSameEnv - child-process', function() { - let originalCwd = process.cwd(); + const originalCwd = process.cwd(); process.chdir(path.join(__dirname, '../../extra/')); const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ config: './nightwatch.json', reporter: 'junit', env: 'mixed,mixed' @@ -211,11 +211,11 @@ describe('test Parallel Execution', function() { }); it('testParallelExecutionSameEnv - worker threads', function() { - let originalCwd = process.cwd(); + const originalCwd = process.cwd(); process.chdir(path.join(__dirname, '../../extra/')); const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ config: './nightwatch.json', reporter: 'junit', env: 'mixed,mixed' @@ -279,7 +279,7 @@ describe('test Parallel Execution', function() { it('testParallelExecutionWithWorkers and multiple environments - child process', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-auto.json'), env: 'default,default' @@ -294,7 +294,7 @@ describe('test Parallel Execution', function() { it('testParallelExecutionWithWorkers and multiple environments - worker threads', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-auto.json'), env: 'default,default' @@ -310,7 +310,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers count - child process', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-count.json') }); @@ -329,7 +329,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers count - worker threads', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-count.json') }); @@ -349,7 +349,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers=count arg', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-count.json'), workers: 2 @@ -364,7 +364,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers count and extended envs', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-workers.conf.js'), env: 'chrome' @@ -382,7 +382,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers disabled per environment', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism-disabled.json') }); @@ -394,7 +394,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers and single source file', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism.json'), _source: [path.join(__dirname, '../../../sampletests/async/test/sample.js')] @@ -408,7 +408,7 @@ describe('test Parallel Execution', function() { it('test parallel execution with workers and single source folder', function() { const CliRunner = common.require('runner/cli/cli.js'); - let runner = new CliRunner({ + const runner = new CliRunner({ reporter: 'junit', config: path.join(__dirname, '../../extra/parallelism.json'), _source: path.join(__dirname, '../../../sampletests/before-after') diff --git a/test/src/index/testProgrammaticApis.js b/test/src/index/testProgrammaticApis.js index 27b2307e38..aa993b65ae 100644 --- a/test/src/index/testProgrammaticApis.js +++ b/test/src/index/testProgrammaticApis.js @@ -2,6 +2,7 @@ const assert = require('assert'); const mockery = require('mockery'); const MockServer = require('../../lib/command-mocks.js'); const common = require('../../common.js'); +const {Logger} = common.require('utils'); describe('test programmatic apis', function () { // [ '-vv', '--port=62625' ] @@ -75,7 +76,8 @@ describe('test programmatic apis', function () { assert.ok(!!global.browser); assert.ok(!!global.browser.page); - assert.deepStrictEqual(Object.keys(client), ['updateCapabilities', 'launchBrowser']); + assert.deepStrictEqual(Object.keys(client), ['updateCapabilities', 'runGlobalBeforeHook', + 'runGlobalAfterHook', 'launchBrowser', 'cleanup']); assert.strictEqual(typeof client.launchBrowser, 'function'); assert.strictEqual(typeof client.settings, 'object'); @@ -570,4 +572,290 @@ describe('test programmatic apis', function () { CliRunner.createDefaultConfig = createDefaultConfig; CliRunner.prototype.loadConfig = loadConfig; }); + + it('test if process listener get disabled', async function() { + let processListenerCalled = false; + mockery.enable({useCleanCache: true, warnOnUnregistered: false}); + mockery.registerMock('../process-listener.js', class { + constructor() { + processListenerCalled = true; + } + + setTestRunner() {} + }); + + const CliRunner = common.require('runner/cli/cli.js'); + const Nightwatch = common.require('index.js'); + + const createDefaultConfig = CliRunner.createDefaultConfig; + const loadConfig = CliRunner.prototype.loadConfig; + const defaultConfig = { + test_settings: { + default: { + launchUrl: 'http://localhost' + } + }, + selenium: { + port: 10195, + start_process: false + }, + selenium_host: 'localhost' + }; + + CliRunner.createDefaultConfig = function(destFileName) { + return defaultConfig; + }; + + CliRunner.prototype.loadConfig = function () { + return defaultConfig; + }; + + const clientWithoutListner = Nightwatch.createClient({ + timeout: 500, + useAsync: false, + output: false, + silent: false, + headless: true, + output_folder: 'output', + globals: { + testGlobal: 'one' + }, + disable_process_listener: true + }); + + assert.ok(!processListenerCalled); + + const client = Nightwatch.createClient({ + timeout: 500, + useAsync: false, + output: false, + silent: false, + headless: true, + output_folder: 'output', + globals: { + testGlobal: 'one' + } + }); + + assert.ok(processListenerCalled); + + CliRunner.createDefaultConfig = createDefaultConfig; + CliRunner.prototype.loadConfig = loadConfig; + }); + + it('test runGlobalBeforeHook() programmatic API', async function() { + const CliRunner = common.require('runner/cli/cli.js'); + const Nightwatch = common.require('index.js'); + MockServer.createFirefoxSession({}); + + let globalBeforeCalled = false; + + const defaultConfig = { + test_settings: { + default: { + launchUrl: 'http://localhost' + } + }, + selenium: { + port: 10195, + start_process: false + }, + selenium_host: 'localhost', + + globals: { + before() { + globalBeforeCalled = true; + } + } + }; + + const createDefaultConfig = CliRunner.createDefaultConfig; + const loadConfig = CliRunner.prototype.loadConfig; + + CliRunner.createDefaultConfig = function(destFileName) { + return defaultConfig; + }; + + CliRunner.prototype.loadConfig = function () { + return defaultConfig; + }; + + const client = Nightwatch.createClient({ + headless: true, + silent: false, + output: false, + enable_global_apis: true + }); + + await client.runGlobalBeforeHook(); + + assert.ok(globalBeforeCalled); + + CliRunner.createDefaultConfig = createDefaultConfig; + CliRunner.prototype.loadConfig = loadConfig; + }); + + it('test runGlobalAfterHook() programmatic API', async function() { + const CliRunner = common.require('runner/cli/cli.js'); + const Nightwatch = common.require('index.js'); + MockServer.createFirefoxSession({}); + + let globalAfterCalled = false; + + const defaultConfig = { + test_settings: { + default: { + launchUrl: 'http://localhost' + } + }, + selenium: { + port: 10195, + start_process: false + }, + selenium_host: 'localhost', + + globals: { + after() { + globalAfterCalled = true; + } + } + }; + + const createDefaultConfig = CliRunner.createDefaultConfig; + const loadConfig = CliRunner.prototype.loadConfig; + + CliRunner.createDefaultConfig = function(destFileName) { + return defaultConfig; + }; + + CliRunner.prototype.loadConfig = function () { + return defaultConfig; + }; + + const client = Nightwatch.createClient({ + headless: true, + silent: false, + output: false, + enable_global_apis: true + }); + + await client.runGlobalAfterHook(); + + assert.ok(globalAfterCalled); + + CliRunner.createDefaultConfig = createDefaultConfig; + CliRunner.prototype.loadConfig = loadConfig; + }); + + it('test cleanup() programmatic API', async function() { + const CliRunner = common.require('runner/cli/cli.js'); + const Nightwatch = common.require('index.js'); + MockServer.createFirefoxSession({}); + + const defaultConfig = { + test_settings: { + default: { + launchUrl: 'http://localhost' + } + }, + selenium: { + port: 10195, + start_process: false + }, + selenium_host: 'localhost' + }; + + const createDefaultConfig = CliRunner.createDefaultConfig; + const loadConfig = CliRunner.prototype.loadConfig; + + CliRunner.createDefaultConfig = function(destFileName) { + return defaultConfig; + }; + + CliRunner.prototype.loadConfig = function () { + return defaultConfig; + }; + + const client = Nightwatch.createClient({ + headless: true, + silent: false, + output: false, + enable_global_apis: true + }); + + const session = await client.launchBrowser(); + + await client.cleanup(); + + const httpOutput = Logger.collectOutput(); + const httpSectionOutput = Logger.collectTestSectionOutput(); + + assert.equal(httpOutput.length, 0); + assert.equal(httpSectionOutput.length, 0); + CliRunner.createDefaultConfig = createDefaultConfig; + CliRunner.prototype.loadConfig = loadConfig; + }); + + it('should test updateCapabilities() programmatic API with multiple nested caps', async function() { + const CliRunner = common.require('runner/cli/cli.js'); + const Nightwatch = common.require('index.js'); + MockServer.createFirefoxSession({}); + + const defaultConfig = { + test_settings: { + default: { + launchUrl: 'http://localhost' + } + }, + selenium: { + port: 10195, + start_process: false + }, + selenium_host: 'localhost' + }; + + const createDefaultConfig = CliRunner.createDefaultConfig; + const loadConfig = CliRunner.prototype.loadConfig; + + CliRunner.createDefaultConfig = function(destFileName) { + return defaultConfig; + }; + + CliRunner.prototype.loadConfig = function () { + return defaultConfig; + }; + + const client = Nightwatch.createClient({ + headless: true, + silent: false, + output: false, + enable_global_apis: true + }); + + client.updateCapabilities({ + 'testName': 'newCaps', + 'options': { + 'testCapabilitiesOne': 'capabilityOne' + } + }); + + client.updateCapabilities({ + 'testName': 'updatedCaps', + 'options': { + 'testCapabilitiesTwo': 'capabilityTwo' + } + }); + + const session = await client.launchBrowser(); + assert.deepStrictEqual(session.desiredCapabilities, { + browserName: 'firefox', + testName: 'updatedCaps', + options: { + testCapabilitiesOne: 'capabilityOne', + testCapabilitiesTwo: 'capabilityTwo' + } + }); + CliRunner.createDefaultConfig = createDefaultConfig; + CliRunner.prototype.loadConfig = loadConfig; + }); }); diff --git a/test/src/index/transport/testEdgeOptions.js b/test/src/index/transport/testEdgeOptions.js index f566bb1c79..d442579698 100644 --- a/test/src/index/transport/testEdgeOptions.js +++ b/test/src/index/transport/testEdgeOptions.js @@ -130,5 +130,17 @@ describe('Test edge option', function(){ }); }); + it('msedge as browserName', function () { + const client = Nightwatch.createClient({ + desiredCapabilities: { + browserName: 'msedge' + } + }); + const options = client.transport.createSessionOptions(); + + assert.strictEqual(options instanceof EdgeOptions, true); + assert.strictEqual(client.api.isEdge(), true); + assert.strictEqual(client.api.browserName, 'msedge'); + }); }); diff --git a/test/src/runner/cli/testCliRunnerParallel.js b/test/src/runner/cli/testCliRunnerParallel.js index e767cbc50d..13ecb1dde7 100644 --- a/test/src/runner/cli/testCliRunnerParallel.js +++ b/test/src/runner/cli/testCliRunnerParallel.js @@ -3,6 +3,7 @@ const path = require('path'); const mockery = require('mockery'); const common = require('../../../common.js'); const NightwatchClient = common.require('index.js'); +const {settings} = common; describe('Test CLI Runner in Parallel', function () { const ChildProcess = common.require('runner/concurrency/child-process.js'); @@ -105,6 +106,51 @@ describe('Test CLI Runner in Parallel', function () { }); }); + it('run error test file with concurrency - worker threads', function() { + let numberOfTasks = 0; + class RunnerBaseMock extends RunnerBase { + static readTestSource(settings, argv) { + assert.strictEqual(settings.testWorkersEnabled, true); + + return [ + 'test_file_1.js', + 'test_file_2.js' + ]; + } + } + + class WorkerProcessMock extends WorkerProcess { + addTask({colors}) { + + this.__tasks.push(new Promise((resolve, reject) => { + setTimeout(()=>{ + numberOfTasks++; + reject(new Error('Nigtwatch custom error')); + }, 10 * (numberOfTasks + 1)); + })); + + return Promise.resolve(0); + } + } + mockery.registerMock('./worker-process.js', WorkerProcessMock); + mockery.registerMock('../runner.js', RunnerBaseMock); + + return NightwatchClient.runTests({ + env: 'default', + config: path.join(__dirname, '../../../extra/withgeckodriver-concurrent.json') + }, settings({ + globals: { + reporter() { + assert.strictEqual(numberOfTasks, 2); + } + }, + use_child_process: false, + silent: false, + output: false, + output_folder: false + })); + }); + it('start single test run with geckodriver and test workers enabled', function () { const testPath = path.join(__dirname, '../../../sampletests/async/test/sample.js'); const runner = NightwatchClient.CliRunner({ diff --git a/test/src/runner/cucumber-integration/testCliArgs.js b/test/src/runner/cucumber-integration/testCliArgs.js index 3a5f78a101..cf16847624 100644 --- a/test/src/runner/cucumber-integration/testCliArgs.js +++ b/test/src/runner/cucumber-integration/testCliArgs.js @@ -42,7 +42,7 @@ describe('Cucumber cli arguments', function(){ let index = cliArgs.indexOf('--require-module') + 1; assert.strictEqual(cliArgs[index], 'coffeescript/register'); - index = cliArgs.indexOf('--require-module', index)+1; + index = cliArgs.indexOf('--require-module', index) + 1; assert.strictEqual(cliArgs[index], 'ts-node/register'); }); @@ -59,17 +59,18 @@ describe('Cucumber cli arguments', function(){ assert.strictEqual(cliArgs.length, 21); assert.ok(cliArgs.includes('--name')); - assert.strictEqual(cliArgs[cliArgs.indexOf('--name')+1], 'sample'); + assert.strictEqual(cliArgs[cliArgs.indexOf('--name') + 1], 'sample'); assert.ok(cliArgs.includes('--fail-fast')); assert.ok(cliArgs.includes('--retry')); - assert.strictEqual(cliArgs[cliArgs.indexOf('--retry')+1], 2); + assert.strictEqual(cliArgs[cliArgs.indexOf('--retry') + 1], 2); assert.ok(cliArgs.includes('--retry-tag-filter')); - assert.strictEqual(cliArgs[cliArgs.indexOf('--retry-tag-filter')+1], '@nightwatch'); + assert.strictEqual(cliArgs[cliArgs.indexOf('--retry-tag-filter') + 1], '@nightwatch'); assert.ok(cliArgs.includes('--profile')); - assert.strictEqual(cliArgs[cliArgs.indexOf('--profile')+1], 'local'); + assert.strictEqual(cliArgs[cliArgs.indexOf('--profile') + 1], 'local'); assert.ok(cliArgs.includes('--no-strict')); assert.ok(cliArgs.includes('--parallel')); - assert.strictEqual(cliArgs[cliArgs.indexOf('--parallel')+1], 3); + assert.strictEqual(cliArgs[cliArgs.indexOf('--parallel') + 1], 3); + assert.ok(cliArgs.includes('--require')); }); it('Cucumber cli arg --dry-run', function(){ @@ -90,7 +91,7 @@ describe('Cucumber cli arguments', function(){ assert.ok(cliArgs.includes('--dry-run')); }); - it('Cucumbr additional option --retries', function(){ + it('Cucumber additional option --retries', function(){ const runner = new CucumberRunner({ test_runner: { type: 'cucumber', @@ -108,7 +109,7 @@ describe('Cucumber cli arguments', function(){ assert.ok(cliArgs.includes('--retry')); }); - it('Cucumbr additional options --retry and --format', function(){ + it('Cucumber additional options --retry and --format', function(){ const runner = new CucumberRunner({ test_runner: { type: 'cucumber', @@ -126,7 +127,47 @@ describe('Cucumber cli arguments', function(){ assert.ok(cliArgs.includes('--retry')); assert.ok(cliArgs.includes('--format')); - assert.strictEqual(cliArgs[cliArgs.indexOf('--retry')+1], 3); - assert.strictEqual(cliArgs[cliArgs.indexOf('--format')+1], '@cucumber/pretty-formatter'); + assert.strictEqual(cliArgs[cliArgs.indexOf('--retry') + 1], 3); + assert.strictEqual(cliArgs[cliArgs.indexOf('--format') + 1], '@cucumber/pretty-formatter'); + }); + + it('Cucumber cli arg --enable-esm', function(){ + const runner = new CucumberRunner({ + test_runner: { + type: 'cucumber', + options: {} + } + }, { + 'enable-esm': true + }, {}); + + runner.createTestSuite({ + modules: [path.join(__dirname, '../../../cucumber-integration-tests/sample_cucumber_tests/integration/testSample.js')], + modulePath: [path.join(__dirname, '../../../cucumber-integration-tests/sample_cucumber_tests/integration/testSample.js')] + }); + + assert.ok(cliArgs.includes('--import')); + assert.ok(!cliArgs.includes('--require')); + }); + + it('Cucumber options enable esm support', function(){ + const runner = new CucumberRunner({ + test_runner: { + type: 'cucumber', + options: { + enable_esm: true + } + } + }, {}, {}); + + const testModulePath = path.join(__dirname, '../../../cucumber-integration-tests/sample_cucumber_tests/integration/testSample.js'); + + runner.createTestSuite({ + modules: [testModulePath], + modulePath: [testModulePath] + }); + + assert.ok(cliArgs.includes('--import')); + assert.ok(!cliArgs.includes('--require')); }); }); diff --git a/test/src/runner/testRunnerMixedFiles.js b/test/src/runner/testRunnerMixedFiles.js index 40913bca6e..7406a57610 100644 --- a/test/src/runner/testRunnerMixedFiles.js +++ b/test/src/runner/testRunnerMixedFiles.js @@ -10,7 +10,10 @@ describe('testRunnerMixedFiles', function() { let tsNode; before(function(done) { - tsNode = require('ts-node').register(); + tsNode = require('ts-node').register({ + project: path.resolve('tsconfig.json'), + swc: true + }); this.server = MockServer.init(); this.server.on('listening', () => { @@ -35,8 +38,8 @@ describe('testRunnerMixedFiles', function() { this.timeout(5000); it('testRunWithoutDisablingTypescriptExplicitly', function() { - let testsPath = path.join(__dirname, '../../sampletests/mixed-files'); - let globals = { + const testsPath = path.join(__dirname, '../../sampletests/mixed-files'); + const globals = { reporter({lastError, errmessages, modules}) { if (lastError) { throw lastError; @@ -45,7 +48,7 @@ describe('testRunnerMixedFiles', function() { if (errmessages.length) { throw new Error(errmessages[0]); } - + assert.ok('sampleJs' in modules); assert.ok('sampleTs' in modules); assert.strictEqual(modules['sampleJs'].modulePath, path.join(__dirname, '../../sampletests/mixed-files/sampleJs.js')); @@ -62,8 +65,8 @@ describe('testRunnerMixedFiles', function() { }); it('testRunWithoutDisablingTypescriptImplicitly', function() { - let testsPath = path.join(__dirname, '../../sampletests/mixed-files'); - let globals = { + const testsPath = path.join(__dirname, '../../sampletests/mixed-files'); + const globals = { reporter({lastError, errmessages, modules}) { if (lastError) { throw lastError; @@ -87,8 +90,8 @@ describe('testRunnerMixedFiles', function() { }); it('testRunSimpleDisablingTypescript', function() { - let testsPath = path.join(__dirname, '../../sampletests/mixed-files'); - let globals = { + const testsPath = path.join(__dirname, '../../sampletests/mixed-files'); + const globals = { reporter({lastError, errmessages, modules}) { if (lastError) { throw lastError; diff --git a/test/src/service-builders/testSafariDriver.js b/test/src/service-builders/testSafariDriver.js index 8132892cc5..2604b2291e 100644 --- a/test/src/service-builders/testSafariDriver.js +++ b/test/src/service-builders/testSafariDriver.js @@ -272,7 +272,7 @@ describe('SafariDriver Transport Tests', function () { url: '/session', statusCode: 500, postdata: JSON.stringify({ - 'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari'}} + 'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari', 'safari:options': {}}} }), response: JSON.stringify({ value: { @@ -322,7 +322,7 @@ describe('SafariDriver Transport Tests', function () { url: '/session', statusCode: 500, postdata: JSON.stringify({ - 'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari'}} + 'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari', 'safari:options': {}}} }), response: JSON.stringify({ value: { @@ -337,7 +337,7 @@ describe('SafariDriver Transport Tests', function () { url: '/session', statusCode: 200, postdata: JSON.stringify({ - 'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari'}} + 'capabilities': {'firstMatch': [{}], 'alwaysMatch': {'browserName': 'safari', 'safari:options': {}}} }), response: JSON.stringify({ value: { diff --git a/test/src/utils/__data__/meaning-of-life.js b/test/src/utils/__data__/meaning-of-life.js new file mode 100644 index 0000000000..888cae37af --- /dev/null +++ b/test/src/utils/__data__/meaning-of-life.js @@ -0,0 +1 @@ +module.exports = 42; diff --git a/test/src/utils/__data__/meaning-of-life.mjs b/test/src/utils/__data__/meaning-of-life.mjs new file mode 100644 index 0000000000..7a4e8a723a --- /dev/null +++ b/test/src/utils/__data__/meaning-of-life.mjs @@ -0,0 +1 @@ +export default 42; diff --git a/test/src/utils/testRequireModule.js b/test/src/utils/testRequireModule.js new file mode 100644 index 0000000000..12b800cbfd --- /dev/null +++ b/test/src/utils/testRequireModule.js @@ -0,0 +1,21 @@ +const {strict: assert} = require('node:assert'); +const path = require('node:path'); + +const common = require('../../common.js'); +const requireModule = common.require('utils/requireModule.js'); + +describe('test requireModule', function () { + it('should load commonjs file', function () { + const modulePath = path.join(__dirname, './__data__/meaning-of-life.js'); + const meaningOfLife = requireModule(modulePath); + + assert.equal(meaningOfLife, 42); + }); + + it('should load es6 module', async function () { + const modulePath = path.join(__dirname, './__data__/meaning-of-life.mjs'); + const meaningOfLife = await requireModule(modulePath); + + assert.equal(meaningOfLife, 42); + }); +}); diff --git a/test/src/utils/testStackTrace.js b/test/src/utils/testStackTrace.js index 857222517f..488091782f 100644 --- a/test/src/utils/testStackTrace.js +++ b/test/src/utils/testStackTrace.js @@ -4,12 +4,15 @@ const AssertionError = require('assertion-error'); const common = require('../../common.js'); const Utils = common.require('utils'); const beautifyStackTrace = common.require('utils/beautifyStackTrace.js'); -const colors = common.require('utils/colors.js'); +const colors = common.require('utils/chalkColors.js'); const {Logger} = common.require('utils'); describe('test stackTrace parse', function() { - before(() => colors.disable()); + before(() => { + colors.colors.level = 3; + colors.disable(); + }); after(() => colors.enable()); it('filterStackTrace', function() { @@ -121,29 +124,29 @@ describe('test stackTrace parse', function() { const errorMessage = Logger.getErrorContent(error); assert.ok(!errorMessage.includes('\t')); - assert.strictEqual(errorMessage, ` ✖ TypeError - Unknown method - - Error location: + assert.strictEqual(errorMessage, ` \x1B[31m\x1B[1m✖\x1B[22m\x1B[39m \x1B[31m\x1B[1mTypeError\x1B[22m\x1B[39m + \x1B[31mUnknown method\x1B[39m +\x1B[33m\x1B[39m +\x1B[33m Error location:\x1B[39m ${errorFilePath}: –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– 2 | it('failure stack trace', function() { 3 | -  4 | browser.url('http://localhost')  + \x1B[41m\x1B[37m 4 | browser.url('http://localhost') \x1B[39m\x1B[49m 5 | .assert.elementPresen('#badElement'); // mispelled API method 6 | }); –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––– - Stack Trace : - at DescribeInstance. (${errorFilePath}:${lineNumber}:21) - at Context.call (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/context.js:430:35) - at TestCase.run (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/testcase.js:58:31) - at Runnable.__runFn (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:669:80) - at Runnable.run (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/runnable.js:126:21) - at TestSuite.createRunnable (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:776:33) - at TestSuite.handleRunnable (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:781:33) - at /Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:669:21 - at processTicksAndRejections (node:internal/process/task_queues:96:5) +\x1B[33m Stack Trace :\x1B[39m +\x1B[90m at DescribeInstance. (${errorFilePath}:${lineNumber}:21)\x1B[39m +\x1B[90m at Context.call (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/context.js:430:35)\x1B[39m +\x1B[90m at TestCase.run (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/testcase.js:58:31)\x1B[39m +\x1B[90m at Runnable.__runFn (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:669:80)\x1B[39m +\x1B[90m at Runnable.run (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/runnable.js:126:21)\x1B[39m +\x1B[90m at TestSuite.createRunnable (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:776:33)\x1B[39m +\x1B[90m at TestSuite.handleRunnable (/Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:781:33)\x1B[39m +\x1B[90m at /Users/BarnOwl/Documents/Projects/Nightwatch-tests/node_modules/nightwatch/lib/testsuite/index.js:669:21\x1B[39m +\x1B[90m at processTicksAndRejections (node:internal/process/task_queues:96:5)\x1B[39m `); }); }); diff --git a/tsconfig.json b/tsconfig.json index 8a90298884..78c3204d8c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,7 @@ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ + "module": "node16", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ diff --git a/types/assertions.d.ts b/types/assertions.d.ts index 2231b3a6b9..8cae94b424 100644 --- a/types/assertions.d.ts +++ b/types/assertions.d.ts @@ -1,5 +1,5 @@ import {NightwatchCustomAssertions} from './custom-assertion'; -import {Awaitable, Definition, ElementResult, JSON_WEB_OBJECT} from './index'; +import {Awaitable, Definition, ElementResult, JSON_WEB_OBJECT, ScopedSelector} from './index'; import { IfUnknown } from './utils'; export interface NightwatchAssertionsError { @@ -124,7 +124,7 @@ export interface NightwatchCommonAssertions { * */ elementsCount( - selector: Definition, + selector: ScopedSelector, count: number, msg?: string ): Awaitable< @@ -144,7 +144,7 @@ export interface NightwatchCommonAssertions { * ``` */ elementPresent( - selector: Definition, + selector: ScopedSelector, msg?: string ): Awaitable< IfUnknown, diff --git a/types/custom-assertion.d.ts b/types/custom-assertion.d.ts index a93a6e4f06..027cefbeee 100644 --- a/types/custom-assertion.d.ts +++ b/types/custom-assertion.d.ts @@ -11,9 +11,9 @@ interface NightwatchAssertionFailedResult { /** * @example - * import {Definition, NightwatchAssertion} from 'nightwatch'; + * import {ScopedSelector, NightwatchAssertion} from 'nightwatch'; * - * export const assertion = function ElementHasCount(this: NightwatchAssertion, selector: Definition, count: number) { + * export const assertion = function ElementHasCount(this: NightwatchAssertion, selector: ScopedSelector, count: number) { * this.message = `Testing if element <${selector}> has count: ${count}`; * * this.expected = count; diff --git a/types/expect.d.ts b/types/expect.d.ts index 2d4a095cc2..3063ee898a 100644 --- a/types/expect.d.ts +++ b/types/expect.d.ts @@ -1,5 +1,5 @@ import { By, WebElement } from 'selenium-webdriver'; -import {Definition, NightwatchAPI, Awaitable, Element, ELEMENT_KEY} from './index'; +import {Definition, NightwatchAPI, Awaitable, Element, ELEMENT_KEY, ScopedSelector} from './index'; export interface NightwatchExpectResult { value: null; @@ -196,23 +196,23 @@ export interface Expect { /** * Expect assertions operating on a single element, specified by its CSS/Xpath selector. */ - element(property: Definition | WebElement): ExpectElement; + element(property: Definition): ExpectElement; /** * Expect assertions operating on a single component. */ - component(property: Definition | WebElement): ExpectElement; + component(property: Definition): ExpectElement; /** * Expect assertions operating on a page-object section, specified by '`@section_name`'. */ - section(property: Definition): ExpectSection; + section(property: ScopedSelector): ExpectSection; /** * Expect assertions operating on a collection of elements, specified by a CSS/Xpath selector. * So far only .count is available. */ - elements(property: Definition): ExpectElements; + elements(property: ScopedSelector): ExpectElements; /** * Retrieves the page title value in order to be used for performing equal, match or contains assertions on it. diff --git a/types/index.d.ts b/types/index.d.ts index 3c8286b92b..947ee2dd1c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -55,7 +55,8 @@ export interface JSON_WEB_OBJECT extends ElementResult { getId: () => string; } -export type Definition = string | ElementProperties | Element | SeleniumBy | RelativeBy; +export type ScopedSelector = string | ElementProperties | Element | SeleniumBy | RelativeBy; +export type Definition = ScopedSelector | WebElement; export type Awaitable = Omit & PromiseLike; @@ -514,8 +515,10 @@ export interface NamespacedApi { cookies: CookiesNsCommands; alerts: AlertsNsCommands; document: DocumentNsCommands; + logs: LogsNsCommands; window: WindowNsCommands; firefox: FirefoxNsCommands; + network: NetworkNsCommands; assert: Assert; verify: Assert; @@ -566,7 +569,7 @@ export interface NightwatchAPI */ setSessionId(sessionId: string): this; - options: NightwatchTestOptions; + options: NightwatchOptions & Pick; Keys: NightwatchKeys; @@ -685,6 +688,8 @@ export class Element { timeout?: number; } +type ElementGlobalDefinition = string | SeleniumBy | RelativeBy | {selector: string; locateStrategy?: string} | {using: string, value: string}; + export interface ElementGlobal extends Element { /** * Get the server-assigned opaque ID assigned to this element. @@ -696,24 +701,24 @@ export interface ElementGlobal extends Element { /** * Locates the descendants of this element that match the given search criteria, and returns the first one. * - * If no `selector` is passed, returns the[WebElement](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html) + * If no `selector` is passed, returns the [WebElement](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html) * instance for this element. */ findElement(): Awaitable; findElement( - selector: Definition, + selector: ElementGlobalDefinition, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void ): Awaitable; /** * Locates and wraps the first element, that match the given search criteria in the descendants of this element, in global element() api object. * - * If no `selector` is passed, returns the[WebElement](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html) + * If no `selector` is passed, returns the [WebElement](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_WebElement.html) * instance for this element. */ find(): Awaitable; find( - selector: Definition, + selector: ElementGlobalDefinition, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void ): Awaitable; @@ -724,12 +729,12 @@ export interface ElementGlobal extends Element { * Locates all of the descendants of this element that match the given search criteria. */ findElements( - selector: Definition, + selector: ElementGlobalDefinition, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void ): Awaitable; findAll( - selector: Definition, + selector: ElementGlobalDefinition, callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void ): Awaitable; @@ -924,7 +929,7 @@ export interface ElementGlobal extends Element { } export function globalElement( - locator: Definition | WebElement, + locator: Definition, options?: { isComponent?: boolean; type: string; @@ -1383,70 +1388,6 @@ export interface ChromiumClientCommands { ) => void ): Awaitable; - /** - * Capture outgoing network calls from the browser. - * - * @example - * describe('capture network requests', function() { - * it('captures and logs network requests as they occur', function(this: ExtendDescribeThis<{requestCount: number}>) { - * this.requestCount = 1; - * browser - * .captureNetworkRequests((requestParams) => { - * console.log('Request Number:', this.requestCount!++); - * console.log('Request URL:', requestParams.request.url); - * console.log('Request method:', requestParams.request.method); - * console.log('Request headers:', requestParams.request.headers); - * }) - * .navigateTo('https://www.google.com'); - * }); - * }); - * - * @see https://nightwatchjs.org/guide/network-requests/capture-network-calls.html - */ - captureNetworkRequests( - onRequestCallback: ( - requestParams: Protocol.Network.RequestWillBeSentEvent - ) => void, - callback?: ( - this: NightwatchAPI, - result: NightwatchCallbackResult - ) => void - ): Awaitable; - - /** - * Intercept the request made on a particular URL and mock the response. - * - * @example - * describe('mock network response', function() { - * it('intercepts the request made to Google search and mocks its response', function() { - * browser - * .mockNetworkResponse('https://www.google.com/', { - * status: 200, - * headers: { - * 'Content-Type': 'UTF-8' - * }, - * body: 'Hello there!' - * }) - * .navigateTo('https://www.google.com/') - * .pause(2000); - * }); - * }); - * - * @see https://nightwatchjs.org/guide/network-requests/mock-network-response.html - */ - mockNetworkResponse( - urlToIntercept: string, - response?: { - status?: Protocol.Fetch.FulfillRequestRequest['responseCode']; - headers?: { [name: string]: string }; - body?: Protocol.Fetch.FulfillRequestRequest['body']; - }, - callback?: ( - this: NightwatchAPI, - result: NightwatchCallbackResult - ) => void - ): Awaitable; - /** * Override device mode/dimensions. * @@ -1576,69 +1517,15 @@ export interface ChromiumClientCommands { ) => void ): Awaitable; - /** - * Listen to the `console` events (ex. `console.log` event) and - * register callback to process the same. - * - * @example - * describe('capture console events', function() { - * it('captures and logs console.log event', function() { - * browser - * .captureBrowserConsoleLogs((event) => { - * console.log(event.type, event.timestamp, event.args[0].value); - * }) - * .navigateTo('https://www.google.com') - * .executeScript(function() { - * console.log('here'); - * }, []); - * }); - * }); - * - * @see https://nightwatchjs.org/guide/running-tests/capture-console-messages.html - */ - captureBrowserConsoleLogs( - onEventCallback: ( - event: Pick< - Protocol.Runtime.ConsoleAPICalledEvent, - 'type' | 'timestamp' | 'args' - > - ) => void, - callback?: ( - this: NightwatchAPI, - result: NightwatchCallbackResult - ) => void - ): Awaitable; + captureNetworkRequests: NetworkNsCommands['captureRequests']; - /** - * Catch the JavaScript exceptions thrown in the browser. - * - * @example - * describe('catch browser exceptions', function() { - * it('captures the js exceptions thrown in the browser', async function() { - * await browser.captureBrowserExceptions((event) => { - * console.log('>>> Exception:', event); - * }); - * - * await browser.navigateTo('https://duckduckgo.com/'); - * - * const searchBoxElement = await browser.findElement('input[name=q]'); - * await browser.executeScript(function(_searchBoxElement) { - * _searchBoxElement.setAttribute('onclick', 'throw new Error("Hello world!")'); - * }, [searchBoxElement]); - * - * await browser.elementIdClick(searchBoxElement.getId()); - * }); - * }); - * - * @see https://nightwatchjs.org/guide/running-tests/catch-js-exceptions.html - */ - captureBrowserExceptions( - onExceptionCallback: (event: Protocol.Runtime.ExceptionThrownEvent) => void, - callback?: ( - this: NightwatchAPI, - result: NightwatchCallbackResult - ) => void - ): Awaitable; + mockNetworkResponse: NetworkNsCommands['mockResponse']; + + setNetworkConditions: NetworkNsCommands['setConditions']; + + captureBrowserConsoleLogs: LogsNsCommands['captureBrowserConsoleLogs']; + + captureBrowserExceptions: LogsNsCommands['captureBrowserExceptions']; } export interface ClientCommands extends ChromiumClientCommands { @@ -4289,7 +4176,7 @@ export interface ElementCommands { * @see https://nightwatchjs.org/api/getShadowRoot.html */ getShadowRoot( - selector: Definition | WebElement, + selector: Definition, callback?: ( this: NightwatchAPI, result: NightwatchCallbackResult @@ -4297,7 +4184,7 @@ export interface ElementCommands { ): Awaitable; getShadowRoot( using: LocateStrategy, - selector: Definition | WebElement, + selector: Definition, callback?: ( this: NightwatchAPI, result: NightwatchCallbackResult @@ -5214,6 +5101,103 @@ export interface FirefoxNsCommands { uninstallAddon(addonId: string | PromiseLike): Awaitable, null>; } +export interface NetworkNsCommands { + /** + * Capture outgoing network calls from the browser. + * + * @example + * describe('capture network requests', function() { + * it('captures and logs network requests as they occur', function(this: ExtendDescribeThis<{requestCount: number}>) { + * this.requestCount = 1; + * browser + * .network.captureRequests((requestParams) => { + * console.log('Request Number:', this.requestCount!++); + * console.log('Request URL:', requestParams.request.url); + * console.log('Request method:', requestParams.request.method); + * console.log('Request headers:', requestParams.request.headers); + * }) + * .navigateTo('https://www.google.com'); + * }); + * }); + * + * @see https://nightwatchjs.org/guide/network-requests/capture-network-calls.html + */ + captureRequests( + onRequestCallback: ( + requestParams: Protocol.Network.RequestWillBeSentEvent + ) => void, + callback?: ( + this: NightwatchAPI, + result: NightwatchCallbackResult + ) => void + ): Awaitable, null>; + + /** + * Intercept the request made on a particular URL and mock the response. + * + * @example + * describe('mock network response', function() { + * it('intercepts the request made to Google search and mocks its response', function() { + * browser + * .network.mockResponse('https://www.google.com/', { + * status: 200, + * headers: { + * 'Content-Type': 'UTF-8' + * }, + * body: 'Hello there!' + * }) + * .navigateTo('https://www.google.com/') + * .pause(2000); + * }); + * }); + * + * @see https://nightwatchjs.org/guide/network-requests/mock-network-response.html + */ + mockResponse( + urlToIntercept: string, + response?: { + status?: Protocol.Fetch.FulfillRequestRequest['responseCode']; + headers?: { [name: string]: string }; + body?: Protocol.Fetch.FulfillRequestRequest['body']; + }, + callback?: ( + this: NightwatchAPI, + result: NightwatchCallbackResult + ) => void + ): Awaitable, null>; + + /** + * Command to set Chrome network emulation settings. + * + * @example + * describe('set network conditions', function() { + * it('sets the network conditions',function() { + * browser + * .network.setConditions({ + * offline: false, + * latency: 3000, + * download_throughput: 500 * 1024, + * upload_throughput: 500 * 1024 + * }); + * }); + * }); + * + * @see https://nightwatchjs.org/api/setNetworkConditions.html + */ + setConditions( + spec: { + offline: boolean; + latency: number; + download_throughput: number; + upload_throughput: number; + }, + callback?: ( + this: NightwatchAPI, + result: NightwatchCallbackResult + ) => void + ): Awaitable, null>; +} + export interface AlertsNsCommands { /** * Accepts the currently displayed alert dialog. Usually, this is equivalent to clicking on the 'OK' button in the dialog. @@ -5379,6 +5363,175 @@ export interface DocumentNsCommands { ): Awaitable, string>; } +export interface LogsNsCommands { + /** + * Gets a log from Selenium. + * + * @example + * describe('get log from Selenium', function() { + * it('get browser log (default)', function(browser) { + * browser.logs.getSessionLog(function(result) { + * if (result.status === 0) { + * const logEntriesArray = result.value; + * console.log('Log length: ' + logEntriesArray.length); + * logEntriesArray.forEach(function(log) { + * console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); + * }); + * } + * }); + * }); + * + * it('get driver log with ES6 async/await', async function(browser) { + * const driverLogAvailable = await browser.logs.isAvailable('driver'); + * if (driverLogAvailable) { + * const logEntriesArray = await browser.logs.getSessionLog('driver'); + * logEntriesArray.forEach(function(log) { + * console.log('[' + log.level + '] ' + log.timestamp + ' : ' + log.message); + * }); + * } + * }); + * }); + * + * @see https://nightwatchjs.org/api/logs/getSessionLog.html + */ + getSessionLog( + callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void + ): Awaitable, NightwatchLogEntry[]>; + getSessionLog( + typeString: NightwatchLogTypes, + callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void + ): Awaitable, NightwatchLogEntry[]>; + + /** + * Gets the available log types. More info about log types in WebDriver can be found here: https://github.com/SeleniumHQ/selenium/wiki/Logging + * + * @example + * describe('get available log types', function() { + * it('get log types', function(browser) { + * browser.logs.getSessionLogTypes(function(result) { + * if (result.status === 0) { + * const logTypes = result.value; + * console.log('Log types available:', logTypes); + * } + * }); + * }); + * + * it('get log types with ES6 async/await', async function(browser) { + * const logTypes = await browser.logs.getSessionLogTypes(); + * console.log('Log types available:', logTypes); + * }); + * }); + * + * @see https://nightwatchjs.org/api/logs/getSessionLogTypes.html + */ + getSessionLogTypes( + callback?: ( + this: NightwatchAPI, + result: NightwatchCallbackResult + ) => void + ): Awaitable< + IfUnknown, + NightwatchLogTypes[] + >; + + /** + * Utility command to test if the log type is available. + * + * @example + * describe('test if the log type is available', function() { + * it('test browser log type', function(browser) { + * browser.logs.isSessionLogAvailable('browser', function(result) { + * if (result.status === 0) { + * const isAvailable = result.value; + * if (isAvailable) { + * // do something more in here + * } + * } + * }); + * }); + * + * it('test driver log type with ES6 async/await', async function(browser) { + * const isAvailable = await browser.logs.isSessionLogAvailable('driver'); + * if (isAvailable) { + * // do something more in here + * } + * }); + * }); + * + * @see https://nightwatchjs.org/api/logs/isSessionLogAvailable.html + */ + isSessionLogAvailable( + callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void + ): Awaitable, boolean>; + isSessionLogAvailable( + typeString: NightwatchLogTypes, + callback?: (this: NightwatchAPI, result: NightwatchCallbackResult) => void + ): Awaitable, boolean>; + + /** + * Listen to the `console` events (ex. `console.log` event) and + * register callback to process the same. + * + * @example + * describe('capture console events', function() { + * it('captures and logs console.log event', function() { + * browser + * .captureBrowserConsoleLogs((event) => { + * console.log(event.type, event.timestamp, event.args[0].value); + * }) + * .navigateTo('https://www.google.com') + * .executeScript(function() { + * console.log('here'); + * }, []); + * }); + * }); + * + * @see https://nightwatchjs.org/guide/running-tests/capture-console-messages.html + */ + captureBrowserConsoleLogs( + onEventCallback: ( + event: Pick< + Protocol.Runtime.ConsoleAPICalledEvent, + 'type' | 'timestamp' | 'args' + > + ) => void, + callback?: ( + this: NightwatchAPI, + result: NightwatchCallbackResult + ) => void + ): Awaitable, null>; + + /** + * Catch the JavaScript exceptions thrown in the browser. + * + * @example + * describe('catch browser exceptions', function() { + * it('captures the js exceptions thrown in the browser', async function() { + * await browser.captureBrowserExceptions((event) => { + * console.log('>>> Exception:', event); + * }); + * + * await browser.navigateTo('https://duckduckgo.com/'); + * + * const searchBoxElement = await browser.findElement('input[name=q]'); + * await browser.executeScript(function(_searchBoxElement) { + * _searchBoxElement.setAttribute('onclick', 'throw new Error("Hello world!")'); + * }, [searchBoxElement]); + * + * await browser.elementIdClick(searchBoxElement.getId()); + * }); + * }); + * + * @see https://nightwatchjs.org/guide/running-tests/catch-js-exceptions.html + */ + captureBrowserExceptions( + onExceptionCallback: (event: Protocol.Runtime.ExceptionThrownEvent) => void, + callback?: ( + this: NightwatchAPI, + result: NightwatchCallbackResult + ) => void + ): Awaitable, null>; +} export interface WindowNsCommands { /** * Close the current window or tab. This can be useful when you're working with multiple windows/tabs open (e.g. an OAuth login). @@ -6040,34 +6193,6 @@ export interface WebDriverProtocolSessions { result: NightwatchCallbackResult ) => void ): Awaitable; - - /** - * Command to set Chrome network emulation settings. - * - * @example - * this.demoTest = function() { - * browser.setNetworkConditions({ - * offline: false, - * latency: 50000, - * download_throughput: 450 * 1024, - * upload_throughput: 150 * 1024 - * }); - * }; - * - * @see https://nightwatchjs.org/api/setNetworkConditions.html - */ - setNetworkConditions( - spec: { - offline: boolean; - latency: number; - download_throughput: number; - upload_throughput: number; - }, - callback?: ( - this: NightwatchAPI, - result: NightwatchCallbackResult - ) => void - ): Awaitable; } export interface WebDriverProtocolNavigation { diff --git a/types/nightwatch-options.d.ts b/types/nightwatch-options.d.ts index 009bec0331..6ba6e8aa23 100644 --- a/types/nightwatch-options.d.ts +++ b/types/nightwatch-options.d.ts @@ -129,6 +129,7 @@ export interface NightwatchScreenshotOptions { } export interface NightwatchOptions { + /** * Location(s) where custom commands will be loaded from. */ @@ -435,6 +436,12 @@ export interface NightwatchOptions { */ skiptags?: string; + /** + * Tag(s) used/to be used during test execution. + * Can be a single tag or an array of tags. + */ + tag_filter?: string | string[]; + /** * Use xpath as the default locator strategy. */ diff --git a/types/page-object.d.ts b/types/page-object.d.ts index 13c8339f36..1e663c135d 100644 --- a/types/page-object.d.ts +++ b/types/page-object.d.ts @@ -19,7 +19,7 @@ import { export interface SectionProperties { /** - * The element selector name + * The selector string to be used to find the section in the DOM. * * @example * sections: { @@ -30,6 +30,19 @@ export interface SectionProperties { */ selector: string; + /** + * The locate strategy to be used with `selector` when finding the section within the DOM. + * - css selector + * - link text + * - partial link text + * - tag name + * - xpath + * + * @example + * 'css selector' + */ + locateStrategy?: LocateStrategy; + /** * An object, or array of objects, of named element definitions to be used * as element selectors within element commands. @@ -196,12 +209,25 @@ export interface EnhancedPageObjectSections< Props > { /** - * The element selector name + * The selector string used to find the section in the DOM. * * @example - * '@searchBar' + * '#searchBar' */ selector: string; + + /** + * The locate strategy used with `selector` when finding the section within the DOM. + * - css selector + * - link text + * - partial link text + * - tag name + * - xpath + * + * @example + * 'css selector' + */ + locateStrategy: LocateStrategy; } interface EnhancedPageObjectSharedFields< @@ -272,15 +298,15 @@ interface EnhancedPageObjectSharedFields< export interface ElementProperties { /** - * The element selector name + * The selector string to be used to find the element in the DOM. * * @example - * '@searchBar' + * '#searchBar' */ selector: string; /** - * locator strategy can be one of + * The locate strategy to be used with `selector` when finding the element within the DOM. * - css selector * - link text * - partial link text diff --git a/types/tests/chromiumClientCommands.test-d.ts b/types/tests/chromiumClientCommands.test-d.ts index 6feb7641bc..95c0cfa2ab 100644 --- a/types/tests/chromiumClientCommands.test-d.ts +++ b/types/tests/chromiumClientCommands.test-d.ts @@ -97,6 +97,41 @@ describe('capture network requests', function () { expectType(result); }); + + it('captures and logs network requests as they occur', function (this: ExtendDescribeThis<{ requestCount: number }>) { + this.requestCount = 1; + browser + .network.captureRequests((requestParams) => { + console.log('Request Number:', this.requestCount!++); + console.log('Request URL:', requestParams.request.url); + console.log('Request method:', requestParams.request.method); + console.log('Request headers:', requestParams.request.headers); + }) + .navigateTo('https://www.google.com'); + }); + + it('tests different ways of using captureRequests', () => { + // with all parameters + browser.network.captureRequests( + (requestParams) => { + console.log('Request URL:', requestParams.request.url); + console.log('Request method:', requestParams.request.method); + console.log('Request headers:', requestParams.request.headers); + }, + function (result) { + expectType(this); + // without any parameter + expectError(this.network.captureRequests()) + console.log(result.value); + } + ); + }); + + it('tests captureRequests with async', async () => { + const result = await browser.network.captureRequests(() => {}); + + expectType(result); + }); }); // @@ -152,6 +187,197 @@ describe('mock network response', function () { expectType(result); }); + + it('intercepts the request made to Google search and mocks its response', function () { + browser + .network.mockResponse('https://www.google.com/', { + status: 200, + headers: { + 'Content-Type': 'UTF-8', + }, + body: 'Hello there!', + }) + .navigateTo('https://www.google.com/') + .pause(2000); + }); + + it('tests different ways of using mockNetworkResponse', () => { + // with all parameters + browser.network.mockResponse( + 'https://www.google.com/', + { + status: 200, + headers: { + 'Content-Type': 'UTF-8', + }, + body: 'Hello there!', + }, + function (result) { + expectType(this); + // without any parameter (invalid) + expectError(this.network.mockResponse()) + console.log(result.value); + } + ); + + // with no response + browser.network.mockResponse('https://www.google.com/'); + + // with empty response + browser.network.mockResponse('https://www.google.com/', {}); + + // with just one parameter + browser.network.mockResponse('https://www.google.com/', { + body: 'Hello there!', + }); + }); + + it('tests mockResponse with async', async () => { + const result = await browser.network.mockResponse('https://www.google.com/'); + + expectType(result); + }); +}); + +// +//.setNetworkConditions +// +describe('set network conditions', function () { + it('sets the network conditions', function () { + browser + .setNetworkConditions({ + offline: false, + latency: 3000, // Additional latency (ms). + download_throughput: 500 * 1024, // Maximal aggregated download throughput. + upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + }) + .navigateTo('https://www.google.com') + .pause(2000) + }); + + it('tests different ways of using setNetworkConditions', () => { + // with all parameters + browser.setNetworkConditions( + { + offline: false, + latency: 3000, // Additional latency (ms). + download_throughput: 500 * 1024, // Maximal aggregated download throughput. + upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + }, + function (result) { + expectType(this); + // without any parameter (resets the network conditions) + // without any parameter (invalid) + expectError(this.setNetworkConditions()) + // missing 'offline' parameter + expectError(this.setNetworkConditions({ + latency: 3000, + download_throughput: 500 * 1024, + upload_throughput: 500 * 1024, + })); + // missing 'latency' parameter + expectError(this.setNetworkConditions({ + offline: false, + download_throughput: 500 * 1024, + upload_throughput: 500 * 1024, + })); + // missing 'download_throughput' parameter + expectError(this.setNetworkConditions({ + offline: false, + latency: 3000, + upload_throughput: 500 * 1024, + })); + // missing 'upload_throughput' parameter + expectError(this.setNetworkConditions({ + offline: false, + latency: 3000, + download_throughput: 500 * 1024, + })); + + console.log(result.value); + } + ); + + }); + + it('tests setNetworkConditions with async', async () => { + const result = await browser.setNetworkConditions({ + offline: false, + latency: 3000, // Additional latency (ms). + download_throughput: 500 * 1024, // Maximal aggregated download throughput. + upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + }); + + expectType(result); + }); + + it('sets the network conditions', function () { + browser + .network.setConditions({ + offline: false, + latency: 3000, // Additional latency (ms). + download_throughput: 500 * 1024, // Maximal aggregated download throughput. + upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + }) + .navigateTo('https://www.google.com') + .pause(2000) + }); + + it('tests different ways of using setNetworkConditions', () => { + // with all parameters + browser.network.setConditions( + { + offline: false, + latency: 3000, // Additional latency (ms). + download_throughput: 500 * 1024, // Maximal aggregated download throughput. + upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + }, + function (result) { + expectType(this); + // without any parameter (resets the network conditions) + // without any parameter (invalid) + expectError(this.network.setConditions()) + // missing 'offline' parameter + expectError(this.network.setConditions({ + latency: 3000, + download_throughput: 500 * 1024, + upload_throughput: 500 * 1024, + })); + // missing 'latency' parameter + expectError(this.network.setConditions({ + offline: false, + download_throughput: 500 * 1024, + upload_throughput: 500 * 1024, + })); + // missing 'download_throughput' parameter + expectError(this.network.setConditions({ + offline: false, + latency: 3000, + upload_throughput: 500 * 1024, + })); + // missing 'upload_throughput' parameter + expectError(this.network.setConditions({ + offline: false, + latency: 3000, + download_throughput: 500 * 1024, + })); + + console.log(result.value); + } + ); + + }); + + it('tests setConditions with async', async () => { + const result = await browser.network.setConditions({ + offline: false, + latency: 3000, // Additional latency (ms). + download_throughput: 500 * 1024, // Maximal aggregated download throughput. + upload_throughput: 500 * 1024, // Maximal aggregated upload throughput. + }); + + expectType(result); + }); }); // @@ -332,6 +558,17 @@ describe('capture console events', function () { }, []); }); + it('captures and logs console.log event using logs ns', function () { + browser + .logs.captureBrowserConsoleLogs((event) => { + console.log(event.type, event.timestamp, event.args[0].value); + }) + .navigateTo('https://www.google.com') + .executeScript(function () { + console.log('here'); + }, []); + }); + it('tests different ways of using captureBrowserConsoleLogs', () => { // with all parameters browser.captureBrowserConsoleLogs( @@ -378,6 +615,24 @@ describe('catch browser exceptions', function () { await browser.elementIdClick(searchBoxElement.getId()); }); + it('captures the js exceptions thrown in the browser ', async function () { + await browser + .logs.captureBrowserExceptions((event) => { + console.log('>>> Exception:', event); + }) + .navigateTo('https://duckduckgo.com/'); + + const searchBoxElement = await browser.findElement('input[name=q]'); + await browser.executeScript( + function (_searchBoxElement) { + expectError(_searchBoxElement.setAttribute('onclick', 'throw new Error("Hello world!")')) + }, + [searchBoxElement] + ); + + await browser.elementIdClick(searchBoxElement.getId()); + }); + it('tests different ways of using captureBrowserExceptions', () => { // with all parameters browser.captureBrowserExceptions( diff --git a/types/tests/globalElementApi.test-d.ts b/types/tests/globalElementApi.test-d.ts index b999339bde..87b4e5e9ae 100644 --- a/types/tests/globalElementApi.test-d.ts +++ b/types/tests/globalElementApi.test-d.ts @@ -1,4 +1,4 @@ -import { expectAssignable, expectType } from "tsd"; +import { expectAssignable, expectError, expectType } from "tsd"; import { ElementGlobal, NightwatchAPI, NightwatchCallbackResult, NightwatchSizeAndPosition } from ".."; import { WebElement } from "selenium-webdriver"; @@ -55,6 +55,16 @@ describe('global element() api', function () { })); expectType(await elem.find('child-selector')); + elem.find({using: 'css selector', value: 'child-selector'}); + expectError(elem.find({value: 'child-selector'})); + expectError(elem.find({using: 'css selector', value: 'child-selector', abortOnFailure: true})); + + const elemProp = {locateStrategy: 'css selector', selector: 'child-selector', abortOnFailure: true}; + elem.find(elemProp); + expectError(elem.find({locateStrategy: 'css selector', selector: 'child-selector', abortOnFailure: true})); + + expectError(elem.find({locateStrategy: 'css selector', value: 'child-selector'})); + // .get() expectAssignable(elem.get()); expectType(await elem.get()); diff --git a/types/tests/index.test-d.ts b/types/tests/index.test-d.ts index 664a7c9540..da52325807 100644 --- a/types/tests/index.test-d.ts +++ b/types/tests/index.test-d.ts @@ -17,7 +17,8 @@ import { ElementResult, Awaitable, SectionProperties, - ScopedElement + ScopedElement, + LocateStrategy } from '..'; import { element as elementNamedExport } from '..'; import { WebElement } from 'selenium-webdriver'; @@ -34,6 +35,8 @@ const testGeneral: NightwatchTests = { 'Demo test Google 1': () => { browser.registerBasicAuth('test-username', 'test-password').navigateTo('https://google.com').pause(1000); + // check types on browser.options + expectType(browser.options.tag_filter); // expect element to be present in 1000ms browser.expect.element('body').to.be.present.before(1000); @@ -379,6 +382,7 @@ const appsSection = { const menuSection = { selector: '#gb', + locateStrategy: 'css selector', commands: [ { // add section commands here @@ -505,6 +509,12 @@ const testPage = { const menuSection = google.section.menu; + expectType(menuSection.selector); + expectType(menuSection.locateStrategy); + + google.expect.section('@menu').to.be.visible; + google.expect.section(menuSection).to.be.visible; + const result = menuSection .assert.visible('@mail') .assert.visible('@images'); diff --git a/types/tests/namespaceCommands.test-d.ts b/types/tests/namespaceCommands.test-d.ts new file mode 100644 index 0000000000..97bac91122 --- /dev/null +++ b/types/tests/namespaceCommands.test-d.ts @@ -0,0 +1,90 @@ +import { expectAssignable, expectType } from 'tsd'; +import { NightwatchAPI, NightwatchLogEntry, NightwatchLogTypes } from '..'; + + +// +// .logs.getSessionLog +// +describe('get log from Selenium', function() { + it('get browser log (default)', function(browser) { + const result = browser.logs.getSessionLog(function(result) { + expectType(this); + if (result.status === 0) { + const logEntriesArray = result.value; + expectType(logEntriesArray); + } + }); + expectAssignable(result); + + browser.logs.getSessionLog('client', function(result) { + expectType(this); + if (result.status === 0) { + const logEntriesArray = result.value; + expectType(logEntriesArray); + } + }); + }); + + it('get driver log with ES6 async/await', async function(browser) { + const driverLogAvailable = await browser.logs.isSessionLogAvailable('driver'); + expectType(driverLogAvailable); + + if (driverLogAvailable) { + const logEntriesArray = await browser.logs.getSessionLog('driver'); + expectType(logEntriesArray); + } + }); +}); + +// +// .logs.getSessionLogTypes +// +describe('get available log types', function() { + it('get log types', function(browser) { + const result = browser.logs.getSessionLogTypes(function(result) { + expectType(this); + if (result.status === 0) { + const logTypes = result.value; + expectType(logTypes); + } + }); + expectAssignable(result); + }); + + it('get log types with ES6 async/await', async function(browser) { + const logTypes = await browser.logs.getSessionLogTypes(); + expectType(logTypes); + }); +}); + +// +// .logs.isSessionLogAvailable +// +describe('test if the log type is available', function() { + it('test browser log type', function(browser) { + const result = browser.logs.isSessionLogAvailable(function(result) { + expectType(this); + if (result.status === 0) { + const isAvailable = result.value; + expectType(isAvailable); + } + }); + expectAssignable(result); + + browser.logs.isSessionLogAvailable('performance', function(result) { + expectType(this); + if (result.status === 0) { + const isAvailable = result.value; + expectType(isAvailable); + } + }); + }); + + it('test driver log type with ES6 async/await', async function(browser) { + const isAvailable = await browser.logs.isSessionLogAvailable('driver'); + expectType(isAvailable); + if (isAvailable) { + // do something more in here + } + }); +}); diff --git a/types/web-element.d.ts b/types/web-element.d.ts index fe1bdb9a7b..05d68b02f2 100644 --- a/types/web-element.d.ts +++ b/types/web-element.d.ts @@ -67,8 +67,8 @@ export interface ScopedElement extends Element, PromiseLike { } ): ScopedElement; - findAll(selector: ScopedElementSelector): Elements; - getAll(selector: ScopedElementSelector): Elements; + findAll(selector: ScopedSelector | Promise): Elements; + getAll(selector: ScopedSelector | Promise): Elements; findAllByText( text: string, @@ -167,6 +167,8 @@ export interface ScopedElement extends Element, PromiseLike { update(...keys: E): Promise; + upload(file: string): Promise; + getAccessibleName(): ElementValue; getAriaRole(): ElementValue; @@ -197,7 +199,7 @@ type WaitUntilOptions = { abortOnFailure?: boolean; }; -type WaitUntilActions = 'selected' | 'visible' | 'disabled' | 'enabled' | 'not.selected' | 'not.visible' | 'not.enabled'; +type WaitUntilActions = 'selected' | 'visible' | 'disabled' | 'enabled' | 'not.selected' | 'not.visible' | 'not.enabled' | 'present' | 'not.present'; export class Elements implements PromiseLike { constructor(