From 286fc9bb4b695ad53a4bbe6e524a5ccd32757374 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 29 Mar 2023 16:36:18 +0530 Subject: [PATCH 01/41] added build start event --- nightwatch/globals.js | 14 + package-lock.json | 2393 ++++++------------------------ package.json | 7 +- src/testObservability.js | 81 + src/utils/constants.js | 3 + src/utils/helper.js | 343 +++++ src/utils/requestQueueHandler.js | 87 ++ 7 files changed, 1001 insertions(+), 1927 deletions(-) create mode 100644 src/testObservability.js create mode 100644 src/utils/constants.js create mode 100644 src/utils/requestQueueHandler.js diff --git a/nightwatch/globals.js b/nightwatch/globals.js index a73f60f..5f0ff01 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -1,9 +1,16 @@ const LocalTunnel = require('../src/local-tunnel'); +const TestObservability = require('../src/testObservability'); const helper = require('../src/utils/helper'); const localTunnel = new LocalTunnel(); +const testObservability = new TestObservability(); module.exports = { + + reporter: function(results, done) { + done(results); + }, + async before(settings) { localTunnel.configure(settings); await localTunnel.start(); @@ -22,9 +29,16 @@ module.exports = { settings.desiredCapabilities['bstack:options'].localIdentifier = localTunnel._localOpts.localIdentifier; } } + + testObservability.configure(settings); + if (testObservability._user && testObservability._key) { + await testObservability.launchTestSession(settings); + } + }, async after() { + // this.reporter(); localTunnel.stop(); }, diff --git a/package-lock.json b/package-lock.json index 4ae9568..d8fba62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1919 +1,8 @@ { "name": "@nightwatch/browserstack", "version": "0.1.3", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "@nightwatch/browserstack", - "version": "0.1.3", - "license": "MIT", - "dependencies": { - "browserstack-local": "^1.5.1" - }, - "devDependencies": { - "chai": "^4.3.7", - "eslint": "^8.31.0", - "mocha": "^10.2.0", - "mockery": "^2.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "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", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/acorn": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", - "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", - "dependencies": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chai": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", - "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^4.1.2", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", - "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.4.1", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "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.4.0", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "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-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "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==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "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" - } - }, - "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==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz", - "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "13.19.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", - "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==" - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-sdsl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", - "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "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.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "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==", - "dev": true, - "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", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mockery": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz", - "integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==", - "dev": true - }, - "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/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dependencies": { - "through": "~2.3" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "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/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dependencies": { - "duplexer": "~0.1.1" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", - "dependencies": { - "rimraf": "~2.5.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/which": { - "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" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, "dependencies": { "@eslint/eslintrc": { "version": "1.4.1", @@ -1981,6 +70,24 @@ "fastq": "^1.6.0" } }, + "@sindresorhus/is": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.3.0.tgz", + "integrity": "sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==" + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", + "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + }, "acorn": { "version": "8.8.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", @@ -1991,8 +98,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "agent-base": { "version": "6.0.2", @@ -2006,7 +112,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2051,17 +156,53 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "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==", + "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", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "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==" + }, + "aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "requires": { + "tweetnacl": "^0.14.3" + } + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -2104,6 +245,25 @@ "temp-fs": "^0.9.9" } }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==" + }, + "cacheable-request": { + "version": "10.2.9", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.9.tgz", + "integrity": "sha512-CaAMr53AS1Tb9evO1BIWFnZjSr8A4pbXofpsNVWPMDZZj3ZQKHwsQG9BrTqQ4x5ZYJXz1T2b8LLtTZODxSpzbg==", + "requires": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2116,6 +276,11 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "chai": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", @@ -2200,11 +365,24 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2216,6 +394,14 @@ "which": "^2.0.1" } }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "requires": { + "assert-plus": "^1.0.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -2230,6 +416,21 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, "deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -2245,6 +446,16 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -2265,6 +476,15 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2418,17 +638,25 @@ "through": "~2.3.1" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -2506,6 +734,26 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + }, + "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==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==" + }, "from": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", @@ -2535,6 +783,37 @@ "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-last-commit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/git-last-commit/-/git-last-commit-1.0.1.tgz", + "integrity": "sha512-FDSgeMqa7GnJDxt/q0AbrxbfeTyxp4ImxEw1e4nw6NUHA5FMhFUq33dTXI4Xdgcj1VQ1q5QLWF6WxFrJ8KCBOg==" + }, + "git-repo-info": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-2.1.1.tgz", + "integrity": "sha512-8aCohiDo4jwjOwma4FmYFd3i97urZulL8XL24nIPxuE+GZnfsAyy/g2Shqx6OjUiFKUXZM+Yy+KHnOmmA3FVcg==" + }, + "gitconfiglocal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz", + "integrity": "sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg==", + "requires": { + "ini": "^1.3.2" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2566,12 +845,44 @@ "type-fest": "^0.20.2" } }, + "got": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.0.tgz", + "integrity": "sha512-WTcaQ963xV97MN3x0/CbAriXFZcXCfgxVp91I+Ze6pawQOa7SgzwSx2zIJJsX+kTajMnVs0xcFD1TxZKFqhdnQ==", + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2584,6 +895,30 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "http2-wrapper": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.0.tgz", + "integrity": "sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -2629,6 +964,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2682,6 +1022,11 @@ "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==" }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -2694,6 +1039,11 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, "js-sdsl": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz", @@ -2709,11 +1059,25 @@ "argparse": "^2.0.1" } }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -2721,6 +1085,30 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "keyv": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", + "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "requires": { + "json-buffer": "3.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2765,11 +1153,34 @@ "get-func-name": "^2.0.0" } }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" + }, "map-stream": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==" }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2898,6 +1309,16 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2920,6 +1341,11 @@ "word-wrap": "^1.2.3" } }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2978,6 +1404,11 @@ "through": "~2.3" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2998,11 +1429,20 @@ "event-stream": "=3.3.4" } }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" }, "queue-microtask": { "version": "1.2.3", @@ -3010,6 +1450,11 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -3034,18 +1479,58 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "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" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "requires": { + "lowercase-keys": "^3.0.0" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3072,8 +1557,12 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "serialize-javascript": { "version": "6.0.0", @@ -3107,6 +1596,22 @@ "through": "2" } }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "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" + } + }, "stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -3178,6 +1683,28 @@ "is-number": "^7.0.0" } }, + "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==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "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==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3203,11 +1730,25 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 596207a..8eec153 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,12 @@ ], "license": "MIT", "dependencies": { - "browserstack-local": "^1.5.1" + "browserstack-local": "^1.5.1", + "git-last-commit": "^1.0.1", + "git-repo-info": "^2.1.1", + "gitconfiglocal": "^2.1.0", + "got": "^12.6.0", + "request": "^2.88.2" }, "devDependencies": { "chai": "^4.3.7", diff --git a/src/testObservability.js b/src/testObservability.js new file mode 100644 index 0000000..350d2f4 --- /dev/null +++ b/src/testObservability.js @@ -0,0 +1,81 @@ +const os = require('os'); +const helper = require('../src/utils/helper'); + +class TestObservability { + configure(settings = {}) { + this._settings = settings['@nightwatch/browserstack'] || {}; + + this._user = helper.getObservabilityUser(this._settings); + this._key = helper.getObservabilityKey(this._settings); + } + + async launchTestSession() { + const options = this._settings.testObservabilityOptions || {}; + const data = { + format: 'json', + project_name: helper.getObservabilityProject(this._settings), + name: helper.getObservabilityBuild(this._settings), + build_identifier: options.buildIdentifier, + description: options.buildDescription || '', + start_time: (new Date()).toISOString(), + tags: helper.getObservabilityBuildTags(this._settings), + host_info: { + hostname: os.hostname(), + platform: os.platform(), + type: os.type(), + version: os.version(), + arch: os.arch() + }, + ci_info: helper.getCiInfo(), + build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER, + failed_tests_rerun: process.env.BROWSERSTACK_RERUN || false, + version_control: await helper.getGitMetaData(), + observability_version: { + frameworkName: 'nightwatch', + frameworkVersion: helper.getPackageVersion('nightwatch'), + sdkVersion: helper.getAgentVersion() + } + }; + + const config = { + auth: { + username: this._user, + password: this._key + }, + headers: { + 'Content-Type': 'application/json', + 'X-BSTACK-TESTOPS': 'true' + } + }; + + try { + const response = await helper.nodeRequest('POST', 'api/v1/builds', data, config); + console.log('Build creation successfull!'); + process.env.BS_TESTOPS_BUILD_COMPLETED = true; + + if (response.data && response.data.jwt) { + process.env.BS_TESTOPS_JWT = response.data.jwt; + } + if (response.data && response.data.build_hashed_id) { + process.env.BS_TESTOPS_BUILD_HASHED_ID = response.data.build_hashed_id; + } + if (response.data && response.data.allow_screenshots) { + process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = response.data.allow_screenshots.toString(); + } + } catch (error) { + if (error.response) { + console.log(`EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + } else { + if ((error.message && error.message.includes('with status : 401')) || (error && error.toString().includes('with status : 401'))) { + console.log('Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); + } else { + console.log(`EXCEPTION IN BUILD START EVENT : ${error.message || error}`); + } + } + process.env.BS_TESTOPS_BUILD_COMPLETED = false; + + } + } +} + +module.exports = TestObservability; diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 0000000..7d2b45f --- /dev/null +++ b/src/utils/constants.js @@ -0,0 +1,3 @@ +exports.BATCH_SIZE = 1000; +exports.BATCH_INTERVAL = 2000; +exports.API_URL = 'http://testops-collector-stag.us-east-1.elasticbeanstalk.com'; diff --git a/src/utils/helper.js b/src/utils/helper.js index 0f8771d..336efcd 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -1,4 +1,48 @@ const os = require('os'); +const fs = require('fs'); +const path = require('path'); +const http = require('node:http'); +const https = require('node:https'); +const request = require('request'); +const {promisify} = require('util'); +const gitLastCommit = require('git-last-commit'); +const gitRepoInfo = require('git-repo-info'); +const gitconfig = require('gitconfiglocal'); +const {API_URL} = require('./constants'); +const pGitconfig = promisify(gitconfig); +const {execSync} = require('child_process'); + +const httpKeepAliveAgent = new http.Agent({ + keepAlive: true, + timeout: 60000, + maxSockets: 2, + maxTotalSockets: 2 +}); + +const httpsKeepAliveAgent = new https.Agent({ + keepAlive: true, + timeout: 60000, + maxSockets: 2, + maxTotalSockets: 2 +}); + +const httpScreenshotsKeepAliveAgent = new http.Agent({ + keepAlive: true, + timeout: 60000, + maxSockets: 2, + maxTotalSockets: 2 +}); + +const httpsScreenshotsKeepAliveAgent = new https.Agent({ + keepAlive: true, + timeout: 60000, + maxSockets: 2, + maxTotalSockets: 2 +}); + +const GLOBAL_MODULE_PATH = execSync('npm root -g').toString().trim(); +const RequestQueueHandler = require('./requestQueueHandler'); +exports.requestQueueHandler = new RequestQueueHandler(); exports.generateLocalIdentifier = () => { const formattedDate = new Intl.DateTimeFormat('en-GB', { @@ -20,6 +64,305 @@ exports.isUndefined = value => (value === undefined || value === null); exports.isObject = value => (!this.isUndefined(value) && value.constructor === Object); +exports.getObservabilityUser = (config) => { + if (process.env.BROWSERSTACK_USERNAME) { + return process.env.BROWSERSTACK_USERNAME; + } + + return config.user; +}; + +exports.getObservabilityKey = (config) => { + if (process.env.BROWSERSTACK_ACCESS_KEY) { + return process.env.BROWSERSTACK_ACCESS_KEY; + } + + return config.key; +}; + +exports.getObservabilityProject = (options) => { + if (process.env.TEST_OBSERVABILITY_PROJECT_NAME) { + return process.env.TEST_OBSERVABILITY_PROJECT_NAME; + } + if (options.testObservabilityOptions && options.testObservabilityOptions.projectName) { + return options.testObservabilityOptions.projectName; + } + + return ''; +}; + +exports.getObservabilityBuild = (options) => { + if (process.env.TEST_OBSERVABILITY_BUILD_NAME) { + return process.env.TEST_OBSERVABILITY_BUILD_NAME; + } + if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) { + return options.testObservabilityOptions.buildName; + } + + return ''; +}; + +exports.getObservabilityBuildTags = (options) => { + if (process.env.TEST_OBSERVABILITY_BUILD_TAG) { + return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(','); + } + if (options.testObservabilityOptions && options.testObservabilityOptions.buildTag) { + return options.testObservabilityOptions.buildTag; + } + + return []; +}; + +exports.getCiInfo = () => { + var env = process.env; + // Jenkins + if ((typeof env.JENKINS_URL === 'string' && env.JENKINS_URL.length > 0) || (typeof env.JENKINS_HOME === 'string' && env.JENKINS_HOME.length > 0)) { + return { + name: 'Jenkins', + build_url: env.BUILD_URL, + job_name: env.JOB_NAME, + build_number: env.BUILD_NUMBER + }; + } + // CircleCI + if (env.CI === 'true' && env.CIRCLECI === 'true') { + return { + name: 'CircleCI', + build_url: env.CIRCLE_BUILD_URL, + job_name: env.CIRCLE_JOB, + build_number: env.CIRCLE_BUILD_NUM + }; + } + // Travis CI + if (env.CI === 'true' && env.TRAVIS === 'true') { + return { + name: 'Travis CI', + build_url: env.TRAVIS_BUILD_WEB_URL, + job_name: env.TRAVIS_JOB_NAME, + build_number: env.TRAVIS_BUILD_NUMBER + }; + } + // Codeship + if (env.CI === 'true' && env.CI_NAME === 'codeship') { + return { + name: 'Codeship', + build_url: null, + job_name: null, + build_number: null + }; + } + // Bitbucket + if (env.BITBUCKET_BRANCH && env.BITBUCKET_COMMIT) { + return { + name: 'Bitbucket', + build_url: env.BITBUCKET_GIT_HTTP_ORIGIN, + job_name: null, + build_number: env.BITBUCKET_BUILD_NUMBER + }; + } + // Drone + if (env.CI === 'true' && env.DRONE === 'true') { + return { + name: 'Drone', + build_url: env.DRONE_BUILD_LINK, + job_name: null, + build_number: env.DRONE_BUILD_NUMBER + }; + } + // Semaphore + if (env.CI === 'true' && env.SEMAPHORE === 'true') { + return { + name: 'Semaphore', + build_url: env.SEMAPHORE_ORGANIZATION_URL, + job_name: env.SEMAPHORE_JOB_NAME, + build_number: env.SEMAPHORE_JOB_ID + }; + } + // GitLab + if (env.CI === 'true' && env.GITLAB_CI === 'true') { + return { + name: 'GitLab', + build_url: env.CI_JOB_URL, + job_name: env.CI_JOB_NAME, + build_number: env.CI_JOB_ID + }; + } + // Buildkite + if (env.CI === 'true' && env.BUILDKITE === 'true') { + return { + name: 'Buildkite', + build_url: env.BUILDKITE_BUILD_URL, + job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME, + build_number: env.BUILDKITE_BUILD_NUMBER + }; + } + // Visual Studio Team Services + if (env.TF_BUILD === 'True') { + return { + name: 'Visual Studio Team Services', + build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`, + job_name: env.SYSTEM_DEFINITIONID, + build_number: env.BUILD_BUILDID + }; + } + + // if no matches, return null + return null; +}; + +const findGitConfig = (filePath) => { + if (filePath == null || filePath === '' || filePath === '/') { + return null; + } + try { + fs.statSync(filePath + '/.git/config'); + + return filePath; + } catch (e) { + const parentFilePath = filePath.split('/'); + parentFilePath.pop(); + + return findGitConfig(parentFilePath.join('/')); + } +}; + +exports.getGitMetaData = async () => { + var info = gitRepoInfo(); + if (!info.commonGitDir) { + console.log('Unable to find a Git directory'); + + return; + } + + return new Promise(async (resolve, reject) => { + try { + if (!info.author && findGitConfig(process.cwd())) { + /* commit objects are packed */ + gitLastCommit.getLastCommit(async (err, commit) => { + info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`; + info['authorDate'] = info['authorDate'] || commit['authoredOn']; + info['committer'] = info['committer'] || `${commit['committer']['name'].replace(/[“]+/g, '')} <${commit['committer']['email'].replace(/[“]+/g, '')}>`; + info['committerDate'] = info['committerDate'] || commit['committedOn']; + info['commitMessage'] = info['commitMessage'] || commit['subject']; + + const {remote} = await pGitconfig(info.commonGitDir); + const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); + resolve({ + 'name': 'git', + 'sha': info['sha'], + 'short_sha': info['abbreviatedSha'], + 'branch': info['branch'], + 'tag': info['tag'], + 'committer': info['committer'], + 'committer_date': info['committerDate'], + 'author': info['author'], + 'author_date': info['authorDate'], + 'commit_message': info['commitMessage'], + 'root': info['root'], + 'common_git_dir': info['commonGitDir'], + 'worktree_git_dir': info['worktreeGitDir'], + 'last_tag': info['lastTag'], + 'commits_since_last_tag': info['commitsSinceLastTag'], + 'remotes': remotes + }); + }, {dst: findGitConfig(process.cwd())}); + } else { + const {remote} = await pGitconfig(info.commonGitDir); + const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); + resolve({ + 'name': 'git', + 'sha': info['sha'], + 'short_sha': info['abbreviatedSha'], + 'branch': info['branch'], + 'tag': info['tag'], + 'committer': info['committer'], + 'committer_date': info['committerDate'], + 'author': info['author'], + 'author_date': info['authorDate'], + 'commit_message': info['commitMessage'], + 'root': info['root'], + 'common_git_dir': info['commonGitDir'], + 'worktree_git_dir': info['worktreeGitDir'], + 'last_tag': info['lastTag'], + 'commits_since_last_tag': info['commitsSinceLastTag'], + 'remotes': remotes + }); + } + } catch (err) { + console.log(`Exception in populating Git metadata with error : ${err}`); + resolve({}); + } + }); +}; + +exports.requireModule = (module) => { + console.log(`Getting ${module} from ${process.cwd()}`); + const local_path = path.join(process.cwd(), 'node_modules', module); + if (!fs.existsSync(local_path)) { + console.log(`${module} doesn't exist at ${process.cwd()}`); + console.log(`Getting ${module} from ${GLOBAL_MODULE_PATH}`); + + const global_path = path.join(GLOBAL_MODULE_PATH, module); + if (!fs.existsSync(global_path)) { + throw new Error(`${module} doesn't exist.`); + } + + return require(global_path); + } + + return require(local_path); +}; + +exports.getAgentVersion = () => { + const _path = path.join(__dirname, '../../package.json'); + if (fs.existsSync(_path)) {return require(_path).version} +}; + +const packages = {}; + +exports.getPackageVersion = (package_) => { + if (packages[package_]) {return packages[package_]} + + return packages[package_] = this.requireModule(`${package_}/package.json`).version; +}; + +exports.nodeRequest = (type, url, data, config) => { + return new Promise((resolve, reject) => { + const options = {...config, ...{ + method: type, + url: `${API_URL}/${url}`, + body: data, + json: config.headers['Content-Type'] === 'application/json', + agent: API_URL.includes('https') ? httpsKeepAliveAgent : httpKeepAliveAgent + }}; + + if (url === exports.requestQueueHandler.screenshotEventUrl) { + options.agent = API_URL.includes('https') ? httpsScreenshotsKeepAliveAgent : httpScreenshotsKeepAliveAgent; + } + + request(options, function callback(error, response, body) { + if (error) { + reject(error); + } else if (response.statusCode !== 200) { + if (response.statusCode === 401) { + reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); + } else { + reject(`Received response from BrowserStack Server with status : ${response.statusCode}`); + } + } else { + try { + if (typeof(body) !== 'object') {body = JSON.parse(body)} + } catch (e) { + reject('Not a JSON response from BrowserStack Server'); + } + resolve({ + data: body + }); + } + }); + }); +}; + exports.getAccessKey = (settings) => { let accessKey = null; if (this.isObject(settings.desiredCapabilities)) { diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js new file mode 100644 index 0000000..0dff0c6 --- /dev/null +++ b/src/utils/requestQueueHandler.js @@ -0,0 +1,87 @@ +const { BATCH_SIZE, BATCH_INTERVAL } = require('./constants'); +// const { batchAndPostEvents } = require('./helper'); + +class RequestQueueHandler { + constructor() { + this.queue = []; + this.started = false; + this.eventUrl = 'api/v1/batch'; + this.screenshotEventUrl = 'api/v1/screenshots'; + this.BATCH_EVENT_TYPES = ['LogCreated', 'CBTSessionCreated', 'TestRunFinished', 'TestRunSkipped', 'HookRunFinished', 'TestRunStarted', 'HookRunStarted']; + this.pollEventBatchInterval = null; + } + + start = () => { + if(!this.started) { + this.started = true; + this.startEventBatchPolling(); + } + } + + add = (event) => { + if(this.BATCH_EVENT_TYPES.includes(event.event_type)) { + if(event.logs && event.logs[0] && event.logs[0].kind === 'TEST_SCREENSHOT') { + return { + shouldProceed: true, + proceedWithData: [event], + proceedWithUrl: this.screenshotEventUrl + } + } + + this.queue.push(event); + let data = null, shouldProceed = this.shouldProceed(); + if(shouldProceed) { + data = this.queue.slice(0,BATCH_SIZE); + this.queue.splice(0,BATCH_SIZE); + this.resetEventBatchPolling(); + } + return { + shouldProceed: shouldProceed, + proceedWithData: data, + proceedWithUrl: this.eventUrl + } + } else { + return { + shouldProceed: true + } + } + } + + // shutdown = async () => { + // this.removeEventBatchPolling('REMOVING'); + // while(this.queue.length > 0) { + // const data = this.queue.slice(0,BATCH_SIZE); + // this.queue.splice(0,BATCH_SIZE); + // // await batchAndPostEvents(this.eventUrl,'Shutdown-Queue',data); + // } + // } + + // startEventBatchPolling = () => { + // this.pollEventBatchInterval = setInterval(async () => { + // if(this.queue.length > 0) { + // const data = this.queue.slice(0,BATCH_SIZE); + // this.queue.splice(0,BATCH_SIZE); + // // await batchAndPostEvents(this.eventUrl,'Interval-Queue',data); + // } + // }, BATCH_INTERVAL); + // } + + resetEventBatchPolling = () => { + this.removeEventBatchPolling('RESETTING'); + this.startEventBatchPolling(); + } + + removeEventBatchPolling = (tag) => { + if(this.pollEventBatchInterval) { + clearInterval(this.pollEventBatchInterval); + this.pollEventBatchInterval = null; + if(tag === 'REMOVING') this.started = false; + } + } + + shouldProceed = () => { + return this.queue.length >= BATCH_SIZE; + } +} + +module.exports = RequestQueueHandler; From 32369f358c3af415f43e26ceeb1d3b4070781c9c Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Thu, 30 Mar 2023 15:52:37 +0530 Subject: [PATCH 02/41] stop build event --- nightwatch/globals.js | 1 + src/testObservability.js | 48 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 5f0ff01..72f6406 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -40,6 +40,7 @@ module.exports = { async after() { // this.reporter(); localTunnel.stop(); + await testObservability.stopBuildUpstream(); }, beforeChildProcess(settings) { diff --git a/src/testObservability.js b/src/testObservability.js index 350d2f4..b2d2ddd 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -1,5 +1,6 @@ const os = require('os'); const helper = require('../src/utils/helper'); +const {API_URL} = require('./utils/constants'); class TestObservability { configure(settings = {}) { @@ -76,6 +77,53 @@ class TestObservability { } } + + async stopBuildUpstream () { + if (!process.env.BS_TESTOPS_BUILD_COMPLETED) { + return; + } + if (!process.env.BS_TESTOPS_JWT) { + console.log('[STOP_BUILD] Missing Authentication Token/ Build ID'); + + return { + status: 'error', + message: 'Token/buildID is undefined, build creation might have failed' + }; + } + const data = { + 'stop_time': (new Date()).toISOString() + }; + const config = { + headers: { + 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, + 'Content-Type': 'application/json', + 'X-BSTACK-TESTOPS': 'true' + } + }; + + try { + const response = await helper.nodeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); + if (response.data.error) { + throw ({message: response.data.error}); + } else { + return { + status: 'success', + message: '' + }; + } + } catch (error) { + if (error.response) { + console.log(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + } else { + console.log(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + } + + return { + status: 'error', + message: error.message || error.response ? `${error.response.status}:${error.response.statusText}` : error + }; + } + } } module.exports = TestObservability; From fdaaebd85e65fb10597f33799b169c059c417c64 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 11 Apr 2023 10:56:11 +0530 Subject: [PATCH 03/41] populated test and hook events for test observability --- nightwatch/globals.js | 10 ++- package-lock.json | 72 +++++++++++++++- package.json | 4 +- src/testObservability.js | 143 ++++++++++++++++++++++++++++++- src/utils/constants.js | 2 +- src/utils/helper.js | 134 +++++++++++++++++++++++++++-- src/utils/requestQueueHandler.js | 82 +++++++++--------- 7 files changed, 391 insertions(+), 56 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 72f6406..554ced4 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -8,6 +8,12 @@ const testObservability = new TestObservability(); module.exports = { reporter: function(results, done) { + const modulesWithEnv = results['modulesWithEnv']; + for (const testSetting in modulesWithEnv) { + for (const testFile in modulesWithEnv[testSetting]) { + testObservability.processTestFile(modulesWithEnv[testSetting][testFile]); + } + } done(results); }, @@ -37,8 +43,10 @@ module.exports = { }, + async beforeEach() { + }, + async after() { - // this.reporter(); localTunnel.stop(); await testObservability.stopBuildUpstream(); }, diff --git a/package-lock.json b/package-lock.json index d8fba62..8701fcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -128,8 +128,7 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -348,6 +347,23 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "color-convert": { @@ -548,6 +564,23 @@ "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "eslint-scope": { @@ -1629,13 +1662,29 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -1779,6 +1828,23 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "wrappy": { diff --git a/package.json b/package.json index 8eec153..119b882 100644 --- a/package.json +++ b/package.json @@ -42,8 +42,8 @@ "git-last-commit": "^1.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", - "got": "^12.6.0", - "request": "^2.88.2" + "request": "^2.88.2", + "strip-ansi": "^6.0.1" }, "devDependencies": { "chai": "^4.3.7", diff --git a/src/testObservability.js b/src/testObservability.js index b2d2ddd..8292239 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -1,6 +1,7 @@ const os = require('os'); -const helper = require('../src/utils/helper'); -const {API_URL} = require('./utils/constants'); +const stripAnsi = require('strip-ansi'); +const {v4: uuidv4} = require('uuid'); +const helper = require('./utils/helper'); class TestObservability { configure(settings = {}) { @@ -124,6 +125,144 @@ class TestObservability { }; } } + + processTestFile(testFileReport) { + const completedSections = testFileReport['completedSections']; + const completed = testFileReport['completed']; + if (completedSections) { + const globalBeforeEachHookId = uuidv4(); + const beforeHookId = uuidv4(); + const afterHookId = uuidv4(); + const globalAfterEachHookId = uuidv4(); + const hookIds = [globalBeforeEachHookId, beforeHookId, afterHookId, globalAfterEachHookId]; + for (const sectionName in completedSections) { + const eventData = completedSections[sectionName]; + if (sectionName === '__global_beforeEach_hook') { + this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH'); + if (eventData.httpOutput && eventData.httpOutput.length > 0) { + for (let i=0; i 0) { + for (let i=0; i 0) { + for (let i=0; i 0) { + for (let i=0; i 0) { + for (let i=0; i { const formattedDate = new Intl.DateTimeFormat('en-GB', { @@ -65,18 +68,10 @@ exports.isUndefined = value => (value === undefined || value === null); exports.isObject = value => (!this.isUndefined(value) && value.constructor === Object); exports.getObservabilityUser = (config) => { - if (process.env.BROWSERSTACK_USERNAME) { - return process.env.BROWSERSTACK_USERNAME; - } - return config.user; }; exports.getObservabilityKey = (config) => { - if (process.env.BROWSERSTACK_ACCESS_KEY) { - return process.env.BROWSERSTACK_ACCESS_KEY; - } - return config.key; }; @@ -210,6 +205,10 @@ exports.getCiInfo = () => { return null; }; +exports.vcFilePath = (filePath) => { + return findGitConfig(filePath); +}; + const findGitConfig = (filePath) => { if (filePath == null || filePath === '' || filePath === '/') { return null; @@ -363,6 +362,125 @@ exports.nodeRequest = (type, url, data, config) => { }); }; +exports.pending_test_uploads = { + count: 0 +}; + +exports.uploadEventData = async (eventData, run=0) => { + const log_tag = { + ['TestRunStarted']: 'Test_Start_Upload', + ['TestRunFinished']: 'Test_End_Upload', + ['TestRunSkipped']: 'Test_Skipped_Upload', + ['LogCreated']: 'Log_Upload', + ['HookRunStarted']: 'Hook_Start_Upload', + ['HookRunFinished']: 'Hook_End_Upload', + ['CBTSessionCreated']: 'CBT_Upload' + }[eventData.event_type]; + + if (run === 0 && process.env.BS_TESTOPS_JWT !== 'null') {exports.pending_test_uploads.count += 1} + + if (process.env.BS_TESTOPS_BUILD_COMPLETED === 'true') { + if (process.env.BS_TESTOPS_JWT === 'null') { + console.log(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); + exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count-1); + + return { + status: 'error', + message: 'Token/buildID is undefined, build creation might have failed' + }; + } + let data = eventData; + let event_api_url = 'api/v1/event'; + + exports.requestQueueHandler.start(); + const { + shouldProceed, + proceedWithData, + proceedWithUrl + } = exports.requestQueueHandler.add(eventData); + if (!shouldProceed) { + return; + } else if (proceedWithData) { + data = proceedWithData; + event_api_url = proceedWithUrl; + } + + const config = { + headers: { + 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, + 'Content-Type': 'application/json', + 'X-BSTACK-TESTOPS': 'true' + } + }; + + try { + const response = await this.nodeRequest('POST', event_api_url, data, config); + if (response.data.error) { + throw ({message: response.data.error}); + } else { + console.log(`${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'}[${run}] event successfull!`); + exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); + + return { + status: 'success', + message: '' + }; + } + } catch (error) { + if (error.response) { + console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + } else { + console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + } + exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); + + return { + status: 'error', + message: error.message || (error.response ? `${error.response.status}:${error.response.statusText}` : error) + }; + } + + } else if (run >= 5) { + console.log(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : Build Start is not completed and ${log_tag} retry runs exceeded`); + if (process.env.BS_TESTOPS_JWT !== 'null') {exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count-1)} + + return { + status: 'error', + message: 'Retry runs exceeded' + }; + } else if (process.env.BS_TESTOPS_BUILD_COMPLETED !== 'false') { + setTimeout(function(){ exports.uploadEventData(eventData, run+1) }, 1000); + } +}; + +exports.batchAndPostEvents = async (eventUrl, kind, data) => { + const config = { + headers: { + 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, + 'Content-Type': 'application/json', + 'X-BSTACK-TESTOPS': 'true' + } + }; + + try { + console.log(JSON.stringify(data)); + const response = await this.nodeRequest('POST', eventUrl, data, config); + if (response.data.error) { + throw ({message: response.data.error}); + } else { + console.log(`${kind} event successfull!`); + exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); + } + } catch (error) { + if (error.response) { + console.log(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + } else { + console.log(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + } + exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); + } +}; + exports.getAccessKey = (settings) => { let accessKey = null; if (this.isObject(settings.desiredCapabilities)) { diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index 0dff0c6..cbe38e8 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -1,5 +1,5 @@ -const { BATCH_SIZE, BATCH_INTERVAL } = require('./constants'); -// const { batchAndPostEvents } = require('./helper'); +const {BATCH_SIZE, BATCH_INTERVAL} = require('./constants'); +const {batchAndPostEvents} = require('./helper'); class RequestQueueHandler { constructor() { @@ -11,75 +11,79 @@ class RequestQueueHandler { this.pollEventBatchInterval = null; } - start = () => { - if(!this.started) { + start() { + if (!this.started) { this.started = true; this.startEventBatchPolling(); } } - add = (event) => { - if(this.BATCH_EVENT_TYPES.includes(event.event_type)) { - if(event.logs && event.logs[0] && event.logs[0].kind === 'TEST_SCREENSHOT') { + add (event) { + if (this.BATCH_EVENT_TYPES.includes(event.event_type)) { + if (event.logs && event.logs[0] && event.logs[0].kind === 'TEST_SCREENSHOT') { return { shouldProceed: true, proceedWithData: [event], proceedWithUrl: this.screenshotEventUrl - } + }; } this.queue.push(event); - let data = null, shouldProceed = this.shouldProceed(); - if(shouldProceed) { - data = this.queue.slice(0,BATCH_SIZE); - this.queue.splice(0,BATCH_SIZE); + let data = null; const shouldProceed = this.shouldProceed(); + if (shouldProceed) { + data = this.queue.slice(0, BATCH_SIZE); + this.queue.splice(0, BATCH_SIZE); this.resetEventBatchPolling(); } + return { shouldProceed: shouldProceed, proceedWithData: data, proceedWithUrl: this.eventUrl - } - } else { - return { - shouldProceed: true - } + }; } + + return { + shouldProceed: true + }; + } - // shutdown = async () => { - // this.removeEventBatchPolling('REMOVING'); - // while(this.queue.length > 0) { - // const data = this.queue.slice(0,BATCH_SIZE); - // this.queue.splice(0,BATCH_SIZE); - // // await batchAndPostEvents(this.eventUrl,'Shutdown-Queue',data); - // } - // } + async shutdown () { + this.removeEventBatchPolling('REMOVING'); + while (this.queue.length > 0) { + const data = this.queue.slice(0, BATCH_SIZE); + this.queue.splice(0, BATCH_SIZE); + await batchAndPostEvents(this.eventUrl, 'Shutdown-Queue', data); + } + } - // startEventBatchPolling = () => { - // this.pollEventBatchInterval = setInterval(async () => { - // if(this.queue.length > 0) { - // const data = this.queue.slice(0,BATCH_SIZE); - // this.queue.splice(0,BATCH_SIZE); - // // await batchAndPostEvents(this.eventUrl,'Interval-Queue',data); - // } - // }, BATCH_INTERVAL); - // } + startEventBatchPolling () { + this.pollEventBatchInterval = setInterval(async () => { + if (this.queue.length > 0) { + const data = this.queue.slice(0, BATCH_SIZE); + this.queue.splice(0, BATCH_SIZE); + await batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); + } + }, BATCH_INTERVAL); + } - resetEventBatchPolling = () => { + resetEventBatchPolling () { this.removeEventBatchPolling('RESETTING'); this.startEventBatchPolling(); } - removeEventBatchPolling = (tag) => { - if(this.pollEventBatchInterval) { + removeEventBatchPolling (tag) { + if (this.pollEventBatchInterval) { clearInterval(this.pollEventBatchInterval); this.pollEventBatchInterval = null; - if(tag === 'REMOVING') this.started = false; + if (tag === 'REMOVING') { + this.started = false; + } } } - shouldProceed = () => { + shouldProceed () { return this.queue.length >= BATCH_SIZE; } } From a7dcf0650f9ce2b79f3a4702be6f40e77a36f9a3 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 11 Apr 2023 19:07:10 +0530 Subject: [PATCH 04/41] added screenshot for failure case --- src/testObservability.js | 42 ++++++++++++++++++++++++++++++---------- src/utils/helper.js | 1 - 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index 8292239..1ca6f4a 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -141,7 +141,7 @@ class TestObservability { this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH'); if (eventData.httpOutput && eventData.httpOutput.length > 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i { + return 'screenshot' in command; + }).forEach(command => { + this.createScreenshotLogEvent(testUuid, command.screenshot, command.startTime); + }); + } this.sendTestRunEvent(eventData, testFileReport, 'TestRunFinished', testUuid, null, sectionName, hookIds); } } } } - createLogEvent(httpRequest, httpResponse, test_run_uuid) { + createScreenshotLogEvent(testUuid, screenshot, timestamp) { + const eventData = { + event_type: 'LogCreated', + logs: [ + { + test_run_uuid: testUuid, + kind: 'TEST_SCREENSHOT', + timestamp: new Date(timestamp).toISOString(), + message: screenshot + } + ] + }; + helper.uploadEventData(eventData); + } + + createHttpLogEvent(httpRequest, httpResponse, test_run_uuid) { const eventData = { event_type: 'LogCreated', logs: [ @@ -230,14 +252,14 @@ class TestObservability { if (eventType === 'HookRunFinished' || eventType === 'TestRunFinished') { testData.finished_at = new Date(eventData.endTimestamp).toISOString(); testData.result = eventData.status === 'pass' ? 'passed' : 'failed'; - testData.duration_in_ms = eventData.time; + testData.duration_in_ms = 'timeMs' in eventData ? eventData.timeMs : eventData.time; if (eventData.status === 'fail' && eventData.lastError) { testData.failure = [ { - 'backtrace': eventData.lastError.stack + 'backtrace': [eventData.lastError.stack] } ]; - testData.failure_reason = eventData.lastError.message; + testData.failure_reason = stripAnsi(eventData.lastError.message); testData.failure_type = eventData.lastError.name.match(/Assert/) ? 'AssertionError' : 'UnhandledError'; } } diff --git a/src/utils/helper.js b/src/utils/helper.js index 761e971..e6a4d1d 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -463,7 +463,6 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { }; try { - console.log(JSON.stringify(data)); const response = await this.nodeRequest('POST', eventUrl, data, config); if (response.data.error) { throw ({message: response.data.error}); From 67a7a00870446d19793088876249494a6c780968 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 12 Apr 2023 14:50:18 +0530 Subject: [PATCH 05/41] changes for screenshots and integration object in teststart --- nightwatch/globals.js | 36 ++++++++++++++++++++++---------- src/testObservability.js | 23 +++++++++++++------- src/utils/helper.js | 28 ++++++++++++++++++++++--- src/utils/requestQueueHandler.js | 6 +++--- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 554ced4..fb8fe43 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -8,10 +8,16 @@ const testObservability = new TestObservability(); module.exports = { reporter: function(results, done) { - const modulesWithEnv = results['modulesWithEnv']; - for (const testSetting in modulesWithEnv) { - for (const testFile in modulesWithEnv[testSetting]) { - testObservability.processTestFile(modulesWithEnv[testSetting][testFile]); + if (helper.isTestObservabilitySession()) { + try { + const modulesWithEnv = results['modulesWithEnv']; + for (const testSetting in modulesWithEnv) { + for (const testFile in modulesWithEnv[testSetting]) { + testObservability.processTestFile(modulesWithEnv[testSetting][testFile]); + } + } + } catch (error) { + console.log(`Something went wrong in processing report file for test observability - ${error}`); } } done(results); @@ -36,19 +42,27 @@ module.exports = { } } - testObservability.configure(settings); - if (testObservability._user && testObservability._key) { - await testObservability.launchTestSession(settings); + try { + testObservability.configure(settings); + if (testObservability._user && testObservability._key) { + await testObservability.launchTestSession(settings); + } + } catch (error) { + console.log(`Could not configure or launch test observability - ${error}`); } }, - async beforeEach() { - }, - async after() { localTunnel.stop(); - await testObservability.stopBuildUpstream(); + if (helper.isTestObservabilitySession()) { + try { + await testObservability.stopBuildUpstream(); + } catch (error) { + console.log(`Something went wrong in stopping build session for test observability - ${error}`); + } + } + }, beforeChildProcess(settings) { diff --git a/src/testObservability.js b/src/testObservability.js index 1ca6f4a..0c462cc 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -9,6 +9,9 @@ class TestObservability { this._user = helper.getObservabilityUser(this._settings); this._key = helper.getObservabilityKey(this._settings); + if (this._settings && this._settings.testObservability) { + process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.testObservability; + } } async launchTestSession() { @@ -51,7 +54,7 @@ class TestObservability { }; try { - const response = await helper.nodeRequest('POST', 'api/v1/builds', data, config); + const response = await helper.makeRequest('POST', 'api/v1/builds', data, config); console.log('Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; @@ -103,7 +106,7 @@ class TestObservability { }; try { - const response = await helper.nodeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); + const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); if (response.data.error) { throw ({message: response.data.error}); } else { @@ -182,11 +185,11 @@ class TestObservability { this.createHttpLogEvent(eventData.httpOutput[i], eventData.httpOutput[i+1], testUuid); } } - if (eventData.status === 'fail') { - eventData.commands.filter(command => { - return 'screenshot' in command; - }).forEach(command => { - this.createScreenshotLogEvent(testUuid, command.screenshot, command.startTime); + if (process.env.BS_TESTOPS_ALLOW_SCREENSHOTS === 'true') { + eventData.commands.forEach(command => { + if (command.name === 'saveScreenshot') { + this.createScreenshotLogEvent(testUuid, command.args[0], command.startTime); + } }); } this.sendTestRunEvent(eventData, testFileReport, 'TestRunFinished', testUuid, null, sectionName, hookIds); @@ -271,6 +274,12 @@ class TestObservability { testData.identifier = `${testFileReport.name} - ${sectionName}`; } + if (eventType === 'TestRunStarted') { + testData.integrations = {}; + const provider = helper.getCloudProvider(browser); + testData.integrations[provider] = helper.getIntegrationsObject(browser); + } + if (eventType === 'TestRunFinished') { testData.hooks = hooks; } diff --git a/src/utils/helper.js b/src/utils/helper.js index e6a4d1d..8c96b0a 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -67,6 +67,10 @@ exports.isUndefined = value => (value === undefined || value === null); exports.isObject = value => (!this.isUndefined(value) && value.constructor === Object); +exports.isTestObservabilitySession = () => { + return process.env.BROWSERSTACK_TEST_OBSERVABILITY === 'true'; +}; + exports.getObservabilityUser = (config) => { return config.user; }; @@ -325,7 +329,7 @@ exports.getPackageVersion = (package_) => { return packages[package_] = this.requireModule(`${package_}/package.json`).version; }; -exports.nodeRequest = (type, url, data, config) => { +exports.makeRequest = (type, url, data, config) => { return new Promise((resolve, reject) => { const options = {...config, ...{ method: type, @@ -414,7 +418,7 @@ exports.uploadEventData = async (eventData, run=0) => { }; try { - const response = await this.nodeRequest('POST', event_api_url, data, config); + const response = await this.makeRequest('POST', event_api_url, data, config); if (response.data.error) { throw ({message: response.data.error}); } else { @@ -463,7 +467,7 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { }; try { - const response = await this.nodeRequest('POST', eventUrl, data, config); + const response = await this.makeRequest('POST', eventUrl, data, config); if (response.data.error) { throw ({message: response.data.error}); } else { @@ -496,3 +500,21 @@ exports.getAccessKey = (settings) => { return accessKey; }; + +exports.getCloudProvider = (browser) => { + if (browser.options && browser.options.hostname && browser.options.hostname.includes('browserstack')) { + return 'browserstack'; + } + + return 'unknown_grid'; +}; + +exports.getIntegrationsObject = (browser) => { + return { + capabilities: browser.capabilities, + session_id: browser.sessionId, + browser: browser.capabilities.browserName, + browser_version: browser.capabilities.browserVersion, + platform: browser.capabilities.platformName + }; +}; diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index cbe38e8..2c4bd58 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -1,5 +1,5 @@ const {BATCH_SIZE, BATCH_INTERVAL} = require('./constants'); -const {batchAndPostEvents} = require('./helper'); +const helper = require('./helper'); class RequestQueueHandler { constructor() { @@ -54,7 +54,7 @@ class RequestQueueHandler { while (this.queue.length > 0) { const data = this.queue.slice(0, BATCH_SIZE); this.queue.splice(0, BATCH_SIZE); - await batchAndPostEvents(this.eventUrl, 'Shutdown-Queue', data); + await helper.batchAndPostEvents(this.eventUrl, 'Shutdown-Queue', data); } } @@ -63,7 +63,7 @@ class RequestQueueHandler { if (this.queue.length > 0) { const data = this.queue.slice(0, BATCH_SIZE); this.queue.splice(0, BATCH_SIZE); - await batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); + await helper.batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); } }, BATCH_INTERVAL); } From ab028c57468b32477e73e95f2f9eb2691d2a91f1 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 12 Apr 2023 17:21:10 +0530 Subject: [PATCH 06/41] refactored code by removing comments and unncessary code --- nightwatch/globals.js | 1 - src/utils/constants.js | 2 +- src/utils/helper.js | 26 ++++++++------------------ src/utils/requestQueueHandler.js | 2 +- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index fb8fe43..81d82cd 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -62,7 +62,6 @@ module.exports = { console.log(`Something went wrong in stopping build session for test observability - ${error}`); } } - }, beforeChildProcess(settings) { diff --git a/src/utils/constants.js b/src/utils/constants.js index 5961d37..7d2b45f 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,3 +1,3 @@ -exports.BATCH_SIZE = 5; +exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; exports.API_URL = 'http://testops-collector-stag.us-east-1.elasticbeanstalk.com'; diff --git a/src/utils/helper.js b/src/utils/helper.js index 8c96b0a..86660a9 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -370,18 +370,19 @@ exports.pending_test_uploads = { count: 0 }; -exports.uploadEventData = async (eventData, run=0) => { +exports.uploadEventData = async (eventData) => { const log_tag = { ['TestRunStarted']: 'Test_Start_Upload', ['TestRunFinished']: 'Test_End_Upload', ['TestRunSkipped']: 'Test_Skipped_Upload', ['LogCreated']: 'Log_Upload', ['HookRunStarted']: 'Hook_Start_Upload', - ['HookRunFinished']: 'Hook_End_Upload', - ['CBTSessionCreated']: 'CBT_Upload' + ['HookRunFinished']: 'Hook_End_Upload' }[eventData.event_type]; - if (run === 0 && process.env.BS_TESTOPS_JWT !== 'null') {exports.pending_test_uploads.count += 1} + if (process.env.BS_TESTOPS_JWT !== 'null') { + exports.pending_test_uploads.count += 1; + } if (process.env.BS_TESTOPS_BUILD_COMPLETED === 'true') { if (process.env.BS_TESTOPS_JWT === 'null') { @@ -422,7 +423,7 @@ exports.uploadEventData = async (eventData, run=0) => { if (response.data.error) { throw ({message: response.data.error}); } else { - console.log(`${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'}[${run}] event successfull!`); + console.log(`${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} event successfull!`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { @@ -432,9 +433,9 @@ exports.uploadEventData = async (eventData, run=0) => { } } catch (error) { if (error.response) { - console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch-Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); @@ -443,17 +444,6 @@ exports.uploadEventData = async (eventData, run=0) => { message: error.message || (error.response ? `${error.response.status}:${error.response.statusText}` : error) }; } - - } else if (run >= 5) { - console.log(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : Build Start is not completed and ${log_tag} retry runs exceeded`); - if (process.env.BS_TESTOPS_JWT !== 'null') {exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count-1)} - - return { - status: 'error', - message: 'Retry runs exceeded' - }; - } else if (process.env.BS_TESTOPS_BUILD_COMPLETED !== 'false') { - setTimeout(function(){ exports.uploadEventData(eventData, run+1) }, 1000); } }; diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index 2c4bd58..635845e 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -7,7 +7,7 @@ class RequestQueueHandler { this.started = false; this.eventUrl = 'api/v1/batch'; this.screenshotEventUrl = 'api/v1/screenshots'; - this.BATCH_EVENT_TYPES = ['LogCreated', 'CBTSessionCreated', 'TestRunFinished', 'TestRunSkipped', 'HookRunFinished', 'TestRunStarted', 'HookRunStarted']; + this.BATCH_EVENT_TYPES = ['LogCreated', 'TestRunFinished', 'TestRunSkipped', 'HookRunFinished', 'TestRunStarted', 'HookRunStarted']; this.pollEventBatchInterval = null; } From f2f6c0d3c986dd2480066de682c84378f7620972 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 12 Apr 2023 17:45:21 +0530 Subject: [PATCH 07/41] given preference to env variables --- nightwatch/globals.js | 2 +- src/utils/helper.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 81d82cd..47131a2 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -44,7 +44,7 @@ module.exports = { try { testObservability.configure(settings); - if (testObservability._user && testObservability._key) { + if (helper.isTestObservabilitySession() && testObservability._user && testObservability._key) { await testObservability.launchTestSession(settings); } } catch (error) { diff --git a/src/utils/helper.js b/src/utils/helper.js index 86660a9..cfefa7d 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -72,11 +72,11 @@ exports.isTestObservabilitySession = () => { }; exports.getObservabilityUser = (config) => { - return config.user; + return process.env.BROWSERSTACK_USERNAME || config.user; }; exports.getObservabilityKey = (config) => { - return config.key; + return process.env.BROWSERSTACK_ACCESS_KEY || config.key; }; exports.getObservabilityProject = (options) => { From 6133a6aa5264b508feac1b6e632558a8dae6515c Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 12 Apr 2023 17:47:56 +0530 Subject: [PATCH 08/41] added argument support for test observability --- src/testObservability.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/testObservability.js b/src/testObservability.js index 0c462cc..e4cafb7 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -12,6 +12,9 @@ class TestObservability { if (this._settings && this._settings.testObservability) { process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.testObservability; } + if (process.argv.includes('--disable-test-observability')) { + process.env.BROWSERSTACK_TEST_OBSERVABILITY = false; + } } async launchTestSession() { From f9827994fec5f8ff015f594c6481bfa50aae24b0 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Thu, 13 Apr 2023 10:36:29 +0530 Subject: [PATCH 09/41] version control file path bug fixed --- src/testObservability.js | 91 +++++++++++++++++++++------------------- src/utils/helper.js | 81 ++++++++++------------------------- 2 files changed, 69 insertions(+), 103 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index e4cafb7..a0f8678 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -1,4 +1,5 @@ const os = require('os'); +const path = require('path'); const stripAnsi = require('strip-ansi'); const {v4: uuidv4} = require('uuid'); const helper = require('./utils/helper'); @@ -19,6 +20,7 @@ class TestObservability { async launchTestSession() { const options = this._settings.testObservabilityOptions || {}; + this._gitMetadata = await helper.getGitMetaData(); const data = { format: 'json', project_name: helper.getObservabilityProject(this._settings), @@ -37,7 +39,7 @@ class TestObservability { ci_info: helper.getCiInfo(), build_run_identifier: process.env.BROWSERSTACK_BUILD_RUN_IDENTIFIER, failed_tests_rerun: process.env.BROWSERSTACK_RERUN || false, - version_control: await helper.getGitMetaData(), + version_control: this._gitMetadata, observability_version: { frameworkName: 'nightwatch', frameworkVersion: helper.getPackageVersion('nightwatch'), @@ -132,7 +134,7 @@ class TestObservability { } } - processTestFile(testFileReport) { + async processTestFile(testFileReport) { const completedSections = testFileReport['completedSections']; const completed = testFileReport['completed']; if (completedSections) { @@ -144,37 +146,37 @@ class TestObservability { for (const sectionName in completedSections) { const eventData = completedSections[sectionName]; if (sectionName === '__global_beforeEach_hook') { - this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH'); + await this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH'); if (eventData.httpOutput && eventData.httpOutput.length > 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i { - if (command.name === 'saveScreenshot') { - this.createScreenshotLogEvent(testUuid, command.args[0], command.startTime); + for (const command of eventData.commands) { + if (command.name === 'saveScreenshot' && command.args) { + await this.createScreenshotLogEvent(testUuid, command.args[0], command.startTime); + break; } - }); + } } - this.sendTestRunEvent(eventData, testFileReport, 'TestRunFinished', testUuid, null, sectionName, hookIds); + await this.sendTestRunEvent(eventData, testFileReport, 'TestRunFinished', testUuid, null, sectionName, hookIds); } } } } - createScreenshotLogEvent(testUuid, screenshot, timestamp) { + async createScreenshotLogEvent(testUuid, screenshot, timestamp) { const eventData = { event_type: 'LogCreated', logs: [ @@ -213,30 +216,32 @@ class TestObservability { } ] }; - helper.uploadEventData(eventData); + await helper.uploadEventData(eventData); } - createHttpLogEvent(httpRequest, httpResponse, test_run_uuid) { - const eventData = { - event_type: 'LogCreated', - logs: [ - { - test_run_uuid: test_run_uuid, - timestamp: httpResponse[0], - kind: 'HTTP', - http_response: { - path: stripAnsi(httpRequest[1] || '').replace(/'/g, '\'').trim().split(' ')[2], - method: stripAnsi(httpRequest[1] || '').replace(/'/g, '\'').trim().split(' ')[1], - body: stripAnsi(httpRequest[2] || '').replace(/'/g, '\''), - response: stripAnsi(httpResponse[2] || '').replace(/'/g, '\'') + async createHttpLogEvent(httpRequest, httpResponse, test_run_uuid) { + if (httpRequest && httpRequest[1].match(/Request/) && httpResponse && httpResponse[1].match(/Response/)) { + const eventData = { + event_type: 'LogCreated', + logs: [ + { + test_run_uuid: test_run_uuid, + timestamp: httpResponse[0], + kind: 'HTTP', + http_response: { + path: stripAnsi(httpRequest[1] || '').replace(/'/g, '\'').trim().split(' ')[2], + method: stripAnsi(httpRequest[1] || '').replace(/'/g, '\'').trim().split(' ')[1], + body: stripAnsi(httpRequest[2] || '').replace(/'/g, '\''), + response: stripAnsi(httpResponse[2] || '').replace(/'/g, '\'') + } } - } - ] - }; - helper.uploadEventData(eventData); + ] + }; + await helper.uploadEventData(eventData); + } } - sendTestRunEvent(eventData, testFileReport, eventType, uuid, hookType, sectionName, hooks) { + async sendTestRunEvent(eventData, testFileReport, eventType, uuid, hookType, sectionName, hooks) { const testData = { uuid: uuid, type: 'hook', @@ -248,7 +253,7 @@ class TestObservability { identifier: `${testFileReport.name} - ${eventType}`, file_name: testFileReport.modulePath, location: testFileReport.modulePath, - vc_filepath: helper.vcFilePath(process.cwd()), + vc_filepath: this._gitMetadata ? path.relative(this._gitMetadata.root, testFileReport.modulePath) : null, started_at: new Date(eventData.startTimestamp).toISOString(), result: 'pending', framework: 'nightwatch', @@ -295,7 +300,7 @@ class TestObservability { } else { uploadData['test_run'] = testData; } - helper.uploadEventData(uploadData); + await helper.uploadEventData(uploadData); } } diff --git a/src/utils/helper.js b/src/utils/helper.js index cfefa7d..105fdad 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -230,72 +230,33 @@ const findGitConfig = (filePath) => { }; exports.getGitMetaData = async () => { - var info = gitRepoInfo(); + const info = gitRepoInfo(); if (!info.commonGitDir) { console.log('Unable to find a Git directory'); return; } + const {remote} = await pGitconfig(info.commonGitDir); + const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName].url})); - return new Promise(async (resolve, reject) => { - try { - if (!info.author && findGitConfig(process.cwd())) { - /* commit objects are packed */ - gitLastCommit.getLastCommit(async (err, commit) => { - info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`; - info['authorDate'] = info['authorDate'] || commit['authoredOn']; - info['committer'] = info['committer'] || `${commit['committer']['name'].replace(/[“]+/g, '')} <${commit['committer']['email'].replace(/[“]+/g, '')}>`; - info['committerDate'] = info['committerDate'] || commit['committedOn']; - info['commitMessage'] = info['commitMessage'] || commit['subject']; - - const {remote} = await pGitconfig(info.commonGitDir); - const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); - resolve({ - 'name': 'git', - 'sha': info['sha'], - 'short_sha': info['abbreviatedSha'], - 'branch': info['branch'], - 'tag': info['tag'], - 'committer': info['committer'], - 'committer_date': info['committerDate'], - 'author': info['author'], - 'author_date': info['authorDate'], - 'commit_message': info['commitMessage'], - 'root': info['root'], - 'common_git_dir': info['commonGitDir'], - 'worktree_git_dir': info['worktreeGitDir'], - 'last_tag': info['lastTag'], - 'commits_since_last_tag': info['commitsSinceLastTag'], - 'remotes': remotes - }); - }, {dst: findGitConfig(process.cwd())}); - } else { - const {remote} = await pGitconfig(info.commonGitDir); - const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); - resolve({ - 'name': 'git', - 'sha': info['sha'], - 'short_sha': info['abbreviatedSha'], - 'branch': info['branch'], - 'tag': info['tag'], - 'committer': info['committer'], - 'committer_date': info['committerDate'], - 'author': info['author'], - 'author_date': info['authorDate'], - 'commit_message': info['commitMessage'], - 'root': info['root'], - 'common_git_dir': info['commonGitDir'], - 'worktree_git_dir': info['worktreeGitDir'], - 'last_tag': info['lastTag'], - 'commits_since_last_tag': info['commitsSinceLastTag'], - 'remotes': remotes - }); - } - } catch (err) { - console.log(`Exception in populating Git metadata with error : ${err}`); - resolve({}); - } - }); + return { + name: 'git', + sha: info.sha, + short_sha: info.abbreviatedSha, + branch: info.branch, + tag: info.tag, + committer: info.committer, + committer_date: info.committerDate, + author: info.author, + author_date: info.authorDate, + commit_message: info.commitMessage, + root: info.root, + common_git_dir: info.commonGitDir, + worktree_git_dir: info.worktreeGitDir, + last_tag: info.lastTag, + commits_since_last_tag: info.commitsSinceLastTag, + remotes: remotes + }; }; exports.requireModule = (module) => { From 34f2c4525dbf5b2ede379e3ed7e5c153ee5b93c6 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 12:25:59 +0530 Subject: [PATCH 10/41] observability rerun changes and code refactoring --- nightwatch/globals.js | 28 ++++++++-- src/testObservability.js | 38 ++++++++------ src/utils/constants.js | 1 + src/utils/helper.js | 109 +++++++++++++++++++++------------------ 4 files changed, 106 insertions(+), 70 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 47131a2..ad34dee 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -5,6 +5,9 @@ const helper = require('../src/utils/helper'); const localTunnel = new LocalTunnel(); const testObservability = new TestObservability(); +const nightwatchRerun = process.env.NIGHTWATCH_RERUN_FAILED; +const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_FAILED_FILE; + module.exports = { reporter: function(results, done) { @@ -17,7 +20,7 @@ module.exports = { } } } catch (error) { - console.log(`Something went wrong in processing report file for test observability - ${error}`); + console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${error}`); } } done(results); @@ -44,22 +47,37 @@ module.exports = { try { testObservability.configure(settings); - if (helper.isTestObservabilitySession() && testObservability._user && testObservability._key) { - await testObservability.launchTestSession(settings); + if (helper.isTestObservabilitySession()) { + if (testObservability._user && testObservability._key) { + await testObservability.launchTestSession(); + } + if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS) { + const specs = process.env.BROWSERSTACK_RERUN_TESTS.split(','); + helper.handleNightwatchRerun(specs); + } } } catch (error) { - console.log(`Could not configure or launch test observability - ${error}`); + console.log(`nightwatch-browserstack-plugin: Could not configure or launch test observability - ${error}`); } }, + async beforeEach() { + console.log(browser); + }, + async after() { localTunnel.stop(); if (helper.isTestObservabilitySession()) { try { await testObservability.stopBuildUpstream(); } catch (error) { - console.log(`Something went wrong in stopping build session for test observability - ${error}`); + console.log(`nightwatch-browserstack-plugin: Something went wrong in stopping build session for test observability - ${error}`); + } + process.env.NIGHTWATCH_RERUN_FAILED = nightwatchRerun; + process.env.NIGHTWATCH_RERUN_FAILED_FILE = nightwatchRerunFile; + if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS) { + helper.deleteRerunFile(); } } }, diff --git a/src/testObservability.js b/src/testObservability.js index a0f8678..efd7e04 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -7,10 +7,16 @@ const helper = require('./utils/helper'); class TestObservability { configure(settings = {}) { this._settings = settings['@nightwatch/browserstack'] || {}; + this._bstackOptions = {}; + if (settings && settings.desiredCapabilities && settings.desiredCapabilities['bstack:options']) { + this._bstackOptions = settings.desiredCapabilities['bstack:options']; + } - this._user = helper.getObservabilityUser(this._settings); - this._key = helper.getObservabilityKey(this._settings); - if (this._settings && this._settings.testObservability) { + if (this._settings.testObservabilityOptions || this._bstackOptions) { + this._user = helper.getObservabilityUser(this._settings.testObservabilityOptions, this._bstackOptions); + this._key = helper.getObservabilityKey(this._settings.testObservabilityOptions, this._bstackOptions); + } + if (this._settings.testObservability) { process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.testObservability; } if (process.argv.includes('--disable-test-observability')) { @@ -23,12 +29,12 @@ class TestObservability { this._gitMetadata = await helper.getGitMetaData(); const data = { format: 'json', - project_name: helper.getObservabilityProject(this._settings), - name: helper.getObservabilityBuild(this._settings), + project_name: helper.getObservabilityProject(this._settings, this._bstackOptions), + name: helper.getObservabilityBuild(this._settings, this._bstackOptions), build_identifier: options.buildIdentifier, description: options.buildDescription || '', start_time: (new Date()).toISOString(), - tags: helper.getObservabilityBuildTags(this._settings), + tags: helper.getObservabilityBuildTags(this._settings, this._bstackOptions), host_info: { hostname: os.hostname(), platform: os.platform(), @@ -60,7 +66,7 @@ class TestObservability { try { const response = await helper.makeRequest('POST', 'api/v1/builds', data, config); - console.log('Build creation successfull!'); + console.log('nightwatch-browserstack-plugin: Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; if (response.data && response.data.jwt) { @@ -74,12 +80,12 @@ class TestObservability { } } catch (error) { if (error.response) { - console.log(`EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { if ((error.message && error.message.includes('with status : 401')) || (error && error.toString().includes('with status : 401'))) { - console.log('Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); + console.log('nightwatch-browserstack-plugin: Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); } else { - console.log(`EXCEPTION IN BUILD START EVENT : ${error.message || error}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN BUILD START EVENT : ${error.message || error}`); } } process.env.BS_TESTOPS_BUILD_COMPLETED = false; @@ -92,7 +98,7 @@ class TestObservability { return; } if (!process.env.BS_TESTOPS_JWT) { - console.log('[STOP_BUILD] Missing Authentication Token/ Build ID'); + console.log('nightwatch-browserstack-plugin: [STOP_BUILD] Missing Authentication Token/ Build ID'); return { status: 'error', @@ -109,7 +115,7 @@ class TestObservability { 'X-BSTACK-TESTOPS': 'true' } }; - + helper.requestQueueHandler.shutdown(); try { const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); if (response.data.error) { @@ -122,9 +128,9 @@ class TestObservability { } } catch (error) { if (error.response) { - console.log(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } return { @@ -284,8 +290,8 @@ class TestObservability { if (eventType === 'TestRunStarted') { testData.integrations = {}; - const provider = helper.getCloudProvider(browser); - testData.integrations[provider] = helper.getIntegrationsObject(browser); + const provider = helper.getCloudProvider(testFileReport.host); + testData.integrations[provider] = helper.getIntegrationsObject(testFileReport.sessionCapabilities, testFileReport.sessionId); } if (eventType === 'TestRunFinished') { diff --git a/src/utils/constants.js b/src/utils/constants.js index 7d2b45f..3bf54cb 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,3 +1,4 @@ exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; exports.API_URL = 'http://testops-collector-stag.us-east-1.elasticbeanstalk.com'; +exports.RERUN_FILE = 'rerun.json'; diff --git a/src/utils/helper.js b/src/utils/helper.js index 105fdad..d75a31b 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -5,12 +5,10 @@ const http = require('node:http'); const https = require('node:https'); const request = require('request'); const {promisify} = require('util'); -const gitLastCommit = require('git-last-commit'); const gitRepoInfo = require('git-repo-info'); const gitconfig = require('gitconfiglocal'); -const {API_URL} = require('./constants'); +const {API_URL, RERUN_FILE} = require('./constants'); const pGitconfig = promisify(gitconfig); -const {execSync} = require('child_process'); const httpKeepAliveAgent = new http.Agent({ keepAlive: true, @@ -40,7 +38,6 @@ const httpsScreenshotsKeepAliveAgent = new https.Agent({ maxTotalSockets: 2 }); -const GLOBAL_MODULE_PATH = execSync('npm root -g').toString().trim(); const RequestQueueHandler = require('./requestQueueHandler'); exports.requestQueueHandler = new RequestQueueHandler(); exports.pending_test_uploads = { @@ -71,42 +68,40 @@ exports.isTestObservabilitySession = () => { return process.env.BROWSERSTACK_TEST_OBSERVABILITY === 'true'; }; -exports.getObservabilityUser = (config) => { - return process.env.BROWSERSTACK_USERNAME || config.user; +exports.getObservabilityUser = (config, bstackOptions={}) => { + return config.user || bstackOptions.userName || process.env.BROWSERSTACK_USERNAME; }; -exports.getObservabilityKey = (config) => { - return process.env.BROWSERSTACK_ACCESS_KEY || config.key; +exports.getObservabilityKey = (config, bstackOptions={}) => { + return config.key || bstackOptions.accessKey || process.env.BROWSERSTACK_ACCESS_KEY; }; -exports.getObservabilityProject = (options) => { - if (process.env.TEST_OBSERVABILITY_PROJECT_NAME) { - return process.env.TEST_OBSERVABILITY_PROJECT_NAME; - } +exports.getObservabilityProject = (options, bstackOptions={}) => { if (options.testObservabilityOptions && options.testObservabilityOptions.projectName) { return options.testObservabilityOptions.projectName; + } else if (bstackOptions.projectName) { + return bstackOptions.projectName; } return ''; + }; -exports.getObservabilityBuild = (options) => { - if (process.env.TEST_OBSERVABILITY_BUILD_NAME) { - return process.env.TEST_OBSERVABILITY_BUILD_NAME; - } +exports.getObservabilityBuild = (options, bstackOptions={}) => { if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) { return options.testObservabilityOptions.buildName; + } else if (bstackOptions.buildName) { + return bstackOptions.buildName; } - return ''; + return path.basename(path.resolve(process.cwd())); }; -exports.getObservabilityBuildTags = (options) => { - if (process.env.TEST_OBSERVABILITY_BUILD_TAG) { - return process.env.TEST_OBSERVABILITY_BUILD_TAG.split(','); - } +exports.getObservabilityBuildTags = (options, bstackOptions={}) => { if (options.testObservabilityOptions && options.testObservabilityOptions.buildTag) { return options.testObservabilityOptions.buildTag; + } else if (bstackOptions.buildTag) { + return bstackOptions.buildTag; } return []; @@ -232,7 +227,7 @@ const findGitConfig = (filePath) => { exports.getGitMetaData = async () => { const info = gitRepoInfo(); if (!info.commonGitDir) { - console.log('Unable to find a Git directory'); + console.log('nightwatch-browserstack-plugin: Unable to find a Git directory'); return; } @@ -260,19 +255,8 @@ exports.getGitMetaData = async () => { }; exports.requireModule = (module) => { - console.log(`Getting ${module} from ${process.cwd()}`); + console.log(`nightwatch-browserstack-plugin: Getting ${module} from ${process.cwd()}`); const local_path = path.join(process.cwd(), 'node_modules', module); - if (!fs.existsSync(local_path)) { - console.log(`${module} doesn't exist at ${process.cwd()}`); - console.log(`Getting ${module} from ${GLOBAL_MODULE_PATH}`); - - const global_path = path.join(GLOBAL_MODULE_PATH, module); - if (!fs.existsSync(global_path)) { - throw new Error(`${module} doesn't exist.`); - } - - return require(global_path); - } return require(local_path); }; @@ -347,7 +331,7 @@ exports.uploadEventData = async (eventData) => { if (process.env.BS_TESTOPS_BUILD_COMPLETED === 'true') { if (process.env.BS_TESTOPS_JWT === 'null') { - console.log(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count-1); return { @@ -384,7 +368,7 @@ exports.uploadEventData = async (eventData) => { if (response.data.error) { throw ({message: response.data.error}); } else { - console.log(`${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} event successfull!`); + console.log(`nightwatch-browserstack-plugin: ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} event successfull!`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { @@ -394,9 +378,9 @@ exports.uploadEventData = async (eventData) => { } } catch (error) { if (error.response) { - console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); @@ -422,14 +406,14 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { if (response.data.error) { throw ({message: response.data.error}); } else { - console.log(`${kind} event successfull!`); + console.log(`nightwatch-browserstack-plugin: ${kind} event successfull!`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); } } catch (error) { if (error.response) { - console.log(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); } @@ -452,20 +436,47 @@ exports.getAccessKey = (settings) => { return accessKey; }; -exports.getCloudProvider = (browser) => { - if (browser.options && browser.options.hostname && browser.options.hostname.includes('browserstack')) { +exports.getCloudProvider = (hostname) => { + if (hostname.includes('browserstack')) { return 'browserstack'; } return 'unknown_grid'; }; -exports.getIntegrationsObject = (browser) => { +exports.getIntegrationsObject = (capabilities, sessionId) => { return { - capabilities: browser.capabilities, - session_id: browser.sessionId, - browser: browser.capabilities.browserName, - browser_version: browser.capabilities.browserVersion, - platform: browser.capabilities.platformName + capabilities: capabilities, + session_id: sessionId, + browser: capabilities.browserName, + browser_version: capabilities.browserVersion, + platform: capabilities.platformName + }; +}; + +exports.handleNightwatchRerun = (specs) => { + const modules = {}; + specs.forEach(spec => { + modules[spec] = { + modulePath: spec, + status: 'fail' + }; + }); + const data = { + modules: modules }; + + fs.writeFileSync(RERUN_FILE, JSON.stringify(data), (error) => { + if (error) { + console.error(error); + throw error; + } + }); + process.env.NIGHTWATCH_RERUN_FAILED = true; + process.env.NIGHTWATCH_RERUN_FAILED_FILE = path.resolve(RERUN_FILE); }; + +exports.deleteRerunFile = () => { + fs.unlinkSync(path.resolve(RERUN_FILE)); +}; + From a8e58bd6c077e4b2767f9f002aeb6c4bafe2ef1d Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 13:02:06 +0530 Subject: [PATCH 11/41] removed before each --- nightwatch/globals.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index ad34dee..f622bb6 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -62,10 +62,6 @@ module.exports = { }, - async beforeEach() { - console.log(browser); - }, - async after() { localTunnel.stop(); if (helper.isTestObservabilitySession()) { From 7416ae7c4dfc87dec99e4b31b26b76054e14b358 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 13:03:05 +0530 Subject: [PATCH 12/41] removed vc file path unwanted function --- src/utils/helper.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/utils/helper.js b/src/utils/helper.js index d75a31b..b04a131 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -204,10 +204,6 @@ exports.getCiInfo = () => { return null; }; -exports.vcFilePath = (filePath) => { - return findGitConfig(filePath); -}; - const findGitConfig = (filePath) => { if (filePath == null || filePath === '' || filePath === '/') { return null; From 0240650a460cc8c30a139ba50f766d2fdc613031 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 13:55:47 +0530 Subject: [PATCH 13/41] sending screenshot in base64 format --- src/testObservability.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/testObservability.js b/src/testObservability.js index efd7e04..e9a9496 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -1,5 +1,6 @@ const os = require('os'); const path = require('path'); +const fs = require('fs'); const stripAnsi = require('strip-ansi'); const {v4: uuidv4} = require('uuid'); const helper = require('./utils/helper'); @@ -218,7 +219,7 @@ class TestObservability { test_run_uuid: testUuid, kind: 'TEST_SCREENSHOT', timestamp: new Date(timestamp).toISOString(), - message: screenshot + message: fs.readFileSync(screenshot, 'base64') } ] }; From 130ffe5d24646d40a347ec1405b936fa4151c1a0 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 15:41:09 +0530 Subject: [PATCH 14/41] failure stacktrace format changed --- src/testObservability.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/testObservability.js b/src/testObservability.js index e9a9496..2eea5b2 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -190,6 +190,7 @@ class TestObservability { eventData.timeMs = completedEventData.timeMs; eventData.startTimestamp = completedEventData.startTimestamp; eventData.endTimestamp = completedEventData.endTimestamp; + eventData.stackTrace = completedEventData.stackTrace; eventData.lastError = completedEventData.lastError; await this.sendTestRunEvent(eventData, testFileReport, 'TestRunStarted', testUuid, null, sectionName, hookIds); if (eventData.httpOutput && eventData.httpOutput.length > 0) { @@ -274,7 +275,7 @@ class TestObservability { if (eventData.status === 'fail' && eventData.lastError) { testData.failure = [ { - 'backtrace': [eventData.lastError.stack] + 'backtrace': [eventData.stackTrace] } ]; testData.failure_reason = stripAnsi(eventData.lastError.message); From eb8313e9bf5c8f8b150d7b4e14b477c861ec0519 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 16:20:56 +0530 Subject: [PATCH 15/41] clear interval if empty queue --- src/utils/requestQueueHandler.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index 635845e..a512fc7 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -64,6 +64,8 @@ class RequestQueueHandler { const data = this.queue.slice(0, BATCH_SIZE); this.queue.splice(0, BATCH_SIZE); await helper.batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); + } else { + clearInterval(this.pollEventBatchInterval); } }, BATCH_INTERVAL); } From 0978995a7224482d465b3537cfbfc79240aedad8 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 17:17:04 +0530 Subject: [PATCH 16/41] fixed null ptr exception --- src/testObservability.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index 2eea5b2..b66064b 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -272,14 +272,16 @@ class TestObservability { testData.finished_at = new Date(eventData.endTimestamp).toISOString(); testData.result = eventData.status === 'pass' ? 'passed' : 'failed'; testData.duration_in_ms = 'timeMs' in eventData ? eventData.timeMs : eventData.time; - if (eventData.status === 'fail' && eventData.lastError) { + if (eventData.status === 'fail') { testData.failure = [ { 'backtrace': [eventData.stackTrace] } ]; - testData.failure_reason = stripAnsi(eventData.lastError.message); - testData.failure_type = eventData.lastError.name.match(/Assert/) ? 'AssertionError' : 'UnhandledError'; + testData.failure_reason = eventData.lastError ? stripAnsi(eventData.lastError.message) : null; + if (eventData.lastError && eventData.lastError.name) { + testData.failure_type = eventData.lastError.name.match(/Assert/) ? 'AssertionError' : 'UnhandledError'; + } } } From 0a03b14aabecedfad89f48a3fc9f8fc836bb9dd4 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 18 Apr 2023 19:27:42 +0530 Subject: [PATCH 17/41] check for file read and unlink --- src/testObservability.js | 3 +++ src/utils/helper.js | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/testObservability.js b/src/testObservability.js index b66064b..a1ce2be 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -213,6 +213,9 @@ class TestObservability { } async createScreenshotLogEvent(testUuid, screenshot, timestamp) { + if (!fs.existsSync(screenshot)) { + return; + } const eventData = { event_type: 'LogCreated', logs: [ diff --git a/src/utils/helper.js b/src/utils/helper.js index b04a131..89c6248 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -473,6 +473,10 @@ exports.handleNightwatchRerun = (specs) => { }; exports.deleteRerunFile = () => { - fs.unlinkSync(path.resolve(RERUN_FILE)); + try { + fs.unlinkSync(path.resolve(RERUN_FILE)); + } catch (err) { + console.error(err); + } }; From 8ddf1d9089e3cb7e661c9c7d26ed347edef22ea8 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 19 Apr 2023 17:22:02 +0530 Subject: [PATCH 18/41] added times for benchmarking --- nightwatch/globals.js | 2 ++ src/testObservability.js | 1 - src/utils/helper.js | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index f622bb6..4697007 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -27,6 +27,7 @@ module.exports = { }, async before(settings) { + console.log(`Performance start ${Math.floor(Date.now() / 1000)}`); localTunnel.configure(settings); await localTunnel.start(); @@ -63,6 +64,7 @@ module.exports = { }, async after() { + console.log(`Performance stop ${Math.floor(Date.now() / 1000)}`); localTunnel.stop(); if (helper.isTestObservabilitySession()) { try { diff --git a/src/testObservability.js b/src/testObservability.js index a1ce2be..3a4fe59 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -202,7 +202,6 @@ class TestObservability { for (const command of eventData.commands) { if (command.name === 'saveScreenshot' && command.args) { await this.createScreenshotLogEvent(testUuid, command.args[0], command.startTime); - break; } } } diff --git a/src/utils/helper.js b/src/utils/helper.js index 89c6248..361f319 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -361,6 +361,7 @@ exports.uploadEventData = async (eventData) => { try { const response = await this.makeRequest('POST', event_api_url, data, config); + console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (response.data.error) { throw ({message: response.data.error}); } else { @@ -373,6 +374,7 @@ exports.uploadEventData = async (eventData) => { }; } } catch (error) { + console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (error.response) { console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { @@ -399,6 +401,7 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { try { const response = await this.makeRequest('POST', eventUrl, data, config); + console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (response.data.error) { throw ({message: response.data.error}); } else { @@ -406,6 +409,7 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); } } catch (error) { + console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (error.response) { console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { From 9cd7a0dbb353abb93fa1de8a25fc57c6945c9d99 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Thu, 20 Apr 2023 14:30:46 +0530 Subject: [PATCH 19/41] removed console logs used for benchmarking --- nightwatch/globals.js | 2 -- src/utils/helper.js | 6 ------ 2 files changed, 8 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 4697007..f622bb6 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -27,7 +27,6 @@ module.exports = { }, async before(settings) { - console.log(`Performance start ${Math.floor(Date.now() / 1000)}`); localTunnel.configure(settings); await localTunnel.start(); @@ -64,7 +63,6 @@ module.exports = { }, async after() { - console.log(`Performance stop ${Math.floor(Date.now() / 1000)}`); localTunnel.stop(); if (helper.isTestObservabilitySession()) { try { diff --git a/src/utils/helper.js b/src/utils/helper.js index 361f319..f8036c0 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -361,11 +361,9 @@ exports.uploadEventData = async (eventData) => { try { const response = await this.makeRequest('POST', event_api_url, data, config); - console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (response.data.error) { throw ({message: response.data.error}); } else { - console.log(`nightwatch-browserstack-plugin: ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} event successfull!`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { @@ -374,7 +372,6 @@ exports.uploadEventData = async (eventData) => { }; } } catch (error) { - console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (error.response) { console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { @@ -401,15 +398,12 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { try { const response = await this.makeRequest('POST', eventUrl, data, config); - console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (response.data.error) { throw ({message: response.data.error}); } else { - console.log(`nightwatch-browserstack-plugin: ${kind} event successfull!`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); } } catch (error) { - console.log(`Performance check ${Math.floor(Date.now() / 1000)}`); if (error.response) { console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { From aff83d3d477a647a2b0269f87b34997a4bd4c406 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Mon, 24 Apr 2023 12:25:54 +0530 Subject: [PATCH 20/41] changes for test level tags --- src/testObservability.js | 3 ++- src/utils/constants.js | 2 +- src/utils/helper.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index 3a4fe59..05caa12 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -119,7 +119,7 @@ class TestObservability { helper.requestQueueHandler.shutdown(); try { const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); - if (response.data.error) { + if (response.data && response.data.error) { throw ({message: response.data.error}); } else { return { @@ -260,6 +260,7 @@ class TestObservability { scopes: [ testFileReport.name ], + tags: testFileReport.tags, identifier: `${testFileReport.name} - ${eventType}`, file_name: testFileReport.modulePath, location: testFileReport.modulePath, diff --git a/src/utils/constants.js b/src/utils/constants.js index 3bf54cb..2ee04ed 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,4 +1,4 @@ exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; -exports.API_URL = 'http://testops-collector-stag.us-east-1.elasticbeanstalk.com'; +exports.API_URL = 'https://collector-observability.browserstack.com'; exports.RERUN_FILE = 'rerun.json'; diff --git a/src/utils/helper.js b/src/utils/helper.js index f8036c0..eac6381 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -295,7 +295,7 @@ exports.makeRequest = (type, url, data, config) => { } } else { try { - if (typeof(body) !== 'object') {body = JSON.parse(body)} + if (body && typeof(body) !== 'object') {body = JSON.parse(body)} } catch (e) { reject('Not a JSON response from BrowserStack Server'); } From c39e842d12ea3eabe8b8bcad9199ff4c4a4d6bef Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Mon, 24 Apr 2023 21:47:17 +0530 Subject: [PATCH 21/41] changes for displaying http logs --- nightwatch/globals.js | 3 +++ src/testObservability.js | 7 +++++-- src/utils/helper.js | 8 ++++++++ src/utils/requestQueueHandler.js | 2 -- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index f622bb6..4aa0675 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -67,6 +67,9 @@ module.exports = { if (helper.isTestObservabilitySession()) { try { await testObservability.stopBuildUpstream(); + if (process.env.BS_TESTOPS_BUILD_HASHED_ID) { + console.log(`\nVisit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`); + } } catch (error) { console.log(`nightwatch-browserstack-plugin: Something went wrong in stopping build session for test observability - ${error}`); } diff --git a/src/testObservability.js b/src/testObservability.js index 05caa12..22ec757 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -8,6 +8,7 @@ const helper = require('./utils/helper'); class TestObservability { configure(settings = {}) { this._settings = settings['@nightwatch/browserstack'] || {}; + this._testRunner = settings.test_runner; this._bstackOptions = {}; if (settings && settings.desiredCapabilities && settings.desiredCapabilities['bstack:options']) { this._bstackOptions = settings.desiredCapabilities['bstack:options']; @@ -48,7 +49,7 @@ class TestObservability { failed_tests_rerun: process.env.BROWSERSTACK_RERUN || false, version_control: this._gitMetadata, observability_version: { - frameworkName: 'nightwatch', + frameworkName: helper.getFrameworkName(this._testRunner), frameworkVersion: helper.getPackageVersion('nightwatch'), sdkVersion: helper.getAgentVersion() } @@ -149,7 +150,7 @@ class TestObservability { const beforeHookId = uuidv4(); const afterHookId = uuidv4(); const globalAfterEachHookId = uuidv4(); - const hookIds = [globalBeforeEachHookId, beforeHookId, afterHookId, globalAfterEachHookId]; + const hookIds = []; for (const sectionName in completedSections) { const eventData = completedSections[sectionName]; if (sectionName === '__global_beforeEach_hook') { @@ -242,6 +243,8 @@ class TestObservability { path: stripAnsi(httpRequest[1] || '').replace(/'/g, '\'').trim().split(' ')[2], method: stripAnsi(httpRequest[1] || '').replace(/'/g, '\'').trim().split(' ')[1], body: stripAnsi(httpRequest[2] || '').replace(/'/g, '\''), + status_code: stripAnsi(httpResponse[1] || '').replace(/'/g, '\'').trim().split(' ')[1], + duration_ms: new Date(httpResponse[0]).getTime() - new Date(httpRequest[0]).getTime(), response: stripAnsi(httpResponse[2] || '').replace(/'/g, '\'') } } diff --git a/src/utils/helper.js b/src/utils/helper.js index eac6381..9f1c0a0 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -107,6 +107,14 @@ exports.getObservabilityBuildTags = (options, bstackOptions={}) => { return []; }; +exports.getFrameworkName = (testRunner) => { + if (testRunner && testRunner.type) { + return `nightwatch-${testRunner.type}`; + } + + return 'nightwatch-default'; +}; + exports.getCiInfo = () => { var env = process.env; // Jenkins diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index a512fc7..635845e 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -64,8 +64,6 @@ class RequestQueueHandler { const data = this.queue.slice(0, BATCH_SIZE); this.queue.splice(0, BATCH_SIZE); await helper.batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); - } else { - clearInterval(this.pollEventBatchInterval); } }, BATCH_INTERVAL); } From 5e156d8015cbe46171674ebfe2a0b6bf862b40d1 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 25 Apr 2023 13:12:38 +0530 Subject: [PATCH 22/41] made reporter function async --- nightwatch/globals.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 4aa0675..ce36dfd 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -10,13 +10,13 @@ const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_FAILED_FILE; module.exports = { - reporter: function(results, done) { + reporter: async function(results, done) { if (helper.isTestObservabilitySession()) { try { const modulesWithEnv = results['modulesWithEnv']; for (const testSetting in modulesWithEnv) { for (const testFile in modulesWithEnv[testSetting]) { - testObservability.processTestFile(modulesWithEnv[testSetting][testFile]); + await testObservability.processTestFile(modulesWithEnv[testSetting][testFile]); } } } catch (error) { From 40eb90b827c5a3aee4e50dad103f7567988a9c7e Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 25 Apr 2023 17:57:28 +0530 Subject: [PATCH 23/41] await for shutdown que handler --- src/testObservability.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testObservability.js b/src/testObservability.js index 22ec757..d538261 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -117,7 +117,7 @@ class TestObservability { 'X-BSTACK-TESTOPS': 'true' } }; - helper.requestQueueHandler.shutdown(); + await helper.requestQueueHandler.shutdown(); try { const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); if (response.data && response.data.error) { From dbfc5fc7b992c36c78666431dde188e98d9c04b7 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Thu, 27 Apr 2023 21:12:51 +0530 Subject: [PATCH 24/41] bug fixes for custom reporter, rerun build, file name --- nightwatch/globals.js | 20 +++++-- src/testObservability.js | 5 +- src/utils/constants.js | 3 ++ src/utils/helper.js | 110 ++++++++++++++++++++++++++++----------- 4 files changed, 103 insertions(+), 35 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index ce36dfd..8670ca2 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -1,5 +1,6 @@ const LocalTunnel = require('../src/local-tunnel'); const TestObservability = require('../src/testObservability'); +const { CUSTOM_REPORTER_CALLBACK_TIMEOUT } = require('../src/utils/constants'); const helper = require('../src/utils/helper'); const localTunnel = new LocalTunnel(); @@ -10,19 +11,29 @@ const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_FAILED_FILE; module.exports = { - reporter: async function(results, done) { + reporter: function(results, done) { if (helper.isTestObservabilitySession()) { + const promises = []; try { const modulesWithEnv = results['modulesWithEnv']; for (const testSetting in modulesWithEnv) { for (const testFile in modulesWithEnv[testSetting]) { - await testObservability.processTestFile(modulesWithEnv[testSetting][testFile]); + promises.push(testObservability.processTestFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile])))); } } + + Promise.all(promises).then(() => { + done(); + }).catch((err) =>{ + console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${err}`); + done(); + }); + + return; } catch (error) { console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${error}`); } - } + } done(results); }, @@ -48,10 +59,11 @@ module.exports = { try { testObservability.configure(settings); if (helper.isTestObservabilitySession()) { + settings.globals['customReporterCallbackTimeout'] = CUSTOM_REPORTER_CALLBACK_TIMEOUT; if (testObservability._user && testObservability._key) { await testObservability.launchTestSession(); } - if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS) { + if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS && process.env.BROWSERSTACK_RERUN_TESTS!=='null') { const specs = process.env.BROWSERSTACK_RERUN_TESTS.split(','); helper.handleNightwatchRerun(specs); } diff --git a/src/testObservability.js b/src/testObservability.js index d538261..32c2454 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -117,6 +117,7 @@ class TestObservability { 'X-BSTACK-TESTOPS': 'true' } }; + await helper.uploadPending(); await helper.requestQueueHandler.shutdown(); try { const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); @@ -265,8 +266,8 @@ class TestObservability { ], tags: testFileReport.tags, identifier: `${testFileReport.name} - ${eventType}`, - file_name: testFileReport.modulePath, - location: testFileReport.modulePath, + file_name: path.relative(process.cwd(), testFileReport.modulePath), + location: path.relative(process.cwd(), testFileReport.modulePath), vc_filepath: this._gitMetadata ? path.relative(this._gitMetadata.root, testFileReport.modulePath) : null, started_at: new Date(eventData.startTimestamp).toISOString(), result: 'pending', diff --git a/src/utils/constants.js b/src/utils/constants.js index 2ee04ed..489f620 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -2,3 +2,6 @@ exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; exports.API_URL = 'https://collector-observability.browserstack.com'; exports.RERUN_FILE = 'rerun.json'; +exports.DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS = 5000; +exports.DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS = 100; +exports.CUSTOM_REPORTER_CALLBACK_TIMEOUT = 3600000; diff --git a/src/utils/helper.js b/src/utils/helper.js index 9f1c0a0..193a02b 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -7,8 +7,9 @@ const request = require('request'); const {promisify} = require('util'); const gitRepoInfo = require('git-repo-info'); const gitconfig = require('gitconfiglocal'); -const {API_URL, RERUN_FILE} = require('./constants'); const pGitconfig = promisify(gitconfig); +const gitLastCommit = require('git-last-commit'); +const {API_URL, RERUN_FILE, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS} = require('./constants'); const httpKeepAliveAgent = new http.Agent({ keepAlive: true, @@ -228,34 +229,71 @@ const findGitConfig = (filePath) => { } }; -exports.getGitMetaData = async () => { - const info = gitRepoInfo(); - if (!info.commonGitDir) { - console.log('nightwatch-browserstack-plugin: Unable to find a Git directory'); - - return; - } - const {remote} = await pGitconfig(info.commonGitDir); - const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName].url})); - - return { - name: 'git', - sha: info.sha, - short_sha: info.abbreviatedSha, - branch: info.branch, - tag: info.tag, - committer: info.committer, - committer_date: info.committerDate, - author: info.author, - author_date: info.authorDate, - commit_message: info.commitMessage, - root: info.root, - common_git_dir: info.commonGitDir, - worktree_git_dir: info.worktreeGitDir, - last_tag: info.lastTag, - commits_since_last_tag: info.commitsSinceLastTag, - remotes: remotes - }; +exports.getGitMetaData = () => { + return new Promise(async (resolve, reject) => { + try { + var info = gitRepoInfo(); + if (!info.commonGitDir) { + console.log('nightwatch-browserstack-plugin: Unable to find a Git directory'); + resolve({}); + } + if (!info.author && findGitConfig(process.cwd())) { + /* commit objects are packed */ + gitLastCommit.getLastCommit(async (err, commit) => { + info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`; + info['authorDate'] = info['authorDate'] || commit['authoredOn']; + info['committer'] = info['committer'] || `${commit['committer']['name'].replace(/[“]+/g, '')} <${commit['committer']['email'].replace(/[“]+/g, '')}>`; + info['committerDate'] = info['committerDate'] || commit['committedOn']; + info['commitMessage'] = info['commitMessage'] || commit['subject']; + + const {remote} = await pGitconfig(info.commonGitDir); + const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); + resolve({ + 'name': 'git', + 'sha': info['sha'], + 'short_sha': info['abbreviatedSha'], + 'branch': info['branch'], + 'tag': info['tag'], + 'committer': info['committer'], + 'committer_date': info['committerDate'], + 'author': info['author'], + 'author_date': info['authorDate'], + 'commit_message': info['commitMessage'], + 'root': info['root'], + 'common_git_dir': info['commonGitDir'], + 'worktree_git_dir': info['worktreeGitDir'], + 'last_tag': info['lastTag'], + 'commits_since_last_tag': info['commitsSinceLastTag'], + 'remotes': remotes + }); + }, {dst: findGitConfig(process.cwd())}); + } else { + const {remote} = await pGitconfig(info.commonGitDir); + const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); + resolve({ + 'name': 'git', + 'sha': info['sha'], + 'short_sha': info['abbreviatedSha'], + 'branch': info['branch'], + 'tag': info['tag'], + 'committer': info['committer'], + 'committer_date': info['committerDate'], + 'author': info['author'], + 'author_date': info['authorDate'], + 'commit_message': info['commitMessage'], + 'root': info['root'], + 'common_git_dir': info['commonGitDir'], + 'worktree_git_dir': info['worktreeGitDir'], + 'last_tag': info['lastTag'], + 'commits_since_last_tag': info['commitsSinceLastTag'], + 'remotes': remotes + }); + } + } catch (err) { + console.log(`nightwatch-browserstack-plugin: Exception in populating Git metadata with error : ${err}`); + resolve({}); + } + }); }; exports.requireModule = (module) => { @@ -486,3 +524,17 @@ exports.deleteRerunFile = () => { } }; +const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms)); + +exports.uploadPending = async ( + waitTimeout = DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, + waitInterval = DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS +) => { + if (this.pending_test_uploads <= 0 || waitTimeout <= 0) { + return; + } + + await sleep(waitInterval); + + return this.uploadPending(waitTimeout - waitInterval); +}; From 7d188e4a2e222cc7d1afc5e7e1bada1e422c6c9b Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 2 May 2023 10:34:19 +0530 Subject: [PATCH 25/41] functionality added for skipped tests --- nightwatch/globals.js | 2 +- src/testObservability.js | 73 ++++++++++++++++++++++++++++------------ 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 8670ca2..9a0c504 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -1,6 +1,6 @@ const LocalTunnel = require('../src/local-tunnel'); const TestObservability = require('../src/testObservability'); -const { CUSTOM_REPORTER_CALLBACK_TIMEOUT } = require('../src/utils/constants'); +const {CUSTOM_REPORTER_CALLBACK_TIMEOUT} = require('../src/utils/constants'); const helper = require('../src/utils/helper'); const localTunnel = new LocalTunnel(); diff --git a/src/testObservability.js b/src/testObservability.js index 32c2454..0262851 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -146,6 +146,7 @@ class TestObservability { async processTestFile(testFileReport) { const completedSections = testFileReport['completedSections']; const completed = testFileReport['completed']; + const skippedTests = testFileReport['skipped']; if (completedSections) { const globalBeforeEachHookId = uuidv4(); const beforeHookId = uuidv4(); @@ -155,37 +156,37 @@ class TestObservability { for (const sectionName in completedSections) { const eventData = completedSections[sectionName]; if (sectionName === '__global_beforeEach_hook') { - await this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH'); + await this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH', sectionName); if (eventData.httpOutput && eventData.httpOutput.length > 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i 0) { for (let i=0; i 0) { + for (const skippedTest of skippedTests) { + await this.sendSkippedTestEvent(skippedTest, testFileReport); + } + } } } + async sendSkippedTestEvent(skippedTest, testFileReport) { + const testData = { + uuid: uuidv4(), + type: 'test', + name: skippedTest, + scope: `${testFileReport.name} - ${skippedTest}`, + scopes: [ + testFileReport.name + ], + tags: testFileReport.tags, + identifier: `${testFileReport.name} - ${skippedTest}`, + file_name: path.relative(process.cwd(), testFileReport.modulePath), + location: path.relative(process.cwd(), testFileReport.modulePath), + vc_filepath: this._gitMetadata ? path.relative(this._gitMetadata.root, testFileReport.modulePath) : null, + started_at: new Date(testFileReport.endTimestamp).toISOString(), + finished_at: new Date(testFileReport.endTimestamp).toISOString(), + duration_in_ms: 0, + result: 'skipped', + framework: 'nightwatch' + }; + testData.integrations = {}; + const provider = helper.getCloudProvider(testFileReport.host); + testData.integrations[provider] = helper.getIntegrationsObject(testFileReport.sessionCapabilities, testFileReport.sessionId); + const uploadData = { + event_type: 'TestRunFinished' + }; + uploadData['test_run'] = testData; + await helper.uploadEventData(uploadData); + } + async createScreenshotLogEvent(testUuid, screenshot, timestamp) { if (!fs.existsSync(screenshot)) { return; @@ -228,7 +264,7 @@ class TestObservability { } ] }; - await helper.uploadEventData(eventData); + // await helper.uploadEventData(eventData); } async createHttpLogEvent(httpRequest, httpResponse, test_run_uuid) { @@ -259,13 +295,13 @@ class TestObservability { const testData = { uuid: uuid, type: 'hook', - name: eventType, - scope: `${testFileReport.name} - ${eventType}`, + name: sectionName, + scope: `${testFileReport.name} - ${sectionName}`, scopes: [ testFileReport.name ], tags: testFileReport.tags, - identifier: `${testFileReport.name} - ${eventType}`, + identifier: `${testFileReport.name} - ${sectionName}`, file_name: path.relative(process.cwd(), testFileReport.modulePath), location: path.relative(process.cwd(), testFileReport.modulePath), vc_filepath: this._gitMetadata ? path.relative(this._gitMetadata.root, testFileReport.modulePath) : null, @@ -291,21 +327,16 @@ class TestObservability { } } } - - if (eventType === 'TestRunStarted' || eventType === 'TestRunFinished') { - testData.type = 'test'; - testData.name = sectionName; - testData.scope = `${testFileReport.name} - ${sectionName}`; - testData.identifier = `${testFileReport.name} - ${sectionName}`; - } - + if (eventType === 'TestRunStarted') { + testData.type = 'test'; testData.integrations = {}; const provider = helper.getCloudProvider(testFileReport.host); testData.integrations[provider] = helper.getIntegrationsObject(testFileReport.sessionCapabilities, testFileReport.sessionId); } if (eventType === 'TestRunFinished') { + testData.type = 'test'; testData.hooks = hooks; } From 564f67d67ad87384a2a3a08778f3eccbc03e78ba Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Fri, 5 May 2023 14:36:50 +0530 Subject: [PATCH 26/41] skipped tests and crash report added --- nightwatch/globals.js | 13 +++++-- src/testObservability.js | 36 ++++++++++--------- src/utils/crashReporter.js | 73 ++++++++++++++++++++++++++++++++++++++ src/utils/helper.js | 2 +- 4 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 src/utils/crashReporter.js diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 9a0c504..b1cb458 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -1,13 +1,14 @@ const LocalTunnel = require('../src/local-tunnel'); const TestObservability = require('../src/testObservability'); const {CUSTOM_REPORTER_CALLBACK_TIMEOUT} = require('../src/utils/constants'); +const CrashReporter = require('../src/utils/crashReporter'); const helper = require('../src/utils/helper'); const localTunnel = new LocalTunnel(); const testObservability = new TestObservability(); const nightwatchRerun = process.env.NIGHTWATCH_RERUN_FAILED; -const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_FAILED_FILE; +const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_REPORT_FILE; module.exports = { @@ -18,6 +19,12 @@ module.exports = { const modulesWithEnv = results['modulesWithEnv']; for (const testSetting in modulesWithEnv) { for (const testFile in modulesWithEnv[testSetting]) { + for (const completedSection in modulesWithEnv[testSetting][testFile].completed) { + if (modulesWithEnv[testSetting][testFile].completed[completedSection]) { + delete modulesWithEnv[testSetting][testFile].completed[completedSection].steps; + delete modulesWithEnv[testSetting][testFile].completed[completedSection].testcases; + } + } promises.push(testObservability.processTestFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile])))); } } @@ -26,11 +33,13 @@ module.exports = { done(); }).catch((err) =>{ console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${err}`); + CrashReporter.uploadCrashReport(err.message, err.stack); done(); }); return; } catch (error) { + CrashReporter.uploadCrashReport(error.message, error.stack); console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${error}`); } } @@ -86,7 +95,7 @@ module.exports = { console.log(`nightwatch-browserstack-plugin: Something went wrong in stopping build session for test observability - ${error}`); } process.env.NIGHTWATCH_RERUN_FAILED = nightwatchRerun; - process.env.NIGHTWATCH_RERUN_FAILED_FILE = nightwatchRerunFile; + process.env.NIGHTWATCH_RERUN_REPORT_FILE = nightwatchRerunFile; if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS) { helper.deleteRerunFile(); } diff --git a/src/testObservability.js b/src/testObservability.js index 0262851..15b94fc 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -4,10 +4,21 @@ const fs = require('fs'); const stripAnsi = require('strip-ansi'); const {v4: uuidv4} = require('uuid'); const helper = require('./utils/helper'); +const CrashReporter = require('./utils/crashReporter'); class TestObservability { configure(settings = {}) { this._settings = settings['@nightwatch/browserstack'] || {}; + + if (this._settings.testObservability) { + process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.testObservability; + } + if (process.argv.includes('--disable-test-observability')) { + process.env.BROWSERSTACK_TEST_OBSERVABILITY = false; + + return; + } + this._testRunner = settings.test_runner; this._bstackOptions = {}; if (settings && settings.desiredCapabilities && settings.desiredCapabilities['bstack:options']) { @@ -17,12 +28,8 @@ class TestObservability { if (this._settings.testObservabilityOptions || this._bstackOptions) { this._user = helper.getObservabilityUser(this._settings.testObservabilityOptions, this._bstackOptions); this._key = helper.getObservabilityKey(this._settings.testObservabilityOptions, this._bstackOptions); - } - if (this._settings.testObservability) { - process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.testObservability; - } - if (process.argv.includes('--disable-test-observability')) { - process.env.BROWSERSTACK_TEST_OBSERVABILITY = false; + CrashReporter.setCredentialsForCrashReportUpload(this._user, this._key); + CrashReporter.setConfigDetails(settings); } } @@ -70,15 +77,11 @@ class TestObservability { const response = await helper.makeRequest('POST', 'api/v1/builds', data, config); console.log('nightwatch-browserstack-plugin: Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; - - if (response.data && response.data.jwt) { - process.env.BS_TESTOPS_JWT = response.data.jwt; - } - if (response.data && response.data.build_hashed_id) { - process.env.BS_TESTOPS_BUILD_HASHED_ID = response.data.build_hashed_id; - } - if (response.data && response.data.allow_screenshots) { - process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = response.data.allow_screenshots.toString(); + const data = response.data; + if (data) { + process.env.BS_TESTOPS_JWT = data.jwt; + process.env.BS_TESTOPS_BUILD_HASHED_ID = data.build_hashed_id; + process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = data.allow_screenshots && data.allow_screenshots.toString(); } } catch (error) { if (error.response) { @@ -91,7 +94,6 @@ class TestObservability { } } process.env.BS_TESTOPS_BUILD_COMPLETED = false; - } } @@ -146,7 +148,7 @@ class TestObservability { async processTestFile(testFileReport) { const completedSections = testFileReport['completedSections']; const completed = testFileReport['completed']; - const skippedTests = testFileReport['skipped']; + const skippedTests = testFileReport['skippedAtRuntime'].concat(testFileReport['skippedByUser']); if (completedSections) { const globalBeforeEachHookId = uuidv4(); const beforeHookId = uuidv4(); diff --git a/src/utils/crashReporter.js b/src/utils/crashReporter.js new file mode 100644 index 0000000..aed1a1a --- /dev/null +++ b/src/utils/crashReporter.js @@ -0,0 +1,73 @@ +const helper = require('../utils/helper'); + +class CrashReporter { + + static userConfigForReporting = {}; + static credentialsForCrashReportUpload = {}; + + static setCredentialsForCrashReportUpload(username, key) { + this.credentialsForCrashReportUpload = { + username: username, + password: key + }; + } + + static deletePIIKeysFromObject(obj) { + if (!obj) { + return; + } + ['user', 'username', 'userName', 'key', 'accessKey'].forEach(key => delete obj[key]); + } + + static filterPII(settings) { + const configWithoutPII = JSON.parse(JSON.stringify(settings)); + if (configWithoutPII['@nightwatch/browserstack'] && configWithoutPII['@nightwatch/browserstack'].testObservabilityOptions) { + this.deletePIIKeysFromObject(configWithoutPII['@nightwatch/browserstack'].testObservabilityOptions); + } + if (configWithoutPII.desiredCapabilities && configWithoutPII.desiredCapabilities['bstack:options']) { + this.deletePIIKeysFromObject(configWithoutPII.desiredCapabilities['bstack:options']); + } + return configWithoutPII; + } + + static setConfigDetails(settings={}) { + const configWithoutPII = this.filterPII(settings) + + this.userConfigForReporting = { + framework: 'nightwatch-default', + services: configWithoutPII, + capabilities: configWithoutPII.desiredCapabilities + }; + } + + static async uploadCrashReport(exception, stackTrace) { + const config = { + auth: this.credentialsForCrashReportUpload, + headers: { + 'Content-Type': 'application/json', + 'X-BSTACK-TESTOPS': 'true' + } + }; + + try { + const data = { + hashed_id: process.env.BS_TESTOPS_BUILD_HASHED_ID, + observability_version: { + frameworkName: 'nightwatch-default', + frameworkVersion: helper.getPackageVersion('nightwatch'), + sdkVersion: helper.getAgentVersion() + }, + exception: { + error: exception.toString(), + stackTrace: stackTrace + }, + config: this.userConfigForReporting + }; + await helper.makeRequest('POST', 'api/v1/analytics', data, config); + } catch (error) { + console.log(`nightwatch-browserstack-plugin: [Crash_Report_Upload] Failed due to ${error}`); + } + } +} + +module.exports = CrashReporter; diff --git a/src/utils/helper.js b/src/utils/helper.js index 193a02b..d6d5341 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -513,7 +513,7 @@ exports.handleNightwatchRerun = (specs) => { } }); process.env.NIGHTWATCH_RERUN_FAILED = true; - process.env.NIGHTWATCH_RERUN_FAILED_FILE = path.resolve(RERUN_FILE); + process.env.NIGHTWATCH_RERUN_REPORT_FILE = path.resolve(RERUN_FILE); }; exports.deleteRerunFile = () => { From 5fc6c314cac41be75e25d74377856cf8a076c91e Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Mon, 8 May 2023 10:38:31 +0530 Subject: [PATCH 27/41] changes for review comments --- nightwatch/globals.js | 4 +- src/testObservability.js | 136 +++++++++++++++++------------- src/utils/crashReporter.js | 4 +- src/utils/helper.js | 164 +++++++++++++++++++++---------------- 4 files changed, 179 insertions(+), 129 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index b1cb458..c7c983f 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -32,7 +32,7 @@ module.exports = { Promise.all(promises).then(() => { done(); }).catch((err) =>{ - console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${err}`); + console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${err.message} with stacktrace ${err.stack}`); CrashReporter.uploadCrashReport(err.message, err.stack); done(); }); @@ -40,7 +40,7 @@ module.exports = { return; } catch (error) { CrashReporter.uploadCrashReport(error.message, error.stack); - console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${error}`); + console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${error.message} with stacktrace ${error.stack}`); } } done(results); diff --git a/src/testObservability.js b/src/testObservability.js index 15b94fc..b1087cb 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -10,8 +10,8 @@ class TestObservability { configure(settings = {}) { this._settings = settings['@nightwatch/browserstack'] || {}; - if (this._settings.testObservability) { - process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.testObservability; + if (this._settings.test_observability) { + process.env.BROWSERSTACK_TEST_OBSERVABILITY = this._settings.test_observability.enabled; } if (process.argv.includes('--disable-test-observability')) { process.env.BROWSERSTACK_TEST_OBSERVABILITY = false; @@ -25,16 +25,16 @@ class TestObservability { this._bstackOptions = settings.desiredCapabilities['bstack:options']; } - if (this._settings.testObservabilityOptions || this._bstackOptions) { - this._user = helper.getObservabilityUser(this._settings.testObservabilityOptions, this._bstackOptions); - this._key = helper.getObservabilityKey(this._settings.testObservabilityOptions, this._bstackOptions); + if (this._settings.test_observability || this._bstackOptions) { + this._user = helper.getObservabilityUser(this._settings.test_observability, this._bstackOptions); + this._key = helper.getObservabilityKey(this._settings.test_observability, this._bstackOptions); CrashReporter.setCredentialsForCrashReportUpload(this._user, this._key); CrashReporter.setConfigDetails(settings); } } async launchTestSession() { - const options = this._settings.testObservabilityOptions || {}; + const options = this._settings.test_observability || {}; this._gitMetadata = await helper.getGitMetaData(); const data = { format: 'json', @@ -77,11 +77,15 @@ class TestObservability { const response = await helper.makeRequest('POST', 'api/v1/builds', data, config); console.log('nightwatch-browserstack-plugin: Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; - const data = response.data; - if (data) { - process.env.BS_TESTOPS_JWT = data.jwt; - process.env.BS_TESTOPS_BUILD_HASHED_ID = data.build_hashed_id; - process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = data.allow_screenshots && data.allow_screenshots.toString(); + + if (response.data && response.data.jwt) { + process.env.BS_TESTOPS_JWT = response.data.jwt; + } + if (response.data && response.data.build_hashed_id) { + process.env.BS_TESTOPS_BUILD_HASHED_ID = response.data.build_hashed_id; + } + if (response.data && response.data.allow_screenshots) { + process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = response.data.allow_screenshots.toString(); } } catch (error) { if (error.response) { @@ -90,7 +94,7 @@ class TestObservability { if ((error.message && error.message.includes('with status : 401')) || (error && error.toString().includes('with status : 401'))) { console.log('nightwatch-browserstack-plugin: Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); } else { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN BUILD START EVENT : ${error.message || error}`); + console.log(`nightwatch-browserstack-plugin: EXCEPTION IN BUILD START EVENT : ${error.message || error} with stacktrace ${error.stack}`); } } process.env.BS_TESTOPS_BUILD_COMPLETED = false; @@ -157,60 +161,59 @@ class TestObservability { const hookIds = []; for (const sectionName in completedSections) { const eventData = completedSections[sectionName]; - if (sectionName === '__global_beforeEach_hook') { - await this.sendTestRunEvent(eventData, testFileReport, 'HookRunStarted', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH', sectionName); - if (eventData.httpOutput && eventData.httpOutput.length > 0) { - for (let i=0; i 0) { - for (let i=0; i 0) { + for (let i=0; i 0) { - for (let i=0; i 0) { + for (let i=0; i 0) { - for (let i=0; i 0) { + for (let i=0; i 0) { - for (let i=0; i 0) { + for (let i=0; i0) { + for (const retryTest of eventData.retryTestData) { + await this.processTestRunData(completed, retryTest, sectionName, testFileReport, hookIds); } } + await this.processTestRunData(completed, eventData, sectionName, testFileReport, hookIds); + break; } - await this.sendTestRunEvent(eventData, testFileReport, 'TestRunFinished', testUuid, null, sectionName, hookIds); } } if (skippedTests && skippedTests.length > 0) { @@ -221,6 +224,27 @@ class TestObservability { } } + async processTestRunData (completed, eventData, sectionName, testFileReport, hookIds) { + const testUuid = uuidv4(); + const completedEventData = completed[sectionName]; + eventData.stackTrace = completedEventData.stackTrace; + eventData.lastError = completedEventData.lastError; + await this.sendTestRunEvent(eventData, testFileReport, 'TestRunStarted', testUuid, null, sectionName, hookIds); + if (eventData.httpOutput && eventData.httpOutput.length > 0) { + for (let i=0; i { }; exports.getObservabilityProject = (options, bstackOptions={}) => { - if (options.testObservabilityOptions && options.testObservabilityOptions.projectName) { - return options.testObservabilityOptions.projectName; + if (options.test_observability && options.test_observability.projectName) { + return options.test_observability.projectName; } else if (bstackOptions.projectName) { return bstackOptions.projectName; } @@ -89,8 +89,8 @@ exports.getObservabilityProject = (options, bstackOptions={}) => { }; exports.getObservabilityBuild = (options, bstackOptions={}) => { - if (options.testObservabilityOptions && options.testObservabilityOptions.buildName) { - return options.testObservabilityOptions.buildName; + if (options.test_observability && options.test_observability.buildName) { + return options.test_observability.buildName; } else if (bstackOptions.buildName) { return bstackOptions.buildName; } @@ -99,8 +99,8 @@ exports.getObservabilityBuild = (options, bstackOptions={}) => { }; exports.getObservabilityBuildTags = (options, bstackOptions={}) => { - if (options.testObservabilityOptions && options.testObservabilityOptions.buildTag) { - return options.testObservabilityOptions.buildTag; + if (options.test_observability && options.test_observability.buildTag) { + return options.test_observability.buildTag; } else if (bstackOptions.buildTag) { return bstackOptions.buildTag; } @@ -116,101 +116,127 @@ exports.getFrameworkName = (testRunner) => { return 'nightwatch-default'; }; -exports.getCiInfo = () => { +exports.getCIVendor = () => { var env = process.env; // Jenkins if ((typeof env.JENKINS_URL === 'string' && env.JENKINS_URL.length > 0) || (typeof env.JENKINS_HOME === 'string' && env.JENKINS_HOME.length > 0)) { - return { - name: 'Jenkins', - build_url: env.BUILD_URL, - job_name: env.JOB_NAME, - build_number: env.BUILD_NUMBER - }; + return 'Jenkins'; } // CircleCI if (env.CI === 'true' && env.CIRCLECI === 'true') { - return { - name: 'CircleCI', - build_url: env.CIRCLE_BUILD_URL, - job_name: env.CIRCLE_JOB, - build_number: env.CIRCLE_BUILD_NUM - }; + return 'CircleCI'; } // Travis CI if (env.CI === 'true' && env.TRAVIS === 'true') { - return { - name: 'Travis CI', - build_url: env.TRAVIS_BUILD_WEB_URL, - job_name: env.TRAVIS_JOB_NAME, - build_number: env.TRAVIS_BUILD_NUMBER - }; + return 'TravisCI'; } // Codeship if (env.CI === 'true' && env.CI_NAME === 'codeship') { - return { - name: 'Codeship', - build_url: null, - job_name: null, - build_number: null - }; + return 'Codeship'; } // Bitbucket if (env.BITBUCKET_BRANCH && env.BITBUCKET_COMMIT) { - return { - name: 'Bitbucket', - build_url: env.BITBUCKET_GIT_HTTP_ORIGIN, - job_name: null, - build_number: env.BITBUCKET_BUILD_NUMBER - }; + return 'Bitbucket'; } // Drone if (env.CI === 'true' && env.DRONE === 'true') { - return { - name: 'Drone', - build_url: env.DRONE_BUILD_LINK, - job_name: null, - build_number: env.DRONE_BUILD_NUMBER - }; + return 'Drone'; } // Semaphore if (env.CI === 'true' && env.SEMAPHORE === 'true') { - return { - name: 'Semaphore', - build_url: env.SEMAPHORE_ORGANIZATION_URL, - job_name: env.SEMAPHORE_JOB_NAME, - build_number: env.SEMAPHORE_JOB_ID - }; + return 'Semaphore'; } // GitLab if (env.CI === 'true' && env.GITLAB_CI === 'true') { - return { - name: 'GitLab', - build_url: env.CI_JOB_URL, - job_name: env.CI_JOB_NAME, - build_number: env.CI_JOB_ID - }; + return 'GitLab'; } // Buildkite if (env.CI === 'true' && env.BUILDKITE === 'true') { - return { - name: 'Buildkite', - build_url: env.BUILDKITE_BUILD_URL, - job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME, - build_number: env.BUILDKITE_BUILD_NUMBER - }; + return 'Buildkite'; } // Visual Studio Team Services if (env.TF_BUILD === 'True') { - return { - name: 'Visual Studio Team Services', - build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`, - job_name: env.SYSTEM_DEFINITIONID, - build_number: env.BUILD_BUILDID - }; + return 'Visual Studio Team Services'; } +}; - // if no matches, return null - return null; +exports.getCiInfo = () => { + var env = process.env; + const ciVendor = this.getCIVendor(); + switch (ciVendor) { + case 'Jenkins': + return { + name: 'Jenkins', + build_url: env.BUILD_URL, + job_name: env.JOB_NAME, + build_number: env.BUILD_NUMBER + }; + case 'CircleCI': + return { + name: 'CircleCI', + build_url: env.CIRCLE_BUILD_URL, + job_name: env.CIRCLE_JOB, + build_number: env.CIRCLE_BUILD_NUM + }; + case 'TravisCI': + return { + name: 'Travis CI', + build_url: env.TRAVIS_BUILD_WEB_URL, + job_name: env.TRAVIS_JOB_NAME, + build_number: env.TRAVIS_BUILD_NUMBER + }; + case 'Codeship': + return { + name: 'Codeship', + build_url: null, + job_name: null, + build_number: null + }; + case 'Bitbucket': + return { + name: 'Bitbucket', + build_url: env.BITBUCKET_GIT_HTTP_ORIGIN, + job_name: null, + build_number: env.BITBUCKET_BUILD_NUMBER + }; + case 'Drone': + return { + name: 'Drone', + build_url: env.DRONE_BUILD_LINK, + job_name: null, + build_number: env.DRONE_BUILD_NUMBER + }; + case 'Semaphore': + return { + name: 'Semaphore', + build_url: env.SEMAPHORE_ORGANIZATION_URL, + job_name: env.SEMAPHORE_JOB_NAME, + build_number: env.SEMAPHORE_JOB_ID + }; + case 'GitLab': + return { + name: 'GitLab', + build_url: env.CI_JOB_URL, + job_name: env.CI_JOB_NAME, + build_number: env.CI_JOB_ID + }; + case 'Buildkite': + return { + name: 'Buildkite', + build_url: env.BUILDKITE_BUILD_URL, + job_name: env.BUILDKITE_LABEL || env.BUILDKITE_PIPELINE_NAME, + build_number: env.BUILDKITE_BUILD_NUMBER + }; + case 'Visual Studio Team Services': + return { + name: 'Visual Studio Team Services', + build_url: `${env.SYSTEM_TEAMFOUNDATIONSERVERURI}${env.SYSTEM_TEAMPROJECTID}`, + job_name: env.SYSTEM_DEFINITIONID, + build_number: env.BUILD_BUILDID + }; + default: + return null; + } }; const findGitConfig = (filePath) => { From 129d6ff3f70b590c17ef07dceda1b1027576de5f Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Mon, 8 May 2023 14:28:01 +0530 Subject: [PATCH 28/41] retry test data fix --- src/testObservability.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index b1087cb..8fa4542 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -151,7 +151,6 @@ class TestObservability { async processTestFile(testFileReport) { const completedSections = testFileReport['completedSections']; - const completed = testFileReport['completed']; const skippedTests = testFileReport['skippedAtRuntime'].concat(testFileReport['skippedByUser']); if (completedSections) { const globalBeforeEachHookId = uuidv4(); @@ -208,10 +207,10 @@ class TestObservability { default: { if (eventData.retryTestData && eventData.retryTestData.length>0) { for (const retryTest of eventData.retryTestData) { - await this.processTestRunData(completed, retryTest, sectionName, testFileReport, hookIds); + await this.processTestRunData(retryTest, sectionName, testFileReport, hookIds); } } - await this.processTestRunData(completed, eventData, sectionName, testFileReport, hookIds); + await this.processTestRunData(eventData, sectionName, testFileReport, hookIds); break; } } @@ -224,11 +223,10 @@ class TestObservability { } } - async processTestRunData (completed, eventData, sectionName, testFileReport, hookIds) { + async processTestRunData (eventData, sectionName, testFileReport, hookIds) { const testUuid = uuidv4(); - const completedEventData = completed[sectionName]; - eventData.stackTrace = completedEventData.stackTrace; - eventData.lastError = completedEventData.lastError; + const errorData = eventData.commands.find(command => command.result && command.result.stack); + eventData.lastError = errorData ? errorData.result : null; await this.sendTestRunEvent(eventData, testFileReport, 'TestRunStarted', testUuid, null, sectionName, hookIds); if (eventData.httpOutput && eventData.httpOutput.length > 0) { for (let i=0; i Date: Mon, 8 May 2023 20:31:58 +0530 Subject: [PATCH 29/41] logging changes --- nightwatch/globals.js | 9 +++++---- src/testObservability.js | 17 +++++++++-------- src/utils/crashReporter.js | 3 ++- src/utils/helper.js | 17 +++++++++-------- src/utils/logger.js | 11 +++++++++++ 5 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 src/utils/logger.js diff --git a/nightwatch/globals.js b/nightwatch/globals.js index c7c983f..854fd26 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -3,6 +3,7 @@ const TestObservability = require('../src/testObservability'); const {CUSTOM_REPORTER_CALLBACK_TIMEOUT} = require('../src/utils/constants'); const CrashReporter = require('../src/utils/crashReporter'); const helper = require('../src/utils/helper'); +const Logger = require('../src/utils/logger'); const localTunnel = new LocalTunnel(); const testObservability = new TestObservability(); @@ -32,7 +33,7 @@ module.exports = { Promise.all(promises).then(() => { done(); }).catch((err) =>{ - console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${err.message} with stacktrace ${err.stack}`); + Logger.error(`Something went wrong in processing report file for test observability - ${err.message} with stacktrace ${err.stack}`); CrashReporter.uploadCrashReport(err.message, err.stack); done(); }); @@ -40,7 +41,7 @@ module.exports = { return; } catch (error) { CrashReporter.uploadCrashReport(error.message, error.stack); - console.log(`nightwatch-browserstack-plugin: Something went wrong in processing report file for test observability - ${error.message} with stacktrace ${error.stack}`); + Logger.error(`Something went wrong in processing report file for test observability - ${error.message} with stacktrace ${error.stack}`); } } done(results); @@ -78,7 +79,7 @@ module.exports = { } } } catch (error) { - console.log(`nightwatch-browserstack-plugin: Could not configure or launch test observability - ${error}`); + Logger.error(`Could not configure or launch test observability - ${error}`); } }, @@ -92,7 +93,7 @@ module.exports = { console.log(`\nVisit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`); } } catch (error) { - console.log(`nightwatch-browserstack-plugin: Something went wrong in stopping build session for test observability - ${error}`); + Logger.error(`Something went wrong in stopping build session for test observability - ${error}`); } process.env.NIGHTWATCH_RERUN_FAILED = nightwatchRerun; process.env.NIGHTWATCH_RERUN_REPORT_FILE = nightwatchRerunFile; diff --git a/src/testObservability.js b/src/testObservability.js index 8fa4542..cd44a4c 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -5,6 +5,7 @@ const stripAnsi = require('strip-ansi'); const {v4: uuidv4} = require('uuid'); const helper = require('./utils/helper'); const CrashReporter = require('./utils/crashReporter'); +const Logger = require('./utils/logger'); class TestObservability { configure(settings = {}) { @@ -75,7 +76,7 @@ class TestObservability { try { const response = await helper.makeRequest('POST', 'api/v1/builds', data, config); - console.log('nightwatch-browserstack-plugin: Build creation successfull!'); + Logger.info('Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; if (response.data && response.data.jwt) { @@ -89,12 +90,12 @@ class TestObservability { } } catch (error) { if (error.response) { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + Logger.error(`EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { if ((error.message && error.message.includes('with status : 401')) || (error && error.toString().includes('with status : 401'))) { - console.log('nightwatch-browserstack-plugin: Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); + Logger.error('Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); } else { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN BUILD START EVENT : ${error.message || error} with stacktrace ${error.stack}`); + Logger.error(`EXCEPTION IN BUILD START EVENT : ${error.message || error}`); } } process.env.BS_TESTOPS_BUILD_COMPLETED = false; @@ -106,7 +107,7 @@ class TestObservability { return; } if (!process.env.BS_TESTOPS_JWT) { - console.log('nightwatch-browserstack-plugin: [STOP_BUILD] Missing Authentication Token/ Build ID'); + Logger.info('[STOP_BUILD] Missing Authentication Token/ Build ID'); return { status: 'error', @@ -137,9 +138,9 @@ class TestObservability { } } catch (error) { if (error.response) { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + Logger.error(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + Logger.error(`EXCEPTION IN stopBuildUpstream REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } return { @@ -150,7 +151,7 @@ class TestObservability { } async processTestFile(testFileReport) { - const completedSections = testFileReport['completedSections']; + const completedSections = testFileReport['completedSections'].grp.fd; const skippedTests = testFileReport['skippedAtRuntime'].concat(testFileReport['skippedByUser']); if (completedSections) { const globalBeforeEachHookId = uuidv4(); diff --git a/src/utils/crashReporter.js b/src/utils/crashReporter.js index c8e20ee..5ad828a 100644 --- a/src/utils/crashReporter.js +++ b/src/utils/crashReporter.js @@ -1,4 +1,5 @@ const helper = require('../utils/helper'); +const Logger = require('./logger'); class CrashReporter { @@ -65,7 +66,7 @@ class CrashReporter { }; await helper.makeRequest('POST', 'api/v1/analytics', data, config); } catch (error) { - console.log(`nightwatch-browserstack-plugin: [Crash_Report_Upload] Failed due to ${error}`); + Logger.error(`[Crash_Report_Upload] Failed due to ${error}`); } } } diff --git a/src/utils/helper.js b/src/utils/helper.js index e4b1e96..310323c 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -40,6 +40,7 @@ const httpsScreenshotsKeepAliveAgent = new https.Agent({ }); const RequestQueueHandler = require('./requestQueueHandler'); +const Logger = require('./logger'); exports.requestQueueHandler = new RequestQueueHandler(); exports.pending_test_uploads = { count: 0 @@ -260,7 +261,7 @@ exports.getGitMetaData = () => { try { var info = gitRepoInfo(); if (!info.commonGitDir) { - console.log('nightwatch-browserstack-plugin: Unable to find a Git directory'); + Logger.info('Unable to find a Git directory'); resolve({}); } if (!info.author && findGitConfig(process.cwd())) { @@ -316,14 +317,14 @@ exports.getGitMetaData = () => { }); } } catch (err) { - console.log(`nightwatch-browserstack-plugin: Exception in populating Git metadata with error : ${err}`); + Logger.error(`Exception in populating Git metadata with error : ${err}`); resolve({}); } }); }; exports.requireModule = (module) => { - console.log(`nightwatch-browserstack-plugin: Getting ${module} from ${process.cwd()}`); + Logger.info(`Getting ${module} from ${process.cwd()}`); const local_path = path.join(process.cwd(), 'node_modules', module); return require(local_path); @@ -399,7 +400,7 @@ exports.uploadEventData = async (eventData) => { if (process.env.BS_TESTOPS_BUILD_COMPLETED === 'true') { if (process.env.BS_TESTOPS_JWT === 'null') { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); + Logger.info(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count-1); return { @@ -445,9 +446,9 @@ exports.uploadEventData = async (eventData) => { } } catch (error) { if (error.response) { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + Logger.error(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + Logger.error(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); @@ -477,9 +478,9 @@ exports.batchAndPostEvents = async (eventUrl, kind, data) => { } } catch (error) { if (error.response) { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + Logger.error(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - console.log(`nightwatch-browserstack-plugin: EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + Logger.error(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); } diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..00b8fda --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,11 @@ +class Logger { + static info(msg) { + console.log(`nightwatch-browserstack-plugin: ${msg}`); + } + + static error(msg) { + console.error(`nightwatch-browserstack-plugin: ${msg}`); + } +}; + +module.exports = Logger; From 79bc9ee1e326125314ce65bc6715587743b6fe83 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 9 May 2023 11:27:47 +0530 Subject: [PATCH 30/41] removed typo --- src/testObservability.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testObservability.js b/src/testObservability.js index cd44a4c..80cdb6b 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -151,7 +151,7 @@ class TestObservability { } async processTestFile(testFileReport) { - const completedSections = testFileReport['completedSections'].grp.fd; + const completedSections = testFileReport['completedSections']; const skippedTests = testFileReport['skippedAtRuntime'].concat(testFileReport['skippedByUser']); if (completedSections) { const globalBeforeEachHookId = uuidv4(); From c48343eec079112f14c0ec4125556802e9ecb45e Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 10 May 2023 15:21:49 +0530 Subject: [PATCH 31/41] changes for review comments --- nightwatch/globals.js | 4 ++-- src/testObservability.js | 44 +++++++++++++------------------------- src/utils/crashReporter.js | 18 ++++++++-------- src/utils/helper.js | 40 +++++++++++----------------------- 4 files changed, 39 insertions(+), 67 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 854fd26..6bf26ad 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -26,7 +26,7 @@ module.exports = { delete modulesWithEnv[testSetting][testFile].completed[completedSection].testcases; } } - promises.push(testObservability.processTestFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile])))); + promises.push(testObservability.processTestReportFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile])))); } } @@ -90,7 +90,7 @@ module.exports = { try { await testObservability.stopBuildUpstream(); if (process.env.BS_TESTOPS_BUILD_HASHED_ID) { - console.log(`\nVisit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`); + Logger.info(`\nVisit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`); } } catch (error) { Logger.error(`Something went wrong in stopping build session for test observability - ${error}`); diff --git a/src/testObservability.js b/src/testObservability.js index 80cdb6b..6257f73 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -150,7 +150,17 @@ class TestObservability { } } - async processTestFile(testFileReport) { + async sendEvents(eventData, testFileReport, startEventType, finishedEventType, hookId, hookType, sectionName) { + await this.sendTestRunEvent(eventData, testFileReport, startEventType, hookId, hookType, sectionName); + if (eventData.httpOutput && eventData.httpOutput.length > 0) { + for (let i=0; i 0) { - for (let i=0; i 0) { - for (let i=0; i 0) { - for (let i=0; i 0) { - for (let i=0; i delete obj[key]); - } - static filterPII(settings) { + const keysToDelete = ['user', 'username', 'userName', 'key', 'accessKey']; const configWithoutPII = JSON.parse(JSON.stringify(settings)); + const deleteKeys = (obj) => { + if (!obj) { + return; + } + keysToDelete.forEach(key => delete obj[key]); + } if (configWithoutPII['@nightwatch/browserstack'] && configWithoutPII['@nightwatch/browserstack'].test_observability) { - this.deletePIIKeysFromObject(configWithoutPII['@nightwatch/browserstack'].test_observability); + deleteKeys(configWithoutPII['@nightwatch/browserstack'].test_observability); } if (configWithoutPII.desiredCapabilities && configWithoutPII.desiredCapabilities['bstack:options']) { - this.deletePIIKeysFromObject(configWithoutPII.desiredCapabilities['bstack:options']); + deleteKeys(configWithoutPII.desiredCapabilities['bstack:options']); } return configWithoutPII; } diff --git a/src/utils/helper.js b/src/utils/helper.js index 310323c..5da817e 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -11,33 +11,19 @@ const pGitconfig = promisify(gitconfig); const gitLastCommit = require('git-last-commit'); const {API_URL, RERUN_FILE, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS} = require('./constants'); -const httpKeepAliveAgent = new http.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); - -const httpsKeepAliveAgent = new https.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); - -const httpScreenshotsKeepAliveAgent = new http.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); - -const httpsScreenshotsKeepAliveAgent = new https.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 -}); +function createKeepAliveAgent(protocol) { + return new protocol.Agent({ + keepAlive: true, + timeout: 60000, + maxSockets: 2, + maxTotalSockets: 2 + }); +} + +const httpKeepAliveAgent = createKeepAliveAgent(http); +const httpsKeepAliveAgent = createKeepAliveAgent(https); +const httpScreenshotsKeepAliveAgent = createKeepAliveAgent(http); +const httpsScreenshotsKeepAliveAgent = createKeepAliveAgent(https); const RequestQueueHandler = require('./requestQueueHandler'); const Logger = require('./logger'); From bfc77649796293e16fc15f9de9e7bc7c411cb559 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 17 May 2023 13:09:34 +0530 Subject: [PATCH 32/41] added fspromises --- nightwatch/globals.js | 4 ++-- src/testObservability.js | 2 +- src/utils/helper.js | 35 +++++++++++++++++------------------ 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 6bf26ad..bf6aa83 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -75,7 +75,7 @@ module.exports = { } if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS && process.env.BROWSERSTACK_RERUN_TESTS!=='null') { const specs = process.env.BROWSERSTACK_RERUN_TESTS.split(','); - helper.handleNightwatchRerun(specs); + await helper.handleNightwatchRerun(specs); } } } catch (error) { @@ -98,7 +98,7 @@ module.exports = { process.env.NIGHTWATCH_RERUN_FAILED = nightwatchRerun; process.env.NIGHTWATCH_RERUN_REPORT_FILE = nightwatchRerunFile; if (process.env.BROWSERSTACK_RERUN === 'true' && process.env.BROWSERSTACK_RERUN_TESTS) { - helper.deleteRerunFile(); + await helper.deleteRerunFile(); } } }, diff --git a/src/testObservability.js b/src/testObservability.js index 6257f73..42df23f 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -145,7 +145,7 @@ class TestObservability { return { status: 'error', - message: error.message || error.response ? `${error.response.status}:${error.response.statusText}` : error + message: error }; } } diff --git a/src/utils/helper.js b/src/utils/helper.js index 5da817e..afac30c 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -1,5 +1,6 @@ const os = require('os'); const fs = require('fs'); +const fsPromises = fs.promises; const path = require('path'); const http = require('node:http'); const https = require('node:https'); @@ -226,19 +227,19 @@ exports.getCiInfo = () => { } }; -const findGitConfig = (filePath) => { +const findGitConfig = async (filePath) => { if (filePath == null || filePath === '' || filePath === '/') { return null; } try { - fs.statSync(filePath + '/.git/config'); + await fsPromises.stat(filePath + '/.git/config'); return filePath; } catch (e) { const parentFilePath = filePath.split('/'); parentFilePath.pop(); - return findGitConfig(parentFilePath.join('/')); + return await findGitConfig(parentFilePath.join('/')); } }; @@ -250,7 +251,7 @@ exports.getGitMetaData = () => { Logger.info('Unable to find a Git directory'); resolve({}); } - if (!info.author && findGitConfig(process.cwd())) { + if (!info.author && await findGitConfig(process.cwd())) { /* commit objects are packed */ gitLastCommit.getLastCommit(async (err, commit) => { info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`; @@ -279,7 +280,7 @@ exports.getGitMetaData = () => { 'commits_since_last_tag': info['commitsSinceLastTag'], 'remotes': remotes }); - }, {dst: findGitConfig(process.cwd())}); + }, {dst: await findGitConfig(process.cwd())}); } else { const {remote} = await pGitconfig(info.commonGitDir); const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); @@ -507,7 +508,7 @@ exports.getIntegrationsObject = (capabilities, sessionId) => { }; }; -exports.handleNightwatchRerun = (specs) => { +exports.handleNightwatchRerun = async (specs) => { const modules = {}; specs.forEach(spec => { modules[spec] = { @@ -518,22 +519,20 @@ exports.handleNightwatchRerun = (specs) => { const data = { modules: modules }; - - fs.writeFileSync(RERUN_FILE, JSON.stringify(data), (error) => { - if (error) { - console.error(error); - throw error; - } - }); - process.env.NIGHTWATCH_RERUN_FAILED = true; - process.env.NIGHTWATCH_RERUN_REPORT_FILE = path.resolve(RERUN_FILE); + try { + await fsPromises.writeFile(RERUN_FILE, JSON.stringify(data)); + process.env.NIGHTWATCH_RERUN_FAILED = true; + process.env.NIGHTWATCH_RERUN_REPORT_FILE = path.resolve(RERUN_FILE); + } catch (error) { + Logger.error(error); + } }; -exports.deleteRerunFile = () => { +exports.deleteRerunFile = async () => { try { - fs.unlinkSync(path.resolve(RERUN_FILE)); + await fsPromises.unlink(path.resolve(RERUN_FILE)); } catch (err) { - console.error(err); + Logger.error(err); } }; From 0c701c41d5938906dc25079765c385ad0949f5cb Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 17 May 2023 21:43:08 +0530 Subject: [PATCH 33/41] made request handler singleton --- src/testObservability.js | 2 +- src/utils/helper.js | 17 ++++++++++------- src/utils/requestQueueHandler.js | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index 42df23f..23b5c80 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -125,7 +125,7 @@ class TestObservability { } }; await helper.uploadPending(); - await helper.requestQueueHandler.shutdown(); + await helper.shutDownRequestHandler(); try { const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); if (response.data && response.data.error) { diff --git a/src/utils/helper.js b/src/utils/helper.js index afac30c..f0a8503 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -26,9 +26,8 @@ const httpsKeepAliveAgent = createKeepAliveAgent(https); const httpScreenshotsKeepAliveAgent = createKeepAliveAgent(http); const httpsScreenshotsKeepAliveAgent = createKeepAliveAgent(https); -const RequestQueueHandler = require('./requestQueueHandler'); +const requestQueueHandler = require('./requestQueueHandler'); const Logger = require('./logger'); -exports.requestQueueHandler = new RequestQueueHandler(); exports.pending_test_uploads = { count: 0 }; @@ -340,7 +339,7 @@ exports.makeRequest = (type, url, data, config) => { agent: API_URL.includes('https') ? httpsKeepAliveAgent : httpKeepAliveAgent }}; - if (url === exports.requestQueueHandler.screenshotEventUrl) { + if (url === requestQueueHandler.screenshotEventUrl) { options.agent = API_URL.includes('https') ? httpsScreenshotsKeepAliveAgent : httpScreenshotsKeepAliveAgent; } @@ -398,12 +397,12 @@ exports.uploadEventData = async (eventData) => { let data = eventData; let event_api_url = 'api/v1/event'; - exports.requestQueueHandler.start(); + requestQueueHandler.start(); const { shouldProceed, proceedWithData, proceedWithUrl - } = exports.requestQueueHandler.add(eventData); + } = requestQueueHandler.add(eventData); if (!shouldProceed) { return; } else if (proceedWithData) { @@ -433,9 +432,9 @@ exports.uploadEventData = async (eventData) => { } } catch (error) { if (error.response) { - Logger.error(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + Logger.error(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - Logger.error(`EXCEPTION IN ${event_api_url !== exports.requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + Logger.error(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); @@ -550,3 +549,7 @@ exports.uploadPending = async ( return this.uploadPending(waitTimeout - waitInterval); }; + +exports.shutDownRequestHandler = async () => { + await requestQueueHandler.shutdown(); +}; diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index 635845e..29f96c6 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -88,4 +88,4 @@ class RequestQueueHandler { } } -module.exports = RequestQueueHandler; +module.exports = new RequestQueueHandler(); From 173d6ea11e8dd34df8c7ba5ee45e4d8a5e546283 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Thu, 18 May 2023 19:06:49 +0530 Subject: [PATCH 34/41] added observability sync script --- nightwatch/globals.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index bf6aa83..51a74da 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -47,6 +47,12 @@ module.exports = { done(results); }, + onEvent({eventName, hook_type, ...args}) { + if (browser && eventName === 'TestRunStarted') { + browser.execute(`browserstack_executor: {"action": "annotate", "arguments": {"type":"Annotation","data":"ObservabilitySync:${Date.now()}","level": "debug"}}`); + } + }, + async before(settings) { localTunnel.configure(settings); await localTunnel.start(); From 9cff9c0499417fee39bebb5cb84a07cb3cd3b21b Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Fri, 19 May 2023 19:00:07 +0530 Subject: [PATCH 35/41] added pref for env vars --- src/utils/helper.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/helper.js b/src/utils/helper.js index f0a8503..2dee4ce 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -57,11 +57,11 @@ exports.isTestObservabilitySession = () => { }; exports.getObservabilityUser = (config, bstackOptions={}) => { - return config.user || bstackOptions.userName || process.env.BROWSERSTACK_USERNAME; + return process.env.BROWSERSTACK_USERNAME || config.user || bstackOptions.userName; }; exports.getObservabilityKey = (config, bstackOptions={}) => { - return config.key || bstackOptions.accessKey || process.env.BROWSERSTACK_ACCESS_KEY; + return process.env.BROWSERSTACK_ACCESS_KEY || config.key || bstackOptions.accessKey; }; exports.getObservabilityProject = (options, bstackOptions={}) => { From 4783713a01d76293a145767a2264fd395fd5af83 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Mon, 22 May 2023 19:52:13 +0530 Subject: [PATCH 36/41] null check added --- src/utils/helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/helper.js b/src/utils/helper.js index 2dee4ce..75fb742 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -380,7 +380,7 @@ exports.uploadEventData = async (eventData) => { ['HookRunFinished']: 'Hook_End_Upload' }[eventData.event_type]; - if (process.env.BS_TESTOPS_JWT !== 'null') { + if (process.env.BS_TESTOPS_JWT && process.env.BS_TESTOPS_JWT !== 'null') { exports.pending_test_uploads.count += 1; } From eefc0574654c8534456612b9aa9cbf6f0bc572ec Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 23 May 2023 19:17:59 +0530 Subject: [PATCH 37/41] review comments done --- nightwatch/globals.js | 2 +- src/testObservability.js | 8 +-- src/utils/constants.js | 2 + src/utils/crashReporter.js | 5 +- src/utils/helper.js | 102 +++---------------------------- src/utils/requestHelper.js | 56 +++++++++++++++++ src/utils/requestQueueHandler.js | 34 ++++++++++- 7 files changed, 104 insertions(+), 105 deletions(-) create mode 100644 src/utils/requestHelper.js diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 51a74da..136fb06 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -48,7 +48,7 @@ module.exports = { }, onEvent({eventName, hook_type, ...args}) { - if (browser && eventName === 'TestRunStarted') { + if (typeof browser !== 'undefined' && eventName === 'TestRunStarted') { browser.execute(`browserstack_executor: {"action": "annotate", "arguments": {"type":"Annotation","data":"ObservabilitySync:${Date.now()}","level": "debug"}}`); } }, diff --git a/src/testObservability.js b/src/testObservability.js index 23b5c80..615bef3 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -4,6 +4,7 @@ const fs = require('fs'); const stripAnsi = require('strip-ansi'); const {v4: uuidv4} = require('uuid'); const helper = require('./utils/helper'); +const {makeRequest} = require('./utils/requestHelper'); const CrashReporter = require('./utils/crashReporter'); const Logger = require('./utils/logger'); @@ -75,7 +76,7 @@ class TestObservability { }; try { - const response = await helper.makeRequest('POST', 'api/v1/builds', data, config); + const response = await makeRequest('POST', 'api/v1/builds', data, config); Logger.info('Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; @@ -127,7 +128,7 @@ class TestObservability { await helper.uploadPending(); await helper.shutDownRequestHandler(); try { - const response = await helper.makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); + const response = await makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); if (response.data && response.data.error) { throw ({message: response.data.error}); } else { @@ -171,9 +172,6 @@ class TestObservability { const hookIds = []; for (const sectionName in completedSections) { const eventData = completedSections[sectionName]; - if (eventData.commands.length === 0) { - continue; - } switch (sectionName) { case '__global_beforeEach_hook': { await this.sendEvents(eventData, testFileReport, 'HookRunStarted', 'HookRunFinished', globalBeforeEachHookId, 'GLOBAL_BEFORE_EACH', sectionName); diff --git a/src/utils/constants.js b/src/utils/constants.js index 489f620..4f99831 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,6 +1,8 @@ exports.BATCH_SIZE = 1000; exports.BATCH_INTERVAL = 2000; exports.API_URL = 'https://collector-observability.browserstack.com'; +exports.SCREENSHOT_EVENT_URL = 'api/v1/screenshots'; +exports.BATCH_EVENT_URL = 'api/v1/batch'; exports.RERUN_FILE = 'rerun.json'; exports.DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS = 5000; exports.DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS = 100; diff --git a/src/utils/crashReporter.js b/src/utils/crashReporter.js index c3d1f20..531ffa3 100644 --- a/src/utils/crashReporter.js +++ b/src/utils/crashReporter.js @@ -1,4 +1,5 @@ -const helper = require('../utils/helper'); +const helper = require('./helper'); +const {makeRequest} = require('./requestHelper'); const Logger = require('./logger'); class CrashReporter { @@ -64,7 +65,7 @@ class CrashReporter { }, config: this.userConfigForReporting }; - await helper.makeRequest('POST', 'api/v1/analytics', data, config); + await makeRequest('POST', 'api/v1/analytics', data, config); } catch (error) { Logger.error(`[Crash_Report_Upload] Failed due to ${error}`); } diff --git a/src/utils/helper.js b/src/utils/helper.js index 75fb742..08a1980 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -2,35 +2,16 @@ const os = require('os'); const fs = require('fs'); const fsPromises = fs.promises; const path = require('path'); -const http = require('node:http'); -const https = require('node:https'); -const request = require('request'); const {promisify} = require('util'); const gitRepoInfo = require('git-repo-info'); const gitconfig = require('gitconfiglocal'); const pGitconfig = promisify(gitconfig); const gitLastCommit = require('git-last-commit'); -const {API_URL, RERUN_FILE, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS} = require('./constants'); - -function createKeepAliveAgent(protocol) { - return new protocol.Agent({ - keepAlive: true, - timeout: 60000, - maxSockets: 2, - maxTotalSockets: 2 - }); -} - -const httpKeepAliveAgent = createKeepAliveAgent(http); -const httpsKeepAliveAgent = createKeepAliveAgent(https); -const httpScreenshotsKeepAliveAgent = createKeepAliveAgent(http); -const httpsScreenshotsKeepAliveAgent = createKeepAliveAgent(https); +const {makeRequest} = require('./requestHelper'); +const {RERUN_FILE, DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS} = require('./constants'); const requestQueueHandler = require('./requestQueueHandler'); const Logger = require('./logger'); -exports.pending_test_uploads = { - count: 0 -}; exports.generateLocalIdentifier = () => { const formattedDate = new Intl.DateTimeFormat('en-GB', { @@ -329,47 +310,6 @@ exports.getPackageVersion = (package_) => { return packages[package_] = this.requireModule(`${package_}/package.json`).version; }; -exports.makeRequest = (type, url, data, config) => { - return new Promise((resolve, reject) => { - const options = {...config, ...{ - method: type, - url: `${API_URL}/${url}`, - body: data, - json: config.headers['Content-Type'] === 'application/json', - agent: API_URL.includes('https') ? httpsKeepAliveAgent : httpKeepAliveAgent - }}; - - if (url === requestQueueHandler.screenshotEventUrl) { - options.agent = API_URL.includes('https') ? httpsScreenshotsKeepAliveAgent : httpScreenshotsKeepAliveAgent; - } - - request(options, function callback(error, response, body) { - if (error) { - reject(error); - } else if (response.statusCode !== 200) { - if (response.statusCode === 401) { - reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); - } else { - reject(`Received response from BrowserStack Server with status : ${response.statusCode}`); - } - } else { - try { - if (body && typeof(body) !== 'object') {body = JSON.parse(body)} - } catch (e) { - reject('Not a JSON response from BrowserStack Server'); - } - resolve({ - data: body - }); - } - }); - }); -}; - -exports.pending_test_uploads = { - count: 0 -}; - exports.uploadEventData = async (eventData) => { const log_tag = { ['TestRunStarted']: 'Test_Start_Upload', @@ -381,13 +321,13 @@ exports.uploadEventData = async (eventData) => { }[eventData.event_type]; if (process.env.BS_TESTOPS_JWT && process.env.BS_TESTOPS_JWT !== 'null') { - exports.pending_test_uploads.count += 1; + requestQueueHandler.pending_test_uploads += 1; } if (process.env.BS_TESTOPS_BUILD_COMPLETED === 'true') { if (process.env.BS_TESTOPS_JWT === 'null') { Logger.info(`EXCEPTION IN ${log_tag} REQUEST TO TEST OBSERVABILITY : missing authentication token`); - exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count-1); + requestQueueHandler.pending_test_uploads = Math.max(0, requestQueueHandler.pending_test_uploads-1); return { status: 'error', @@ -419,11 +359,11 @@ exports.uploadEventData = async (eventData) => { }; try { - const response = await this.makeRequest('POST', event_api_url, data, config); + const response = await makeRequest('POST', event_api_url, data, config); if (response.data.error) { throw ({message: response.data.error}); } else { - exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); + requestQueueHandler.pending_test_uploads = Math.max(0, requestQueueHandler.pending_test_uploads - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { status: 'success', @@ -436,7 +376,7 @@ exports.uploadEventData = async (eventData) => { } else { Logger.error(`EXCEPTION IN ${event_api_url !== requestQueueHandler.eventUrl ? log_tag : 'Batch_Queue'} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); } - exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - (event_api_url === 'api/v1/event' ? 1 : data.length)); + requestQueueHandler.pending_test_uploads = Math.max(0, requestQueueHandler.pending_test_uploads - (event_api_url === 'api/v1/event' ? 1 : data.length)); return { status: 'error', @@ -446,32 +386,6 @@ exports.uploadEventData = async (eventData) => { } }; -exports.batchAndPostEvents = async (eventUrl, kind, data) => { - const config = { - headers: { - 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, - 'Content-Type': 'application/json', - 'X-BSTACK-TESTOPS': 'true' - } - }; - - try { - const response = await this.makeRequest('POST', eventUrl, data, config); - if (response.data.error) { - throw ({message: response.data.error}); - } else { - exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); - } - } catch (error) { - if (error.response) { - Logger.error(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); - } else { - Logger.error(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); - } - exports.pending_test_uploads.count = Math.max(0, exports.pending_test_uploads.count - data.length); - } -}; - exports.getAccessKey = (settings) => { let accessKey = null; if (this.isObject(settings.desiredCapabilities)) { @@ -541,7 +455,7 @@ exports.uploadPending = async ( waitTimeout = DEFAULT_WAIT_TIMEOUT_FOR_PENDING_UPLOADS, waitInterval = DEFAULT_WAIT_INTERVAL_FOR_PENDING_UPLOADS ) => { - if (this.pending_test_uploads <= 0 || waitTimeout <= 0) { + if (requestQueueHandler.pending_test_uploads <= 0 || waitTimeout <= 0) { return; } diff --git a/src/utils/requestHelper.js b/src/utils/requestHelper.js new file mode 100644 index 0000000..af94f15 --- /dev/null +++ b/src/utils/requestHelper.js @@ -0,0 +1,56 @@ +const {API_URL, SCREENSHOT_EVENT_URL} = require('./constants'); +const http = require('node:http'); +const https = require('node:https'); +const request = require('request'); + +function createKeepAliveAgent(protocol) { + return new protocol.Agent({ + keepAlive: true, + timeout: 60000, + maxSockets: 2, + maxTotalSockets: 2 + }); +} + +const httpKeepAliveAgent = createKeepAliveAgent(http); +const httpsKeepAliveAgent = createKeepAliveAgent(https); +const httpScreenshotsKeepAliveAgent = createKeepAliveAgent(http); +const httpsScreenshotsKeepAliveAgent = createKeepAliveAgent(https); + +exports.makeRequest = (type, url, data, config) => { + + return new Promise((resolve, reject) => { + const options = {...config, ...{ + method: type, + url: `${API_URL}/${url}`, + body: data, + json: config.headers['Content-Type'] === 'application/json', + agent: API_URL.includes('https') ? httpsKeepAliveAgent : httpKeepAliveAgent + }}; + + if (url === SCREENSHOT_EVENT_URL) { + options.agent = API_URL.includes('https') ? httpsScreenshotsKeepAliveAgent : httpScreenshotsKeepAliveAgent; + } + + request(options, function callback(error, response, body) { + if (error) { + reject(error); + } else if (response.statusCode !== 200) { + if (response.statusCode === 401) { + reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); + } else { + reject(`Received response from BrowserStack Server with status : ${response.statusCode}`); + } + } else { + try { + if (body && typeof(body) !== 'object') {body = JSON.parse(body)} + } catch (e) { + reject('Not a JSON response from BrowserStack Server'); + } + resolve({ + data: body + }); + } + }); + }); +}; diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index 29f96c6..73783c4 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -1,7 +1,9 @@ const {BATCH_SIZE, BATCH_INTERVAL} = require('./constants'); -const helper = require('./helper'); +const Logger = require('./logger'); +const {makeRequest} = require('./requestHelper'); class RequestQueueHandler { + pending_test_uploads = 0; constructor() { this.queue = []; this.started = false; @@ -54,7 +56,7 @@ class RequestQueueHandler { while (this.queue.length > 0) { const data = this.queue.slice(0, BATCH_SIZE); this.queue.splice(0, BATCH_SIZE); - await helper.batchAndPostEvents(this.eventUrl, 'Shutdown-Queue', data); + await this.batchAndPostEvents(this.eventUrl, 'Shutdown-Queue', data); } } @@ -63,7 +65,7 @@ class RequestQueueHandler { if (this.queue.length > 0) { const data = this.queue.slice(0, BATCH_SIZE); this.queue.splice(0, BATCH_SIZE); - await helper.batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); + await this.batchAndPostEvents(this.eventUrl, 'Interval-Queue', data); } }, BATCH_INTERVAL); } @@ -86,6 +88,32 @@ class RequestQueueHandler { shouldProceed () { return this.queue.length >= BATCH_SIZE; } + + async batchAndPostEvents (eventUrl, kind, data) { + const config = { + headers: { + 'Authorization': `Bearer ${process.env.BS_TESTOPS_JWT}`, + 'Content-Type': 'application/json', + 'X-BSTACK-TESTOPS': 'true' + } + }; + + try { + const response = await makeRequest('POST', eventUrl, data, config); + if (response.data.error) { + throw ({message: response.data.error}); + } else { + this.pending_test_uploads = Math.max(0, this.pending_test_uploads - data.length); + } + } catch (error) { + if (error.response) { + Logger.error(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); + } else { + Logger.error(`EXCEPTION IN ${kind} REQUEST TO TEST OBSERVABILITY : ${error.message || error}`); + } + this.pending_test_uploads = Math.max(0, this.pending_test_uploads - data.length); + } + }; } module.exports = new RequestQueueHandler(); From 4cedf201e500b4e8784da98e525fdf88a5c9ecab Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Tue, 23 May 2023 20:30:01 +0530 Subject: [PATCH 38/41] added fixes for review comments --- nightwatch/globals.js | 50 +++++++++++++++++++--------------------- src/testObservability.js | 45 ++++++++++++++++++++++-------------- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/nightwatch/globals.js b/nightwatch/globals.js index 136fb06..977566e 100644 --- a/nightwatch/globals.js +++ b/nightwatch/globals.js @@ -13,37 +13,35 @@ const nightwatchRerunFile = process.env.NIGHTWATCH_RERUN_REPORT_FILE; module.exports = { - reporter: function(results, done) { - if (helper.isTestObservabilitySession()) { + reporter: async function(results, done) { + if (!helper.isTestObservabilitySession()) { + done(results); + + return; + } + try { + const modulesWithEnv = results['modulesWithEnv']; const promises = []; - try { - const modulesWithEnv = results['modulesWithEnv']; - for (const testSetting in modulesWithEnv) { - for (const testFile in modulesWithEnv[testSetting]) { - for (const completedSection in modulesWithEnv[testSetting][testFile].completed) { - if (modulesWithEnv[testSetting][testFile].completed[completedSection]) { - delete modulesWithEnv[testSetting][testFile].completed[completedSection].steps; - delete modulesWithEnv[testSetting][testFile].completed[completedSection].testcases; - } + for (const testSetting in modulesWithEnv) { + for (const testFile in modulesWithEnv[testSetting]) { + const completedSections = modulesWithEnv[testSetting][testFile].completed; + + for (const completedSection in completedSections) { + if (completedSections[completedSection]) { + delete completedSections[completedSection].steps; + delete completedSections[completedSection].testcases; } - promises.push(testObservability.processTestReportFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile])))); } + promises.push(testObservability.processTestReportFile(JSON.parse(JSON.stringify(modulesWithEnv[testSetting][testFile])))); } - - Promise.all(promises).then(() => { - done(); - }).catch((err) =>{ - Logger.error(`Something went wrong in processing report file for test observability - ${err.message} with stacktrace ${err.stack}`); - CrashReporter.uploadCrashReport(err.message, err.stack); - done(); - }); - - return; - } catch (error) { - CrashReporter.uploadCrashReport(error.message, error.stack); - Logger.error(`Something went wrong in processing report file for test observability - ${error.message} with stacktrace ${error.stack}`); } - } + + await Promise.all(promises); + done(); + } catch (error) { + CrashReporter.uploadCrashReport(error.message, error.stack); + Logger.error(`Something went wrong in processing report file for test observability - ${error.message} with stacktrace ${error.stack}`); + } done(results); }, diff --git a/src/testObservability.js b/src/testObservability.js index 615bef3..b17bde7 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -44,7 +44,7 @@ class TestObservability { name: helper.getObservabilityBuild(this._settings, this._bstackOptions), build_identifier: options.buildIdentifier, description: options.buildDescription || '', - start_time: (new Date()).toISOString(), + start_time: new Date().toISOString(), tags: helper.getObservabilityBuildTags(this._settings, this._bstackOptions), host_info: { hostname: os.hostname(), @@ -80,14 +80,15 @@ class TestObservability { Logger.info('Build creation successfull!'); process.env.BS_TESTOPS_BUILD_COMPLETED = true; - if (response.data && response.data.jwt) { - process.env.BS_TESTOPS_JWT = response.data.jwt; + const responseData = response.data || {}; + if (responseData.jwt) { + process.env.BS_TESTOPS_JWT = responseData.jwt; } - if (response.data && response.data.build_hashed_id) { - process.env.BS_TESTOPS_BUILD_HASHED_ID = response.data.build_hashed_id; + if (responseData.build_hashed_id) { + process.env.BS_TESTOPS_BUILD_HASHED_ID = responseData.build_hashed_id; } - if (response.data && response.data.allow_screenshots) { - process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = response.data.allow_screenshots.toString(); + if (responseData.allow_screenshots) { + process.env.BS_TESTOPS_ALLOW_SCREENSHOTS = responseData.allow_screenshots.toString(); } } catch (error) { if (error.response) { @@ -116,7 +117,7 @@ class TestObservability { }; } const data = { - 'stop_time': (new Date()).toISOString() + 'stop_time': new Date().toISOString() }; const config = { headers: { @@ -129,8 +130,8 @@ class TestObservability { await helper.shutDownRequestHandler(); try { const response = await makeRequest('PUT', `api/v1/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID}/stop`, data, config); - if (response.data && response.data.error) { - throw ({message: response.data.error}); + if (response.data?.error) { + throw {message: response.data.error}; } else { return { status: 'success', @@ -154,8 +155,10 @@ class TestObservability { async sendEvents(eventData, testFileReport, startEventType, finishedEventType, hookId, hookType, sectionName) { await this.sendTestRunEvent(eventData, testFileReport, startEventType, hookId, hookType, sectionName); if (eventData.httpOutput && eventData.httpOutput.length > 0) { - for (let i=0; i0) { + if (eventData.retryTestData?.length>0) { for (const retryTest of eventData.retryTestData) { await this.processTestRunData(retryTest, sectionName, testFileReport, hookIds); } @@ -200,7 +203,7 @@ class TestObservability { } } } - if (skippedTests && skippedTests.length > 0) { + if (skippedTests?.length > 0) { for (const skippedTest of skippedTests) { await this.sendSkippedTestEvent(skippedTest, testFileReport); } @@ -210,12 +213,14 @@ class TestObservability { async processTestRunData (eventData, sectionName, testFileReport, hookIds) { const testUuid = uuidv4(); - const errorData = eventData.commands.find(command => command.result && command.result.stack); + const errorData = eventData.commands.find(command => command.result?.stack); eventData.lastError = errorData ? errorData.result : null; await this.sendTestRunEvent(eventData, testFileReport, 'TestRunStarted', testUuid, null, sectionName, hookIds); if (eventData.httpOutput && eventData.httpOutput.length > 0) { - for (let i=0; i Date: Wed, 24 May 2023 15:29:40 +0530 Subject: [PATCH 39/41] linting changes and fixes for new review comments --- .eslintrc | 2 +- src/utils/crashReporter.js | 7 ++-- src/utils/helper.js | 86 +++++++++++++++++++------------------- src/utils/requestHelper.js | 28 +++++++------ 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/.eslintrc b/.eslintrc index e8e50eb..102f74a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,7 +3,7 @@ "eslint:recommended" ], "parserOptions": { - "ecmaVersion": 2018, + "ecmaVersion": 2022, "sourceType": "module", "ecmaFeatures": { "jsx": false diff --git a/src/utils/crashReporter.js b/src/utils/crashReporter.js index 531ffa3..f2f9949 100644 --- a/src/utils/crashReporter.js +++ b/src/utils/crashReporter.js @@ -22,18 +22,19 @@ class CrashReporter { return; } keysToDelete.forEach(key => delete obj[key]); - } + }; if (configWithoutPII['@nightwatch/browserstack'] && configWithoutPII['@nightwatch/browserstack'].test_observability) { deleteKeys(configWithoutPII['@nightwatch/browserstack'].test_observability); } if (configWithoutPII.desiredCapabilities && configWithoutPII.desiredCapabilities['bstack:options']) { deleteKeys(configWithoutPII.desiredCapabilities['bstack:options']); } + return configWithoutPII; } static setConfigDetails(settings={}) { - const configWithoutPII = this.filterPII(settings) + const configWithoutPII = this.filterPII(settings); this.userConfigForReporting = { framework: 'nightwatch-default', @@ -67,7 +68,7 @@ class CrashReporter { }; await makeRequest('POST', 'api/v1/analytics', data, config); } catch (error) { - Logger.error(`[Crash_Report_Upload] Failed due to ${error}`); + Logger.error(`[Crash_Report_Upload] Failed due to ${error}`); } } } diff --git a/src/utils/helper.js b/src/utils/helper.js index 08a1980..d86270c 100644 --- a/src/utils/helper.js +++ b/src/utils/helper.js @@ -224,22 +224,45 @@ const findGitConfig = async (filePath) => { }; exports.getGitMetaData = () => { - return new Promise(async (resolve, reject) => { - try { - var info = gitRepoInfo(); - if (!info.commonGitDir) { - Logger.info('Unable to find a Git directory'); - resolve({}); - } - if (!info.author && await findGitConfig(process.cwd())) { - /* commit objects are packed */ - gitLastCommit.getLastCommit(async (err, commit) => { - info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`; - info['authorDate'] = info['authorDate'] || commit['authoredOn']; - info['committer'] = info['committer'] || `${commit['committer']['name'].replace(/[“]+/g, '')} <${commit['committer']['email'].replace(/[“]+/g, '')}>`; - info['committerDate'] = info['committerDate'] || commit['committedOn']; - info['commitMessage'] = info['commitMessage'] || commit['subject']; - + return new Promise((resolve, reject) => { + (async () => { + try { + var info = gitRepoInfo(); + if (!info.commonGitDir) { + Logger.info('Unable to find a Git directory'); + resolve({}); + } + if (!info.author && await findGitConfig(process.cwd())) { + /* commit objects are packed */ + gitLastCommit.getLastCommit(async (err, commit) => { + info['author'] = info['author'] || `${commit['author']['name'].replace(/[“]+/g, '')} <${commit['author']['email'].replace(/[“]+/g, '')}>`; + info['authorDate'] = info['authorDate'] || commit['authoredOn']; + info['committer'] = info['committer'] || `${commit['committer']['name'].replace(/[“]+/g, '')} <${commit['committer']['email'].replace(/[“]+/g, '')}>`; + info['committerDate'] = info['committerDate'] || commit['committedOn']; + info['commitMessage'] = info['commitMessage'] || commit['subject']; + + const {remote} = await pGitconfig(info.commonGitDir); + const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); + resolve({ + 'name': 'git', + 'sha': info['sha'], + 'short_sha': info['abbreviatedSha'], + 'branch': info['branch'], + 'tag': info['tag'], + 'committer': info['committer'], + 'committer_date': info['committerDate'], + 'author': info['author'], + 'author_date': info['authorDate'], + 'commit_message': info['commitMessage'], + 'root': info['root'], + 'common_git_dir': info['commonGitDir'], + 'worktree_git_dir': info['worktreeGitDir'], + 'last_tag': info['lastTag'], + 'commits_since_last_tag': info['commitsSinceLastTag'], + 'remotes': remotes + }); + }, {dst: await findGitConfig(process.cwd())}); + } else { const {remote} = await pGitconfig(info.commonGitDir); const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); resolve({ @@ -260,33 +283,12 @@ exports.getGitMetaData = () => { 'commits_since_last_tag': info['commitsSinceLastTag'], 'remotes': remotes }); - }, {dst: await findGitConfig(process.cwd())}); - } else { - const {remote} = await pGitconfig(info.commonGitDir); - const remotes = Object.keys(remote).map(remoteName => ({name: remoteName, url: remote[remoteName]['url']})); - resolve({ - 'name': 'git', - 'sha': info['sha'], - 'short_sha': info['abbreviatedSha'], - 'branch': info['branch'], - 'tag': info['tag'], - 'committer': info['committer'], - 'committer_date': info['committerDate'], - 'author': info['author'], - 'author_date': info['authorDate'], - 'commit_message': info['commitMessage'], - 'root': info['root'], - 'common_git_dir': info['commonGitDir'], - 'worktree_git_dir': info['worktreeGitDir'], - 'last_tag': info['lastTag'], - 'commits_since_last_tag': info['commitsSinceLastTag'], - 'remotes': remotes - }); + } + } catch (err) { + Logger.error(`Exception in populating Git metadata with error : ${err}`); + resolve({}); } - } catch (err) { - Logger.error(`Exception in populating Git metadata with error : ${err}`); - resolve({}); - } + })(); }); }; diff --git a/src/utils/requestHelper.js b/src/utils/requestHelper.js index af94f15..07effdd 100644 --- a/src/utils/requestHelper.js +++ b/src/utils/requestHelper.js @@ -18,20 +18,24 @@ const httpScreenshotsKeepAliveAgent = createKeepAliveAgent(http); const httpsScreenshotsKeepAliveAgent = createKeepAliveAgent(https); exports.makeRequest = (type, url, data, config) => { + const isHttps = API_URL.includes('https'); + let agent; + if (url === SCREENSHOT_EVENT_URL) { + agent = isHttps ? httpsScreenshotsKeepAliveAgent : httpScreenshotsKeepAliveAgent; + } else { + agent = isHttps ? httpsKeepAliveAgent : httpKeepAliveAgent; + } + const options = { + ...config, + method: type, + url: `${API_URL}/${url}`, + body: data, + json: config.headers['Content-Type'] === 'application/json', + agent + }; + return new Promise((resolve, reject) => { - const options = {...config, ...{ - method: type, - url: `${API_URL}/${url}`, - body: data, - json: config.headers['Content-Type'] === 'application/json', - agent: API_URL.includes('https') ? httpsKeepAliveAgent : httpKeepAliveAgent - }}; - - if (url === SCREENSHOT_EVENT_URL) { - options.agent = API_URL.includes('https') ? httpsScreenshotsKeepAliveAgent : httpScreenshotsKeepAliveAgent; - } - request(options, function callback(error, response, body) { if (error) { reject(error); From 534711439b8d3335011446d50030db0017137ce6 Mon Sep 17 00:00:00 2001 From: Pranay Kumar Date: Wed, 24 May 2023 17:17:44 +0530 Subject: [PATCH 40/41] handling of error cases in 401 and 403 --- src/testObservability.js | 7 ++----- src/utils/requestHelper.js | 6 +----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/testObservability.js b/src/testObservability.js index b17bde7..60166c2 100644 --- a/src/testObservability.js +++ b/src/testObservability.js @@ -94,13 +94,10 @@ class TestObservability { if (error.response) { Logger.error(`EXCEPTION IN BUILD START EVENT : ${error.response.status} ${error.response.statusText} ${JSON.stringify(error.response.data)}`); } else { - if ((error.message && error.message.includes('with status : 401')) || (error && error.toString().includes('with status : 401'))) { - Logger.error('Either your BrowserStack access credentials are incorrect or you do not have access to BrowserStack Test Observability yet.'); - } else { - Logger.error(`EXCEPTION IN BUILD START EVENT : ${error.message || error}`); - } + Logger.error(`EXCEPTION IN BUILD START EVENT : ${error.message || error}`); } process.env.BS_TESTOPS_BUILD_COMPLETED = false; + process.env.BROWSERSTACK_TEST_OBSERVABILITY = false; } } diff --git a/src/utils/requestHelper.js b/src/utils/requestHelper.js index 07effdd..f5305ce 100644 --- a/src/utils/requestHelper.js +++ b/src/utils/requestHelper.js @@ -40,11 +40,7 @@ exports.makeRequest = (type, url, data, config) => { if (error) { reject(error); } else if (response.statusCode !== 200) { - if (response.statusCode === 401) { - reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); - } else { - reject(`Received response from BrowserStack Server with status : ${response.statusCode}`); - } + reject(response && response.body ? response.body : `Received response from BrowserStack Server with status : ${response.statusCode}`); } else { try { if (body && typeof(body) !== 'object') {body = JSON.parse(body)} From 5148bffa8271534e05cf6edc17896dfb7bae54b2 Mon Sep 17 00:00:00 2001 From: Binayak Ghosh Date: Wed, 24 May 2023 17:49:30 +0530 Subject: [PATCH 41/41] use ES2020 --- .eslintrc | 2 +- src/utils/crashReporter.js | 7 ++----- src/utils/requestQueueHandler.js | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.eslintrc b/.eslintrc index 102f74a..89f8137 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,7 +3,7 @@ "eslint:recommended" ], "parserOptions": { - "ecmaVersion": 2022, + "ecmaVersion": 2020, "sourceType": "module", "ecmaFeatures": { "jsx": false diff --git a/src/utils/crashReporter.js b/src/utils/crashReporter.js index f2f9949..c01a36c 100644 --- a/src/utils/crashReporter.js +++ b/src/utils/crashReporter.js @@ -4,9 +4,6 @@ const Logger = require('./logger'); class CrashReporter { - static userConfigForReporting = {}; - static credentialsForCrashReportUpload = {}; - static setCredentialsForCrashReportUpload(username, key) { this.credentialsForCrashReportUpload = { username: username, @@ -45,7 +42,7 @@ class CrashReporter { static async uploadCrashReport(exception, stackTrace) { const config = { - auth: this.credentialsForCrashReportUpload, + auth: this.credentialsForCrashReportUpload || {}, headers: { 'Content-Type': 'application/json', 'X-BSTACK-TESTOPS': 'true' @@ -64,7 +61,7 @@ class CrashReporter { error: exception.toString(), stackTrace: stackTrace }, - config: this.userConfigForReporting + config: this.userConfigForReporting || {} }; await makeRequest('POST', 'api/v1/analytics', data, config); } catch (error) { diff --git a/src/utils/requestQueueHandler.js b/src/utils/requestQueueHandler.js index 73783c4..ddb97a8 100644 --- a/src/utils/requestQueueHandler.js +++ b/src/utils/requestQueueHandler.js @@ -3,7 +3,6 @@ const Logger = require('./logger'); const {makeRequest} = require('./requestHelper'); class RequestQueueHandler { - pending_test_uploads = 0; constructor() { this.queue = []; this.started = false; @@ -11,6 +10,7 @@ class RequestQueueHandler { this.screenshotEventUrl = 'api/v1/screenshots'; this.BATCH_EVENT_TYPES = ['LogCreated', 'TestRunFinished', 'TestRunSkipped', 'HookRunFinished', 'TestRunStarted', 'HookRunStarted']; this.pollEventBatchInterval = null; + RequestQueueHandler.pending_test_uploads = 0; } start() {