From 1aeb5167aca6f096832298b440151a98467a36db Mon Sep 17 00:00:00 2001 From: DudaGod Date: Thu, 21 Mar 2024 12:37:58 +0300 Subject: [PATCH] feat: ability to run unit tests in browser --- package-lock.json | 1675 ++++++++++++++--- package.json | 9 +- src/browser/types.ts | 2 + src/config/defaults.js | 3 +- src/config/options.js | 46 + src/config/types.ts | 7 + src/constants/config.js | 2 + src/hermione.ts | 11 +- src/runner/browser-env/index.ts | 42 + .../vite/browser-modules/communicator.ts | 26 + .../vite/browser-modules/constants.ts | 14 + .../vite/browser-modules/errors/base.ts | 11 + .../vite/browser-modules/errors/browser.ts | 23 + .../vite/browser-modules/errors/index.ts | 58 + .../vite/browser-modules/errors/load-page.ts | 17 + .../vite/browser-modules/errors/vite.ts | 30 + .../vite/browser-modules/globals.ts | 75 + .../browser-env/vite/browser-modules/index.ts | 4 + .../vite/browser-modules/mocha/events.ts | 6 + .../vite/browser-modules/mocha/index.ts | 85 + .../vite/browser-modules/mocha/parser.ts | 33 + .../vite/browser-modules/package.json | 3 + .../vite/browser-modules/tsconfig.json | 14 + .../browser-env/vite/browser-modules/types.ts | 51 + .../vite/browser-modules/utils/index.ts | 1 + .../vite/browser-modules/utils/selector.ts | 3 + src/runner/browser-env/vite/communicator.ts | 106 ++ src/runner/browser-env/vite/constants.ts | 15 + .../vite/plugins/generate-index-html.ts | 73 + .../vite/plugins/resolve-module-paths.ts | 36 + src/runner/browser-env/vite/server.ts | 108 ++ src/runner/browser-env/vite/types.ts | 31 + src/runner/browser-env/vite/utils.ts | 36 + src/types/index.ts | 10 +- src/utils/config.ts | 6 + src/worker/browser-env/communicator.ts | 109 ++ src/worker/browser-env/constants.ts | 1 + .../runner/test-runner/execution-thread.ts | 49 + .../browser-env/runner/test-runner/index.ts | 59 + src/worker/browser-env/types.ts | 32 + src/worker/hermione-facade.js | 7 + src/worker/runner/index.js | 11 +- src/worker/runner/test-runner/index.js | 36 +- src/worker/runner/test-runner/runner.ts | 9 + src/worker/runner/test-runner/types.ts | 27 + test/fixtures/vite.conf.ts | 8 + test/src/config/options.js | 105 +- test/src/hermione.js | 142 +- test/src/runner/browser-env/index.ts | 98 + test/src/runner/browser-env/vite/server.ts | 248 +++ test/src/runner/index.js | 2 +- test/src/utils/config.ts | 27 + .../browser-env/runner/test-runner/index.ts | 188 ++ test/src/worker/runner/index.js | 148 +- test/src/worker/runner/test-runner/index.js | 3 +- test/utils.js | 36 +- tsconfig.json | 15 +- tsconfig.spec.json | 1 + 58 files changed, 3603 insertions(+), 430 deletions(-) create mode 100644 src/runner/browser-env/index.ts create mode 100644 src/runner/browser-env/vite/browser-modules/communicator.ts create mode 100644 src/runner/browser-env/vite/browser-modules/constants.ts create mode 100644 src/runner/browser-env/vite/browser-modules/errors/base.ts create mode 100644 src/runner/browser-env/vite/browser-modules/errors/browser.ts create mode 100644 src/runner/browser-env/vite/browser-modules/errors/index.ts create mode 100644 src/runner/browser-env/vite/browser-modules/errors/load-page.ts create mode 100644 src/runner/browser-env/vite/browser-modules/errors/vite.ts create mode 100644 src/runner/browser-env/vite/browser-modules/globals.ts create mode 100644 src/runner/browser-env/vite/browser-modules/index.ts create mode 100644 src/runner/browser-env/vite/browser-modules/mocha/events.ts create mode 100644 src/runner/browser-env/vite/browser-modules/mocha/index.ts create mode 100644 src/runner/browser-env/vite/browser-modules/mocha/parser.ts create mode 100644 src/runner/browser-env/vite/browser-modules/package.json create mode 100644 src/runner/browser-env/vite/browser-modules/tsconfig.json create mode 100644 src/runner/browser-env/vite/browser-modules/types.ts create mode 100644 src/runner/browser-env/vite/browser-modules/utils/index.ts create mode 100644 src/runner/browser-env/vite/browser-modules/utils/selector.ts create mode 100644 src/runner/browser-env/vite/communicator.ts create mode 100644 src/runner/browser-env/vite/constants.ts create mode 100644 src/runner/browser-env/vite/plugins/generate-index-html.ts create mode 100644 src/runner/browser-env/vite/plugins/resolve-module-paths.ts create mode 100644 src/runner/browser-env/vite/server.ts create mode 100644 src/runner/browser-env/vite/types.ts create mode 100644 src/runner/browser-env/vite/utils.ts create mode 100644 src/utils/config.ts create mode 100644 src/worker/browser-env/communicator.ts create mode 100644 src/worker/browser-env/constants.ts create mode 100644 src/worker/browser-env/runner/test-runner/execution-thread.ts create mode 100644 src/worker/browser-env/runner/test-runner/index.ts create mode 100644 src/worker/browser-env/types.ts create mode 100644 src/worker/runner/test-runner/runner.ts create mode 100644 src/worker/runner/test-runner/types.ts create mode 100644 test/fixtures/vite.conf.ts create mode 100644 test/src/runner/browser-env/index.ts create mode 100644 test/src/runner/browser-env/vite/server.ts create mode 100644 test/src/utils/config.ts create mode 100644 test/src/worker/browser-env/runner/test-runner/index.ts diff --git a/package-lock.json b/package-lock.json index 6f2f73db7..cdfc5b1a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,9 @@ "fastq": "1.13.0", "fs-extra": "5.0.0", "gemini-configparser": "1.3.0", + "get-port": "5.1.1", "glob-extra": "5.0.2", + "import-meta-resolve": "4.0.0", "lodash": "4.17.21", "looks-same": "9.0.0", "micromatch": "4.0.5", @@ -40,6 +42,7 @@ "uglifyify": "3.0.4", "urijs": "1.19.11", "url-join": "4.0.1", + "vite": "5.1.6", "webdriverio": "8.21.0", "worker-farm": "1.7.0", "yallist": "3.1.1" @@ -61,12 +64,14 @@ "@types/bluebird": "3.5.38", "@types/chai": "4.3.4", "@types/chai-as-promised": "7.1.5", + "@types/debug": "4.1.12", "@types/lodash": "4.14.191", "@types/node": "18.19.3", "@types/proxyquire": "1.3.28", "@types/sharp": "0.31.1", "@types/sinon": "4.3.3", "@types/sinonjs__fake-timers": "8.1.2", + "@types/urijs": "1.19.25", "@typescript-eslint/eslint-plugin": "6.12.0", "@typescript-eslint/parser": "6.12.0", "app-module-path": "2.2.0", @@ -1045,16 +1050,6 @@ "node": ">=v18" } }, - "node_modules/@commitlint/resolve-extends/node_modules/import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1222,13 +1217,12 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "aix" @@ -1238,13 +1232,12 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -1254,13 +1247,12 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -1270,13 +1262,12 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -1302,13 +1293,12 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1318,13 +1308,12 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -1334,13 +1323,12 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -1350,13 +1338,12 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1366,13 +1353,12 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1382,13 +1368,12 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1398,13 +1383,12 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1414,13 +1398,12 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1430,13 +1413,12 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1446,13 +1428,12 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1462,13 +1443,12 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1478,13 +1458,12 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1494,13 +1473,12 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -1510,13 +1488,12 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -1526,13 +1503,12 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -1542,13 +1518,12 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -1558,13 +1533,12 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -1574,13 +1548,12 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2135,6 +2108,162 @@ "node": ">=12" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", @@ -2548,6 +2677,20 @@ "@types/node": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -2614,6 +2757,12 @@ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==" }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true + }, "node_modules/@types/node": { "version": "18.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", @@ -2679,6 +2828,12 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, + "node_modules/@types/urijs": { + "version": "1.19.25", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", + "integrity": "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==", + "dev": true + }, "node_modules/@types/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", @@ -3248,6 +3403,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@wdio/config/node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@wdio/config/node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -3336,6 +3502,15 @@ "node": ">= 14" } }, + "node_modules/@wdio/config/node_modules/import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@wdio/config/node_modules/json-parse-even-better-errors": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", @@ -7065,6 +7240,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/devtools/node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/devtools/node_modules/got": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", @@ -7113,6 +7299,15 @@ "node": ">= 14" } }, + "node_modules/devtools/node_modules/import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/devtools/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -7698,73 +7893,425 @@ "once": "^1.4.0" } }, - "node_modules/entities": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", - "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], "dev": true, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=12" } }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" + "node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" + "node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "node_modules/esbuild": { + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escalade": { @@ -8665,9 +9212,9 @@ "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==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "optional": true, "os": [ @@ -8937,11 +9484,11 @@ } }, "node_modules/get-port": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", - "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", "engines": { - "node": ">=16" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9649,9 +10196,9 @@ } }, "node_modules/import-meta-resolve": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", - "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -11591,6 +12138,23 @@ "resolved": "https://registry.npmjs.org/n12/-/n12-0.4.0.tgz", "integrity": "sha512-p/hj4zQ8d3pbbFLQuN1K9honUxiDDhueOWyFLw/XgBv+wZCE44bcLH4CIcsolOceJQduh4Jf7m/LfaTxyGmGtQ==" }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -12308,6 +12872,33 @@ "resolved": "https://registry.npmjs.org/png-validator/-/png-validator-1.1.0.tgz", "integrity": "sha512-MlRLyPI1p3/dJbsjVH+4xOPucycrz8T3EvO0BzCXaNtrUhZkZROtzib9J6mnC81AJO8eBIwiDZwTFel2cMmSuQ==" }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -13042,6 +13633,37 @@ "inherits": "^2.0.1" } }, + "node_modules/rollup": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", + "fsevents": "~2.3.2" + } + }, "node_modules/rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -13467,6 +14089,14 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -14708,6 +15338,112 @@ "node": ">=4" } }, + "node_modules/vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, "node_modules/vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -15038,6 +15774,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webdriver/node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webdriver/node_modules/got": { "version": "12.6.1", "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", @@ -15086,6 +15833,15 @@ "node": ">= 14" } }, + "node_modules/webdriver/node_modules/import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webdriver/node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -15502,6 +16258,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/webdriverio/node_modules/get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webdriverio/node_modules/got": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", @@ -15550,6 +16317,15 @@ "node": ">= 14" } }, + "node_modules/webdriverio/node_modules/import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webdriverio/node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -16863,12 +17639,6 @@ "resolve-from": "^5.0.0" }, "dependencies": { - "import-meta-resolve": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", - "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", - "dev": true - }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -16983,33 +17753,29 @@ "requires": { "@jridgewell/trace-mapping": "0.3.9" } - }, - "@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", - "dev": true, + }, + "@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", "optional": true }, "@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "optional": true }, "@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "optional": true }, "@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "optional": true }, "@esbuild/darwin-arm64": { @@ -17020,129 +17786,111 @@ "optional": true }, "@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "optional": true }, "@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "optional": true }, "@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "optional": true }, "@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "optional": true }, "@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "optional": true }, "@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "optional": true }, "@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "optional": true }, "@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "optional": true }, "@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "optional": true }, "@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "optional": true }, "@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", - "dev": true, + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "optional": true }, "@eslint-community/eslint-utils": { @@ -17537,6 +18285,84 @@ } } }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", + "optional": true + }, "@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", @@ -17839,6 +18665,20 @@ "@types/node": "*" } }, + "@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, "@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -17905,6 +18745,12 @@ "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==" }, + "@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "dev": true + }, "@types/node": { "version": "18.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", @@ -17970,6 +18816,12 @@ "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==", "dev": true }, + "@types/urijs": { + "version": "1.19.25", + "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", + "integrity": "sha512-XOfUup9r3Y06nFAZh3WvO0rBU4OtlfPB/vgxpjg+NRdGU6CN6djdc6OEiH+PcqHCY6eFLo9Ista73uarf4gnBg==", + "dev": true + }, "@types/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", @@ -18348,6 +19200,11 @@ "signal-exit": "^4.0.1" } }, + "get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==" + }, "glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -18411,6 +19268,11 @@ "debug": "4" } }, + "import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==" + }, "json-parse-even-better-errors": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", @@ -21252,6 +22114,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==" }, + "get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==" + }, "got": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", @@ -21288,6 +22155,11 @@ "debug": "4" } }, + "import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==" + }, "isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -21767,6 +22639,162 @@ "@esbuild/win32-arm64": "0.20.2", "@esbuild/win32-ia32": "0.20.2", "@esbuild/win32-x64": "0.20.2" + }, + "dependencies": { + "@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "dev": true, + "optional": true + } } }, "escalade": { @@ -22417,9 +23445,9 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "optional": true }, "fstream": { @@ -22608,9 +23636,9 @@ } }, "get-port": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.0.0.tgz", - "integrity": "sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==" }, "get-stream": { "version": "6.0.1", @@ -23112,9 +24140,9 @@ } }, "import-meta-resolve": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz", - "integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==" }, "imurmurhash": { "version": "0.1.4", @@ -24572,6 +25600,11 @@ "resolved": "https://registry.npmjs.org/n12/-/n12-0.4.0.tgz", "integrity": "sha512-p/hj4zQ8d3pbbFLQuN1K9honUxiDDhueOWyFLw/XgBv+wZCE44bcLH4CIcsolOceJQduh4Jf7m/LfaTxyGmGtQ==" }, + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" + }, "napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", @@ -25114,6 +26147,16 @@ "resolved": "https://registry.npmjs.org/png-validator/-/png-validator-1.1.0.tgz", "integrity": "sha512-MlRLyPI1p3/dJbsjVH+4xOPucycrz8T3EvO0BzCXaNtrUhZkZROtzib9J6mnC81AJO8eBIwiDZwTFel2cMmSuQ==" }, + "postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "requires": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + } + }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -25669,6 +26712,28 @@ "inherits": "^2.0.1" } }, + "rollup": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", + "requires": { + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", + "@types/estree": "1.0.5", + "fsevents": "~2.3.2" + } + }, "rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -25978,6 +27043,11 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "devOptional": true }, + "source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" + }, "spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -26910,6 +27980,55 @@ "unist-util-stringify-position": "^2.0.0" } }, + "vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "requires": { + "esbuild": "^0.19.3", + "fsevents": "~2.3.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "dependencies": { + "@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "optional": true + }, + "esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "requires": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + } + } + }, "vm-browserify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -27146,6 +28265,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==" }, + "get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==" + }, "got": { "version": "12.6.1", "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", @@ -27182,6 +28306,11 @@ "debug": "4" } }, + "import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==" + }, "lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -27472,6 +28601,11 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==" }, + "get-port": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.1.0.tgz", + "integrity": "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw==" + }, "got": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", @@ -27508,6 +28642,11 @@ "debug": "4" } }, + "import-meta-resolve": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.1.1.tgz", + "integrity": "sha512-qeywsE/KC3w9Fd2ORrRDUw6nS/nLwZpXgfrOc2IILvZYnCaEMd+D56Vfg9k4G29gIeVi3XKql1RQatME8iYsiw==" + }, "is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", diff --git a/package.json b/package.json index 3ef52bb1d..38fd276c5 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "typings" ], "scripts": { - "build": "tsc && npm run copy-static && npm run build-bundle -- --minify", + "build": "tsc --build && npm run copy-static && npm run build-bundle -- --minify", "build-bundle": "esbuild ./src/bundle/index.ts --outdir=./build/src/bundle --bundle --format=cjs --platform=node --target=ES2021", "copy-static": "copyfiles 'src/browser/client-scripts/*' build", "check-types": "tsc --project tsconfig.spec.json", @@ -25,7 +25,7 @@ "commitmsg": "commitlint -e", "release": "standard-version", "watch": "npm run copy-static && concurrently -c 'auto' 'npm:watch:src' 'npm:watch:bundle'", - "watch:src": "tsc --watch", + "watch:src": "tsc --build --watch", "watch:bundle": "npm run build-bundle -- --watch" }, "repository": { @@ -62,7 +62,9 @@ "fastq": "1.13.0", "fs-extra": "5.0.0", "gemini-configparser": "1.3.0", + "get-port": "5.1.1", "glob-extra": "5.0.2", + "import-meta-resolve": "4.0.0", "lodash": "4.17.21", "looks-same": "9.0.0", "micromatch": "4.0.5", @@ -77,6 +79,7 @@ "uglifyify": "3.0.4", "urijs": "1.19.11", "url-join": "4.0.1", + "vite": "5.1.6", "webdriverio": "8.21.0", "worker-farm": "1.7.0", "yallist": "3.1.1" @@ -95,12 +98,14 @@ "@types/bluebird": "3.5.38", "@types/chai": "4.3.4", "@types/chai-as-promised": "7.1.5", + "@types/debug": "4.1.12", "@types/lodash": "4.14.191", "@types/node": "18.19.3", "@types/proxyquire": "1.3.28", "@types/sharp": "0.31.1", "@types/sinon": "4.3.3", "@types/sinonjs__fake-timers": "8.1.2", + "@types/urijs": "1.19.25", "@typescript-eslint/eslint-plugin": "6.12.0", "@typescript-eslint/parser": "6.12.0", "app-module-path": "2.2.0", diff --git a/src/browser/types.ts b/src/browser/types.ts index ae572288e..6950e9c52 100644 --- a/src/browser/types.ts +++ b/src/browser/types.ts @@ -1,6 +1,7 @@ import type { AssertViewCommand, AssertViewElementCommand } from "./commands/types"; import type { BrowserConfig } from "./../config/browser-config"; import type { AssertViewResult, RunnerTest, RunnerHook } from "../types"; +import Callstack from "./history/callstack"; export interface BrowserMeta { pid: number; @@ -13,6 +14,7 @@ export interface Browser { config: BrowserConfig; state: Record; applyState: (state: Record) => void; + callstackHistory: Callstack; } declare global { diff --git a/src/config/defaults.js b/src/config/defaults.js index 0e2b34613..49f2153d0 100644 --- a/src/config/defaults.js +++ b/src/config/defaults.js @@ -1,6 +1,6 @@ "use strict"; -const { WEBDRIVER_PROTOCOL, SAVE_HISTORY_MODE } = require("../constants/config"); +const { WEBDRIVER_PROTOCOL, SAVE_HISTORY_MODE, NODEJS_TEST_RUN_ENV } = require("../constants/config"); module.exports = { baseUrl: "http://localhost", @@ -93,6 +93,7 @@ module.exports = { region: null, headless: null, isolation: null, + testRunEnv: NODEJS_TEST_RUN_ENV, }; module.exports.configPaths = [".hermione.conf.ts", ".hermione.conf.js"]; diff --git a/src/config/options.js b/src/config/options.js index 3994963cc..0da5bfa6e 100644 --- a/src/config/options.js +++ b/src/config/options.js @@ -5,6 +5,7 @@ const { root, section, map, option } = require("gemini-configparser"); const browserOptions = require("./browser-options"); const defaults = require("./defaults"); const optionsBuilder = require("./options-builder"); +const { NODEJS_TEST_RUN_ENV, BROWSER_TEST_RUN_ENV } = require("../constants/config"); const options = optionsBuilder(_.propertyOf(defaults)); @@ -57,6 +58,51 @@ const rootSection = section( } }, }), + + testRunEnv: option({ + defaultValue: defaults.testRunEnv, + validate: value => { + if (!_.isArray(value) && !_.isString(value)) { + throw new Error(`"testRunEnv" must be an array or string but got ${JSON.stringify(value)}`); + } + + if (_.isString(value)) { + if (value !== NODEJS_TEST_RUN_ENV && value !== BROWSER_TEST_RUN_ENV) { + throw new Error( + `"testRunEnv" specified as string must be "${NODEJS_TEST_RUN_ENV}" or "${BROWSER_TEST_RUN_ENV}" but got "${value}"`, + ); + } + + return; + } + + const [testRunEnv, options] = value; + + if (testRunEnv === NODEJS_TEST_RUN_ENV) { + throw new Error( + `"testRunEnv" with "${NODEJS_TEST_RUN_ENV}" value must be specified as string but got ${JSON.stringify( + value, + )}`, + ); + } + + if (testRunEnv === BROWSER_TEST_RUN_ENV && !options) { + throw new Error( + `"testRunEnv" specified as array must also contain options as second argument but got ${JSON.stringify( + value, + )}`, + ); + } + + if (testRunEnv !== BROWSER_TEST_RUN_ENV) { + throw new Error( + `"testRunEnv" specified as array must be in format ["${BROWSER_TEST_RUN_ENV}", ] but got ${JSON.stringify( + value, + )}`, + ); + } + }, + }), }), plugins: options.anyObject(), diff --git a/src/config/types.ts b/src/config/types.ts index 8d939e70e..7d38d3650 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -1,5 +1,6 @@ import type { SetRequired } from "type-fest"; import type { BrowserConfig } from "./browser-config"; +import type { BrowserTestRunEnvOptions } from "../runner/browser-env/vite/types"; import type { Test } from "../types"; export interface CompareOptsConfig { @@ -71,6 +72,7 @@ export interface SystemConfig { tempDir: string; parallelLimit: number; fileExtensions: Array; + testRunEnv: "nodejs" | "browser" | ["browser", BrowserTestRunEnvOptions]; } export interface CommonConfig { @@ -144,6 +146,11 @@ export type ConfigInput = { prepareEnvironment?: () => void | null; }; +export interface RuntimeConfig { + extend: (data: unknown) => this; + [key: string]: unknown; +} + declare module "." { export interface Config extends CommonConfig { browsers: Record; diff --git a/src/constants/config.js b/src/constants/config.js index dea828481..eab3753ca 100644 --- a/src/constants/config.js +++ b/src/constants/config.js @@ -8,4 +8,6 @@ module.exports = { NONE: "none", ONLY_FAILED: "onlyFailed", }, + NODEJS_TEST_RUN_ENV: "nodejs", + BROWSER_TEST_RUN_ENV: "browser", }; diff --git a/src/hermione.ts b/src/hermione.ts index d9ba2ac05..1ea309ca4 100644 --- a/src/hermione.ts +++ b/src/hermione.ts @@ -2,7 +2,8 @@ import { CommanderStatic } from "@gemini-testing/commander"; import * as _ from "lodash"; import { Stats as RunnerStats } from "./stats"; import { BaseHermione } from "./base-hermione"; -import { MainRunner } from "./runner"; +import { MainRunner as NodejsEnvRunner } from "./runner"; +import { MainRunner as BrowserEnvRunner } from "./runner/browser-env"; import RuntimeConfig from "./config/runtime-config"; import { MasterAsyncEvents, MasterEvents, MasterSyncEvents } from "./events"; import eventsUtils from "./events/utils"; @@ -12,6 +13,7 @@ import { TestCollection } from "./test-collection"; import { validateUnknownBrowsers } from "./validators"; import { initReporters } from "./reporters"; import logger from "./utils/logger"; +import { isRunInNodeJsEnv } from "./utils/config"; import { ConfigInput } from "./config/types"; import { MasterEventHandler, Test } from "./types"; @@ -47,7 +49,7 @@ export interface Hermione { export class Hermione extends BaseHermione { protected failed: boolean; - protected runner: MainRunner | null; + protected runner: NodejsEnvRunner | BrowserEnvRunner | null; constructor(config?: string | ConfigInput) { super(config); @@ -82,7 +84,10 @@ export class Hermione extends BaseHermione { this._config.system.mochaOpts.timeout = 0; } - const runner = MainRunner.create(this._config, this._interceptors); + const runner = (isRunInNodeJsEnv(this._config) ? NodejsEnvRunner : BrowserEnvRunner).create( + this._config, + this._interceptors, + ); this.runner = runner; this.on(MasterEvents.TEST_FAIL, () => this._fail()).on(MasterEvents.ERROR, (err: Error) => this.halt(err)); diff --git a/src/runner/browser-env/index.ts b/src/runner/browser-env/index.ts new file mode 100644 index 000000000..505425a27 --- /dev/null +++ b/src/runner/browser-env/index.ts @@ -0,0 +1,42 @@ +import { ViteServer } from "./vite/server"; +import { MainRunner as NodejsEnvRunner } from ".."; +import { TestCollection } from "../../test-collection"; +import { Config } from "../../config"; +import { Interceptor } from "../../events"; +import type { Stats as RunnerStats } from "../../stats"; + +export class MainRunner extends NodejsEnvRunner { + #viteServer: ViteServer; + + constructor(config: Config, interceptors: Interceptor[]) { + super(config, interceptors); + + this.#viteServer = ViteServer.create(config); + } + + async run(testCollection: TestCollection, stats: RunnerStats): Promise { + try { + await this.#viteServer.start(); + } catch (err) { + throw new Error(`Vite server failed to start: ${(err as Error).message}`); + } + + this.useBaseUrlFromVite(); + await super.run(testCollection, stats); + } + + private useBaseUrlFromVite(): void { + const viteBaseUrl = this.#viteServer.baseUrl!; + + this.config.baseUrl = viteBaseUrl; + for (const broConfig of Object.values(this.config.browsers)) { + broConfig.baseUrl = viteBaseUrl; + } + } + + cancel(): void { + super.cancel(); + + this.#viteServer.close(); + } +} diff --git a/src/runner/browser-env/vite/browser-modules/communicator.ts b/src/runner/browser-env/vite/browser-modules/communicator.ts new file mode 100644 index 000000000..c7bb686e6 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/communicator.ts @@ -0,0 +1,26 @@ +import { prepareError } from "./errors/index.js"; +import { BrowserEventNames, BrowserMessage, WorkerEventNames, WorkerMessage } from "./types.js"; + +export class ViteBrowserCommunicator { + static create(this: new () => T): T { + return new this(); + } + + subscribeOnMessage(event: WorkerEventNames, handler: (msg: WorkerMessage) => Promise): void { + import.meta.hot?.on(event, handler); + } + + sendMessage(event: BrowserEventNames, msg?: Partial): void { + if (msg && msg.errors) { + msg.errors = msg.errors.map(prepareError); + } + + import.meta.hot?.send(event, { + pid: window.__hermione__.pid, + runUuid: window.__hermione__.runUuid, + cmdUuid: window.__hermione__.cmdUuid, + errors: [], + ...msg, + }); + } +} diff --git a/src/runner/browser-env/vite/browser-modules/constants.ts b/src/runner/browser-env/vite/browser-modules/constants.ts new file mode 100644 index 000000000..fa9538340 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/constants.ts @@ -0,0 +1,14 @@ +export const DOCUMENT_TITLE = "Hermione Browser Test"; +export const VITE_OVERLAY_SELECTOR = "vite-error-overlay"; + +export const VITE_SELECTORS = { + overlay: "vite-error-overlay", + overlayMessage: ".message", + overlayStack: ".stack", + overlayFile: ".file", + overlayFrame: ".frame", + overlayTip: ".tip", +}; + +export const HERMIONE_BROWSER_EVENT_SUFFIX = "hermione:browser"; +export const HERMIONE_WORKER_EVENT_SUFFIX = "hermione:worker"; diff --git a/src/runner/browser-env/vite/browser-modules/errors/base.ts b/src/runner/browser-env/vite/browser-modules/errors/base.ts new file mode 100644 index 000000000..00b694b8f --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/errors/base.ts @@ -0,0 +1,11 @@ +export class BaseError extends Error { + constructor({ message, stack }: { message: string; stack?: string }) { + super(message); + + this.name = this.constructor.name; + + if (stack) { + this.stack = stack; + } + } +} diff --git a/src/runner/browser-env/vite/browser-modules/errors/browser.ts b/src/runner/browser-env/vite/browser-modules/errors/browser.ts new file mode 100644 index 000000000..bda5f9c37 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/errors/browser.ts @@ -0,0 +1,23 @@ +import { BaseError } from "./base.js"; + +interface BrowserErrorData { + message: string; + stack?: string; + file?: string; +} + +export class BrowserError extends BaseError { + file?: string; + + static create(this: new (opts: BrowserErrorData) => T, opts: BrowserErrorData): T { + return new this(opts); + } + + constructor({ message, stack, file }: BrowserErrorData) { + super({ message, stack }); + + if (file) { + this.file = file; + } + } +} diff --git a/src/runner/browser-env/vite/browser-modules/errors/index.ts b/src/runner/browser-env/vite/browser-modules/errors/index.ts new file mode 100644 index 000000000..4d5f6db82 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/errors/index.ts @@ -0,0 +1,58 @@ +import { BrowserError } from "./browser.js"; +import { LoadPageError } from "./load-page.js"; +import { ViteError } from "./vite.js"; +import { getSelectorTextFromShadowRoot } from "../utils/index.js"; +import { DOCUMENT_TITLE, VITE_SELECTORS } from "../constants.js"; + +export type ErrorOnPageLoad = LoadPageError | ViteError | BrowserError; +export type ErrorOnRunRunnable = ViteError | BrowserError | Error; +export type AvailableError = ErrorOnPageLoad | Error; + +const getLoadPageErrors = (): LoadPageError[] => { + if (document.title === DOCUMENT_TITLE && window.__hermione__) { + return []; + } + + return [LoadPageError.create()]; +}; + +const getViteErrors = (): ViteError[] => { + const viteErrorElem = document.querySelector(VITE_SELECTORS.overlay); + + if (!viteErrorElem || !viteErrorElem.shadowRoot) { + return []; + } + + const shadowRoot = viteErrorElem.shadowRoot; + + const message = getSelectorTextFromShadowRoot(VITE_SELECTORS.overlayMessage, shadowRoot); + const stack = getSelectorTextFromShadowRoot(VITE_SELECTORS.overlayStack, shadowRoot); + const file = getSelectorTextFromShadowRoot(VITE_SELECTORS.overlayFile, shadowRoot); + const frame = getSelectorTextFromShadowRoot(VITE_SELECTORS.overlayFrame, shadowRoot); + const tip = getSelectorTextFromShadowRoot(VITE_SELECTORS.overlayTip, shadowRoot); + + return [ViteError.create({ message, stack, file, frame, tip })]; +}; + +const getBrowserErrors = (): BrowserError[] => { + return window.__hermione__.errors; +}; + +const findErrors = (errors: AvailableError | AvailableError[] = []): AvailableError[] => { + return [errors, getViteErrors(), getBrowserErrors()].flat().filter(Boolean); +}; + +export const findErrorsOnPageLoad = (): ErrorOnPageLoad[] => { + return findErrors(getLoadPageErrors()); +}; + +export const findErrorsOnRunRunnable = (runnableError?: Error): AvailableError[] => { + return findErrors(runnableError); +}; + +export const prepareError = (error: Error): Error => { + // in order to correctly pass errors through websocket + return JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error))); +}; + +export { BrowserError, LoadPageError, ViteError }; diff --git a/src/runner/browser-env/vite/browser-modules/errors/load-page.ts b/src/runner/browser-env/vite/browser-modules/errors/load-page.ts new file mode 100644 index 000000000..6eced4c70 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/errors/load-page.ts @@ -0,0 +1,17 @@ +import { BaseError } from "./base.js"; + +interface LoadPageErrorData { + message?: string; +} + +type BrowserErrorCtor = new (opts?: LoadPageErrorData) => T; + +export class LoadPageError extends BaseError { + static create(this: BrowserErrorCtor, opts?: LoadPageErrorData): T { + return new this(opts); + } + + constructor({ message = "failed to load Vite test page" }: LoadPageErrorData = {}) { + super({ message }); + } +} diff --git a/src/runner/browser-env/vite/browser-modules/errors/vite.ts b/src/runner/browser-env/vite/browser-modules/errors/vite.ts new file mode 100644 index 000000000..73090e399 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/errors/vite.ts @@ -0,0 +1,30 @@ +import { BaseError } from "./base.js"; + +interface ViteErrorData { + message: string; + stack: string; + file: string; + frame: string; + tip: string; +} + +type ViteErrorCtor = new (opts: ViteErrorData) => T; + +export class ViteError extends BaseError { + file: string; + frame: string; + tip: string; + + static create(this: ViteErrorCtor, opts: ViteErrorData): T { + return new this(opts); + } + + constructor({ message, stack, file, frame, tip }: ViteErrorData) { + super({ message }); + + this.stack = `${this.constructor.name}: ${this.message}\n${stack}`; + this.file = file; + this.frame = frame; + this.tip = tip; + } +} diff --git a/src/runner/browser-env/vite/browser-modules/globals.ts b/src/runner/browser-env/vite/browser-modules/globals.ts new file mode 100644 index 000000000..018d3f807 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/globals.ts @@ -0,0 +1,75 @@ +import { BrowserError } from "./errors/index.js"; + +type ParsedUrlParams = Record<"file" | "pid" | "runUuid" | "cmdUuid", string>; + +const parseUrlParams = (): ParsedUrlParams => { + const urlParams = new URLSearchParams(window.location.search); + const parsedParams = ["file", "pid", "runUuid", "cmdUuid"].reduce( + (acc, key) => ({ + ...acc, + [key]: urlParams.get(key) || "", + }), + { file: "", pid: "", runUuid: "", cmdUuid: "" }, + ); + + if (!parsedParams.file) { + console.error(`Query parameter "file" must be specified in url: ${window.location.href}`); + } + + return parsedParams; +}; + +const proxyHermione = (): void => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const proxyHandler: ProxyHandler = { + get(target, prop) { + return prop in target ? target[prop] : new Proxy(() => {}, this); + }, + apply() { + return new Proxy(() => {}, this); + }, + }; + + window.hermione = new Proxy(window.hermione || {}, proxyHandler); +}; + +const subscribeOnBrowserErrors = (): void => { + addEventListener("error", e => + window.__hermione__.errors.push( + BrowserError.create({ + message: e.message, + stack: e.error.stack, + file: e.filename, + }), + ), + ); +}; + +const mockDialog = + ({ name, value }: { name: string; value: T }) => + (...params: unknown[]): T => { + const formatedParams = params.map(p => JSON.stringify(p)).join(", "); + + console.warn( + `Hermione encountered a \`${name}(${formatedParams})\` call that blocks web page and does not allow the test to continue, so it mocked and return \`${value}\`.`, + ); + + return value; + }; + +const mockBlockingDialogs = (): void => { + window.alert = mockDialog({ name: "alert", value: undefined }); + window.confirm = mockDialog({ name: "confirm", value: false }); + window.prompt = mockDialog({ name: "prompt", value: null }); +}; + +const urlParams = parseUrlParams(); +window.__hermione__ = { + ...urlParams, + pid: Number(urlParams.pid), + errors: [], +}; + +proxyHermione(); +subscribeOnBrowserErrors(); +mockBlockingDialogs(); diff --git a/src/runner/browser-env/vite/browser-modules/index.ts b/src/runner/browser-env/vite/browser-modules/index.ts new file mode 100644 index 000000000..b692c7872 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/index.ts @@ -0,0 +1,4 @@ +import { MochaWrapper } from "./mocha/index.js"; + +const mocha = MochaWrapper.create(); +await mocha.init(); diff --git a/src/runner/browser-env/vite/browser-modules/mocha/events.ts b/src/runner/browser-env/vite/browser-modules/mocha/events.ts new file mode 100644 index 000000000..668df82a4 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/mocha/events.ts @@ -0,0 +1,6 @@ +export const MochaEvents = { + ADD_SUITE: "suite", + ADD_TEST: "test", + ADD_HOOK_BEFORE_EACH: "beforeEach", + ADD_HOOK_AFTER_EACH: "afterEach", +} as const; diff --git a/src/runner/browser-env/vite/browser-modules/mocha/index.ts b/src/runner/browser-env/vite/browser-modules/mocha/index.ts new file mode 100644 index 000000000..dd460e632 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/mocha/index.ts @@ -0,0 +1,85 @@ +import { TestParser } from "./parser.js"; +import { ViteBrowserCommunicator } from "../communicator.js"; +import { findErrorsOnPageLoad, findErrorsOnRunRunnable, BrowserError } from "../errors/index.js"; +import { BrowserEventNames, WorkerEventNames, type WorkerMessage } from "../types.js"; + +export class MochaWrapper { + #communicator: ViteBrowserCommunicator = ViteBrowserCommunicator.create(); + #runnables: Map = new Map(); + #parser: TestParser; + + static create(this: new () => T): T { + return new this(); + } + + constructor() { + this.#validate(); + + this.#parser = TestParser.create(); + } + + async init(): Promise { + mocha.setup("bdd"); + + this.#subscribeOnWorkerMessages(); + + try { + await this.#parser.loadFile(window.__hermione__.file, runnable => { + this.#runnables.set(runnable.fullTitle(), runnable); + }); + } catch (err) { + window.__hermione__.errors.push(BrowserError.create(err as Error)); + } + + this.#communicator.sendMessage(BrowserEventNames.init, { + errors: findErrorsOnPageLoad(), + }); + } + + #validate(): never | void { + if (!window.Mocha) { + const error = BrowserError.create({ + message: "Can't find Mocha inside hermione dependencies. Try to reinstall hermione.", + }); + this.#communicator.sendMessage(BrowserEventNames.init, { errors: [error] }); + + throw error; + } + } + + #subscribeOnWorkerMessages(): void { + this.#communicator.subscribeOnMessage(WorkerEventNames.runRunnable, async (msg: WorkerMessage) => { + const runnableToRun = this.#runnables.get(msg.fullTitle); + + if (!runnableToRun) { + const error = BrowserError.create({ + message: `Can't find a runnable with the title "${msg.fullTitle}" to run`, + }); + + this.#communicator.sendMessage(BrowserEventNames.runnableResult, { + pid: msg.pid, + runUuid: msg.runUuid, + cmdUuid: msg.cmdUuid, + errors: [error], + }); + + throw error; + } + + let error: Error | undefined = undefined; + + try { + await (runnableToRun.fn as Mocha.AsyncFunc)?.call({} as unknown as Mocha.Context); + } catch (err) { + error = err as Error; + } + + this.#communicator.sendMessage(BrowserEventNames.runnableResult, { + pid: msg.pid, + runUuid: msg.runUuid, + cmdUuid: msg.cmdUuid, + errors: findErrorsOnRunRunnable(error), + }); + }); + } +} diff --git a/src/runner/browser-env/vite/browser-modules/mocha/parser.ts b/src/runner/browser-env/vite/browser-modules/mocha/parser.ts new file mode 100644 index 000000000..998ea80f6 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/mocha/parser.ts @@ -0,0 +1,33 @@ +import { ValueOf } from "type-fest"; +import { MochaEvents } from "./events.js"; + +type RunnableHandler = (runnable: Mocha.Runnable) => void; + +export class TestParser { + #rootSuite: Mocha.Suite = mocha.suite; + + static create(this: new () => T): T { + return new this(); + } + + async loadFile(file: string, runnableHandler: RunnableHandler): Promise { + this.#subscribeOnRunnableEvents(runnableHandler); + + await import(file); + } + + #subscribeOnRunnableEvents(runnableHandler: RunnableHandler): void { + [MochaEvents.ADD_TEST, MochaEvents.ADD_HOOK_BEFORE_EACH, MochaEvents.ADD_HOOK_AFTER_EACH].forEach(event => { + this.#addRecursiveHandler(this.#rootSuite, event, runnableHandler); + }); + } + + #addRecursiveHandler( + suite: Mocha.Suite, + event: ValueOf, + cb: (runnable: Mocha.Runnable) => void, + ): void { + suite.on(MochaEvents.ADD_SUITE, subSuite => this.#addRecursiveHandler(subSuite as Mocha.Suite, event, cb)); + suite.on(event, cb); + } +} diff --git a/src/runner/browser-env/vite/browser-modules/package.json b/src/runner/browser-env/vite/browser-modules/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/src/runner/browser-env/vite/browser-modules/tsconfig.json b/src/runner/browser-env/vite/browser-modules/tsconfig.json new file mode 100644 index 000000000..5ad64e4ba --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../../tsconfig.common.json", + "include": ["."], + "compilerOptions": { + "outDir": "../../../../../build/src/runner/browser-env/vite/browser-modules", + "composite": true, + + "module": "NodeNext", + "target": "es2022", + "moduleResolution": "NodeNext", + + "types": ["mocha", "vite/client"] + } +} diff --git a/src/runner/browser-env/vite/browser-modules/types.ts b/src/runner/browser-env/vite/browser-modules/types.ts new file mode 100644 index 000000000..547481658 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/types.ts @@ -0,0 +1,51 @@ +import { HERMIONE_BROWSER_EVENT_SUFFIX, HERMIONE_WORKER_EVENT_SUFFIX } from "./constants.js"; +import { BrowserError, type AvailableError } from "./errors/index.js"; + +declare global { + interface Window { + Mocha: Mocha; + __hermione__: { + pid: number; + file: string; + runUuid: string; + cmdUuid: string; + errors: BrowserError[]; + }; + hermione: typeof Proxy; + } +} + +export enum BrowserEventNames { + init = `${HERMIONE_BROWSER_EVENT_SUFFIX}:init`, + runnableResult = `${HERMIONE_BROWSER_EVENT_SUFFIX}:runnableResult`, +} + +export interface BrowserMessage { + pid: number; + runUuid: string; + cmdUuid: string; + errors: AvailableError[]; +} + +// TODO: use from nodejs code when migrate to esm +export enum WorkerEventNames { + init = `${HERMIONE_WORKER_EVENT_SUFFIX}:init`, + runRunnable = `${HERMIONE_WORKER_EVENT_SUFFIX}:runRunnable`, +} + +export interface WorkerMessage { + pid: number; + runUuid: string; + cmdUuid: string; + fullTitle: string; +} + +export interface WorkerRunRunnablePayload { + type: "custom"; + event: WorkerEventNames.runRunnable; + data: { + pid: number; + uuid: string; + fullTitle: string; + }; +} diff --git a/src/runner/browser-env/vite/browser-modules/utils/index.ts b/src/runner/browser-env/vite/browser-modules/utils/index.ts new file mode 100644 index 000000000..fb0867b37 --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/utils/index.ts @@ -0,0 +1 @@ +export * from "./selector.js"; diff --git a/src/runner/browser-env/vite/browser-modules/utils/selector.ts b/src/runner/browser-env/vite/browser-modules/utils/selector.ts new file mode 100644 index 000000000..a622d861c --- /dev/null +++ b/src/runner/browser-env/vite/browser-modules/utils/selector.ts @@ -0,0 +1,3 @@ +export const getSelectorTextFromShadowRoot = (selector: string, shadowRoot: ShadowRoot): string => { + return (shadowRoot.querySelector(selector) as HTMLElement).innerText; +}; diff --git a/src/runner/browser-env/vite/communicator.ts b/src/runner/browser-env/vite/communicator.ts new file mode 100644 index 000000000..6a39c7ac5 --- /dev/null +++ b/src/runner/browser-env/vite/communicator.ts @@ -0,0 +1,106 @@ +import logger from "../../../utils/logger"; +import { isBrowserMessage, isWorkerMessage } from "./utils"; +import { WorkerEventNames } from "../../../worker/browser-env/types"; +import { BrowserEventNames, type BrowserPayload } from "./types"; +import type { WebSocketServer, WebSocket } from "vite"; +import type { WorkerPayload, WorkerRunRunnablePayload } from "../../../worker/browser-env/types"; + +export class BrowserWorkerCommunicator { + #viteWs: WebSocketServer; + #browserWsByRunUuid: Map; + #workerWsByPid: Map; + + static create( + this: new (viteWs: WebSocketServer) => T, + viteWs: WebSocketServer, + ): T { + return new this(viteWs); + } + + constructor(viteWs: WebSocketServer) { + this.#viteWs = viteWs; + this.#browserWsByRunUuid = new Map(); + this.#workerWsByPid = new Map(); + } + + handleMessages(): void { + this.#viteWs.on("connection", ws => { + ws.on("message", rawMsg => { + const msg = JSON.parse(rawMsg.toString()) as BrowserPayload | WorkerPayload; + + if (isBrowserMessage(msg)) { + this.#handleBrowserMessages(msg, ws); + return; + } + + if (isWorkerMessage(msg)) { + this.#handleWorkerMessages(msg, ws); + return; + } + }); + }); + } + + #handleBrowserMessages(msg: BrowserPayload, ws: WebSocket): void { + if (msg.event === BrowserEventNames.init) { + this.#registerBrowserWsConnection(msg, ws); + return; + } + + if (msg.event === BrowserEventNames.runnableResult) { + this.#sendMsgToWorker(msg); + return; + } + } + + #handleWorkerMessages(msg: WorkerPayload, ws: WebSocket): void { + if (msg.event === WorkerEventNames.init) { + this.#registerWorkerWsConnection(msg.data.pid, ws); + return; + } + + if (msg.event === WorkerEventNames.runRunnable) { + this.#sendMsgToBrowser(msg); + return; + } + } + + #registerBrowserWsConnection(msg: BrowserPayload, ws: WebSocket): void { + if (this.#browserWsByRunUuid.has(msg.data.runUuid)) { + return; + } + + this.#browserWsByRunUuid.set(msg.data.runUuid, ws); + this.#sendMsgToWorker(msg); + } + + #registerWorkerWsConnection(pid: number, ws: WebSocket): void { + if (this.#workerWsByPid.has(pid)) { + return; + } + + this.#workerWsByPid.set(pid, ws); + } + + #sendMsgToWorker(msg: BrowserPayload): void { + const wsConnection = this.#workerWsByPid.get(msg.data.pid); + + if (!wsConnection) { + logger.warn(`Cannot find worker websocket connection by pid: ${msg.data.pid}`); + return; + } + + wsConnection.send(JSON.stringify(msg)); + } + + #sendMsgToBrowser(msg: WorkerRunRunnablePayload): void { + const wsConnection = this.#browserWsByRunUuid.get(msg.data.runUuid); + + if (!wsConnection) { + logger.warn(`Cannot find browser websocket connection by runUuid: ${msg.data.runUuid}`); + return; + } + + wsConnection.send(JSON.stringify(msg)); + } +} diff --git a/src/runner/browser-env/vite/constants.ts b/src/runner/browser-env/vite/constants.ts new file mode 100644 index 000000000..f5af5a658 --- /dev/null +++ b/src/runner/browser-env/vite/constants.ts @@ -0,0 +1,15 @@ +import type { ConfigEnv } from "vite"; + +export const MODULE_PREFIX = "@hermione"; +export const MODULE_NAMES = { + mocha: `${MODULE_PREFIX}/mocha`, + browserRunner: `${MODULE_PREFIX}/browser-runner`, + globals: `${MODULE_PREFIX}/globals`, +}; + +export const VITE_DEFAULT_CONFIG_ENV: ConfigEnv = { + command: "serve", + mode: process.env.NODE_ENV === "production" ? "production" : "development", +}; + +export const HERMIONE_BROWSER_EVENT_SUFFIX = "hermione:browser"; diff --git a/src/runner/browser-env/vite/plugins/generate-index-html.ts b/src/runner/browser-env/vite/plugins/generate-index-html.ts new file mode 100644 index 000000000..17dbaf1ed --- /dev/null +++ b/src/runner/browser-env/vite/plugins/generate-index-html.ts @@ -0,0 +1,73 @@ +import url from "node:url"; +import createDebug from "debug"; +import { MODULE_NAMES } from "../constants"; +import logger from "../../../../utils/logger"; + +import type { Plugin } from "vite"; + +const debug = createDebug("vite:plugin:generateIndexHtml"); + +export const plugin = (): Plugin[] => { + return [ + { + name: "hermione:generateIndexHtml", + enforce: "pre", + configureServer(server) { + return () => { + server.middlewares.use(async (req, res, next) => { + debug(`Received request for: ${req.originalUrl}`); + + if (!req.url?.endsWith("index.html") || !req.originalUrl) { + return next(); + } + + try { + const template = generateTemplate(req.originalUrl); + res.end(await server.transformIndexHtml(`${req.originalUrl}`, template)); + } catch (err) { + const template = generateErrorTemplate(err as Error); + logger.error(`Failed to render template: ${err}`); + res.end(await server.transformIndexHtml(`${req.originalUrl}`, template)); + } + + return next(); + }); + }; + }, + }, + ]; +}; + +function generateTemplate(reqUrl: string): string { + const urlParsed = url.parse(reqUrl); + const urlParamString = new URLSearchParams(urlParsed.query || ""); + + const pid = urlParamString.get("pid"); + const file = urlParamString.get("file"); + const runUuid = urlParamString.get("runUuid"); + const cmdUuid = urlParamString.get("cmdUuid"); + + return ` + + + + Hermione Browser Test + + + + + + +`; +} + +function generateErrorTemplate(error: Error): string { + return ` + + + +
${error.stack}
+ + +`; +} diff --git a/src/runner/browser-env/vite/plugins/resolve-module-paths.ts b/src/runner/browser-env/vite/plugins/resolve-module-paths.ts new file mode 100644 index 000000000..d07a24d34 --- /dev/null +++ b/src/runner/browser-env/vite/plugins/resolve-module-paths.ts @@ -0,0 +1,36 @@ +import url from "node:url"; +import path from "node:path"; + +import { getImportMetaUrl } from "../utils"; +import { MODULE_NAMES } from "../constants"; + +import type { Plugin } from "vite"; +import type { VitePluginOptions } from "../types"; + +export const plugin = (options: VitePluginOptions): Plugin[] => { + const dirname = url.fileURLToPath(new URL(".", getImportMetaUrl(__filename))); + + const browserModulesPath = path.resolve(dirname, "..", "browser-modules"); + const browserRunnerModulePath = path.resolve(browserModulesPath, "index.js"); + const globalsModulePath = path.resolve(browserModulesPath, "globals.js"); + + return [ + { + name: "hermione:resolveModulePaths", + enforce: "pre", + resolveId: (id): string | void => { + if (id.endsWith(MODULE_NAMES.browserRunner)) { + return browserRunnerModulePath; + } + + if (id.endsWith(MODULE_NAMES.globals)) { + return globalsModulePath; + } + + if (id.endsWith(MODULE_NAMES.mocha)) { + return options.modulePaths.mocha; + } + }, + }, + ]; +}; diff --git a/src/runner/browser-env/vite/server.ts b/src/runner/browser-env/vite/server.ts new file mode 100644 index 000000000..c5a0a9124 --- /dev/null +++ b/src/runner/browser-env/vite/server.ts @@ -0,0 +1,108 @@ +import path from "node:path"; +import url from "node:url"; +import { createServer } from "vite"; +import _ from "lodash"; +import getPort from "get-port"; +import chalk from "chalk"; + +import logger from "../../../utils/logger"; +import { plugin as generateIndexHtml } from "./plugins/generate-index-html"; +import { plugin as resolveModulePaths } from "./plugins/resolve-module-paths"; +import { Config } from "../../../config"; +import { VITE_DEFAULT_CONFIG_ENV } from "./constants"; +import { getNodeModulePath } from "./utils"; +import { BrowserWorkerCommunicator } from "./communicator"; + +import type { ViteDevServer, InlineConfig } from "vite"; +import type { BrowserTestRunEnvOptions } from "./types"; + +export class ViteServer { + #hermioneConfig: Config; + #viteConfig: Partial; + #options?: BrowserTestRunEnvOptions; + #server?: ViteDevServer; + + static create(this: new (hermioneConfig: Config) => T, hermioneConfig: Config): T { + return new this(hermioneConfig); + } + + constructor(hermioneConfig: Config) { + this.#hermioneConfig = hermioneConfig; + this.#viteConfig = { + server: { host: "localhost" }, + configFile: false, + logLevel: "silent", + build: { + sourcemap: "inline", + }, + optimizeDeps: { + esbuildOptions: { + logLevel: "silent", + }, + }, + }; + + this.#options = _.isArray(this.#hermioneConfig.system.testRunEnv) + ? this.#hermioneConfig.system.testRunEnv[1] + : undefined; + } + + async start(): Promise { + await this.#applyUserViteConfig(); + await this.#addRequiredVitePlugins(); + + if (!this.#viteConfig.server!.port) { + this.#viteConfig.server!.port = await getPort(); + } + + this.#server = await createServer(this.#viteConfig); + BrowserWorkerCommunicator.create(this.#server.ws).handleMessages(); + + await this.#server.listen(); + + logger.log(chalk.green(`Vite server started on ${this.baseUrl}`)); + } + + async close(): Promise { + await this.#server?.close(); + } + + async #applyUserViteConfig(): Promise { + if (!this.#options?.viteConfig) { + return; + } + + const config = this.#options.viteConfig; + let preparedConfig: InlineConfig; + + if (_.isString(config)) { + preparedConfig = (await import(path.resolve(process.cwd(), config))).default as InlineConfig; + } else if (_.isFunction(config)) { + preparedConfig = await config(VITE_DEFAULT_CONFIG_ENV); + } else { + preparedConfig = config; + } + + this.#viteConfig = _.merge(this.#viteConfig, preparedConfig); + } + + async #addRequiredVitePlugins(): Promise { + const mochaPath = await getNodeModulePath({ + moduleName: "mocha", + parent: path.join("node_modules", "hermione", "node_modules"), + }); + const modulePaths = { + mocha: path.join(url.fileURLToPath(path.dirname(mochaPath)), "mocha.js"), + }; + + this.#viteConfig.plugins = [ + ...(this.#viteConfig.plugins || []), + generateIndexHtml(), + resolveModulePaths({ ...this.#options, modulePaths }), + ]; + } + + get baseUrl(): string | undefined { + return this.#server?.resolvedUrls!.local[0]; + } +} diff --git a/src/runner/browser-env/vite/types.ts b/src/runner/browser-env/vite/types.ts new file mode 100644 index 000000000..02a6269d0 --- /dev/null +++ b/src/runner/browser-env/vite/types.ts @@ -0,0 +1,31 @@ +import { HERMIONE_BROWSER_EVENT_SUFFIX } from "./constants"; +import type { InlineConfig, ConfigEnv } from "vite"; +import type { BrowserMessage } from "./browser-modules/types"; + +export interface BrowserTestRunEnvOptions { + viteConfig?: string | InlineConfig | ((env: ConfigEnv) => InlineConfig | Promise); +} + +export interface VitePluginOptions extends BrowserTestRunEnvOptions { + modulePaths: { + mocha: string; + }; +} + +// TODO: use from "./browser-modules/types" after migrate to esm +export enum BrowserEventNames { + init = `${HERMIONE_BROWSER_EVENT_SUFFIX}:init`, + runnableResult = `${HERMIONE_BROWSER_EVENT_SUFFIX}:runnableResult`, +} + +export interface BrowserInitPayload { + event: BrowserEventNames.init; + data: BrowserMessage; +} + +export interface BrowserRunRunnablePayload { + event: BrowserEventNames.runnableResult; + data: BrowserMessage; +} + +export type BrowserPayload = BrowserInitPayload | BrowserRunRunnablePayload; diff --git a/src/runner/browser-env/vite/utils.ts b/src/runner/browser-env/vite/utils.ts new file mode 100644 index 000000000..54116f3e4 --- /dev/null +++ b/src/runner/browser-env/vite/utils.ts @@ -0,0 +1,36 @@ +import url from "node:url"; +import path from "node:path"; +import { HERMIONE_BROWSER_EVENT_SUFFIX } from "./constants"; +import { HERMIONE_WORKER_EVENT_SUFFIX } from "../../../worker/browser-env/constants"; +import type { BrowserPayload } from "./types"; +import type { WorkerPayload } from "../../../worker/browser-env/types"; + +// TODO: use import.meta.url after move to esm +export const getImportMetaUrl = (path: string): string => { + return url.pathToFileURL(path).toString(); +}; + +export const getNodeModulePath = async ({ + moduleName, + rootDir = process.cwd(), + parent = "node_modules", +}: { + moduleName: string; + rootDir?: string; + parent?: string; +}): Promise => { + const rootFileUrl = url.pathToFileURL(rootDir).href; + + // TODO: use import at the beginning of the file after migrate to esm + const { resolve } = await eval(`import("import-meta-resolve")`); + + return resolve(moduleName, path.join(rootFileUrl, parent)); +}; + +export const isBrowserMessage = (msg: BrowserPayload | WorkerPayload): msg is BrowserPayload => { + return msg.event.startsWith(HERMIONE_BROWSER_EVENT_SUFFIX); +}; + +export const isWorkerMessage = (msg: BrowserPayload | WorkerPayload): msg is WorkerPayload => { + return msg.event.startsWith(HERMIONE_WORKER_EVENT_SUFFIX); +}; diff --git a/src/types/index.ts b/src/types/index.ts index cb93dbdfd..c651c40ee 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,6 @@ import { Events } from "../events"; -import { MainRunner } from "../runner"; +import { MainRunner as NodejsEnvRunner } from "../runner"; +import { MainRunner as BrowserEnvRunner } from "../runner/browser-env/index"; import { TestCollection } from "../test-collection"; import type { Test } from "../test-reader/test-object/test"; import type { Suite } from "../test-reader/test-object/suite"; @@ -11,6 +12,7 @@ import { SkipController } from "../test-reader/controllers/skip-controller"; import { BrowserVersionController } from "../test-reader/controllers/browser-version-controller"; import { WorkerProcess } from "../utils/worker-process"; import { BaseHermione } from "../base-hermione"; +import Callstack from "../browser/history/callstack"; import { CoordBounds, LooksSameOptions } from "looks-same"; export { Suite as RunnerSuite, Test as RunnerTest, Hook as RunnerHook } from "mocha"; @@ -151,6 +153,10 @@ export interface BeforeFileReadData extends AfterFileReadData { testParser: TestParserAPI; } +export interface BrowserHistory { + runGroup: (callstack: Callstack, name: string, fn: () => unknown | Promise) => Promise; +} + export type SyncSessionEventCallback = ( browser: WebdriverIO.Browser, browserInfo: { browserId: string; browserVersion: string }, @@ -158,7 +164,7 @@ export type SyncSessionEventCallback = ( export type MasterEventHandler = { (event: Events["INIT"], callback: () => Promise | void): T; - (event: Events["RUNNER_START"], callback: (runner: MainRunner) => Promise | void): T; + (event: Events["RUNNER_START"], callback: (runner: NodejsEnvRunner | BrowserEnvRunner) => Promise | void): T; (event: Events["RUNNER_END"], callback: (result: StatsResult) => Promise | void): T; (event: Events["SESSION_START"], callback: AsyncSessionEventCallback): T; (event: Events["SESSION_END"], callback: AsyncSessionEventCallback): T; diff --git a/src/utils/config.ts b/src/utils/config.ts new file mode 100644 index 000000000..a66873698 --- /dev/null +++ b/src/utils/config.ts @@ -0,0 +1,6 @@ +import { NODEJS_TEST_RUN_ENV } from "../constants/config"; +import type { CommonConfig } from "../config/types"; + +export const isRunInNodeJsEnv = (config: CommonConfig): boolean => { + return config.system.testRunEnv === NODEJS_TEST_RUN_ENV; +}; diff --git a/src/worker/browser-env/communicator.ts b/src/worker/browser-env/communicator.ts new file mode 100644 index 000000000..5ffcbe3df --- /dev/null +++ b/src/worker/browser-env/communicator.ts @@ -0,0 +1,109 @@ +import WebSocket from "ws"; +import URI from "urijs"; +import { WorkerEventNames } from "./types"; +import logger from "../../utils/logger"; + +import type { SetOptional } from "type-fest"; +import type { Config } from "../../config"; +import type { WorkerPayload } from "./types"; + +import type { BrowserPayload } from "../../runner/browser-env/vite/types"; + +interface WaitMessageOpts { + cmdUuid: string; + timeout?: number; + interval?: number; +} + +const WS_CONNECTION_TIMEOUT = 5000; +const WAIT_MESSAGE_TIMEOUT = 5000; +const WAIT_MESSAGE_INTERVAL = 100; + +export class ViteWorkerCommunicator { + readonly #config: Config; + #workerWs: WebSocket; + #recievedMsgs: Map; + + static create(this: new (config: Config) => T, config: Config): T { + return new this(config); + } + + constructor(config: Config) { + this.#config = config; + this.#recievedMsgs = new Map(); + + const viteWsUrl = new URI(this.#config.baseUrl).protocol("ws").toString(); + this.#workerWs = new WebSocket(viteWsUrl, "vite-hmr"); + + this.#handleMessages(); + } + + #handleMessages(): void { + const timerId = setTimeout(() => { + throw new Error(`Could't open ws connection to Vite for ${WS_CONNECTION_TIMEOUT} seconds`); + }, WS_CONNECTION_TIMEOUT); + + this.#workerWs.on("error", err => { + throw err; + }); + + this.#workerWs.on("close", (code, reason) => { + throw new Error(`Websocket connection to Vite is closed with code: ${code} and reason: ${reason}`); + }); + + this.#workerWs.on("open", () => { + clearTimeout(timerId); + + this.sendMessage({ event: WorkerEventNames.init, data: { pid: process.pid } }); + }); + + this.#workerWs.on("message", buff => { + let msg: BrowserPayload | undefined; + + try { + msg = JSON.parse(buff.toString()) as BrowserPayload; + } catch (err) { + logger.warn(`Cannot parse message: ${buff.toString()}`); + return; + } + + if (msg?.data?.cmdUuid) { + this.#recievedMsgs.set(msg.data.cmdUuid, msg); + } + }); + } + + sendMessage(payload: SetOptional): void { + this.#workerWs.send(JSON.stringify({ type: "custom", ...payload })); + } + + async waitMessage({ + cmdUuid, + timeout = WAIT_MESSAGE_TIMEOUT, + interval = WAIT_MESSAGE_INTERVAL, + }: WaitMessageOpts): Promise { + return new Promise((resolve, reject) => { + const timerId = + timeout > 0 + ? setTimeout(() => { + reject(new Error(`Didn't wait for message from browser in ${timeout} seconds`)); + }, timeout) + : 0; + + const intervalId = setInterval(() => { + const msg = this.#recievedMsgs.get(cmdUuid); + + if (!msg) { + return; + } + + clearTimeout(timerId); + clearInterval(intervalId); + + this.#recievedMsgs.delete(cmdUuid); + + resolve(msg); + }, interval); + }); + } +} diff --git a/src/worker/browser-env/constants.ts b/src/worker/browser-env/constants.ts new file mode 100644 index 000000000..eb0d46930 --- /dev/null +++ b/src/worker/browser-env/constants.ts @@ -0,0 +1 @@ +export const HERMIONE_WORKER_EVENT_SUFFIX = "hermione:worker"; diff --git a/src/worker/browser-env/runner/test-runner/execution-thread.ts b/src/worker/browser-env/runner/test-runner/execution-thread.ts new file mode 100644 index 000000000..44a03634f --- /dev/null +++ b/src/worker/browser-env/runner/test-runner/execution-thread.ts @@ -0,0 +1,49 @@ +import crypto from "node:crypto"; +import _ from "lodash"; +import NodejsEnvExecutionThread from "../../../runner/test-runner/execution-thread"; +import RuntimeConfig from "../../../../config/runtime-config"; + +import { WorkerEventNames } from "../../types"; + +import type { ViteWorkerCommunicator } from "../../communicator"; +import type { BrowserEnvRuntimeConfig } from "../../types"; +import type { ExecutionThreadCtorOpts } from "../../../runner/test-runner/types"; +import type { Test } from "../../../../test-reader/test-object/test"; +import type { Hook } from "../../../../test-reader/test-object/hook"; + +export const wrapExecutionThread = (runUuid: string): typeof NodejsEnvExecutionThread => { + return class ExecutionThread extends NodejsEnvExecutionThread { + #communicator: ViteWorkerCommunicator; + #runUuid: string = runUuid; + + constructor(opts: ExecutionThreadCtorOpts & { runUuid: string }) { + super(opts); + + this.#communicator = (RuntimeConfig.getInstance() as BrowserEnvRuntimeConfig).viteWorkerCommunicator; + } + + async _call(runnable: Test | Hook): Promise { + const cmdUuid = crypto.randomUUID(); + + runnable.fn = async (): Promise => { + this.#communicator.sendMessage({ + event: WorkerEventNames.runRunnable, + data: { + pid: process.pid, + runUuid: this.#runUuid, + cmdUuid: cmdUuid, + fullTitle: runnable.fullTitle(), + }, + }); + + const runRunnableResult = await this.#communicator.waitMessage({ cmdUuid, timeout: runnable.timeout }); + + if (!_.isEmpty(runRunnableResult.data.errors)) { + throw runRunnableResult.data.errors[0]; + } + }; + + return super._call(runnable); + } + }; +}; diff --git a/src/worker/browser-env/runner/test-runner/index.ts b/src/worker/browser-env/runner/test-runner/index.ts new file mode 100644 index 000000000..d17673224 --- /dev/null +++ b/src/worker/browser-env/runner/test-runner/index.ts @@ -0,0 +1,59 @@ +import crypto from "node:crypto"; +import URI from "urijs"; +import _ from "lodash"; +import NodejsEnvTestRunner from "../../../runner/test-runner"; +import RuntimeConfig from "../../../../config/runtime-config"; +import { wrapExecutionThread } from "./execution-thread"; + +import type { ViteWorkerCommunicator } from "../../communicator"; +import type { WorkerTestRunnerRunOpts, WorkerTestRunnerCtorOpts } from "../../../runner/test-runner/types"; +import type { WorkerRunTestResult } from "../../../hermione"; +import type { BrowserHistory } from "../../../../types"; +import type { Browser } from "../../../../browser/types"; +import type { BrowserEnvRuntimeConfig } from "../../types"; + +export class TestRunner extends NodejsEnvTestRunner { + #communicator: ViteWorkerCommunicator; + #runUuid: string = crypto.randomUUID(); + + constructor(opts: WorkerTestRunnerCtorOpts) { + super(opts); + + this.#communicator = (RuntimeConfig.getInstance() as BrowserEnvRuntimeConfig).viteWorkerCommunicator; + } + + async run(opts: WorkerTestRunnerRunOpts): Promise { + return super.run({ ...opts, ExecutionThreadCls: wrapExecutionThread(this.#runUuid) }); + } + + _getPreparePageActions(browser: Browser, history: BrowserHistory): (() => Promise)[] { + return [ + async (): Promise => { + await history.runGroup(browser.callstackHistory, "openVite", async () => { + const { publicAPI: session } = browser; + const cmdUuid = crypto.randomUUID(); + + const uri = new URI(this._config.baseUrl) + .query({ + pid: process.pid, + file: this._file, + runUuid: this.#runUuid, + cmdUuid, + }) + .toString(); + await session.url(uri); + + const msg = await this.#communicator.waitMessage({ + cmdUuid, + timeout: this._config.urlHttpTimeout || this._config.httpTimeout, + }); + + if (!_.isEmpty(msg.data.errors)) { + throw msg.data.errors[0]; + } + }); + }, + ...super._getPreparePageActions(browser, history), + ]; + } +} diff --git a/src/worker/browser-env/types.ts b/src/worker/browser-env/types.ts new file mode 100644 index 000000000..6392b3564 --- /dev/null +++ b/src/worker/browser-env/types.ts @@ -0,0 +1,32 @@ +import { HERMIONE_WORKER_EVENT_SUFFIX } from "./constants"; +import type { CustomPayload } from "vite"; +import type { ViteWorkerCommunicator } from "./communicator"; +import type { RuntimeConfig } from "../../config/types"; + +export enum WorkerEventNames { + init = `${HERMIONE_WORKER_EVENT_SUFFIX}:init`, + runRunnable = `${HERMIONE_WORKER_EVENT_SUFFIX}:runRunnable`, +} + +export interface WorkerInitPayload extends CustomPayload { + event: WorkerEventNames.init; + data: { + pid: number; + }; +} + +export interface WorkerRunRunnablePayload extends CustomPayload { + event: WorkerEventNames.runRunnable; + data: { + pid: number; + runUuid: string; + cmdUuid: string; + fullTitle: string; + }; +} + +export type WorkerPayload = WorkerInitPayload | WorkerRunRunnablePayload; + +export interface BrowserEnvRuntimeConfig extends RuntimeConfig { + viteWorkerCommunicator: ViteWorkerCommunicator; +} diff --git a/src/worker/hermione-facade.js b/src/worker/hermione-facade.js index 2a48a6a30..c761d4be1 100644 --- a/src/worker/hermione-facade.js +++ b/src/worker/hermione-facade.js @@ -7,6 +7,8 @@ const debug = require("debug")(`hermione:worker:${process.pid}`); const ipc = require("../utils/ipc"); const { MASTER_INIT, MASTER_SYNC_CONFIG, WORKER_INIT, WORKER_SYNC_CONFIG } = require("../constants/process-messages"); const { requireModule } = require("../utils/module"); +const { isRunInNodeJsEnv } = require("../utils/config"); +const { ViteWorkerCommunicator } = require("./browser-env/communicator"); module.exports = class HermioneFacade { static create() { @@ -79,6 +81,11 @@ module.exports = class HermioneFacade { delete config.system.mochaOpts.grep; // grep affects only master this._hermione.config.mergeWith(config); + if (!isRunInNodeJsEnv(this._hermione.config)) { + const communicator = ViteWorkerCommunicator.create(this._hermione.config); + RuntimeConfig.getInstance().extend({ viteWorkerCommunicator: communicator }); + } + debug("config synced"); resolve(); }); diff --git a/src/worker/runner/index.js b/src/worker/runner/index.js index 39af6525a..364d5a53a 100644 --- a/src/worker/runner/index.js +++ b/src/worker/runner/index.js @@ -5,8 +5,10 @@ const { passthroughEvent } = require("../../events/utils"); const { WorkerEvents } = require("../../events"); const BrowserPool = require("./browser-pool"); const BrowserAgent = require("./browser-agent"); -const TestRunner = require("./test-runner"); +const NodejsEnvTestRunner = require("./test-runner"); +const { TestRunner: BrowserEnvTestRunner } = require("../browser-env/runner/test-runner"); const CachingTestParser = require("./caching-test-parser"); +const { isRunInNodeJsEnv } = require("../../utils/config"); module.exports = class Runner extends AsyncEmitter { static create(config) { @@ -31,7 +33,12 @@ module.exports = class Runner extends AsyncEmitter { const tests = await this._testParser.parse({ file, browserId }); const test = tests.find(t => t.fullTitle() === fullTitle); const browserAgent = BrowserAgent.create({ id: browserId, version: browserVersion, pool: this._browserPool }); - const runner = TestRunner.create(test, this._config.forBrowser(browserId), browserAgent); + const runner = (isRunInNodeJsEnv(this._config) ? NodejsEnvTestRunner : BrowserEnvTestRunner).create({ + test, + file, + config: this._config.forBrowser(browserId), + browserAgent, + }); return runner.run({ sessionId, sessionCaps, sessionOpts, state }); } diff --git a/src/worker/runner/test-runner/index.js b/src/worker/runner/test-runner/index.js index 4446b11f5..e0cb0b55a 100644 --- a/src/worker/runner/test-runner/index.js +++ b/src/worker/runner/test-runner/index.js @@ -2,6 +2,7 @@ "use strict"; const _ = require("lodash"); +const { Runner } = require("./runner"); const HookRunner = require("./hook-runner"); const ExecutionThread = require("./execution-thread"); const OneTimeScreenshooter = require("./one-time-screenshooter"); @@ -9,20 +10,23 @@ const { AssertViewError } = require("../../../browser/commands/assert-view/error const history = require("../../../browser/history"); const { SAVE_HISTORY_MODE } = require("../../../constants/config"); -module.exports = class TestRunner { +module.exports = class TestRunner extends Runner { static create(...args) { return new this(...args); } - constructor(test, config, browserAgent) { + constructor({ test, file, config, browserAgent }) { + super(); + this._test = test.clone(); this._test.hermioneCtx = _.cloneDeep(test.hermioneCtx); + this._file = file; this._config = config; this._browserAgent = browserAgent; } - async run({ sessionId, sessionCaps, sessionOpts, state }) { + async run({ sessionId, sessionCaps, sessionOpts, state, ExecutionThreadCls = ExecutionThread }) { const test = this._test; const hermioneCtx = test.hermioneCtx || {}; @@ -35,23 +39,20 @@ module.exports = class TestRunner { } const screenshooter = OneTimeScreenshooter.create(this._config, browser); - const executionThread = ExecutionThread.create({ test, browser, hermioneCtx, screenshooter }); + const executionThread = ExecutionThreadCls.create({ test, browser, hermioneCtx, screenshooter }); const hookRunner = HookRunner.create(test, executionThread); const { callstackHistory } = browser; let error; try { - const { resetCursor } = browser.config; - const shouldRunBeforeEach = resetCursor || hookRunner.hasBeforeEachHooks(); + const preparePageActions = this._getPreparePageActions(browser, history); + const shouldRunBeforeEach = preparePageActions.length || hookRunner.hasBeforeEachHooks(); if (shouldRunBeforeEach) { await history.runGroup(callstackHistory, "beforeEach", async () => { - if (resetCursor) { - // TODO: make it on browser.init when "actions" method will be implemented in all webdrivers - await history.runGroup(callstackHistory, "resetCursor", () => - this._resetCursorPosition(browser), - ); + for (const action of preparePageActions) { + await action(); } await hookRunner.runBeforeEachHooks(); @@ -117,6 +118,19 @@ module.exports = class TestRunner { return results; } + _getPreparePageActions(browser, history) { + if (!browser.config.resetCursor) { + return []; + } + + const fn = async () => { + // TODO: make it on browser.init when "actions" method will be implemented in all webdrivers + await history.runGroup(browser.callstackHistory, "resetCursor", () => this._resetCursorPosition(browser)); + }; + + return [fn]; + } + async _resetCursorPosition({ publicAPI: session }) { const body = await session.$("body"); if (!body) { diff --git a/src/worker/runner/test-runner/runner.ts b/src/worker/runner/test-runner/runner.ts new file mode 100644 index 000000000..cca7ab116 --- /dev/null +++ b/src/worker/runner/test-runner/runner.ts @@ -0,0 +1,9 @@ +import { Constructor } from "type-fest"; + +export abstract class Runner { + static create(this: Constructor, ...args: unknown[]): T { + return new this(...args); + } + + abstract run(...args: unknown[]): Promise; +} diff --git a/src/worker/runner/test-runner/types.ts b/src/worker/runner/test-runner/types.ts new file mode 100644 index 000000000..65bc4381f --- /dev/null +++ b/src/worker/runner/test-runner/types.ts @@ -0,0 +1,27 @@ +import ExecutionThread from "./execution-thread"; + +import type { WorkerRunTestOpts, WorkerRunTestHermioneCtx } from "../../hermione"; +import type { Test } from "../../../test-reader/test-object/test"; +import type { BrowserConfig } from "../../../config/browser-config"; +import type BrowserAgent from "../browser-agent"; +import type { Browser } from "../../../browser/types"; +import type OneTimeScreenshooter from "./one-time-screenshooter"; + +export interface WorkerTestRunnerRunOpts + extends Pick { + ExecutionThreadCls: typeof ExecutionThread; +} + +export interface WorkerTestRunnerCtorOpts { + test: Test; + file: string; + config: BrowserConfig; + browserAgent: BrowserAgent; +} + +export interface ExecutionThreadCtorOpts { + test: Test; + browser: Browser; + hermioneCtx: WorkerRunTestHermioneCtx; + screenshooter: OneTimeScreenshooter; +} diff --git a/test/fixtures/vite.conf.ts b/test/fixtures/vite.conf.ts new file mode 100644 index 000000000..2cb8e192f --- /dev/null +++ b/test/fixtures/vite.conf.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite"; + +module.exports = defineConfig({ + server: { + host: "0.0.0.0", + port: 4000, + }, +}); diff --git a/test/src/config/options.js b/test/src/config/options.js index db8a4b3ea..d26164a2a 100644 --- a/test/src/config/options.js +++ b/test/src/config/options.js @@ -1,10 +1,11 @@ "use strict"; const _ = require("lodash"); +const { MissingOptionError } = require("gemini-configparser"); const { Config } = require("src/config"); const defaults = require("src/config/defaults"); const parser = require("src/config/options"); -const { MissingOptionError } = require("gemini-configparser"); +const { NODEJS_TEST_RUN_ENV, BROWSER_TEST_RUN_ENV } = require("src/constants/config"); describe("config options", () => { const sandbox = sinon.createSandbox(); @@ -346,6 +347,108 @@ describe("config options", () => { assert.deepEqual(config.system.fileExtensions, fileExtensions); }); }); + + describe("testRunEnv", () => { + it("should set default test run environment", () => { + const config = createConfig(); + + assert.deepEqual(config.system.testRunEnv, defaults.testRunEnv); + }); + + describe('should throw error if "testRunEnv" option', () => { + it("is not string or array", () => { + const value = 123; + const readConfig = _.set({}, "system.testRunEnv", value); + Config.read.returns(readConfig); + + assert.throws( + () => createConfig(), + Error, + `"testRunEnv" must be an array or string but got ${JSON.stringify(value)}`, + ); + }); + + it(`is string but not "${NODEJS_TEST_RUN_ENV}" or "${BROWSER_TEST_RUN_ENV}"`, () => { + const readConfig = _.set({}, "system.testRunEnv", "foo"); + Config.read.returns(readConfig); + + assert.throws( + () => createConfig(), + Error, + `"testRunEnv" specified as string must be "${NODEJS_TEST_RUN_ENV}" or "${BROWSER_TEST_RUN_ENV}" but got "foo"`, + ); + }); + + it(`is array with "${NODEJS_TEST_RUN_ENV}" value`, () => { + const value = [NODEJS_TEST_RUN_ENV]; + const readConfig = _.set({}, "system.testRunEnv", value); + Config.read.returns(readConfig); + + assert.throws( + () => createConfig(), + Error, + `"testRunEnv" with "${NODEJS_TEST_RUN_ENV}" value must be specified as string but got ${JSON.stringify( + value, + )}`, + ); + }); + + it(`is array with "${BROWSER_TEST_RUN_ENV}" but without options as second element`, () => { + const value = [BROWSER_TEST_RUN_ENV]; + const readConfig = _.set({}, "system.testRunEnv", value); + Config.read.returns(readConfig); + + assert.throws( + () => createConfig(), + Error, + `"testRunEnv" specified as array must also contain options as second argument but got ${JSON.stringify( + value, + )}`, + ); + }); + + it(`is array without "${BROWSER_TEST_RUN_ENV}" as first element`, () => { + const value = ["foo"]; + const readConfig = _.set({}, "system.testRunEnv", value); + Config.read.returns(readConfig); + + assert.throws( + () => createConfig(), + Error, + `"testRunEnv" specified as array must be in format ["${BROWSER_TEST_RUN_ENV}", ] but got ${JSON.stringify( + value, + )}`, + ); + }); + }); + + it(`should set "testRunEnv" option with ${NODEJS_TEST_RUN_ENV}`, () => { + const readConfig = _.set({}, "system.testRunEnv", NODEJS_TEST_RUN_ENV); + Config.read.returns(readConfig); + + const config = createConfig(); + + assert.deepEqual(config.system.testRunEnv, NODEJS_TEST_RUN_ENV); + }); + + it(`should set "testRunEnv" option with ${BROWSER_TEST_RUN_ENV}`, () => { + const readConfig = _.set({}, "system.testRunEnv", BROWSER_TEST_RUN_ENV); + Config.read.returns(readConfig); + + const config = createConfig(); + + assert.deepEqual(config.system.testRunEnv, BROWSER_TEST_RUN_ENV); + }); + + it(`should set "testRunEnv" option with ${BROWSER_TEST_RUN_ENV} and options`, () => { + const readConfig = _.set({}, "system.testRunEnv", [BROWSER_TEST_RUN_ENV, {}]); + Config.read.returns(readConfig); + + const config = createConfig(); + + assert.deepEqual(config.system.testRunEnv, [BROWSER_TEST_RUN_ENV, {}]); + }); + }); }); describe("prepareEnvironment", () => { diff --git a/test/src/hermione.js b/test/src/hermione.js index 12fa5ef6f..673d7c737 100644 --- a/test/src/hermione.js +++ b/test/src/hermione.js @@ -16,7 +16,8 @@ const TestReader = require("src/test-reader"); const { TestCollection } = require("src/test-collection"); const { MasterEvents: RunnerEvents, CommonSyncEvents, MasterAsyncEvents, MasterSyncEvents } = require("src/events"); const signalHandler = require("src/signal-handler"); -const { MainRunner: Runner } = require("src/runner"); +const { MainRunner: NodejsEnvRunner } = require("src/runner"); +const { MainRunner: BrowserEnvRunner } = require("src/runner/browser-env"); const logger = require("src/utils/logger"); const { makeConfigStub } = require("../utils"); @@ -29,17 +30,19 @@ describe("hermione", () => { return Hermione.create(); }; - const mkRunnerStub_ = runFn => { + const mkRunnerStubHelper_ = (RunnerCls, runFn) => { const runner = new AsyncEmitter(); - runner.run = sandbox.stub(Runner.prototype, "run").callsFake(runFn && runFn.bind(null, runner)); - runner.addTestToRun = sandbox.stub(Runner.prototype, "addTestToRun"); - runner.init = sandbox.stub(Runner.prototype, "init").named("RunnerInit"); + runner.run = sandbox.stub(RunnerCls.prototype, "run").callsFake(runFn && runFn.bind(null, runner)); + runner.addTestToRun = sandbox.stub(RunnerCls.prototype, "addTestToRun"); + runner.init = sandbox.stub(RunnerCls.prototype, "init").named("RunnerInit"); - sandbox.stub(Runner, "create").returns(runner); + sandbox.stub(RunnerCls, "create").returns(runner); return runner; }; + const mkNodejsEnvRunner_ = runFn => mkRunnerStubHelper_(NodejsEnvRunner, runFn); + beforeEach(() => { sandbox.stub(logger, "warn"); sandbox.stub(Config, "create").returns(makeConfigStub()); @@ -59,7 +62,7 @@ describe("hermione", () => { describe("constructor", () => { beforeEach(() => { - sandbox.stub(Runner, "create").returns(new EventEmitter()); + sandbox.stub(NodejsEnvRunner, "create").returns(new EventEmitter()); }); describe("logLevel", () => { @@ -136,40 +139,47 @@ describe("hermione", () => { sandbox.stub(Hermione.prototype, "halt"); }); - it("should create runner", () => { - mkRunnerStub_(); + [ + { name: "nodejs", mkRunner_: mkNodejsEnvRunner_, RunnerCls: NodejsEnvRunner }, + { name: "browser", mkRunner_: mkNodejsEnvRunner_, RunnerCls: BrowserEnvRunner }, + ].forEach(({ name, mkRunner_, RunnerCls }) => { + describe(`${name} environment runner`, () => { + it("should create runner", () => { + mkRunner_(); - return runHermione().then(() => assert.calledOnce(Runner.create)); - }); + return runHermione().then(() => assert.calledOnce(RunnerCls.create)); + }); - it("should create runner with config", () => { - mkRunnerStub_(); + it("should create runner with config", () => { + mkRunner_(); - const config = makeConfigStub(); - Config.create.returns(config); + const config = makeConfigStub(); + Config.create.returns(config); - return mkHermione_(config).run(() => assert.calledWith(Runner.create, config)); - }); + return mkHermione_(config).run(() => assert.calledWith(RunnerCls.create, config)); + }); - it("should create runner with interceptors", async () => { - mkRunnerStub_(); + it("should create runner with interceptors", async () => { + mkRunner_(); - const hermione = mkHermione_(); - const fooHandler = () => {}; - const barHandler = () => {}; + const hermione = mkHermione_(); + const fooHandler = () => {}; + const barHandler = () => {}; - hermione.intercept("foo", fooHandler).intercept("bar", barHandler); + hermione.intercept("foo", fooHandler).intercept("bar", barHandler); - await hermione.run(); + await hermione.run(); - assert.calledWith(Runner.create, sinon.match.any, [ - { event: "foo", handler: fooHandler }, - { event: "bar", handler: barHandler }, - ]); + assert.calledWith(RunnerCls.create, sinon.match.any, [ + { event: "foo", handler: fooHandler }, + { event: "bar", handler: barHandler }, + ]); + }); + }); }); it("should warn about unknown browsers from cli", () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); return runHermione([], { browsers: ["bro3"] }).then(() => assert.calledWithMatch(logger.warn, /Unknown browser ids: bro3/), @@ -177,7 +187,7 @@ describe("hermione", () => { }); it("should init runtime config", async () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); await runHermione([], { updateRefs: true, @@ -199,12 +209,12 @@ describe("hermione", () => { replMode: { enabled: true }, devtools: true, }); - assert.callOrder(RuntimeConfig.getInstance, Runner.create); + assert.callOrder(RuntimeConfig.getInstance, NodejsEnvRunner.create); }); describe("repl mode", () => { it("should not reset test timeout to 0 if run not in repl", async () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); const hermione = mkHermione_({ system: { mochaOpts: { timeout: 100500 } } }); await hermione.run([], { replMode: { enabled: false } }); @@ -213,7 +223,7 @@ describe("hermione", () => { }); it("should reset test timeout to 0 if run in repl", async () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); const hermione = mkHermione_({ system: { mochaOpts: { timeout: 100500 } } }); await hermione.run([], { replMode: { enabled: true } }); @@ -223,7 +233,7 @@ describe("hermione", () => { }); describe("INIT", () => { - beforeEach(() => mkRunnerStub_()); + beforeEach(() => mkNodejsEnvRunner_()); it("should emit INIT on run", () => { const onInit = sinon.spy(); @@ -242,14 +252,14 @@ describe("hermione", () => { const afterInit = sinon.spy(); const hermione = mkHermione_().on(RunnerEvents.INIT, () => Promise.delay(20).then(afterInit)); - return hermione.run().then(() => assert.callOrder(afterInit, Runner.prototype.run)); + return hermione.run().then(() => assert.callOrder(afterInit, NodejsEnvRunner.prototype.run)); }); it("should init runner after emit INIT", () => { const onInit = sinon.spy(); const hermione = mkHermione_().on(RunnerEvents.INIT, onInit); - return hermione.run().then(() => assert.callOrder(onInit, Runner.prototype.init)); + return hermione.run().then(() => assert.callOrder(onInit, NodejsEnvRunner.prototype.init)); }); it("should send INIT event only once", () => { @@ -268,7 +278,7 @@ describe("hermione", () => { let runner; beforeEach(() => { - runner = mkRunnerStub_(); + runner = mkNodejsEnvRunner_(); }); it("should initialize passed reporters", async () => { @@ -293,7 +303,7 @@ describe("hermione", () => { }); describe("reading the tests", () => { - beforeEach(() => mkRunnerStub_()); + beforeEach(() => mkNodejsEnvRunner_()); it("should read tests", async () => { const testPaths = ["foo/bar"]; @@ -314,7 +324,7 @@ describe("hermione", () => { await runHermione(testCollection); - assert.calledOnceWith(Runner.prototype.run, testCollection); + assert.calledOnceWith(NodejsEnvRunner.prototype.run, testCollection); }); it("should not read tests if test collection passed instead of paths", async () => { @@ -329,24 +339,24 @@ describe("hermione", () => { describe("running of tests", () => { it("should run tests", () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); - return runHermione().then(() => assert.calledOnce(Runner.prototype.run)); + return runHermione().then(() => assert.calledOnce(NodejsEnvRunner.prototype.run)); }); it("should use read tests", async () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); const testCollection = TestCollection.create(); sandbox.stub(Hermione.prototype, "readTests").resolves(testCollection); await runHermione(); - assert.calledWith(Runner.prototype.run, testCollection); + assert.calledWith(NodejsEnvRunner.prototype.run, testCollection); }); it("should create runner stats", async () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); const hermione = mkHermione_(); @@ -356,23 +366,23 @@ describe("hermione", () => { }); it("should use created runner stats ", async () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); RunnerStats.create.returns("foo bar"); await runHermione(); - assert.calledWith(Runner.prototype.run, sinon.match.any, "foo bar"); + assert.calledWith(NodejsEnvRunner.prototype.run, sinon.match.any, "foo bar"); }); it('should return "true" if there are no failed tests', () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); return runHermione().then(success => assert.isTrue(success)); }); it('should return "false" if there are failed tests', () => { - mkRunnerStub_(runner => runner.emit(RunnerEvents.TEST_FAIL)); + mkNodejsEnvRunner_(runner => runner.emit(RunnerEvents.TEST_FAIL)); return runHermione().then(success => assert.isFalse(success)); }); @@ -381,7 +391,7 @@ describe("hermione", () => { const hermione = mkHermione_(); const err = new Error(); - mkRunnerStub_(runner => runner.emit(RunnerEvents.ERROR, err)); + mkNodejsEnvRunner_(runner => runner.emit(RunnerEvents.ERROR, err)); return hermione.run().then(() => assert.calledOnceWith(hermione.halt, err)); }); @@ -389,7 +399,7 @@ describe("hermione", () => { describe("should passthrough", () => { it("all synchronous runner events", () => { - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); return hermione.run().then(() => { @@ -406,7 +416,7 @@ describe("hermione", () => { it('synchronous runner events before "Runner.run" called', () => { sandbox.stub(eventsUtils, "passthroughEvent"); - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); return hermione.run().then(() => { @@ -421,7 +431,7 @@ describe("hermione", () => { }); it("all asynchronous runner events", () => { - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); return hermione.run().then(() => { @@ -438,7 +448,7 @@ describe("hermione", () => { it('asynchronous runner events before "Runner.run" called', () => { sandbox.stub(eventsUtils, "passthroughEventAsync"); - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); return hermione.run().then(() => { @@ -453,7 +463,7 @@ describe("hermione", () => { }); it("all runner events with passed event data", () => { - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); const omitEvents = ["EXIT", "NEW_BROWSER", "UPDATE_REFERENCE"]; @@ -470,7 +480,7 @@ describe("hermione", () => { }); it("exit event from signalHandler", () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); const hermione = mkHermione_(); const onExit = sinon.spy().named("onExit"); @@ -487,7 +497,7 @@ describe("hermione", () => { it('exit event before "Runner.run" called', () => { sandbox.stub(eventsUtils, "passthroughEventAsync"); - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); return hermione.run().then(() => { @@ -505,7 +515,7 @@ describe("hermione", () => { describe("addTestToRun", () => { it("should pass test to the existing runner", async () => { - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); const test = {}; @@ -516,7 +526,7 @@ describe("hermione", () => { }); it("should return false when hermione is not running", () => { - const runner = mkRunnerStub_(); + const runner = mkNodejsEnvRunner_(); const hermione = mkHermione_(); const added = hermione.addTestToRun({}); @@ -739,7 +749,7 @@ describe("hermione", () => { }); it('should return "false" if there are no failed tests or errors', () => { - mkRunnerStub_(); + mkNodejsEnvRunner_(); const hermione = mkHermione_(); @@ -749,7 +759,7 @@ describe("hermione", () => { it('should return "true" after some test fail', () => { const hermione = mkHermione_(); - mkRunnerStub_(runner => { + mkNodejsEnvRunner_(runner => { runner.emit(RunnerEvents.TEST_FAIL); assert.isTrue(hermione.isFailed()); @@ -775,8 +785,10 @@ describe("hermione", () => { sandbox.stub(logger, "error"); sandbox.stub(process, "exit"); - sandbox.stub(Runner.prototype, "run").callsFake(() => hermione.emitAndWait(RunnerEvents.RUNNER_START)); - sandbox.stub(Runner.prototype, "cancel"); + sandbox + .stub(NodejsEnvRunner.prototype, "run") + .callsFake(() => hermione.emitAndWait(RunnerEvents.RUNNER_START)); + sandbox.stub(NodejsEnvRunner.prototype, "cancel"); }); it("should log provided error", () => { @@ -794,7 +806,7 @@ describe("hermione", () => { it("should not cancel test runner if runner is not inited", () => { hermione.halt(new Error("test error")); - assert.notCalled(Runner.prototype.cancel); + assert.notCalled(NodejsEnvRunner.prototype.cancel); }); it("should cancel test runner", () => { @@ -803,7 +815,7 @@ describe("hermione", () => { }); return hermione.run().finally(() => { - assert.calledOnce(Runner.prototype.cancel); + assert.calledOnce(NodejsEnvRunner.prototype.cancel); }); }); @@ -826,7 +838,7 @@ describe("hermione", () => { await hermione.run(); - assert.callOrder(global.setTimeout, Runner.prototype.cancel); + assert.callOrder(global.setTimeout, NodejsEnvRunner.prototype.cancel); }); it("should force exit if timeout is reached", () => { diff --git a/test/src/runner/browser-env/index.ts b/test/src/runner/browser-env/index.ts new file mode 100644 index 000000000..70c8cee1e --- /dev/null +++ b/test/src/runner/browser-env/index.ts @@ -0,0 +1,98 @@ +import sinon, { SinonStub } from "sinon"; +import { MainRunner as BrowserEnvRunner } from "../../../../src/runner/browser-env"; +import { MainRunner as NodejsEnvRunner } from "../../../../src/runner"; +import { ViteServer } from "../../../../src/runner/browser-env/vite/server"; +import { TestCollection } from "../../../../src/test-collection"; +import { Stats as RunnerStats } from "../../../../src/stats"; + +import { makeConfigStub } from "../../../utils"; +import type { Config } from "../../../../src/config"; + +describe("BrowserEnvRunner", () => { + const sandbox = sinon.createSandbox(); + + const run_ = async ( + opts: { config?: Config; testCollection?: TestCollection; stats?: RunnerStats } = {}, + ): Promise => { + const config = opts.config || makeConfigStub(); + const testCollection = opts.testCollection || TestCollection.create({}); + const stats = opts.stats || sinon.createStubInstance(RunnerStats); + + const runner = BrowserEnvRunner.create(config); + runner.init(); + + return runner.run(testCollection, stats); + }; + + beforeEach(() => { + sandbox.stub(NodejsEnvRunner.prototype, "run").resolves(); + sandbox.stub(NodejsEnvRunner.prototype, "cancel"); + + sandbox.stub(ViteServer, "create").returns(Object.create(ViteServer.prototype)); + sandbox.stub(ViteServer.prototype, "start").resolves(); + sandbox.stub(ViteServer.prototype, "close").resolves(); + sandbox.stub(ViteServer.prototype, "baseUrl").get(() => "http://vite-default.com"); + }); + + afterEach(() => sandbox.restore()); + + describe("constructor", () => { + it("should create vite server", () => { + const config = makeConfigStub(); + + BrowserEnvRunner.create(config); + + assert.calledOnceWith(ViteServer.create, config); + }); + }); + + describe("run", () => { + it("should start vite server", async () => { + await run_(); + + assert.calledOnceWith(ViteServer.prototype.start); + }); + + it("should throw error if vite server failed", async () => { + (ViteServer.prototype.start as SinonStub).rejects(new Error("o.O")); + + await assert.isRejected(run_(), "Vite server failed to start: o.O"); + }); + + it("should use base url from vite", async () => { + const viteUrl = "http://localhost:4000"; + sandbox.stub(ViteServer.prototype, "baseUrl").get(() => viteUrl); + + const config = makeConfigStub({ + baseUrl: "http://default.com", + browsers: ["b1", "b2"], + }) as Config; + + await run_({ config }); + + assert.equal(config.baseUrl, viteUrl); + assert.equal(config.browsers.b1.baseUrl, viteUrl); + assert.equal(config.browsers.b2.baseUrl, viteUrl); + }); + + it('should call "run" command of base runner at the end', async () => { + await run_(); + + assert.callOrder(ViteServer.prototype.start as SinonStub, NodejsEnvRunner.prototype.run as SinonStub); + }); + }); + + describe("cancel", () => { + it('should call "cancel" command of base runner', () => { + BrowserEnvRunner.create(makeConfigStub()).cancel(); + + assert.calledOnce(NodejsEnvRunner.prototype.cancel as SinonStub); + }); + + it("should close vite server", () => { + BrowserEnvRunner.create(makeConfigStub()).cancel(); + + assert.calledOnce(ViteServer.prototype.close as SinonStub); + }); + }); +}); diff --git a/test/src/runner/browser-env/vite/server.ts b/test/src/runner/browser-env/vite/server.ts new file mode 100644 index 000000000..c9655b978 --- /dev/null +++ b/test/src/runner/browser-env/vite/server.ts @@ -0,0 +1,248 @@ +import path from "node:path"; +import proxyquire from "proxyquire"; +import sinon, { SinonStub } from "sinon"; +import Vite from "vite"; +import P from "bluebird"; +import chalk from "chalk"; + +import { ViteServer } from "../../../../../src/runner/browser-env/vite/server"; +import { BrowserWorkerCommunicator } from "../../../../../src/runner/browser-env/vite/communicator"; +import logger from "../../../../../src/utils/logger"; +import { makeConfigStub } from "../../../../utils"; +import { BROWSER_TEST_RUN_ENV } from "../../../../../src/constants/config"; + +import type { Config } from "../../../../../src/config"; +import type { BrowserTestRunEnvOptions } from "../../../../../src/runner/browser-env/vite/types"; + +describe("runner/browser-env/vite", () => { + const sandbox = sinon.createSandbox(); + let ViteServerStub: typeof ViteServer; + let getPortStub: SinonStub; + let getNodeModulePathStub: SinonStub; + let generateIndexHtmlPlugin: () => Vite.Plugin[]; + let resolveModulePathsPlugin: () => Vite.Plugin[]; + + const mkViteServer_ = (opts: Partial = {}): Partial => ({ + listen: sandbox.stub(), + close: sandbox.stub(), + resolvedUrls: { + local: ["http://localhost:12345"], + network: [], + }, + ws: {} as Vite.WebSocketServer, + ...opts, + }); + + const mkConfig_ = (opts?: Partial): Config => makeConfigStub(opts) as Config; + const mkConfigWithVite_ = (viteConfig: BrowserTestRunEnvOptions["viteConfig"]): Config => { + return mkConfig_({ + system: { + testRunEnv: [ + BROWSER_TEST_RUN_ENV, + { + viteConfig, + }, + ], + }, + } as Partial); + }; + + beforeEach(() => { + sandbox.stub(Vite, "createServer").resolves(mkViteServer_()); + sandbox.stub(BrowserWorkerCommunicator, "create").returns(Object.create(BrowserWorkerCommunicator.prototype)); + sandbox.stub(BrowserWorkerCommunicator.prototype, "handleMessages"); + + sandbox.stub(logger, "log"); + + getPortStub = sandbox.stub().resolves(12345); + getNodeModulePathStub = sandbox.stub().resolves("file:///default-cwd"); + generateIndexHtmlPlugin = sandbox.stub().returns([{ name: "default-plugin-1" }]); + resolveModulePathsPlugin = sandbox.stub().returns([{ name: "default-plugin-2" }]); + + ({ ViteServer: ViteServerStub } = proxyquire("../../../../../src/runner/browser-env/vite/server", { + "get-port": getPortStub, + "./plugins/generate-index-html": { plugin: generateIndexHtmlPlugin }, + "./plugins/resolve-module-paths": { plugin: resolveModulePathsPlugin }, + "./utils": { getNodeModulePath: getNodeModulePathStub }, + })); + }); + + afterEach(() => sandbox.restore()); + + describe("start", () => { + describe("should create vite server", () => { + describe("with default config", () => { + it("on localhost", async () => { + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ server: { host: "localhost" } })); + }); + + it("without config file", async () => { + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ configFile: false })); + }); + + it("with inlined source map", async () => { + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ build: { sourcemap: "inline" } })); + }); + + it("with silent log level", async () => { + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith( + Vite.createServer, + sinon.match({ + logLevel: "silent", + optimizeDeps: { + esbuildOptions: { + logLevel: "silent", + }, + }, + }), + ); + }); + + it("with generated port", async () => { + getPortStub.resolves(98765); + + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ server: { port: 98765 } })); + }); + }); + + describe("with user config from file", () => { + it("on specified host and port", async () => { + const config = mkConfigWithVite_("./test/fixtures/vite.conf.ts"); + + await ViteServerStub.create(config).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ server: { host: "0.0.0.0", port: 4000 } })); + }); + }); + + describe("with user config as function", () => { + it("on specified host and port", async () => { + const userConfigFn = async (): Promise => { + await P.delay(20); + return { + server: { + host: "1.1.1.1", + port: 5000, + }, + }; + }; + const config = mkConfigWithVite_(userConfigFn); + + await ViteServerStub.create(config).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ server: { host: "1.1.1.1", port: 5000 } })); + }); + }); + + describe("with user config as object", () => { + it("on specified host and port", async () => { + const config = mkConfigWithVite_({ + server: { host: "2.2.2.2", port: 6000 }, + }); + + await ViteServerStub.create(config).start(); + + assert.calledOnceWith(Vite.createServer, sinon.match({ server: { host: "2.2.2.2", port: 6000 } })); + }); + }); + + it("with plugins", async () => { + const config = mkConfigWithVite_({ + plugins: [{ name: "user-plugin-1" }, { name: "user-plugin-2" }], + }); + (generateIndexHtmlPlugin as SinonStub).returns([{ name: "gen-index-html" }]); + (resolveModulePathsPlugin as SinonStub).returns([{ name: "resolve-module-paths" }]); + + await ViteServerStub.create(config).start(); + + assert.calledOnceWith( + Vite.createServer, + sinon.match({ + plugins: [ + { name: "user-plugin-1" }, + { name: "user-plugin-2" }, + [{ name: "gen-index-html" }], + [{ name: "resolve-module-paths" }], + ], + }), + ); + }); + }); + + it('should generate path to "mocha" from "node_modules"', async () => { + (getNodeModulePathStub as SinonStub) + .withArgs({ + moduleName: "mocha", + parent: path.join("node_modules", "hermione", "node_modules"), + }) + .resolves("file:///cwd/node_modules/mocha/index.js"); + + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(resolveModulePathsPlugin, { + modulePaths: { + mocha: "/cwd/node_modules/mocha/mocha.js", + }, + }); + }); + + it("should create communicator and handle ws messages", async () => { + const viteServer = mkViteServer_(); + (Vite.createServer as SinonStub).resolves(viteServer); + + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(BrowserWorkerCommunicator.create, viteServer.ws); + assert.calledOnceWith(BrowserWorkerCommunicator.prototype.handleMessages); + }); + + it("should listen vite server after subscribe on messages in communicator", async () => { + const viteServer = mkViteServer_(); + (Vite.createServer as SinonStub).resolves(viteServer); + + await ViteServerStub.create(mkConfig_()).start(); + + assert.callOrder( + BrowserWorkerCommunicator.prototype.handleMessages as SinonStub, + viteServer.listen as SinonStub, + ); + }); + + it("should inform on which address vite server started", async () => { + const viteServer = mkViteServer_({ + resolvedUrls: { + local: ["http://localhost:4444"], + network: [], + }, + }); + (Vite.createServer as SinonStub).resolves(viteServer); + + await ViteServerStub.create(mkConfig_()).start(); + + assert.calledOnceWith(logger.log, chalk.green("Vite server started on http://localhost:4444")); + }); + }); + + describe("close", () => { + it("should close server", async () => { + const viteServer = mkViteServer_(); + (Vite.createServer as SinonStub).resolves(viteServer); + + const viteServerWrapper = ViteServerStub.create(mkConfig_()); + await viteServerWrapper.start(); + await viteServerWrapper.close(); + + assert.calledOnce(viteServer.close as SinonStub); + }); + }); +}); diff --git a/test/src/runner/index.js b/test/src/runner/index.js index c934d704d..1fdf44248 100644 --- a/test/src/runner/index.js +++ b/test/src/runner/index.js @@ -15,7 +15,7 @@ const { TestCollection } = require("src/test-collection"); const { makeConfigStub } = require("../../utils"); const proxyquire = require("proxyquire"); -describe("Runner", () => { +describe("NodejsEnvRunner", () => { const sandbox = sinon.createSandbox(); let BrowserPool; let Runner; diff --git a/test/src/utils/config.ts b/test/src/utils/config.ts new file mode 100644 index 000000000..f4c0dde04 --- /dev/null +++ b/test/src/utils/config.ts @@ -0,0 +1,27 @@ +import { isRunInNodeJsEnv } from "../../../src/utils/config"; +import { NODEJS_TEST_RUN_ENV, BROWSER_TEST_RUN_ENV } from "../../../src/constants/config"; +import type { CommonConfig } from "../../../src/config/types"; + +describe("config-utils", () => { + describe("isRunInNodeJsEnv", () => { + it("should return 'true' if running in node environment", () => { + const config = { + system: { + testRunEnv: NODEJS_TEST_RUN_ENV, + }, + } as CommonConfig; + + assert.isTrue(isRunInNodeJsEnv(config)); + }); + + it("should return 'false' if running in browser environment", () => { + const config = { + system: { + testRunEnv: BROWSER_TEST_RUN_ENV, + }, + } as CommonConfig; + + assert.isFalse(isRunInNodeJsEnv(config)); + }); + }); +}); diff --git a/test/src/worker/browser-env/runner/test-runner/index.ts b/test/src/worker/browser-env/runner/test-runner/index.ts new file mode 100644 index 000000000..cb9c6e885 --- /dev/null +++ b/test/src/worker/browser-env/runner/test-runner/index.ts @@ -0,0 +1,188 @@ +import process from "node:process"; +import crypto from "node:crypto"; +import _ from "lodash"; +import sinon, { SinonStub } from "sinon"; +import { TestRunner as BrowserEnvRunner } from "../../../../../../src/worker/browser-env/runner/test-runner"; +import { makeBrowserConfigStub } from "../../../../../utils"; +import RuntimeConfig from "../../../../../../src/config/runtime-config"; +import { Test, Suite } from "../../../../../../src/test-reader/test-object"; +import BrowserAgent from "../../../../../../src/worker/runner/browser-agent"; +import history from "../../../../../../src/browser/history"; +import { ViteWorkerCommunicator } from "../../../../../../src/worker/browser-env/communicator"; + +import OneTimeScreenshooter from "../../../../../../src/worker/runner/test-runner/one-time-screenshooter"; + +import type { + WorkerTestRunnerRunOpts, + WorkerTestRunnerCtorOpts, +} from "../../../../../../src/worker/runner/test-runner/types"; +import type { Test as TestType } from "../../../../../../src/test-reader/test-object/test"; +import type { BrowserConfig } from "../../../../../../src/config/browser-config"; +import type { WorkerRunTestResult } from "../../../../../../src/worker/hermione"; +import type { Browser } from "../../../../../../src/browser/types.ts"; + +interface TestOpts { + title: string; + file: string; + id: string; + fn: VoidFunction; +} +interface RunOpts extends WorkerTestRunnerRunOpts { + runner?: BrowserEnvRunner; +} + +describe("worker/browser-env/runner/test-runner", () => { + const sandbox = sinon.createSandbox(); + + const mkTest_ = (opts?: Partial): TestType => { + const test = Test.create({ + ...opts, + title: "default", + file: "/default/file/path", + id: "12345", + fn: sinon.stub(), + }) as TestType; + test.parent = Suite.create(); + + return test; + }; + + const mkRunner_ = (opts?: Partial): BrowserEnvRunner => { + opts = { + test: mkTest_(), + file: "/default/file/path", + config: makeBrowserConfigStub({ + baseUrl: "http://localhost:12345", + system: { patternsOnReject: [] }, + }) as BrowserConfig, + browserAgent: Object.create(BrowserAgent.prototype), + ...opts, + }; + + return BrowserEnvRunner.create(opts as WorkerTestRunnerCtorOpts) as BrowserEnvRunner; + }; + + const run_ = (opts?: Partial): Promise => { + const test = mkTest_(); + const runner = opts?.runner || mkRunner_({ test }); + + opts = { + sessionId: "default-sessionId", + sessionCaps: {}, + sessionOpts: {} as WorkerTestRunnerRunOpts["sessionOpts"], + state: {}, + ..._.omit(opts, "runer"), + }; + + return runner.run(opts as WorkerTestRunnerRunOpts); + }; + + const mkBrowser_ = (): Browser => ({ + publicAPI: { + url: sandbox.stub().resolves(), + } as unknown as Browser["publicAPI"], + config: makeBrowserConfigStub({ saveHistoryMode: "none" }) as BrowserConfig, + state: { + isBroken: false, + }, + applyState: sandbox.stub(), + callstackHistory: { + enter: sandbox.stub(), + leave: sandbox.stub(), + markError: sandbox.stub(), + release: sandbox.stub(), + } as unknown as Browser["callstackHistory"], + }); + + beforeEach(() => { + sandbox.stub(BrowserAgent.prototype, "getBrowser").resolves(mkBrowser_()); + sandbox.stub(BrowserAgent.prototype, "freeBrowser"); + + sandbox.stub(OneTimeScreenshooter, "create").returns(Object.create(OneTimeScreenshooter.prototype)); + sandbox.stub(OneTimeScreenshooter.prototype, "extendWithScreenshot").resolves(); + + sandbox.stub(crypto, "randomUUID").returns("00000"); + sandbox.stub(process, "pid").value(11111); + + sandbox.stub(ViteWorkerCommunicator.prototype, "sendMessage").resolves(); + sandbox.stub(ViteWorkerCommunicator.prototype, "waitMessage").resolves({ data: {} }); + + sandbox.stub(RuntimeConfig, "getInstance").returns({ + viteWorkerCommunicator: ViteWorkerCommunicator.prototype, + }); + }); + + afterEach(() => sandbox.restore()); + + describe("run", () => { + beforeEach(() => { + sandbox.spy(history, "runGroup"); + }); + + it('should log "openVite" in history', async () => { + const browser = mkBrowser_(); + (BrowserAgent.prototype.getBrowser as SinonStub).resolves(browser); + + await run_(); + + assert.calledWith(history.runGroup as SinonStub, browser.callstackHistory, "openVite", sinon.match.func); + }); + + it("should open url to vite server with query args", async () => { + const pid = 77777; + const file = "/some/file"; + const runUuid = "12345"; + const cmdUuid = "54321"; + + (crypto.randomUUID as SinonStub).onFirstCall().returns(runUuid).onSecondCall().returns(cmdUuid); + sandbox.stub(process, "pid").value(pid); + + const browser = mkBrowser_(); + (BrowserAgent.prototype.getBrowser as SinonStub).resolves(browser); + + const runner = mkRunner_({ + test: mkTest_({ file }), + file, + config: makeBrowserConfigStub({ baseUrl: "http://localhost:4444", system: {} }) as BrowserConfig, + }); + + await run_({ runner }); + + assert.calledOnceWith( + browser.publicAPI.url, + `http://localhost:4444/?pid=${pid}&file=${encodeURIComponent( + file, + )}&runUuid=${runUuid}&cmdUuid=${cmdUuid}`, + ); + }); + + it("should wait response from communicator", async () => { + (crypto.randomUUID as SinonStub).onFirstCall().returns("12345").onSecondCall().returns("54321"); + + const runner = mkRunner_({ + config: makeBrowserConfigStub({ + urlHttpTimeout: 5000, + system: {}, + baseUrl: "http://localhost", + }) as BrowserConfig, + }); + + await run_({ runner }); + + assert.calledWith((ViteWorkerCommunicator.prototype.waitMessage as SinonStub).firstCall, { + cmdUuid: "54321", + timeout: 5000, + }); + }); + + it("should throw if got message with errors", async () => { + (ViteWorkerCommunicator.prototype.waitMessage as SinonStub).resolves({ + data: { + errors: [new Error("o.O")], + }, + }); + + await assert.isRejected(run_(), /o.O/); + }); + }); +}); diff --git a/test/src/worker/runner/index.js b/test/src/worker/runner/index.js index 623022332..8f290aaee 100644 --- a/test/src/worker/runner/index.js +++ b/test/src/worker/runner/index.js @@ -5,7 +5,9 @@ const BrowserPool = require("src/worker/runner/browser-pool"); const CachingTestParser = require("src/worker/runner/caching-test-parser"); const BrowserAgent = require("src/worker/runner/browser-agent"); const { WorkerEvents: RunnerEvents } = require("src/events"); -const TestRunner = require("src/worker/runner/test-runner"); +const NodejsEnvTestRunner = require("src/worker/runner/test-runner"); +const { TestRunner: BrowserEnvTestRunner } = require("src/worker/browser-env/runner/test-runner"); +const { NODEJS_TEST_RUN_ENV, BROWSER_TEST_RUN_ENV } = require("src/constants/config"); const { makeConfigStub, makeTest } = require("../../../utils"); describe("worker/runner", () => { @@ -22,8 +24,11 @@ describe("worker/runner", () => { sandbox.stub(CachingTestParser, "create").returns(Object.create(CachingTestParser.prototype)); sandbox.stub(CachingTestParser.prototype, "parse").resolves([]); - sandbox.stub(TestRunner, "create").returns(Object.create(TestRunner.prototype)); - sandbox.stub(TestRunner.prototype, "run").resolves(); + sandbox.stub(NodejsEnvTestRunner, "create").returns(Object.create(NodejsEnvTestRunner.prototype)); + sandbox.stub(NodejsEnvTestRunner.prototype, "run").resolves(); + + // sandbox.stub(BrowserEnvTestRunner, "create").returns(Object.create(BrowserEnvTestRunner.prototype)); + sandbox.stub(BrowserEnvTestRunner.prototype, "run").resolves(); sandbox.stub(BrowserAgent, "create").returns(Object.create(BrowserAgent.prototype)); }); @@ -60,84 +65,111 @@ describe("worker/runner", () => { }); }); - describe("runTest", () => { - it("should parse passed file in passed browser", async () => { - const runner = mkRunner_(); + [ + { name: "NodejsEnvTestRunner", TestRunner: NodejsEnvTestRunner, testRunEnv: NODEJS_TEST_RUN_ENV }, + { name: "BrowserEnvTestRunner", TestRunner: BrowserEnvTestRunner, testRunEnv: BROWSER_TEST_RUN_ENV }, + ].forEach(({ name, TestRunner, testRunEnv }) => { + describe(name, () => { + let runner; + + beforeEach(() => { + runner = mkRunner_( + makeConfigStub({ + system: { testRunEnv }, + }), + ); + }); - await runner.runTest(null, { file: "some/file.js", browserId: "bro" }); + describe("runTest", () => { + it("should parse passed file in passed browser", async () => { + await runner.runTest(null, { file: "some/file.js", browserId: "bro" }); - assert.calledOnceWith(CachingTestParser.prototype.parse, { file: "some/file.js", browserId: "bro" }); - }); + assert.calledOnceWith(CachingTestParser.prototype.parse, { + file: "some/file.js", + browserId: "bro", + }); + }); - it("should create test runner for parsed test", async () => { - const runner = mkRunner_(); + it("should create test runner for parsed test", async () => { + const test = makeTest({ fullTitle: () => "some test" }); + CachingTestParser.prototype.parse.resolves([test]); - const test = makeTest({ fullTitle: () => "some test" }); - CachingTestParser.prototype.parse.resolves([test]); + await runner.runTest("some test", {}); - await runner.runTest("some test", {}); + assert.calledOnceWith(TestRunner.create, sinon.match({ test })); + }); - assert.calledOnceWith(TestRunner.create, test); - }); + it("should pass browser config to test runner", async () => { + const config = makeConfigStub({ browsers: ["bro"], system: { testRunEnv } }); + const runner = mkRunner_({ config }); - it("should pass browser config to test runner", async () => { - const config = makeConfigStub({ browsers: ["bro"] }); - const runner = mkRunner_({ config }); + const test = makeTest({ fullTitle: () => "some test" }); + CachingTestParser.prototype.parse.resolves([test]); - const test = makeTest({ fullTitle: () => "some test" }); - CachingTestParser.prototype.parse.resolves([test]); + await runner.runTest("some test", { browserId: "bro" }); - await runner.runTest("some test", { browserId: "bro" }); + assert.calledOnceWith(TestRunner.create, sinon.match({ config: config.forBrowser("bro") })); + }); - assert.calledOnceWith(TestRunner.create, test, config.forBrowser("bro")); - }); + it("should pass file to test runner", async () => { + const runner = mkRunner_(); - it("should create browser agent for test runner", async () => { - const pool = { browser: "pool" }; - BrowserPool.create.returns(pool); - const runner = mkRunner_(); + const test = makeTest({ fullTitle: () => "some test" }); + CachingTestParser.prototype.parse.resolves([test]); - const test = makeTest({ fullTitle: () => "some test" }); - CachingTestParser.prototype.parse.resolves([test]); + await runner.runTest("some test", { file: "/path/to/file" }); - const browserAgent = Object.create(BrowserAgent.prototype); - BrowserAgent.create.withArgs({ id: "bro", version: "1.0", pool }).returns(browserAgent); + assert.calledOnceWith(TestRunner.create, sinon.match({ file: "/path/to/file" })); + }); - await runner.runTest("some test", { browserId: "bro", browserVersion: "1.0" }); + it("should create browser agent for test runner", async () => { + const pool = { browser: "pool" }; + BrowserPool.create.returns(pool); + const runner = mkRunner_(); - assert.calledOnceWith(TestRunner.create, test, sinon.match.any, browserAgent); - }); + const test = makeTest({ fullTitle: () => "some test" }); + CachingTestParser.prototype.parse.resolves([test]); - it("should create test runner only for passed test", async () => { - const runner = mkRunner_(); + const browserAgent = Object.create(BrowserAgent.prototype); + BrowserAgent.create.withArgs({ id: "bro", version: "1.0", pool }).returns(browserAgent); - const test1 = makeTest({ fullTitle: () => "some test" }); - const test2 = makeTest({ fullTitle: () => "other test" }); - CachingTestParser.prototype.parse.resolves([test1, test2]); + await runner.runTest("some test", { browserId: "bro", browserVersion: "1.0" }); - await runner.runTest("other test", {}); + assert.calledOnceWith(TestRunner.create, sinon.match({ browserAgent })); + }); - assert.calledOnceWith(TestRunner.create, test2); - }); + it("should create test runner only for passed test", async () => { + const runner = mkRunner_(); - it("should run test in passed session", async () => { - const runner = mkRunner_(); + const test1 = makeTest({ fullTitle: () => "some test" }); + const test2 = makeTest({ fullTitle: () => "other test" }); + CachingTestParser.prototype.parse.resolves([test1, test2]); - const test = makeTest({ fullTitle: () => "some test" }); - CachingTestParser.prototype.parse.resolves([test]); + await runner.runTest("other test", {}); - await runner.runTest("some test", { - sessionId: "100500", - sessionCaps: "some-caps", - sessionOpts: "some-opts", - state: {}, - }); + assert.calledOnceWith(TestRunner.create, sinon.match({ test: test2 })); + }); + + it("should run test in passed session", async () => { + const runner = mkRunner_(); + + const test = makeTest({ fullTitle: () => "some test" }); + CachingTestParser.prototype.parse.resolves([test]); + + await runner.runTest("some test", { + sessionId: "100500", + sessionCaps: "some-caps", + sessionOpts: "some-opts", + state: {}, + }); - assert.calledOnceWith(TestRunner.prototype.run, { - sessionId: "100500", - sessionCaps: "some-caps", - sessionOpts: "some-opts", - state: {}, + assert.calledOnceWith(NodejsEnvTestRunner.prototype.run, { + sessionId: "100500", + sessionCaps: "some-caps", + sessionOpts: "some-opts", + state: {}, + }); + }); }); }); }); diff --git a/test/src/worker/runner/test-runner/index.js b/test/src/worker/runner/test-runner/index.js index c09724cab..bae0fd51f 100644 --- a/test/src/worker/runner/test-runner/index.js +++ b/test/src/worker/runner/test-runner/index.js @@ -27,10 +27,11 @@ describe("worker/runner/test-runner", () => { const mkRunner_ = (opts = {}) => { const test = opts.test || mkTest_(); + const file = opts.file || "/default/file/path"; const config = opts.config || makeConfigStub(); const browserAgent = opts.browserAgent || Object.create(BrowserAgent.prototype); - return TestRunner.create(test, config, browserAgent); + return TestRunner.create({ test, file, config, browserAgent }); }; const mkElement_ = proto => { diff --git a/test/utils.js b/test/utils.js index 8fbbb341b..168f32ed2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,6 +2,7 @@ const _ = require("lodash"); const Browser = require("../src/browser/new-browser"); +const { NODEJS_TEST_RUN_ENV } = require("../src/constants/config"); function browserWithId(id) { const config = { browsers: {}, system: { debug: false } }; @@ -13,6 +14,7 @@ function browserWithId(id) { function makeConfigStub(opts = {}) { opts = _.defaults(opts, { + baseUrl: "http://default.com", browsers: ["some-default-browser"], version: "1.0", desiredCapabilities: {}, @@ -25,6 +27,7 @@ function makeConfigStub(opts = {}) { mochaOpts: {}, expectOpts: {}, patternsOnReject: [], + testRunEnv: NODEJS_TEST_RUN_ENV, }, sets: {}, }); @@ -37,7 +40,23 @@ function makeConfigStub(opts = {}) { configPath: opts.configPath, }; - const mkBrowserConfig_ = browserId => ({ + opts.browsers.forEach(browserId => { + config.browsers[browserId] = makeBrowserConfigStub(opts, browserId); + }); + + config.forBrowser = sinon + .stub() + .callsFake(browserId => config.browsers[browserId] || makeBrowserConfigStub(opts, browserId)); + config.getBrowserIds = () => opts.browsers; + config.serialize = sinon.stub().returns(config); + config.mergeWith = sinon.stub(); + + return config; +} + +function makeBrowserConfigStub(opts = {}, browserId) { + return { + baseUrl: opts.baseUrl, retry: opts.retry, shouldRetry: opts.shouldRetry, sessionsPerBrowser: opts.sessionsPerBrowser, @@ -47,18 +66,8 @@ function makeConfigStub(opts = {}) { : opts.desiredCapabilities, testTimeout: opts.testTimeout, system: opts.system, - }); - - opts.browsers.forEach(browserId => { - config.browsers[browserId] = mkBrowserConfig_(browserId); - }); - - config.forBrowser = sinon.stub().callsFake(browserId => config.browsers[browserId] || mkBrowserConfig_(browserId)); - config.getBrowserIds = () => opts.browsers; - config.serialize = sinon.stub().returns(config); - config.mergeWith = sinon.stub(); - - return config; + urlHttpTimeout: opts.urlHttpTimeout, + }; } function makeSuite(opts = {}) { @@ -84,5 +93,6 @@ function makeTest(opts = {}) { exports.browserWithId = browserWithId; exports.makeConfigStub = makeConfigStub; +exports.makeBrowserConfigStub = makeBrowserConfigStub; exports.makeSuite = makeSuite; exports.makeTest = makeTest; diff --git a/tsconfig.json b/tsconfig.json index 5f9495253..20aebe158 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,19 @@ { "extends": "./tsconfig.common.json", "include": ["src"], - "exclude": ["src/**/__*", "src/**/*.test.ts", "src/browser/client-scripts", "src/bundle"], + "exclude": [ + "src/**/__*", + "src/**/*.test.ts", + "src/browser/client-scripts", + "src/bundle", + "src/runner/browser-env/vite/browser-modules" + ], "compilerOptions": { "outDir": "build" - } + }, + "references": [ + { + "path": "./src/runner/browser-env/vite/browser-modules/tsconfig.json" + } + ] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json index 9ddd53cda..b375d413e 100644 --- a/tsconfig.spec.json +++ b/tsconfig.spec.json @@ -1,6 +1,7 @@ { "extends": "./tsconfig.common.json", "include": ["src", "test"], + "exclude": ["src/runner/browser-env/vite/browser-modules"], "compilerOptions": { "baseUrl": ".", "noEmit": true,