From 3efd6582053f6948d0643d5a86baeefecd9fe30c Mon Sep 17 00:00:00 2001 From: Harshit Date: Wed, 5 Aug 2026 22:28:41 +0530 Subject: [PATCH 1/6] feat(SDK-7063): report bail-skipped tests as skipped Tests that never run because of `bail` were absent from the Test Run entirely. Report them as `skipped` so the report accounts for every declared test. WebdriverIO has two unrelated bail mechanisms, handled separately: - `mochaOpts.bail` halts the failing spec. `service.afterTest` walks `test.ctx.test.parent` to the spec's root suite and reuses the existing `reportSuiteSkipped` cascade, so sibling describes are covered. Gated strictly on mocha's own option -- under wdio's `bail` every in-spec test still runs, so triggering on it would report tests that are about to execute. - wdio's `bail: ` counts failed spec files and stops scheduling more. `launcher.onComplete` diffs dispatched specs against the run's spec list and enumerates the rest via mocha's declaration pass, without executing any test body. Launcher-side reporting only happens when bail demonstrably fired (`failedRunners >= bail`, counted the way wdio counts it -- a failed spec with retries left is requeued, not counted). It is skipped entirely under sharding, when capabilities define their own specs/exclude, and for excluded specs. A spec can fail to run for many reasons unrelated to bail, and reporting one of those as skipped would corrupt the report; under-reporting is recoverable, phantom skips are not. Events are relayed through the binary's EnqueueTestEvent RPC. The launcher's TESTHUB_JWT is the binary's account token on the CLI path and carries no build claim, so posting to the collector directly returns 401; the binary holds the build-scoped credential. The RPC already existed in the proto -- only the client wrapper was missing. Reported identity matches `getUniqueIdentifier` so a test reported skipped and the same test on a later run share one identity. Covers the CLI/gRPC path only. Enumerating never-started specs re-executes their top-level code. --- package-lock.json | 736 +++++++++++++----- packages/browserstack-service/README.md | 11 + packages/browserstack-service/package.json | 1 + .../src/cli/grpcClient.ts | 29 + .../browserstack-service/src/grpc/index.ts | 11 + packages/browserstack-service/src/launcher.ts | 70 +- packages/browserstack-service/src/service.ts | 42 + .../src/testOps/specEnumerator.ts | 90 +++ .../src/testOps/unlaunchedSpecReporter.ts | 176 +++++ .../tests/service.test.ts | 82 ++ .../tests/testOps/specEnumerator.test.ts | 143 ++++ .../testOps/unlaunchedSpecReporter.test.ts | 204 +++++ 12 files changed, 1420 insertions(+), 175 deletions(-) create mode 100644 packages/browserstack-service/src/testOps/specEnumerator.ts create mode 100644 packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts create mode 100644 packages/browserstack-service/tests/testOps/specEnumerator.test.ts create mode 100644 packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts diff --git a/package-lock.json b/package-lock.json index b8d072b..b7a8965 100644 --- a/package-lock.json +++ b/package-lock.json @@ -494,7 +494,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -511,7 +510,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -528,7 +526,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -545,7 +542,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -562,7 +558,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -579,7 +574,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -596,7 +590,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -613,7 +606,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -630,7 +622,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -647,7 +638,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -664,7 +654,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -681,7 +670,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -698,7 +686,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -715,7 +702,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -732,7 +718,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -749,7 +734,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -766,7 +750,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -783,7 +766,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -800,7 +782,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -817,7 +798,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -834,7 +814,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -851,7 +830,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -868,7 +846,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -885,7 +862,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -902,7 +878,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -919,7 +894,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1123,7 +1097,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1133,7 +1106,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -1158,7 +1130,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -1180,7 +1151,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", @@ -1208,7 +1178,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/external-editor": "^1.0.3", @@ -1231,7 +1200,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", @@ -1275,7 +1243,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -1285,7 +1252,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -1307,7 +1273,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -1329,7 +1294,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -1352,7 +1316,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", @@ -1382,7 +1345,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", @@ -1405,7 +1367,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", @@ -1429,7 +1390,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -1454,7 +1414,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1493,7 +1452,6 @@ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "license": "MIT", - "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -1503,7 +1461,6 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0" }, @@ -1516,7 +1473,6 @@ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "license": "MIT", - "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -1526,7 +1482,6 @@ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "jest-regex-util": "30.4.0" @@ -1540,7 +1495,6 @@ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "license": "MIT", - "peer": true, "dependencies": { "@sinclair/typebox": "^0.34.0" }, @@ -1553,7 +1507,6 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "license": "MIT", - "peer": true, "dependencies": { "@jest/pattern": "30.4.0", "@jest/schemas": "30.4.1", @@ -1572,7 +1525,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1588,7 +1540,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2243,22 +2194,19 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@sinclair/typebox": { "version": "0.34.49", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2307,15 +2255,13 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -2325,7 +2271,6 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-report": "*" } @@ -2342,6 +2287,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2362,8 +2308,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/tar": { "version": "7.0.87", @@ -2402,7 +2347,6 @@ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -2411,8 +2355,7 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/yauzl": { "version": "2.10.3", @@ -2464,6 +2407,7 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -2717,7 +2661,6 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "license": "MIT", - "peer": true, "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -2790,7 +2733,6 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", @@ -2818,7 +2760,6 @@ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", @@ -2833,7 +2774,6 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "license": "MIT", - "peer": true, "dependencies": { "tinyrainbow": "^3.1.0" }, @@ -2846,7 +2786,6 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -3091,6 +3030,7 @@ "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.28.0.tgz", "integrity": "sha512-poYsF7Gbm8kfYX6tdsPG862anOQKyUT8roe+rwdXaKSorz/s4XDJBm4kJiid6LgWKeAMSXDYzFODzDxhYhugWg==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.20.0" }, @@ -3112,6 +3052,7 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", "license": "MIT", + "peer": true, "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", @@ -3251,6 +3192,7 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3328,7 +3270,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -3336,6 +3277,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/anynum": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", @@ -3755,7 +3710,6 @@ "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.12.0" } @@ -3974,6 +3928,19 @@ "node": ">=4" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -4005,6 +3972,13 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, "node_modules/browserslist": { "version": "4.28.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", @@ -4025,6 +3999,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.38", "caniuse-lite": "^1.0.30001799", @@ -4166,6 +4141,19 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001800", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", @@ -4301,7 +4289,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", - "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -4364,7 +4351,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "license": "ISC", - "peer": true, "engines": { "node": ">= 12" } @@ -4450,7 +4436,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" } @@ -4493,8 +4478,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/core-js-compat": { "version": "3.49.0", @@ -4546,7 +4530,6 @@ "resolved": "https://registry.npmjs.org/create-wdio/-/create-wdio-9.28.0.tgz", "integrity": "sha512-3Oa7tGK5QA9z1bdTFonnEb3OTTyNJtI2np7YzEC6F9es+94PFsFLLn5nIWs+fhJSdoueQyLy8P2cSWBO3Ohijw==", "license": "MIT", - "peer": true, "dependencies": { "chalk": "^5.3.0", "commander": "^14.0.0", @@ -4923,7 +4906,6 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=12" }, @@ -5051,7 +5033,6 @@ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "jake": "^10.8.5" }, @@ -5324,7 +5305,6 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5374,7 +5354,6 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5407,6 +5386,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6113,7 +6093,6 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "license": "MIT", - "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", @@ -6140,7 +6119,6 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "license": "MIT", - "peer": true, "dependencies": { "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", @@ -6168,7 +6146,6 @@ "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.6.8.tgz", "integrity": "sha512-VfLC9o84B40LEw+zX7UykUptKkscX1rPYY4jaAsQ6KyKL0X0ltDkWzKIUiY9g/u0ApNerrkhM/QEY0TDT8pJOQ==", "license": "MIT", - "peer": true, "dependencies": { "@vitest/snapshot": "^4.1.7", "deep-eql": "^5.0.2", @@ -6200,7 +6177,6 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "license": "MIT", - "peer": true, "dependencies": { "tinyrainbow": "^3.1.0" }, @@ -6213,7 +6189,6 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "license": "MIT", - "peer": true, "dependencies": { "@vitest/pretty-format": "4.1.8", "@vitest/utils": "4.1.8", @@ -6228,15 +6203,13 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/expect-webdriverio/node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -6413,7 +6386,6 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "license": "MIT", - "peer": true, "dependencies": { "is-unicode-supported": "^2.0.0" }, @@ -6442,7 +6414,6 @@ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "minimatch": "^5.0.1" } @@ -6451,15 +6422,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/filelist/node_modules/brace-expansion": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -6469,7 +6438,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6504,6 +6472,16 @@ "node": ">=8" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -6859,7 +6837,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "license": "MIT", - "peer": true, "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" @@ -7146,6 +7123,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/headers-utils": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.5.tgz", @@ -7157,7 +7144,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "license": "ISC", - "peer": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -7252,7 +7238,6 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18.18.0" } @@ -7395,7 +7380,6 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -7501,6 +7485,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -7816,7 +7813,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -7893,7 +7889,6 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -7989,7 +7984,6 @@ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", @@ -8007,7 +8001,6 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "license": "MIT", - "peer": true, "dependencies": { "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", @@ -8023,7 +8016,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8039,7 +8031,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8056,7 +8047,6 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", @@ -8072,7 +8062,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8088,7 +8077,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8105,7 +8093,6 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.4.1", @@ -8127,7 +8114,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8143,7 +8129,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8160,7 +8145,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8173,7 +8157,6 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "30.4.1", "@types/node": "*", @@ -8188,7 +8171,6 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "license": "MIT", - "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -8198,7 +8180,6 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "30.4.1", "@types/node": "*", @@ -8216,7 +8197,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8232,7 +8212,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8249,7 +8228,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8262,6 +8240,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -8320,7 +8299,6 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "license": "MIT", - "peer": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -8484,7 +8462,6 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", "license": "MIT", - "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -8557,8 +8534,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -8571,8 +8547,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.startcase": { "version": "4.4.0", @@ -8585,8 +8560,7 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.zip": { "version": "4.2.0", @@ -8594,6 +8568,69 @@ "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -8774,16 +8811,333 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, - "node_modules/modern-tar": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", - "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, "engines": { - "node": ">=18.0.0" + "node": ">= 14.0.0" } }, - "node_modules/mri": { + "node_modules/mocha/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/mocha/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/modern-tar": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", + "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", @@ -8804,7 +9158,6 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "license": "ISC", - "peer": true, "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -8900,7 +9253,6 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.1.tgz", "integrity": "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "hosted-git-info": "^8.0.0", "semver": "^7.3.5", @@ -8924,7 +9276,6 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "license": "MIT", - "peer": true, "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" @@ -8941,7 +9292,6 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9282,7 +9632,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.21.4", "error-ex": "^1.3.2", @@ -9302,7 +9651,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=14.16" }, @@ -9315,7 +9663,6 @@ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -9462,8 +9809,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/pathval": { "version": "2.0.1", @@ -9590,7 +9936,6 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", @@ -9606,7 +9951,6 @@ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "license": "MIT", - "peer": true, "dependencies": { "parse-ms": "^4.0.0" }, @@ -9793,28 +10137,35 @@ ], "license": "MIT" }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react-is-18": { "name": "react-is", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-is-19": { "name": "react-is", "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/read-pkg": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/normalize-package-data": "^2.4.1", "normalize-package-data": "^6.0.0", @@ -9833,7 +10184,6 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "license": "MIT", - "peer": true, "dependencies": { "find-up": "^6.3.0", "read-pkg": "^8.1.0", @@ -9851,7 +10201,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -9868,7 +10217,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^6.0.0" }, @@ -9884,7 +10232,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "license": "MIT", - "peer": true, "dependencies": { "yocto-queue": "^1.0.0" }, @@ -9900,7 +10247,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^4.0.0" }, @@ -9916,7 +10262,6 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "license": "MIT", - "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -9926,7 +10271,6 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "license": "ISC", - "peer": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -9939,7 +10283,6 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", @@ -10046,7 +10389,6 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 14.18.0" }, @@ -10060,7 +10402,6 @@ "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "license": "MIT", - "peer": true, "dependencies": { "minimatch": "^3.0.5" }, @@ -10072,15 +10413,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/recursive-readdir/node_modules/brace-expansion": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10091,7 +10430,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10341,7 +10679,6 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.12.0" } @@ -10375,7 +10712,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -10542,6 +10878,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10869,7 +11215,6 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -11060,7 +11405,6 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -11289,6 +11633,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -11311,7 +11656,6 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -11439,7 +11783,6 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -11562,6 +11905,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11609,7 +11953,6 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -11718,6 +12061,7 @@ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -12325,6 +12669,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12438,6 +12783,7 @@ "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.28.0.tgz", "integrity": "sha512-ieFWi8dq57uZC6QMC2x6TllxKTRyInIMcOrVvwbHqVRYvJP8OLDtlH1bideGRIN0pgGHWStqplez2A95jS9bqA==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", @@ -12677,12 +13023,18 @@ "node": ">=0.10.0" } }, + "node_modules/workerpool": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -12730,7 +13082,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -12828,6 +13179,45 @@ "node": ">=12" } }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/yauzl": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", @@ -12845,7 +13235,6 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=12.20" }, @@ -12858,7 +13247,6 @@ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -12871,7 +13259,6 @@ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -12895,7 +13282,7 @@ }, "packages/browserstack-service": { "name": "@wdio/browserstack-service", - "version": "9.29.1", + "version": "9.33.1", "license": "MIT", "dependencies": { "@browserstack/ai-sdk-node": "1.5.17", @@ -12931,6 +13318,7 @@ "@wdio/reporter": "^9.0.0", "@wdio/types": "^9.0.0", "esbuild": "^0.27.2", + "mocha": "^10.8.2", "rimraf": "^6.0.1", "ts-proto": "^2.7.5", "typescript": "^5.8.3", diff --git a/packages/browserstack-service/README.md b/packages/browserstack-service/README.md index cd0127d..99ef781 100644 --- a/packages/browserstack-service/README.md +++ b/packages/browserstack-service/README.md @@ -76,6 +76,17 @@ export const config = { You can explore all the features of Test Reporting and Analytics in [this sandbox](https://automation.browserstack.com/) or read more about it [here](https://www.browserstack.com/docs/test-reporting-and-analytics/overview/what-is-test-observability). +#### Reporting of tests skipped by `bail` + +When a run is cut short by `bail`, the tests that never executed are still reported, with the status `skipped`, so the report accounts for every declared test rather than silently omitting them. This applies to both forms of `bail`, which stop a run at different granularities: + +| Setting | What it stops | What gets reported as `skipped` | +|---|---|---| +| `mochaOpts: { bail: true }` | Mocha halts on the first failure, so the rest of that spec never runs | The remaining tests in that spec file, including those in sibling `describe` blocks | +| `bail: ` (WebdriverIO) | The runner stops scheduling new spec files once `n` spec files have failed | Every test in the spec files that never started | + +Requires `mocha` as the framework and Test Reporting enabled. To list the tests in spec files that never started, the service loads those files without executing any test bodies — so a spec whose top-level code has side effects will run that code once more at the end of the run. + ### browserstackLocal Set this to true to enable routing connections from BrowserStack cloud through your computer. diff --git a/packages/browserstack-service/package.json b/packages/browserstack-service/package.json index e81b7ba..7a15804 100644 --- a/packages/browserstack-service/package.json +++ b/packages/browserstack-service/package.json @@ -89,6 +89,7 @@ "@wdio/reporter": "^9.0.0", "@wdio/types": "^9.0.0", "esbuild": "^0.27.2", + "mocha": "^10.8.2", "rimraf": "^6.0.1", "ts-proto": "^2.7.5", "typescript": "^5.8.3", diff --git a/packages/browserstack-service/src/cli/grpcClient.ts b/packages/browserstack-service/src/cli/grpcClient.ts index 5124318..5d14e0c 100644 --- a/packages/browserstack-service/src/cli/grpcClient.ts +++ b/packages/browserstack-service/src/cli/grpcClient.ts @@ -11,6 +11,7 @@ import { ConnectBinSessionRequestConstructor, TestFrameworkEventRequestConstructor, TestSessionEventRequestConstructor, + EnqueueTestEventRequestConstructor, ExecutionContextConstructor, LogCreatedEventRequestConstructor, // eslint-disable-next-line camelcase @@ -34,6 +35,8 @@ import type { StartBinSessionResponse, TestFrameworkEventResponse, TestSessionEventResponse, + EnqueueTestEventRequest, + EnqueueTestEventResponse, LogCreatedEventResponse, DriverInitResponse, FetchDriverExecuteParamsEventResponse, @@ -366,6 +369,32 @@ export class GrpcClient { * Send TestFrameworkEvent */ + /** + * Relay a single observability event through the binary, which forwards it using the + * build-scoped credential it holds internally. The launcher's own + * `BROWSERSTACK_TESTHUB_JWT` is the binary's account-auth token on this path (no build + * claim), so posting to the collector directly from here returns 401 — see SDK-7063. + * `eventUrl` is advisory; the binary logs it and routes by session. + */ + async enqueueTestEvent(eventUrl: string, eventData: unknown) { + try { + if (!this.client) { + this.logger.info('enqueueTestEvent: gRPC client not initialized.') + return + } + const request = EnqueueTestEventRequestConstructor.create({ + binSessionId: this.binSessionId, + eventUrl, + eventDataJson: Buffer.from(JSON.stringify(eventData)) + }) + const enqueuePromise = promisify(this.client!.enqueueTestEvent).bind(this.client!) as + (arg0: EnqueueTestEventRequest) => Promise + return await enqueuePromise(request) + } catch (error) { + this.logger.error(`Error in EnqueueTestEvent: ${util.format(error)}`) + } + } + async testFrameworkEvent(data: Omit) { // Generate unique event ID per call to avoid timing conflicts const uniqueEventId = `${PERFORMANCE_SDK_EVENTS.DISPATCHER_EVENTS.TEST_FRAMEWORK}-${Date.now()}-${Math.random().toString(36).substring(7)}` diff --git a/packages/browserstack-service/src/grpc/index.ts b/packages/browserstack-service/src/grpc/index.ts index 62a2f75..cc56d46 100644 --- a/packages/browserstack-service/src/grpc/index.ts +++ b/packages/browserstack-service/src/grpc/index.ts @@ -117,6 +117,17 @@ export const StopBinSessionRequestConstructor = { } } +export const EnqueueTestEventRequestConstructor = { + create: (init: Partial = {}): SdkMessages.EnqueueTestEventRequest => { + return { + binSessionId: '', + eventUrl: '', + eventDataJson: new Uint8Array(), + ...init + } + } +} + export const ConnectBinSessionRequestConstructor = { create: (init: Partial = {}): SdkMessages.ConnectBinSessionRequest => { return { diff --git a/packages/browserstack-service/src/launcher.ts b/packages/browserstack-service/src/launcher.ts index 00fb918..7e68956 100644 --- a/packages/browserstack-service/src/launcher.ts +++ b/packages/browserstack-service/src/launcher.ts @@ -51,6 +51,7 @@ import { } from './util.js' import CrashReporter from './crash-reporter.js' import { finalizeOrphanedRuns } from './testOps/openRunsJournal.js' +import { applyExcludes, hasPerCapabilitySpecFilters, reportUnlaunchedSpecs, resolveSpecPatterns } from './testOps/unlaunchedSpecReporter.js' import { BStackLogger } from './bstackLogger.js' import { PercyLogger } from './Percy/PercyLogger.js' import type Percy from './Percy/Percy.js' @@ -80,6 +81,13 @@ export default class BrowserstackLauncherService implements Services.ServiceInst private _percy?: Percy private _percyBestPlatformCaps?: WebdriverIO.Capabilities private readonly browserStackConfig: BrowserStackConfig + /** Spec files that actually reached a worker. Anything in the run's spec list that is + * missing from this by onComplete was dropped by wdio's top-level `bail`. */ + private _dispatchedSpecs: Set = new Set() + private _requestedCapabilities?: Capabilities.TestrunnerCapabilities + /** Workers that exited non-zero. wdio's `bail` counts failed runners, so this is what + * tells us whether bail actually fired and dropped work. */ + private _failedRunners: number = 0 constructor ( private _options: BrowserstackConfig & BrowserstackOptions, @@ -119,6 +127,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst process.env.TEST_OBSERVABILITY_BUILD_TAG = process.env.TEST_REPORTING_BUILD_TAG } + this._requestedCapabilities = capabilities this.browserStackConfig = BrowserStackConfig.getInstance(_options, _config, capabilities) BStackLogger.debug(`_options data: ${JSON.stringify(_options)}`) BStackLogger.debug(`webdriver capabilities data: ${JSON.stringify(capabilities)}`) @@ -232,8 +241,20 @@ export default class BrowserstackLauncherService implements Services.ServiceInst } } + async onWorkerEnd (cid: string, exitCode: number, specs?: string[], retries?: number) { + // Mirror wdio's own `_runnerFailed` accounting: a failed spec with retries left is + // requeued, not counted. Counting it here would let us conclude bail had fired while + // those specs are still going to run. + if (exitCode !== 0 && !(typeof retries === 'number' && retries > 0)) { + this._failedRunners++ + } + } + @PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SETUP) - async onWorkerStart (cid: string, caps: WebdriverIO.Capabilities) { + async onWorkerStart (cid: string, caps: WebdriverIO.Capabilities, specs?: string[]) { + for (const spec of specs || []) { + this._dispatchedSpecs.add(spec) + } try { if (this._options.percy && this._percyBestPlatformCaps) { const isThisBestPercyPlatform = ObjectsAreEqual(caps, this._percyBestPlatformCaps) @@ -616,6 +637,49 @@ export default class BrowserstackLauncherService implements Services.ServiceInst }) } + private async reportSpecsDroppedByBail() { + try { + // enumeration means loading spec files, which re-runs their top level — only worth + // it when bail could actually have dropped something and we have somewhere to report + if (this._config?.framework !== 'mocha' || !this._options.testObservability) { + return + } + const bail = this._config.bail + if (!(typeof bail === 'number' && bail > 0)) { + return + } + // Only report when bail actually fired. Without this, ANY spec wdio legitimately + // chose not to run (--spec, sharding, a filter we don't model) looks like it was + // dropped by bail. Gating on the real trigger makes those cases moot. + if (this._failedRunners < bail) { + BStackLogger.debug(`Skipping bail-dropped spec reporting: bail not reached (${this._failedRunners}/${bail} runners failed)`) + return + } + // Under sharding this process only ever owns a slice of the specs; the rest are + // another shard's work, not bail's doing, and the two are indistinguishable here. + const shard = this._config.shard + if (shard && typeof shard.total === 'number' && shard.total > 1) { + BStackLogger.debug(`Skipping bail-dropped spec reporting: run is sharded (${shard.current}/${shard.total})`) + return + } + // wdio narrows the spec set per capability; the top-level config then no longer + // describes what the run was going to execute, so opting out beats reporting a + // spec the user deliberately excluded + if (hasPerCapabilitySpecFilters(this._requestedCapabilities)) { + BStackLogger.debug('Skipping bail-dropped spec reporting: capabilities define their own specs/exclude') + return + } + const rootDir = this._config.rootDir || process.cwd() + const allSpecs = await resolveSpecPatterns((this._config.specs || []) as unknown[], rootDir) + // excluded files are never scheduled, so without this they would look "never ran" + const excludes = ((this._config.exclude || []) as unknown[]).filter((e): e is string => typeof e === 'string') + const candidates = applyExcludes(allSpecs, excludes, rootDir) + await reportUnlaunchedSpecs(candidates, this._dispatchedSpecs, rootDir) + } catch (err) { + BStackLogger.debug(`Error reporting specs dropped by bail: ${err}`) + } + } + @PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP) async onComplete () { PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP) @@ -629,6 +693,10 @@ export default class BrowserstackLauncherService implements Services.ServiceInst // SDK-4671: before stopping the build, synthesize TestRunFinished for any // test runs whose worker died mid-test, else they stay 'in progress' on TRA. await finalizeOrphanedRuns() + // SDK-7063: wdio's top-level `bail` drops whole spec files before they reach a + // worker, so their tests never report at all. Account for them as skipped while + // the build is still open. (mocha's in-spec bail is handled in service.afterTest.) + await this.reportSpecsDroppedByBail() // SDK-7061: capture the stop result so we only mark the build stopped when it // actually succeeded. A failed stop must leave buildStopped=false so the // process-exit cleanup re-stop path can still close the build. diff --git a/packages/browserstack-service/src/service.ts b/packages/browserstack-service/src/service.ts index b9e2bbe..04156b4 100644 --- a/packages/browserstack-service/src/service.ts +++ b/packages/browserstack-service/src/service.ts @@ -8,6 +8,7 @@ import { isBrowserstackSession, patchConsoleLogs, isTrue, + isFalse, getUniqueIdentifier, getHookType } from './util.js' @@ -87,6 +88,13 @@ export default class BrowserstackService implements Services.ServiceInstance { private _percyCaptureMode: string | undefined = undefined private _percyHandler?: PercyHandler private _turboScale + /** + * Only mocha's own bail drops the rest of a spec. wdio's top-level `bail` is a launcher + * spec-scheduling filter (it stops queueing further specs once N runners have failed) and + * never reaches mocha, so under it every test in the current spec still runs — enumerating + * on it would report tests as skipped that are about to execute. + */ + private _mochaBail: boolean = false constructor ( options: BrowserstackConfig & Options.Testrunner, @@ -104,6 +112,10 @@ export default class BrowserstackService implements Services.ServiceInstance { this._percy = isTrue(process.env.BROWSERSTACK_PERCY) this._percyCaptureMode = process.env.BROWSERSTACK_PERCY_CAPTURE_MODE this._turboScale = this._options.turboScale + // mirror mocha's own truthiness check on the option rather than isTrue(), which is a + // strict 'true' string compare and would miss a numeric `bail: 1` + const bailOpt: unknown = this._config?.mochaOpts?.bail + this._mochaBail = this._config?.framework === 'mocha' && Boolean(bailOpt) && !isFalse(bailOpt) PerformanceTester.startMonitoring('performance-report-service.csv') if (shouldProcessEventForTesthub('')) { @@ -542,6 +554,7 @@ export default class BrowserstackService implements Services.ServiceInstance { } await BrowserstackCLI.getInstance().getTestFramework()!.trackEvent(TestFrameworkState.LOG_REPORT, HookState.POST, { test, result: results }) await BrowserstackCLI.getInstance().getTestFramework()!.trackEvent(TestFrameworkState.TEST, HookState.POST, { test, result: results, suiteTitle: this._suiteTitle }) + await this.reportBailSkippedTests(test, results) return } @@ -550,6 +563,35 @@ export default class BrowserstackService implements Services.ServiceInstance { await this._percyHandler?.afterTest() } + /** + * mocha's `bail` aborts the run on the first failure, so every test the spec had not reached + * yet is dropped without emitting any event and never appears on the dashboard. Report them + * as skipped — same cascade the failed-hook path uses, from the spec's root suite so sibling + * describes are covered too (bail kills the whole spec, not just the failing describe). + */ + private async reportBailSkippedTests(test: Frameworks.Test, results: Frameworks.TestResult) { + if (!this._mochaBail || results.passed || results.skipped) { + return + } + // a retry is still queued — mocha has not dropped anything yet + if (results.retries && results.retries.attempts < results.retries.limit) { + return + } + try { + const framework = BrowserstackCLI.getInstance().getTestFramework() + let suite = test.ctx?.test?.parent + if (!framework || !suite) { + return + } + while (suite.parent) { + suite = suite.parent + } + await reportSuiteSkipped(framework, suite) + } catch (err) { + BStackLogger.debug(`Failed reporting bail-skipped tests: ${err}`) + } + } + @PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_HOOK, { hookType: 'after' }) async after (result: number) { try { diff --git a/packages/browserstack-service/src/testOps/specEnumerator.ts b/packages/browserstack-service/src/testOps/specEnumerator.ts new file mode 100644 index 0000000..cab5bb6 --- /dev/null +++ b/packages/browserstack-service/src/testOps/specEnumerator.ts @@ -0,0 +1,90 @@ +import path from 'node:path' +import { createRequire } from 'node:module' + +import { BStackLogger } from '../bstackLogger.js' + +/** + * Enumerates the tests declared in a spec file WITHOUT running them, so tests that never + * execute (mocha `bail` aborting a spec, wdio `bail` dropping whole spec files) can still be + * reported as skipped instead of vanishing from the run. + * + * Mocha builds its full suite tree during the declaration pass, before executing anything, so + * loading a file is enough to learn every test in it. Test bodies are never invoked — only the + * file's top level and its `describe` callbacks run. That is also the limitation: a spec whose + * top level performs real side effects will perform them again here. + * + * Loop-generated tests are covered, which is why this loads rather than statically parsing. + */ + +export interface EnumeratedTest { + /** `describe > nested describe > test` joined as mocha's fullTitle */ + fullTitle: string + title: string + /** titles of the enclosing suites, outermost first */ + scopes: string[] + file: string +} + +/** + * mocha is the user's dependency, not ours — resolve it from their project, never from the + * SDK's own tree. + */ +function loadMocha(cwd: string) { + try { + return createRequire(path.join(cwd, 'noop.js'))('mocha') + } catch (err) { + BStackLogger.debug(`specEnumerator: mocha not resolvable from ${cwd}: ${err}`) + return null + } +} + +/** Exported for tests: mocha is the user's dependency, so the walk is verified against a + * mocha-shaped fixture here and against real mocha end-to-end. */ +export function collect(suite: MochaSuite, file: string, scopes: string[], out: EnumeratedTest[]) { + for (const test of suite.tests || []) { + out.push({ + fullTitle: typeof test.fullTitle === 'function' ? test.fullTitle() : test.title, + title: test.title, + scopes: [...scopes], + file: test.file || file + }) + } + for (const child of suite.suites || []) { + collect(child, file, [...scopes, child.title], out) + } +} + +export interface MochaSuite { + title: string + tests?: { title: string, file?: string, fullTitle?: () => string }[] + suites?: MochaSuite[] +} + +/** + * @returns every test declared across `specs`, or an empty array if enumeration is not possible. + * Never throws — a failure here must not break the user's run. + */ +export async function enumerateSpecs(specs: string[], cwd: string = process.cwd()): Promise { + if (!specs?.length) { + return [] + } + const Mocha = loadMocha(cwd) + if (!Mocha) { + return [] + } + + const found: EnumeratedTest[] = [] + for (const spec of specs) { + const file = spec.startsWith('file://') ? spec.replace(/^file:\/\//, '') : spec + try { + // one instance per spec so a file that throws cannot poison the others + const mocha = new Mocha({ ui: 'bdd', reporter: 'min' }) + mocha.addFile(file) + await mocha.loadFilesAsync() + collect(mocha.suite as MochaSuite, file, [], found) + } catch (err) { + BStackLogger.debug(`specEnumerator: could not enumerate ${file}: ${err}`) + } + } + return found +} diff --git a/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts b/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts new file mode 100644 index 0000000..c055c2f --- /dev/null +++ b/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts @@ -0,0 +1,176 @@ +import path from 'node:path' + +import { v4 as uuidv4 } from 'uuid' + +import type { TestData, UploadType } from '../types.js' +import { batchAndPostEvents } from '../util.js' +import { DATA_BATCH_ENDPOINT } from '../constants.js' +import { BStackLogger } from '../bstackLogger.js' +import { BrowserstackCLI } from '../cli/index.js' +import { GrpcClient } from '../cli/grpcClient.js' +import { enumerateSpecs } from './specEnumerator.js' + +/** + * wdio's top-level `bail` stops the launcher scheduling further spec files once N runners have + * failed — those files never reach a worker, so nothing in them is ever reported and the tests + * simply vanish from the run. (This is distinct from mocha's `bail`, which drops the remaining + * tests inside a spec that IS running; that case is handled worker-side in service.afterTest.) + * + * Here we enumerate the dropped spec files and report their tests as skipped, so the Test Run + * accounts for every declared case. + * + * Emitting over HTTP from the launcher mirrors `finalizeOrphanedRuns` — by this point the + * workers, and their gRPC trackers, are gone. + */ + +/** + * Expand a wdio `specs` config (glob patterns, plain paths, or nested arrays used for grouping) + * into absolute file paths. Mirrors what the runner does when it builds its schedule. + */ +export async function resolveSpecPatterns(specs: unknown[], rootDir: string): Promise { + const out: string[] = [] + try { + const { sync: globSync } = await import('glob') + for (const entry of (specs || []).flat(2)) { + if (typeof entry !== 'string') { + continue + } + if (entry.startsWith('file://')) { + out.push(entry.replace(/^file:\/\//, '')) + continue + } + const pattern = entry.replace(/\\/g, '/') + const matches = globSync(pattern, { cwd: rootDir, absolute: true }) + if (matches.length) { + out.push(...matches) + } else if (!pattern.includes('*')) { + out.push(path.resolve(rootDir, pattern)) + } + } + } catch (e) { + BStackLogger.debug('unlaunchedSpecReporter: failed to resolve spec patterns: ' + e) + } + return [...new Set(out)] +} + +/** + * On the CLI/gRPC path the launcher's `BROWSERSTACK_TESTHUB_JWT` is the binary's account-auth + * token — it carries no build claim, so posting straight to the collector's batch endpoint + * returns 401. Relay through the binary instead; it holds the build-scoped credential. + * Falls back to the direct HTTP batch when no binary is running. + */ +export async function emitEvents(events: UploadType[]): Promise { + if (!BrowserstackCLI.getInstance().isRunning()) { + await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'UNLAUNCHED_SPEC_SKIP_REPORTING', events) + return + } + // the binary's handler takes one event per call, not a batch + for (const event of events) { + await GrpcClient.getInstance().enqueueTestEvent(DATA_BATCH_ENDPOINT, event) + } +} + +export function findUnlaunchedSpecs(allSpecs: string[], dispatchedSpecs: Set): string[] { + const normalize = (s: string) => path.resolve(s.startsWith('file://') ? s.replace(/^file:\/\//, '') : s) + const dispatched = new Set([...dispatchedSpecs].map(normalize)) + return allSpecs.filter((spec) => !dispatched.has(normalize(spec))) +} + +/** + * Mirrors wdio's own `ConfigParser.filterSpecs`: when every exclude entry looks like a path + * (contains a separator or a glob) it matches exactly, otherwise entries are treated as + * substrings — `exclude: ['flaky']` drops any spec whose path contains "flaky". Matching only + * on resolved paths would silently under-exclude and report those specs as skipped. + */ +export function applyExcludes(specs: string[], excludes: string[], rootDir: string): string[] { + if (!excludes?.length) { + return specs + } + const pathLike = excludes.every(e => e.includes('/') || e.includes('\\') || e.includes('*')) + if (pathLike) { + const resolved = new Set(excludes.map(e => path.resolve(rootDir, e))) + return specs.filter(spec => !resolved.has(path.resolve(spec))) + } + return specs.filter(spec => !excludes.some(e => spec.includes(e))) +} + +/** + * Per-capability `wdio:specs` / `wdio:exclude` mean the set of specs a run was ever going to + * execute cannot be derived from the top-level config alone. Rather than guess and risk + * reporting a deliberately-excluded spec as skipped, we opt out of reporting entirely. + */ +export function hasPerCapabilitySpecFilters(capabilities: unknown): boolean { + const caps = Array.isArray(capabilities) + ? capabilities + : Object.values((capabilities ?? {}) as Record).map(c => c?.capabilities ?? c) + + return caps.some((cap) => { + const c = (cap ?? {}) as Record + // multiremote nests the real caps one level down + const nested = (c.capabilities ?? {}) as Record + return Boolean( + c['wdio:specs'] || c['wdio:exclude'] || c.specs || c.exclude || + nested['wdio:specs'] || nested['wdio:exclude'] + ) + }) +} + +export async function reportUnlaunchedSpecs( + allSpecs: string[], + dispatchedSpecs: Set, + cwd: string = process.cwd() +): Promise { + try { + const unlaunched = findUnlaunchedSpecs(allSpecs, dispatchedSpecs) + if (!unlaunched.length) { + return 0 + } + BStackLogger.debug(`unlaunchedSpecReporter: ${unlaunched.length} spec file(s) never ran; enumerating`) + + const tests = await enumerateSpecs(unlaunched, cwd) + if (!tests.length) { + return 0 + } + + const at = new Date().toISOString() + const events: UploadType[] = [] + // rootDir is the wdio config's directory, which may sit below the project root — a + // relative path from there can escape upwards ('../tests/specs/x.js'). Anchor on cwd + // and keep the absolute path if the result still escapes, rather than emit a path the + // backend will reject. + const relativize = (file: string) => { + const rel = path.relative(process.cwd(), file) + return !rel || rel.startsWith('..') ? file : rel + } + for (const test of tests) { + // Same shape `getUniqueIdentifier` produces for tests that DO run, so a test + // reported skipped here and the same test on a later successful run carry one + // identity. Mocha's own `fullTitle` (space-joined) would not match. + const identifier = `${test.scopes.at(-1) ?? ''} - ${test.title}` + const testData: TestData = { + uuid: uuidv4(), + type: 'test', + name: test.title, + scope: identifier, + scopes: test.scopes, + identifier, + file_name: relativize(test.file), + location: relativize(test.file), + started_at: at, + finished_at: at, + duration_in_ms: 0, + framework: 'mocha', + result: 'skipped' + } + events.push({ event_type: 'TestRunStarted', test_run: testData }) + events.push({ event_type: 'TestRunFinished', test_run: testData }) + } + + await emitEvents(events) + BStackLogger.info(`Reported ${tests.length} test(s) as skipped from ${unlaunched.length} spec file(s) that never ran`) + return tests.length + } catch (e) { + BStackLogger.debug('unlaunchedSpecReporter: failed to report unlaunched specs: ' + e) + return 0 + } +} diff --git a/packages/browserstack-service/tests/service.test.ts b/packages/browserstack-service/tests/service.test.ts index 95a2e32..5e6e1d4 100644 --- a/packages/browserstack-service/tests/service.test.ts +++ b/packages/browserstack-service/tests/service.test.ts @@ -2427,3 +2427,85 @@ describe('_isAppAutomate honors skipAppOverride', () => { expect(svc._isAppAutomate()).toBe(false) }) }) + +describe('afterTest bail skip cascade (SDK-7063)', () => { + let getInstanceSpy: ReturnType + + // reportSkippedTest de-dupes on `${parent} - ${title}` in a module-scope Set that outlives + // each test, so every case here needs its own titles. + const buildTree = (tag: string) => { + const root: any = { title: '', tests: [], suites: [], parent: undefined } + const suiteA: any = { title: `${tag} Suite A`, tests: [], suites: [], parent: root } + const suiteB: any = { title: `${tag} Suite B`, tests: [], suites: [], parent: root } + root.suites.push(suiteA, suiteB) + + const ran: any = { title: `${tag} A1`, state: 'passed', parent: suiteA, file: '/spec/a.js' } + const failing: any = { title: `${tag} A2`, state: 'failed', parent: suiteA, file: '/spec/a.js' } + const dropped: any = { title: `${tag} A3`, parent: suiteA, file: '/spec/a.js' } + suiteA.tests.push(ran, failing, dropped) + // sibling top-level describe — only reachable because the cascade walks up to root + suiteB.tests.push({ title: `${tag} B1`, parent: suiteB, file: '/spec/a.js' }) + + failing.ctx = { test: { parent: suiteA } } + return { failing, root } + } + + const makeService = (config: Record) => new BrowserstackService( + { testObservability: false } as any, + [] as any, + { user: 'foo', key: 'bar', ...config } as any + ) + + const runAfterTest = async (svc: BrowserstackService, failing: any, results: Record) => { + const trackEvent = vi.fn().mockResolvedValue(undefined) + getInstanceSpy = vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ + isRunning: () => true, + getTestFramework: () => ({ trackEvent }) + } as any) + await svc.afterTest(failing, undefined as never, results as any) + return trackEvent + } + + afterEach(() => { + getInstanceSpy?.mockRestore() + }) + + it('reports un-run tests across sibling describes when mocha bail is on', async () => { + const { failing } = buildTree('bail1') + const svc = makeService({ framework: 'mocha', mochaOpts: { bail: true } }) + const trackEvent = await runAfterTest(svc, failing, { passed: false }) + + // 2 events close the failing test (LOG_REPORT/POST + TEST/POST), then 4 per skipped test. + // A3 (same describe) and B1 (SIBLING describe) => 2 skipped => 8. + expect(trackEvent).toHaveBeenCalledTimes(2 + 8) + }) + + it('does not cascade when only wdio-level bail is set', async () => { + // wdio's `bail` never halts a spec, so those tests still run — reporting them + // as skipped here would double-report them. + const { failing } = buildTree('bail2') + const svc = makeService({ framework: 'mocha', bail: 1 }) + const trackEvent = await runAfterTest(svc, failing, { passed: false }) + + expect(trackEvent).toHaveBeenCalledTimes(2) + }) + + it('does not cascade while a retry is still queued', async () => { + const { failing } = buildTree('bail3') + const svc = makeService({ framework: 'mocha', mochaOpts: { bail: true } }) + const trackEvent = await runAfterTest(svc, failing, { + passed: false, + retries: { attempts: 0, limit: 2 } + }) + + expect(trackEvent).toHaveBeenCalledTimes(2) + }) + + it('does not cascade when the test passed', async () => { + const { failing } = buildTree('bail4') + const svc = makeService({ framework: 'mocha', mochaOpts: { bail: true } }) + const trackEvent = await runAfterTest(svc, failing, { passed: true }) + + expect(trackEvent).toHaveBeenCalledTimes(2) + }) +}) diff --git a/packages/browserstack-service/tests/testOps/specEnumerator.test.ts b/packages/browserstack-service/tests/testOps/specEnumerator.test.ts new file mode 100644 index 0000000..94d8c93 --- /dev/null +++ b/packages/browserstack-service/tests/testOps/specEnumerator.test.ts @@ -0,0 +1,143 @@ +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' + +import { afterAll, describe, expect, it } from 'vitest' + +import { collect, enumerateSpecs } from '../../src/testOps/specEnumerator.js' +import type { EnumeratedTest, MochaSuite } from '../../src/testOps/specEnumerator.js' + +// Mirrors the shape mocha builds during its declaration pass: nested suites, `fullTitle()` +// on each test. Verified against real mocha end-to-end; kept as a fixture here because mocha +// is the user's dependency, not the SDK's. +const mochaShapedSuite = (): MochaSuite => ({ + title: '', + tests: [], + suites: [ + { + title: 'Suite A', + tests: [ + { title: 'A1', file: '/spec/a.js', fullTitle: () => 'Suite A A1' }, + { title: 'A2', file: '/spec/a.js', fullTitle: () => 'Suite A A2' } + ], + suites: [ + { + title: 'Nested', + tests: [{ title: 'N1', file: '/spec/a.js', fullTitle: () => 'Suite A Nested N1' }], + suites: [] + } + ] + }, + { + title: 'Suite B', + tests: [{ title: 'B1', file: '/spec/a.js', fullTitle: () => 'Suite B B1' }], + suites: [] + } + ] +}) + +describe('specEnumerator.collect', () => { + it('walks nested and sibling suites, recording scopes outermost-first', () => { + const out: EnumeratedTest[] = [] + collect(mochaShapedSuite(), '/spec/a.js', [], out) + + expect(out.map(t => t.fullTitle)).toEqual([ + 'Suite A A1', + 'Suite A A2', + 'Suite A Nested N1', + 'Suite B B1' + ]) + expect(out.find(t => t.title === 'N1')?.scopes).toEqual(['Suite A', 'Nested']) + // sibling top-level describe is reached — the case bail must not miss + expect(out.find(t => t.title === 'B1')?.scopes).toEqual(['Suite B']) + }) + + it('falls back to title when a test has no fullTitle fn, and to the spec file when unset', () => { + const out: EnumeratedTest[] = [] + collect( + { title: '', tests: [{ title: 'bare' }], suites: [] }, + '/spec/fallback.js', + [], + out + ) + + expect(out).toHaveLength(1) + expect(out[0].fullTitle).toBe('bare') + expect(out[0].file).toBe('/spec/fallback.js') + }) + + it('tolerates suites with no tests or suites arrays', () => { + const out: EnumeratedTest[] = [] + collect({ title: 'empty' } as MochaSuite, '/spec/e.js', [], out) + expect(out).toEqual([]) + }) +}) + +describe('specEnumerator.enumerateSpecs', () => { + it('returns empty for no specs', async () => { + expect(await enumerateSpecs([])).toEqual([]) + }) + + it('degrades to empty rather than throwing when a spec cannot be loaded', async () => { + // instrumentation must never break the user's run + await expect(enumerateSpecs(['/spec/does-not-exist.js'])).resolves.toEqual([]) + }) +}) + +describe('specEnumerator.enumerateSpecs against real mocha', () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sdk7063-')) + afterAll(() => fs.rmSync(dir, { recursive: true, force: true })) + + const write = (name: string, body: string) => { + const file = path.join(dir, name) + fs.writeFileSync(file, body) + return file + } + + it('enumerates declared tests without executing their bodies', async () => { + // `browser` is undefined here — if a body ran, this would throw + const spec = write('basic.spec.cjs', ` + describe('Outer', () => { + it('T1', async () => { await browser.url('x') }) + describe('Inner', () => { + it('T2', async () => { await browser.url('x') }) + }) + }) + describe('Sibling', () => { + it('T3', async () => { await browser.url('x') }) + }) + `) + + const found = await enumerateSpecs([spec]) + + expect(found.map(t => t.fullTitle)).toEqual(['Outer T1', 'Outer Inner T2', 'Sibling T3']) + expect(found.find(t => t.title === 'T2')?.scopes).toEqual(['Outer', 'Inner']) + expect(found.every(t => t.file === spec)).toBe(true) + }) + + it('enumerates loop-generated tests — the case static parsing would miss', async () => { + const spec = write('dynamic.spec.cjs', ` + describe('Generated', () => { + for (let i = 1; i <= 3; i++) { + it('case ' + i, async () => { await browser.url('x') }) + } + }) + `) + + const found = await enumerateSpecs([spec]) + + expect(found.map(t => t.fullTitle)).toEqual([ + 'Generated case 1', 'Generated case 2', 'Generated case 3' + ]) + }) + + it('keeps enumerating other specs when one fails to load', async () => { + // one Mocha instance per spec, so a throwing file must not poison the rest + const bad = write('broken.spec.cjs', 'throw new Error("boom at load time")') + const good = write('good.spec.cjs', "describe('Fine', () => { it('OK', () => {}) })") + + const found = await enumerateSpecs([bad, good]) + + expect(found.map(t => t.fullTitle)).toEqual(['Fine OK']) + }) +}) diff --git a/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts b/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts new file mode 100644 index 0000000..5a75ad1 --- /dev/null +++ b/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts @@ -0,0 +1,204 @@ +import path from 'node:path' + +import { describe, expect, it, vi, beforeEach } from 'vitest' + +import { + applyExcludes, + emitEvents, + findUnlaunchedSpecs, + hasPerCapabilitySpecFilters, + reportUnlaunchedSpecs, + resolveSpecPatterns +} from '../../src/testOps/unlaunchedSpecReporter.js' +import * as specEnumerator from '../../src/testOps/specEnumerator.js' +import { BrowserstackCLI } from '../../src/cli/index.js' +import { GrpcClient } from '../../src/cli/grpcClient.js' +import * as util from '../../src/util.js' +import type { UploadType } from '../../src/types.js' + +describe('findUnlaunchedSpecs', () => { + it('returns specs that never reached a worker', () => { + const all = ['/p/a.spec.js', '/p/b.spec.js', '/p/c.spec.js'] + expect(findUnlaunchedSpecs(all, new Set(['/p/a.spec.js']))) + .toEqual(['/p/b.spec.js', '/p/c.spec.js']) + }) + + it('matches regardless of file:// prefix or relative form', () => { + const all = ['/p/a.spec.js', '/p/b.spec.js'] + // workers report specs as file:// URLs; the config list does not + expect(findUnlaunchedSpecs(all, new Set(['file:///p/a.spec.js']))) + .toEqual(['/p/b.spec.js']) + }) + + it('returns nothing when every spec ran', () => { + const all = ['/p/a.spec.js'] + expect(findUnlaunchedSpecs(all, new Set(['/p/a.spec.js']))).toEqual([]) + }) + + it('returns everything when no worker ever started', () => { + const all = ['/p/a.spec.js', '/p/b.spec.js'] + expect(findUnlaunchedSpecs(all, new Set())).toEqual(all) + }) +}) + +describe('resolveSpecPatterns', () => { + const fixtures = path.join(process.cwd(), 'tests', 'testOps') + + it('resolves a concrete relative path even when it matches no glob', async () => { + const resolved = await resolveSpecPatterns(['unlaunchedSpecReporter.test.ts'], fixtures) + expect(resolved).toEqual([path.join(fixtures, 'unlaunchedSpecReporter.test.ts')]) + }) + + it('expands glob patterns to absolute paths', async () => { + const resolved = await resolveSpecPatterns(['*.test.ts'], fixtures) + expect(resolved.length).toBeGreaterThan(0) + expect(resolved.every(p => path.isAbsolute(p))).toBe(true) + expect(resolved).toContain(path.join(fixtures, 'unlaunchedSpecReporter.test.ts')) + }) + + it('flattens nested arrays (wdio spec grouping) and de-dupes', async () => { + const resolved = await resolveSpecPatterns( + [['unlaunchedSpecReporter.test.ts'], 'unlaunchedSpecReporter.test.ts'], + fixtures + ) + expect(resolved).toHaveLength(1) + }) + + it('strips the file:// prefix', async () => { + const resolved = await resolveSpecPatterns(['file:///p/a.spec.js'], fixtures) + expect(resolved).toEqual(['/p/a.spec.js']) + }) + + it('ignores non-string entries instead of throwing', async () => { + await expect(resolveSpecPatterns([null, 42, undefined], fixtures)).resolves.toEqual([]) + }) +}) + +describe('applyExcludes', () => { + const root = '/proj' + const specs = ['/proj/tests/a.spec.js', '/proj/tests/flaky-b.spec.js', '/proj/tests/c.spec.js'] + + it('matches exactly when every entry looks like a path', () => { + expect(applyExcludes(specs, ['tests/flaky-b.spec.js'], root)) + .toEqual(['/proj/tests/a.spec.js', '/proj/tests/c.spec.js']) + }) + + it('matches by substring when entries are bare keywords — wdio filterSpecs semantics', () => { + // `exclude: ['flaky']` must drop any spec whose path contains it; resolving it as a + // literal path would match nothing and the spec would be reported as skipped + expect(applyExcludes(specs, ['flaky'], root)) + .toEqual(['/proj/tests/a.spec.js', '/proj/tests/c.spec.js']) + }) + + it('handles glob entries as path-like', () => { + expect(applyExcludes(specs, ['/proj/tests/*.spec.js'], root)).toEqual(specs) + }) + + it('returns specs untouched for an empty or missing exclude list', () => { + expect(applyExcludes(specs, [], root)).toEqual(specs) + expect(applyExcludes(specs, undefined as unknown as string[], root)).toEqual(specs) + }) +}) + +describe('hasPerCapabilitySpecFilters', () => { + // when a capability narrows the spec set, the top-level config no longer describes what + // the run intended to execute — reporting from it would mark excluded specs as skipped + it('is false for plain capabilities', () => { + expect(hasPerCapabilitySpecFilters([{ browserName: 'chrome' }])).toBe(false) + }) + + it('detects wdio:specs and wdio:exclude', () => { + expect(hasPerCapabilitySpecFilters([{ browserName: 'chrome', 'wdio:specs': ['a.js'] }])).toBe(true) + expect(hasPerCapabilitySpecFilters([{ browserName: 'chrome', 'wdio:exclude': ['b.js'] }])).toBe(true) + }) + + it('detects the deprecated specs/exclude spelling', () => { + expect(hasPerCapabilitySpecFilters([{ exclude: ['b.js'] }])).toBe(true) + }) + + it('detects filters nested under multiremote capabilities', () => { + expect(hasPerCapabilitySpecFilters({ + browserA: { capabilities: { browserName: 'chrome', 'wdio:exclude': ['b.js'] } } + })).toBe(true) + }) + + it('is false for multiremote without filters, and for empty input', () => { + expect(hasPerCapabilitySpecFilters({ + browserA: { capabilities: { browserName: 'chrome' } } + })).toBe(false) + expect(hasPerCapabilitySpecFilters([])).toBe(false) + expect(hasPerCapabilitySpecFilters(undefined)).toBe(false) + }) +}) + +describe('reported identity matches getUniqueIdentifier', () => { + // A test reported skipped here and the same test on a later successful run must carry one + // identity, or TM sees two tests and rerun reconciliation breaks. getUniqueIdentifier + // (util.ts) builds `${parent} - ${title}`; mocha's own fullTitle is space-joined and would + // not match. This pins the format. + it('builds `parent - title`, not mocha fullTitle', async () => { + const enqueueTestEvent = vi.fn().mockResolvedValue({ success: true }) + vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => true } as any) + vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) + vi.spyOn(specEnumerator, 'enumerateSpecs').mockResolvedValue([ + { + fullTitle: 'Suite B SB-TC1', + title: 'SB-TC1', + scopes: ['Suite B'], + file: '/proj/tests/b.spec.js' + } + ]) + + await reportUnlaunchedSpecs(['/proj/tests/b.spec.js'], new Set(), '/proj') + + const sent = enqueueTestEvent.mock.calls.map(c => c[1] as { test_run: Record }) + expect(sent).toHaveLength(2) // TestRunStarted + TestRunFinished + for (const event of sent) { + expect(event.test_run.identifier).toBe('Suite B - SB-TC1') + expect(event.test_run.scope).toBe('Suite B - SB-TC1') + expect(event.test_run.name).toBe('SB-TC1') + expect(event.test_run.scopes).toEqual(['Suite B']) + expect(event.test_run.result).toBe('skipped') + } + }) +}) + +describe('emitEvents transport routing', () => { + const events: UploadType[] = [ + { event_type: 'TestRunStarted', test_run: { uuid: 'u1' } }, + { event_type: 'TestRunFinished', test_run: { uuid: 'u1' } } + ] + + beforeEach(() => { + vi.restoreAllMocks() + }) + + it('relays one gRPC call per event when the binary is running', async () => { + // the launcher's TESTHUB_JWT has no build claim on this path, so the collector would + // 401 — events must go through the binary, which holds the build-scoped credential + const enqueueTestEvent = vi.fn().mockResolvedValue({ success: true }) + vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => true } as any) + vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) + const batchSpy = vi.spyOn(util, 'batchAndPostEvents').mockResolvedValue(undefined as any) + + await emitEvents(events) + + // one call per event — the binary's handler takes a single event, not a batch + expect(enqueueTestEvent).toHaveBeenCalledTimes(2) + expect(enqueueTestEvent.mock.calls[0][1]).toEqual(events[0]) + expect(batchSpy).not.toHaveBeenCalled() + }) + + it('falls back to the HTTP batch when no binary is running', async () => { + vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => false } as any) + const enqueueTestEvent = vi.fn() + vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) + const batchSpy = vi.spyOn(util, 'batchAndPostEvents').mockResolvedValue(undefined as any) + + await emitEvents(events) + + expect(batchSpy).toHaveBeenCalledTimes(1) + expect(batchSpy.mock.calls[0][2]).toEqual(events) + expect(enqueueTestEvent).not.toHaveBeenCalled() + }) +}) From 67c3d11f368e53ad5e9c03a44ee85fa17e3c365c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:00:09 +0000 Subject: [PATCH 2/6] chore(changeset): auto-generate from PR template (minor) --- .changeset/pr-122.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/pr-122.md diff --git a/.changeset/pr-122.md b/.changeset/pr-122.md new file mode 100644 index 0000000..64cf117 --- /dev/null +++ b/.changeset/pr-122.md @@ -0,0 +1,6 @@ +--- +"@wdio/browserstack-service": minor +--- + +- Tests that never run because of `bail` are now reported as `skipped` instead of being left out of the report — so a Test Run accounts for every test it was meant to execute. +- Covers both `mochaOpts.bail` (the rest of the failing spec) and WebdriverIO's `bail` (spec files that never started). From 6dd98d61a9b732690322e9de782a44b133589962 Mon Sep 17 00:00:00 2001 From: Harshit Date: Thu, 6 Aug 2026 09:56:30 +0530 Subject: [PATCH 3/6] fix(SDK-7063): match executed-test identity field-for-field Wire evidence showed a test that runs emits `name` and `identifier` as the bare title, with only `scope` carrying the parent: identifier: 'MA-TC1 passes' scope: 'Multi Spec A - MA-TC1 passes' Skipped tests reported for never-launched specs were setting `identifier` to the parent-qualified form, so the same test reported skipped and later run would not have matched on that field. Verified on build bpt0jvulwfonuoucepdu892xhzborork2hy7tgqb: executed and skipped tests now emit identical conventions for all three fields. --- .../src/testOps/unlaunchedSpecReporter.ts | 13 +++++++------ .../tests/testOps/unlaunchedSpecReporter.test.ts | 7 ++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts b/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts index c055c2f..29b568b 100644 --- a/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts +++ b/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts @@ -143,17 +143,18 @@ export async function reportUnlaunchedSpecs( return !rel || rel.startsWith('..') ? file : rel } for (const test of tests) { - // Same shape `getUniqueIdentifier` produces for tests that DO run, so a test - // reported skipped here and the same test on a later successful run carry one - // identity. Mocha's own `fullTitle` (space-joined) would not match. - const identifier = `${test.scopes.at(-1) ?? ''} - ${test.title}` + // Match field-for-field what a test that DOES run emits, so a test reported + // skipped here and the same test on a later successful run are one test: + // `name`/`identifier` are the bare title and only `scope` carries the parent. + // (Observed on the wire; mocha's space-joined `fullTitle` matches neither.) + const scope = `${test.scopes.at(-1) ?? ''} - ${test.title}` const testData: TestData = { uuid: uuidv4(), type: 'test', name: test.title, - scope: identifier, + scope, scopes: test.scopes, - identifier, + identifier: test.title, file_name: relativize(test.file), location: relativize(test.file), started_at: at, diff --git a/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts b/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts index 5a75ad1..d387173 100644 --- a/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts +++ b/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts @@ -136,7 +136,7 @@ describe('reported identity matches getUniqueIdentifier', () => { // identity, or TM sees two tests and rerun reconciliation breaks. getUniqueIdentifier // (util.ts) builds `${parent} - ${title}`; mocha's own fullTitle is space-joined and would // not match. This pins the format. - it('builds `parent - title`, not mocha fullTitle', async () => { + it('emits bare title for name/identifier and `parent - title` for scope', async () => { const enqueueTestEvent = vi.fn().mockResolvedValue({ success: true }) vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => true } as any) vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) @@ -154,9 +154,10 @@ describe('reported identity matches getUniqueIdentifier', () => { const sent = enqueueTestEvent.mock.calls.map(c => c[1] as { test_run: Record }) expect(sent).toHaveLength(2) // TestRunStarted + TestRunFinished for (const event of sent) { - expect(event.test_run.identifier).toBe('Suite B - SB-TC1') - expect(event.test_run.scope).toBe('Suite B - SB-TC1') + // field-for-field parity with what an executed test emits expect(event.test_run.name).toBe('SB-TC1') + expect(event.test_run.identifier).toBe('SB-TC1') + expect(event.test_run.scope).toBe('Suite B - SB-TC1') expect(event.test_run.scopes).toEqual(['Suite B']) expect(event.test_run.result).toBe('skipped') } From 752f1e69a42d6051fffaed84a4dd54ba25bb04c4 Mon Sep 17 00:00:00 2001 From: Harshit Date: Thu, 6 Aug 2026 12:40:57 +0530 Subject: [PATCH 4/6] refactor(SDK-7063): scope PR to the in-spec bail fix only The customer's config is `bail: 0` with `mochaOpts.bail: true`, so only Mocha's in-spec bail applies to them. WebdriverIO's spec-file-level bail is disabled in their setup and the launcher-side reporting never runs. Drop the never-launched-spec work from this PR so the change that unblocks them is small and built on the already-proven `reportSuiteSkipped` path. That work is preserved on `feat/sdk-7063-unlaunched-specs` and can land separately on its own review. Removes: specEnumerator, unlaunchedSpecReporter, the launcher hooks, the EnqueueTestEvent client wrapper, and the mocha devDependency they needed. README and changeset narrowed to match. --- .changeset/pr-122.md | 3 +- package-lock.json | 734 +++++------------- packages/browserstack-service/README.md | 9 +- packages/browserstack-service/package.json | 1 - .../src/cli/grpcClient.ts | 29 - .../browserstack-service/src/grpc/index.ts | 11 - packages/browserstack-service/src/launcher.ts | 70 +- .../src/testOps/specEnumerator.ts | 90 --- .../src/testOps/unlaunchedSpecReporter.ts | 177 ----- .../tests/testOps/specEnumerator.test.ts | 143 ---- .../testOps/unlaunchedSpecReporter.test.ts | 205 ----- 11 files changed, 178 insertions(+), 1294 deletions(-) delete mode 100644 packages/browserstack-service/src/testOps/specEnumerator.ts delete mode 100644 packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts delete mode 100644 packages/browserstack-service/tests/testOps/specEnumerator.test.ts delete mode 100644 packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts diff --git a/.changeset/pr-122.md b/.changeset/pr-122.md index 64cf117..6067414 100644 --- a/.changeset/pr-122.md +++ b/.changeset/pr-122.md @@ -2,5 +2,4 @@ "@wdio/browserstack-service": minor --- -- Tests that never run because of `bail` are now reported as `skipped` instead of being left out of the report — so a Test Run accounts for every test it was meant to execute. -- Covers both `mochaOpts.bail` (the rest of the failing spec) and WebdriverIO's `bail` (spec files that never started). +- Tests that never run because Mocha's `bail` halted a spec are now reported as `skipped` instead of being left out of the report — so a Test Run accounts for every test in that spec, including tests in sibling `describe` blocks. diff --git a/package-lock.json b/package-lock.json index b7a8965..b8d072b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -494,6 +494,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -510,6 +511,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -526,6 +528,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -542,6 +545,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -558,6 +562,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -574,6 +579,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -590,6 +596,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -606,6 +613,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -622,6 +630,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -638,6 +647,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -654,6 +664,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -670,6 +681,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -686,6 +698,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -702,6 +715,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -718,6 +732,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -734,6 +749,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -750,6 +766,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -766,6 +783,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -782,6 +800,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -798,6 +817,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -814,6 +834,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -830,6 +851,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -846,6 +868,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -862,6 +885,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -878,6 +902,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -894,6 +919,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -1097,6 +1123,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1106,6 +1133,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -1130,6 +1158,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -1151,6 +1180,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/figures": "^1.0.15", @@ -1178,6 +1208,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/external-editor": "^1.0.3", @@ -1200,6 +1231,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", @@ -1243,6 +1275,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1252,6 +1285,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -1273,6 +1307,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10" @@ -1294,6 +1329,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -1316,6 +1352,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", @@ -1345,6 +1382,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/type": "^3.0.10", @@ -1367,6 +1405,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", @@ -1390,6 +1429,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -1414,6 +1454,7 @@ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1452,6 +1493,7 @@ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "license": "MIT", + "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -1461,6 +1503,7 @@ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/get-type": "30.1.0" }, @@ -1473,6 +1516,7 @@ "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "license": "MIT", + "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -1482,6 +1526,7 @@ "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "jest-regex-util": "30.4.0" @@ -1495,6 +1540,7 @@ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "license": "MIT", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.34.0" }, @@ -1507,6 +1553,7 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/pattern": "30.4.0", "@jest/schemas": "30.4.1", @@ -1525,6 +1572,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1540,6 +1588,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2194,19 +2243,22 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@sinclair/typebox": { "version": "0.34.49", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2255,13 +2307,15 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "license": "MIT", + "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -2271,6 +2325,7 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/istanbul-lib-report": "*" } @@ -2287,7 +2342,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2308,7 +2362,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/tar": { "version": "7.0.87", @@ -2347,6 +2402,7 @@ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "license": "MIT", + "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -2355,7 +2411,8 @@ "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/yauzl": { "version": "2.10.3", @@ -2407,7 +2464,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -2661,6 +2717,7 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "license": "MIT", + "peer": true, "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -2733,6 +2790,7 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", @@ -2760,6 +2818,7 @@ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "4.1.8", "convert-source-map": "^2.0.0", @@ -2774,6 +2833,7 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "license": "MIT", + "peer": true, "dependencies": { "tinyrainbow": "^3.1.0" }, @@ -2786,6 +2846,7 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -3030,7 +3091,6 @@ "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.28.0.tgz", "integrity": "sha512-poYsF7Gbm8kfYX6tdsPG862anOQKyUT8roe+rwdXaKSorz/s4XDJBm4kJiid6LgWKeAMSXDYzFODzDxhYhugWg==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.20.0" }, @@ -3052,7 +3112,6 @@ "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.18.0.tgz", "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==", "license": "MIT", - "peer": true, "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", @@ -3192,7 +3251,6 @@ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3270,6 +3328,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -3277,20 +3336,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/anynum": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", @@ -3710,6 +3755,7 @@ "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.12.0" } @@ -3928,19 +3974,6 @@ "node": ">=4" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3972,13 +4005,6 @@ "node": ">=8" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true, - "license": "ISC" - }, "node_modules/browserslist": { "version": "4.28.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", @@ -3999,7 +4025,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.38", "caniuse-lite": "^1.0.30001799", @@ -4141,19 +4166,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001800", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", @@ -4289,6 +4301,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", + "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -4351,6 +4364,7 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "license": "ISC", + "peer": true, "engines": { "node": ">= 12" } @@ -4436,6 +4450,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20" } @@ -4478,7 +4493,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/core-js-compat": { "version": "3.49.0", @@ -4530,6 +4546,7 @@ "resolved": "https://registry.npmjs.org/create-wdio/-/create-wdio-9.28.0.tgz", "integrity": "sha512-3Oa7tGK5QA9z1bdTFonnEb3OTTyNJtI2np7YzEC6F9es+94PFsFLLn5nIWs+fhJSdoueQyLy8P2cSWBO3Ohijw==", "license": "MIT", + "peer": true, "dependencies": { "chalk": "^5.3.0", "commander": "^14.0.0", @@ -4906,6 +4923,7 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=12" }, @@ -5033,6 +5051,7 @@ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "jake": "^10.8.5" }, @@ -5305,6 +5324,7 @@ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -5354,6 +5374,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -5386,7 +5407,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6093,6 +6113,7 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", "license": "MIT", + "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", @@ -6119,6 +6140,7 @@ "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "license": "MIT", + "peer": true, "dependencies": { "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", @@ -6146,6 +6168,7 @@ "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.6.8.tgz", "integrity": "sha512-VfLC9o84B40LEw+zX7UykUptKkscX1rPYY4jaAsQ6KyKL0X0ltDkWzKIUiY9g/u0ApNerrkhM/QEY0TDT8pJOQ==", "license": "MIT", + "peer": true, "dependencies": { "@vitest/snapshot": "^4.1.7", "deep-eql": "^5.0.2", @@ -6177,6 +6200,7 @@ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "license": "MIT", + "peer": true, "dependencies": { "tinyrainbow": "^3.1.0" }, @@ -6189,6 +6213,7 @@ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "license": "MIT", + "peer": true, "dependencies": { "@vitest/pretty-format": "4.1.8", "@vitest/utils": "4.1.8", @@ -6203,13 +6228,15 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/expect-webdriverio/node_modules/tinyrainbow": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -6386,6 +6413,7 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "license": "MIT", + "peer": true, "dependencies": { "is-unicode-supported": "^2.0.0" }, @@ -6414,6 +6442,7 @@ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "minimatch": "^5.0.1" } @@ -6422,13 +6451,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/filelist/node_modules/brace-expansion": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -6438,6 +6469,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6472,16 +6504,6 @@ "node": ">=8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -6837,6 +6859,7 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "license": "MIT", + "peer": true, "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" @@ -7123,16 +7146,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/headers-utils": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.5.tgz", @@ -7144,6 +7157,7 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", "license": "ISC", + "peer": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -7238,6 +7252,7 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18.18.0" } @@ -7380,6 +7395,7 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", "license": "MIT", + "peer": true, "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", @@ -7485,19 +7501,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", @@ -7813,6 +7816,7 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -7889,6 +7893,7 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -7984,6 +7989,7 @@ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", @@ -8001,6 +8007,7 @@ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "license": "MIT", + "peer": true, "dependencies": { "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", @@ -8016,6 +8023,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8031,6 +8039,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8047,6 +8056,7 @@ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "license": "MIT", + "peer": true, "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", @@ -8062,6 +8072,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8077,6 +8088,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8093,6 +8105,7 @@ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.4.1", @@ -8114,6 +8127,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8129,6 +8143,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8145,6 +8160,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8157,6 +8173,7 @@ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.4.1", "@types/node": "*", @@ -8171,6 +8188,7 @@ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "license": "MIT", + "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } @@ -8180,6 +8198,7 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "30.4.1", "@types/node": "*", @@ -8197,6 +8216,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -8212,6 +8232,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -8228,6 +8249,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8240,7 +8262,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "license": "MIT", - "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -8299,6 +8320,7 @@ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "license": "MIT", + "peer": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -8462,6 +8484,7 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -8534,7 +8557,8 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -8547,7 +8571,8 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.startcase": { "version": "4.4.0", @@ -8560,7 +8585,8 @@ "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.zip": { "version": "4.2.0", @@ -8568,69 +8594,6 @@ "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", "license": "MIT" }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -8811,330 +8774,13 @@ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, + "node_modules/modern-tar": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", + "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", "license": "MIT", - "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/mocha/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/diff": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", - "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", - "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/modern-tar": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz", - "integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" + "node": ">=18.0.0" } }, "node_modules/mri": { @@ -9158,6 +8804,7 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "license": "ISC", + "peer": true, "engines": { "node": "^18.17.0 || >=20.5.0" } @@ -9253,6 +8900,7 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-7.0.1.tgz", "integrity": "sha512-linxNAT6M0ebEYZOx2tO6vBEFsVgnPpv+AVjk0wJHfaUIbq31Jm3T6vvZaarnOeWDh8ShnwXuaAyM7WT3RzErA==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "hosted-git-info": "^8.0.0", "semver": "^7.3.5", @@ -9276,6 +8924,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "license": "MIT", + "peer": true, "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" @@ -9292,6 +8941,7 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9632,6 +9282,7 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.21.4", "error-ex": "^1.3.2", @@ -9651,6 +9302,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=14.16" }, @@ -9663,6 +9315,7 @@ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -9809,7 +9462,8 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/pathval": { "version": "2.0.1", @@ -9936,6 +9590,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", @@ -9951,6 +9606,7 @@ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "license": "MIT", + "peer": true, "dependencies": { "parse-ms": "^4.0.0" }, @@ -10137,35 +9793,28 @@ ], "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/react-is-18": { "name": "react-is", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-is-19": { "name": "react-is", "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/read-pkg": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/normalize-package-data": "^2.4.1", "normalize-package-data": "^6.0.0", @@ -10184,6 +9833,7 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", "license": "MIT", + "peer": true, "dependencies": { "find-up": "^6.3.0", "read-pkg": "^8.1.0", @@ -10201,6 +9851,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "license": "MIT", + "peer": true, "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" @@ -10217,6 +9868,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "license": "MIT", + "peer": true, "dependencies": { "p-locate": "^6.0.0" }, @@ -10232,6 +9884,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "license": "MIT", + "peer": true, "dependencies": { "yocto-queue": "^1.0.0" }, @@ -10247,6 +9900,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "license": "MIT", + "peer": true, "dependencies": { "p-limit": "^4.0.0" }, @@ -10262,6 +9916,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } @@ -10271,6 +9926,7 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "license": "ISC", + "peer": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -10283,6 +9939,7 @@ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", "license": "BSD-2-Clause", + "peer": true, "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", @@ -10389,6 +10046,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 14.18.0" }, @@ -10402,6 +10060,7 @@ "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "license": "MIT", + "peer": true, "dependencies": { "minimatch": "^3.0.5" }, @@ -10413,13 +10072,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/recursive-readdir/node_modules/brace-expansion": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "license": "MIT", + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10430,6 +10091,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10679,6 +10341,7 @@ "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.12.0" } @@ -10712,6 +10375,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -10878,16 +10542,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -11215,6 +10869,7 @@ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "license": "MIT", + "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -11405,6 +11060,7 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -11633,7 +11289,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11656,6 +11311,7 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=14.0.0" } @@ -11783,6 +11439,7 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -11905,7 +11562,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11953,6 +11609,7 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -12061,7 +11718,6 @@ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -12669,7 +12325,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -12783,7 +12438,6 @@ "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.28.0.tgz", "integrity": "sha512-ieFWi8dq57uZC6QMC2x6TllxKTRyInIMcOrVvwbHqVRYvJP8OLDtlH1bideGRIN0pgGHWStqplez2A95jS9bqA==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "^20.11.30", "@types/sinonjs__fake-timers": "^8.1.5", @@ -13023,18 +12677,12 @@ "node": ">=0.10.0" } }, - "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -13082,6 +12730,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -13179,45 +12828,6 @@ "node": ">=12" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yauzl": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", @@ -13235,6 +12845,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=12.20" }, @@ -13247,6 +12858,7 @@ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -13259,6 +12871,7 @@ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -13282,7 +12895,7 @@ }, "packages/browserstack-service": { "name": "@wdio/browserstack-service", - "version": "9.33.1", + "version": "9.29.1", "license": "MIT", "dependencies": { "@browserstack/ai-sdk-node": "1.5.17", @@ -13318,7 +12931,6 @@ "@wdio/reporter": "^9.0.0", "@wdio/types": "^9.0.0", "esbuild": "^0.27.2", - "mocha": "^10.8.2", "rimraf": "^6.0.1", "ts-proto": "^2.7.5", "typescript": "^5.8.3", diff --git a/packages/browserstack-service/README.md b/packages/browserstack-service/README.md index 99ef781..17f3216 100644 --- a/packages/browserstack-service/README.md +++ b/packages/browserstack-service/README.md @@ -78,14 +78,11 @@ You can explore all the features of Test Reporting and Analytics in [this sandbo #### Reporting of tests skipped by `bail` -When a run is cut short by `bail`, the tests that never executed are still reported, with the status `skipped`, so the report accounts for every declared test rather than silently omitting them. This applies to both forms of `bail`, which stop a run at different granularities: +With `mochaOpts: { bail: true }`, Mocha halts a spec on its first failure and the remaining tests in that file never run. Those tests are now reported with the status `skipped`, so the report accounts for every test in the spec rather than silently omitting them. Tests in sibling `describe` blocks in the same file are covered too. -| Setting | What it stops | What gets reported as `skipped` | -|---|---|---| -| `mochaOpts: { bail: true }` | Mocha halts on the first failure, so the rest of that spec never runs | The remaining tests in that spec file, including those in sibling `describe` blocks | -| `bail: ` (WebdriverIO) | The runner stops scheduling new spec files once `n` spec files have failed | Every test in the spec files that never started | +Requires `mocha` as the framework and Test Reporting enabled. -Requires `mocha` as the framework and Test Reporting enabled. To list the tests in spec files that never started, the service loads those files without executing any test bodies — so a spec whose top-level code has side effects will run that code once more at the end of the run. +Note that WebdriverIO's own top-level `bail` option is a different setting: it counts failed *spec files* and stops scheduling further ones, rather than stopping tests within a spec. Tests in spec files that never start are not currently reported. ### browserstackLocal Set this to true to enable routing connections from BrowserStack cloud through your computer. diff --git a/packages/browserstack-service/package.json b/packages/browserstack-service/package.json index 7a15804..e81b7ba 100644 --- a/packages/browserstack-service/package.json +++ b/packages/browserstack-service/package.json @@ -89,7 +89,6 @@ "@wdio/reporter": "^9.0.0", "@wdio/types": "^9.0.0", "esbuild": "^0.27.2", - "mocha": "^10.8.2", "rimraf": "^6.0.1", "ts-proto": "^2.7.5", "typescript": "^5.8.3", diff --git a/packages/browserstack-service/src/cli/grpcClient.ts b/packages/browserstack-service/src/cli/grpcClient.ts index 5d14e0c..5124318 100644 --- a/packages/browserstack-service/src/cli/grpcClient.ts +++ b/packages/browserstack-service/src/cli/grpcClient.ts @@ -11,7 +11,6 @@ import { ConnectBinSessionRequestConstructor, TestFrameworkEventRequestConstructor, TestSessionEventRequestConstructor, - EnqueueTestEventRequestConstructor, ExecutionContextConstructor, LogCreatedEventRequestConstructor, // eslint-disable-next-line camelcase @@ -35,8 +34,6 @@ import type { StartBinSessionResponse, TestFrameworkEventResponse, TestSessionEventResponse, - EnqueueTestEventRequest, - EnqueueTestEventResponse, LogCreatedEventResponse, DriverInitResponse, FetchDriverExecuteParamsEventResponse, @@ -369,32 +366,6 @@ export class GrpcClient { * Send TestFrameworkEvent */ - /** - * Relay a single observability event through the binary, which forwards it using the - * build-scoped credential it holds internally. The launcher's own - * `BROWSERSTACK_TESTHUB_JWT` is the binary's account-auth token on this path (no build - * claim), so posting to the collector directly from here returns 401 — see SDK-7063. - * `eventUrl` is advisory; the binary logs it and routes by session. - */ - async enqueueTestEvent(eventUrl: string, eventData: unknown) { - try { - if (!this.client) { - this.logger.info('enqueueTestEvent: gRPC client not initialized.') - return - } - const request = EnqueueTestEventRequestConstructor.create({ - binSessionId: this.binSessionId, - eventUrl, - eventDataJson: Buffer.from(JSON.stringify(eventData)) - }) - const enqueuePromise = promisify(this.client!.enqueueTestEvent).bind(this.client!) as - (arg0: EnqueueTestEventRequest) => Promise - return await enqueuePromise(request) - } catch (error) { - this.logger.error(`Error in EnqueueTestEvent: ${util.format(error)}`) - } - } - async testFrameworkEvent(data: Omit) { // Generate unique event ID per call to avoid timing conflicts const uniqueEventId = `${PERFORMANCE_SDK_EVENTS.DISPATCHER_EVENTS.TEST_FRAMEWORK}-${Date.now()}-${Math.random().toString(36).substring(7)}` diff --git a/packages/browserstack-service/src/grpc/index.ts b/packages/browserstack-service/src/grpc/index.ts index cc56d46..62a2f75 100644 --- a/packages/browserstack-service/src/grpc/index.ts +++ b/packages/browserstack-service/src/grpc/index.ts @@ -117,17 +117,6 @@ export const StopBinSessionRequestConstructor = { } } -export const EnqueueTestEventRequestConstructor = { - create: (init: Partial = {}): SdkMessages.EnqueueTestEventRequest => { - return { - binSessionId: '', - eventUrl: '', - eventDataJson: new Uint8Array(), - ...init - } - } -} - export const ConnectBinSessionRequestConstructor = { create: (init: Partial = {}): SdkMessages.ConnectBinSessionRequest => { return { diff --git a/packages/browserstack-service/src/launcher.ts b/packages/browserstack-service/src/launcher.ts index 7e68956..00fb918 100644 --- a/packages/browserstack-service/src/launcher.ts +++ b/packages/browserstack-service/src/launcher.ts @@ -51,7 +51,6 @@ import { } from './util.js' import CrashReporter from './crash-reporter.js' import { finalizeOrphanedRuns } from './testOps/openRunsJournal.js' -import { applyExcludes, hasPerCapabilitySpecFilters, reportUnlaunchedSpecs, resolveSpecPatterns } from './testOps/unlaunchedSpecReporter.js' import { BStackLogger } from './bstackLogger.js' import { PercyLogger } from './Percy/PercyLogger.js' import type Percy from './Percy/Percy.js' @@ -81,13 +80,6 @@ export default class BrowserstackLauncherService implements Services.ServiceInst private _percy?: Percy private _percyBestPlatformCaps?: WebdriverIO.Capabilities private readonly browserStackConfig: BrowserStackConfig - /** Spec files that actually reached a worker. Anything in the run's spec list that is - * missing from this by onComplete was dropped by wdio's top-level `bail`. */ - private _dispatchedSpecs: Set = new Set() - private _requestedCapabilities?: Capabilities.TestrunnerCapabilities - /** Workers that exited non-zero. wdio's `bail` counts failed runners, so this is what - * tells us whether bail actually fired and dropped work. */ - private _failedRunners: number = 0 constructor ( private _options: BrowserstackConfig & BrowserstackOptions, @@ -127,7 +119,6 @@ export default class BrowserstackLauncherService implements Services.ServiceInst process.env.TEST_OBSERVABILITY_BUILD_TAG = process.env.TEST_REPORTING_BUILD_TAG } - this._requestedCapabilities = capabilities this.browserStackConfig = BrowserStackConfig.getInstance(_options, _config, capabilities) BStackLogger.debug(`_options data: ${JSON.stringify(_options)}`) BStackLogger.debug(`webdriver capabilities data: ${JSON.stringify(capabilities)}`) @@ -241,20 +232,8 @@ export default class BrowserstackLauncherService implements Services.ServiceInst } } - async onWorkerEnd (cid: string, exitCode: number, specs?: string[], retries?: number) { - // Mirror wdio's own `_runnerFailed` accounting: a failed spec with retries left is - // requeued, not counted. Counting it here would let us conclude bail had fired while - // those specs are still going to run. - if (exitCode !== 0 && !(typeof retries === 'number' && retries > 0)) { - this._failedRunners++ - } - } - @PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SETUP) - async onWorkerStart (cid: string, caps: WebdriverIO.Capabilities, specs?: string[]) { - for (const spec of specs || []) { - this._dispatchedSpecs.add(spec) - } + async onWorkerStart (cid: string, caps: WebdriverIO.Capabilities) { try { if (this._options.percy && this._percyBestPlatformCaps) { const isThisBestPercyPlatform = ObjectsAreEqual(caps, this._percyBestPlatformCaps) @@ -637,49 +616,6 @@ export default class BrowserstackLauncherService implements Services.ServiceInst }) } - private async reportSpecsDroppedByBail() { - try { - // enumeration means loading spec files, which re-runs their top level — only worth - // it when bail could actually have dropped something and we have somewhere to report - if (this._config?.framework !== 'mocha' || !this._options.testObservability) { - return - } - const bail = this._config.bail - if (!(typeof bail === 'number' && bail > 0)) { - return - } - // Only report when bail actually fired. Without this, ANY spec wdio legitimately - // chose not to run (--spec, sharding, a filter we don't model) looks like it was - // dropped by bail. Gating on the real trigger makes those cases moot. - if (this._failedRunners < bail) { - BStackLogger.debug(`Skipping bail-dropped spec reporting: bail not reached (${this._failedRunners}/${bail} runners failed)`) - return - } - // Under sharding this process only ever owns a slice of the specs; the rest are - // another shard's work, not bail's doing, and the two are indistinguishable here. - const shard = this._config.shard - if (shard && typeof shard.total === 'number' && shard.total > 1) { - BStackLogger.debug(`Skipping bail-dropped spec reporting: run is sharded (${shard.current}/${shard.total})`) - return - } - // wdio narrows the spec set per capability; the top-level config then no longer - // describes what the run was going to execute, so opting out beats reporting a - // spec the user deliberately excluded - if (hasPerCapabilitySpecFilters(this._requestedCapabilities)) { - BStackLogger.debug('Skipping bail-dropped spec reporting: capabilities define their own specs/exclude') - return - } - const rootDir = this._config.rootDir || process.cwd() - const allSpecs = await resolveSpecPatterns((this._config.specs || []) as unknown[], rootDir) - // excluded files are never scheduled, so without this they would look "never ran" - const excludes = ((this._config.exclude || []) as unknown[]).filter((e): e is string => typeof e === 'string') - const candidates = applyExcludes(allSpecs, excludes, rootDir) - await reportUnlaunchedSpecs(candidates, this._dispatchedSpecs, rootDir) - } catch (err) { - BStackLogger.debug(`Error reporting specs dropped by bail: ${err}`) - } - } - @PerformanceTester.Measure(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP) async onComplete () { PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP) @@ -693,10 +629,6 @@ export default class BrowserstackLauncherService implements Services.ServiceInst // SDK-4671: before stopping the build, synthesize TestRunFinished for any // test runs whose worker died mid-test, else they stay 'in progress' on TRA. await finalizeOrphanedRuns() - // SDK-7063: wdio's top-level `bail` drops whole spec files before they reach a - // worker, so their tests never report at all. Account for them as skipped while - // the build is still open. (mocha's in-spec bail is handled in service.afterTest.) - await this.reportSpecsDroppedByBail() // SDK-7061: capture the stop result so we only mark the build stopped when it // actually succeeded. A failed stop must leave buildStopped=false so the // process-exit cleanup re-stop path can still close the build. diff --git a/packages/browserstack-service/src/testOps/specEnumerator.ts b/packages/browserstack-service/src/testOps/specEnumerator.ts deleted file mode 100644 index cab5bb6..0000000 --- a/packages/browserstack-service/src/testOps/specEnumerator.ts +++ /dev/null @@ -1,90 +0,0 @@ -import path from 'node:path' -import { createRequire } from 'node:module' - -import { BStackLogger } from '../bstackLogger.js' - -/** - * Enumerates the tests declared in a spec file WITHOUT running them, so tests that never - * execute (mocha `bail` aborting a spec, wdio `bail` dropping whole spec files) can still be - * reported as skipped instead of vanishing from the run. - * - * Mocha builds its full suite tree during the declaration pass, before executing anything, so - * loading a file is enough to learn every test in it. Test bodies are never invoked — only the - * file's top level and its `describe` callbacks run. That is also the limitation: a spec whose - * top level performs real side effects will perform them again here. - * - * Loop-generated tests are covered, which is why this loads rather than statically parsing. - */ - -export interface EnumeratedTest { - /** `describe > nested describe > test` joined as mocha's fullTitle */ - fullTitle: string - title: string - /** titles of the enclosing suites, outermost first */ - scopes: string[] - file: string -} - -/** - * mocha is the user's dependency, not ours — resolve it from their project, never from the - * SDK's own tree. - */ -function loadMocha(cwd: string) { - try { - return createRequire(path.join(cwd, 'noop.js'))('mocha') - } catch (err) { - BStackLogger.debug(`specEnumerator: mocha not resolvable from ${cwd}: ${err}`) - return null - } -} - -/** Exported for tests: mocha is the user's dependency, so the walk is verified against a - * mocha-shaped fixture here and against real mocha end-to-end. */ -export function collect(suite: MochaSuite, file: string, scopes: string[], out: EnumeratedTest[]) { - for (const test of suite.tests || []) { - out.push({ - fullTitle: typeof test.fullTitle === 'function' ? test.fullTitle() : test.title, - title: test.title, - scopes: [...scopes], - file: test.file || file - }) - } - for (const child of suite.suites || []) { - collect(child, file, [...scopes, child.title], out) - } -} - -export interface MochaSuite { - title: string - tests?: { title: string, file?: string, fullTitle?: () => string }[] - suites?: MochaSuite[] -} - -/** - * @returns every test declared across `specs`, or an empty array if enumeration is not possible. - * Never throws — a failure here must not break the user's run. - */ -export async function enumerateSpecs(specs: string[], cwd: string = process.cwd()): Promise { - if (!specs?.length) { - return [] - } - const Mocha = loadMocha(cwd) - if (!Mocha) { - return [] - } - - const found: EnumeratedTest[] = [] - for (const spec of specs) { - const file = spec.startsWith('file://') ? spec.replace(/^file:\/\//, '') : spec - try { - // one instance per spec so a file that throws cannot poison the others - const mocha = new Mocha({ ui: 'bdd', reporter: 'min' }) - mocha.addFile(file) - await mocha.loadFilesAsync() - collect(mocha.suite as MochaSuite, file, [], found) - } catch (err) { - BStackLogger.debug(`specEnumerator: could not enumerate ${file}: ${err}`) - } - } - return found -} diff --git a/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts b/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts deleted file mode 100644 index 29b568b..0000000 --- a/packages/browserstack-service/src/testOps/unlaunchedSpecReporter.ts +++ /dev/null @@ -1,177 +0,0 @@ -import path from 'node:path' - -import { v4 as uuidv4 } from 'uuid' - -import type { TestData, UploadType } from '../types.js' -import { batchAndPostEvents } from '../util.js' -import { DATA_BATCH_ENDPOINT } from '../constants.js' -import { BStackLogger } from '../bstackLogger.js' -import { BrowserstackCLI } from '../cli/index.js' -import { GrpcClient } from '../cli/grpcClient.js' -import { enumerateSpecs } from './specEnumerator.js' - -/** - * wdio's top-level `bail` stops the launcher scheduling further spec files once N runners have - * failed — those files never reach a worker, so nothing in them is ever reported and the tests - * simply vanish from the run. (This is distinct from mocha's `bail`, which drops the remaining - * tests inside a spec that IS running; that case is handled worker-side in service.afterTest.) - * - * Here we enumerate the dropped spec files and report their tests as skipped, so the Test Run - * accounts for every declared case. - * - * Emitting over HTTP from the launcher mirrors `finalizeOrphanedRuns` — by this point the - * workers, and their gRPC trackers, are gone. - */ - -/** - * Expand a wdio `specs` config (glob patterns, plain paths, or nested arrays used for grouping) - * into absolute file paths. Mirrors what the runner does when it builds its schedule. - */ -export async function resolveSpecPatterns(specs: unknown[], rootDir: string): Promise { - const out: string[] = [] - try { - const { sync: globSync } = await import('glob') - for (const entry of (specs || []).flat(2)) { - if (typeof entry !== 'string') { - continue - } - if (entry.startsWith('file://')) { - out.push(entry.replace(/^file:\/\//, '')) - continue - } - const pattern = entry.replace(/\\/g, '/') - const matches = globSync(pattern, { cwd: rootDir, absolute: true }) - if (matches.length) { - out.push(...matches) - } else if (!pattern.includes('*')) { - out.push(path.resolve(rootDir, pattern)) - } - } - } catch (e) { - BStackLogger.debug('unlaunchedSpecReporter: failed to resolve spec patterns: ' + e) - } - return [...new Set(out)] -} - -/** - * On the CLI/gRPC path the launcher's `BROWSERSTACK_TESTHUB_JWT` is the binary's account-auth - * token — it carries no build claim, so posting straight to the collector's batch endpoint - * returns 401. Relay through the binary instead; it holds the build-scoped credential. - * Falls back to the direct HTTP batch when no binary is running. - */ -export async function emitEvents(events: UploadType[]): Promise { - if (!BrowserstackCLI.getInstance().isRunning()) { - await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'UNLAUNCHED_SPEC_SKIP_REPORTING', events) - return - } - // the binary's handler takes one event per call, not a batch - for (const event of events) { - await GrpcClient.getInstance().enqueueTestEvent(DATA_BATCH_ENDPOINT, event) - } -} - -export function findUnlaunchedSpecs(allSpecs: string[], dispatchedSpecs: Set): string[] { - const normalize = (s: string) => path.resolve(s.startsWith('file://') ? s.replace(/^file:\/\//, '') : s) - const dispatched = new Set([...dispatchedSpecs].map(normalize)) - return allSpecs.filter((spec) => !dispatched.has(normalize(spec))) -} - -/** - * Mirrors wdio's own `ConfigParser.filterSpecs`: when every exclude entry looks like a path - * (contains a separator or a glob) it matches exactly, otherwise entries are treated as - * substrings — `exclude: ['flaky']` drops any spec whose path contains "flaky". Matching only - * on resolved paths would silently under-exclude and report those specs as skipped. - */ -export function applyExcludes(specs: string[], excludes: string[], rootDir: string): string[] { - if (!excludes?.length) { - return specs - } - const pathLike = excludes.every(e => e.includes('/') || e.includes('\\') || e.includes('*')) - if (pathLike) { - const resolved = new Set(excludes.map(e => path.resolve(rootDir, e))) - return specs.filter(spec => !resolved.has(path.resolve(spec))) - } - return specs.filter(spec => !excludes.some(e => spec.includes(e))) -} - -/** - * Per-capability `wdio:specs` / `wdio:exclude` mean the set of specs a run was ever going to - * execute cannot be derived from the top-level config alone. Rather than guess and risk - * reporting a deliberately-excluded spec as skipped, we opt out of reporting entirely. - */ -export function hasPerCapabilitySpecFilters(capabilities: unknown): boolean { - const caps = Array.isArray(capabilities) - ? capabilities - : Object.values((capabilities ?? {}) as Record).map(c => c?.capabilities ?? c) - - return caps.some((cap) => { - const c = (cap ?? {}) as Record - // multiremote nests the real caps one level down - const nested = (c.capabilities ?? {}) as Record - return Boolean( - c['wdio:specs'] || c['wdio:exclude'] || c.specs || c.exclude || - nested['wdio:specs'] || nested['wdio:exclude'] - ) - }) -} - -export async function reportUnlaunchedSpecs( - allSpecs: string[], - dispatchedSpecs: Set, - cwd: string = process.cwd() -): Promise { - try { - const unlaunched = findUnlaunchedSpecs(allSpecs, dispatchedSpecs) - if (!unlaunched.length) { - return 0 - } - BStackLogger.debug(`unlaunchedSpecReporter: ${unlaunched.length} spec file(s) never ran; enumerating`) - - const tests = await enumerateSpecs(unlaunched, cwd) - if (!tests.length) { - return 0 - } - - const at = new Date().toISOString() - const events: UploadType[] = [] - // rootDir is the wdio config's directory, which may sit below the project root — a - // relative path from there can escape upwards ('../tests/specs/x.js'). Anchor on cwd - // and keep the absolute path if the result still escapes, rather than emit a path the - // backend will reject. - const relativize = (file: string) => { - const rel = path.relative(process.cwd(), file) - return !rel || rel.startsWith('..') ? file : rel - } - for (const test of tests) { - // Match field-for-field what a test that DOES run emits, so a test reported - // skipped here and the same test on a later successful run are one test: - // `name`/`identifier` are the bare title and only `scope` carries the parent. - // (Observed on the wire; mocha's space-joined `fullTitle` matches neither.) - const scope = `${test.scopes.at(-1) ?? ''} - ${test.title}` - const testData: TestData = { - uuid: uuidv4(), - type: 'test', - name: test.title, - scope, - scopes: test.scopes, - identifier: test.title, - file_name: relativize(test.file), - location: relativize(test.file), - started_at: at, - finished_at: at, - duration_in_ms: 0, - framework: 'mocha', - result: 'skipped' - } - events.push({ event_type: 'TestRunStarted', test_run: testData }) - events.push({ event_type: 'TestRunFinished', test_run: testData }) - } - - await emitEvents(events) - BStackLogger.info(`Reported ${tests.length} test(s) as skipped from ${unlaunched.length} spec file(s) that never ran`) - return tests.length - } catch (e) { - BStackLogger.debug('unlaunchedSpecReporter: failed to report unlaunched specs: ' + e) - return 0 - } -} diff --git a/packages/browserstack-service/tests/testOps/specEnumerator.test.ts b/packages/browserstack-service/tests/testOps/specEnumerator.test.ts deleted file mode 100644 index 94d8c93..0000000 --- a/packages/browserstack-service/tests/testOps/specEnumerator.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import fs from 'node:fs' -import os from 'node:os' -import path from 'node:path' - -import { afterAll, describe, expect, it } from 'vitest' - -import { collect, enumerateSpecs } from '../../src/testOps/specEnumerator.js' -import type { EnumeratedTest, MochaSuite } from '../../src/testOps/specEnumerator.js' - -// Mirrors the shape mocha builds during its declaration pass: nested suites, `fullTitle()` -// on each test. Verified against real mocha end-to-end; kept as a fixture here because mocha -// is the user's dependency, not the SDK's. -const mochaShapedSuite = (): MochaSuite => ({ - title: '', - tests: [], - suites: [ - { - title: 'Suite A', - tests: [ - { title: 'A1', file: '/spec/a.js', fullTitle: () => 'Suite A A1' }, - { title: 'A2', file: '/spec/a.js', fullTitle: () => 'Suite A A2' } - ], - suites: [ - { - title: 'Nested', - tests: [{ title: 'N1', file: '/spec/a.js', fullTitle: () => 'Suite A Nested N1' }], - suites: [] - } - ] - }, - { - title: 'Suite B', - tests: [{ title: 'B1', file: '/spec/a.js', fullTitle: () => 'Suite B B1' }], - suites: [] - } - ] -}) - -describe('specEnumerator.collect', () => { - it('walks nested and sibling suites, recording scopes outermost-first', () => { - const out: EnumeratedTest[] = [] - collect(mochaShapedSuite(), '/spec/a.js', [], out) - - expect(out.map(t => t.fullTitle)).toEqual([ - 'Suite A A1', - 'Suite A A2', - 'Suite A Nested N1', - 'Suite B B1' - ]) - expect(out.find(t => t.title === 'N1')?.scopes).toEqual(['Suite A', 'Nested']) - // sibling top-level describe is reached — the case bail must not miss - expect(out.find(t => t.title === 'B1')?.scopes).toEqual(['Suite B']) - }) - - it('falls back to title when a test has no fullTitle fn, and to the spec file when unset', () => { - const out: EnumeratedTest[] = [] - collect( - { title: '', tests: [{ title: 'bare' }], suites: [] }, - '/spec/fallback.js', - [], - out - ) - - expect(out).toHaveLength(1) - expect(out[0].fullTitle).toBe('bare') - expect(out[0].file).toBe('/spec/fallback.js') - }) - - it('tolerates suites with no tests or suites arrays', () => { - const out: EnumeratedTest[] = [] - collect({ title: 'empty' } as MochaSuite, '/spec/e.js', [], out) - expect(out).toEqual([]) - }) -}) - -describe('specEnumerator.enumerateSpecs', () => { - it('returns empty for no specs', async () => { - expect(await enumerateSpecs([])).toEqual([]) - }) - - it('degrades to empty rather than throwing when a spec cannot be loaded', async () => { - // instrumentation must never break the user's run - await expect(enumerateSpecs(['/spec/does-not-exist.js'])).resolves.toEqual([]) - }) -}) - -describe('specEnumerator.enumerateSpecs against real mocha', () => { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sdk7063-')) - afterAll(() => fs.rmSync(dir, { recursive: true, force: true })) - - const write = (name: string, body: string) => { - const file = path.join(dir, name) - fs.writeFileSync(file, body) - return file - } - - it('enumerates declared tests without executing their bodies', async () => { - // `browser` is undefined here — if a body ran, this would throw - const spec = write('basic.spec.cjs', ` - describe('Outer', () => { - it('T1', async () => { await browser.url('x') }) - describe('Inner', () => { - it('T2', async () => { await browser.url('x') }) - }) - }) - describe('Sibling', () => { - it('T3', async () => { await browser.url('x') }) - }) - `) - - const found = await enumerateSpecs([spec]) - - expect(found.map(t => t.fullTitle)).toEqual(['Outer T1', 'Outer Inner T2', 'Sibling T3']) - expect(found.find(t => t.title === 'T2')?.scopes).toEqual(['Outer', 'Inner']) - expect(found.every(t => t.file === spec)).toBe(true) - }) - - it('enumerates loop-generated tests — the case static parsing would miss', async () => { - const spec = write('dynamic.spec.cjs', ` - describe('Generated', () => { - for (let i = 1; i <= 3; i++) { - it('case ' + i, async () => { await browser.url('x') }) - } - }) - `) - - const found = await enumerateSpecs([spec]) - - expect(found.map(t => t.fullTitle)).toEqual([ - 'Generated case 1', 'Generated case 2', 'Generated case 3' - ]) - }) - - it('keeps enumerating other specs when one fails to load', async () => { - // one Mocha instance per spec, so a throwing file must not poison the rest - const bad = write('broken.spec.cjs', 'throw new Error("boom at load time")') - const good = write('good.spec.cjs', "describe('Fine', () => { it('OK', () => {}) })") - - const found = await enumerateSpecs([bad, good]) - - expect(found.map(t => t.fullTitle)).toEqual(['Fine OK']) - }) -}) diff --git a/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts b/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts deleted file mode 100644 index d387173..0000000 --- a/packages/browserstack-service/tests/testOps/unlaunchedSpecReporter.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -import path from 'node:path' - -import { describe, expect, it, vi, beforeEach } from 'vitest' - -import { - applyExcludes, - emitEvents, - findUnlaunchedSpecs, - hasPerCapabilitySpecFilters, - reportUnlaunchedSpecs, - resolveSpecPatterns -} from '../../src/testOps/unlaunchedSpecReporter.js' -import * as specEnumerator from '../../src/testOps/specEnumerator.js' -import { BrowserstackCLI } from '../../src/cli/index.js' -import { GrpcClient } from '../../src/cli/grpcClient.js' -import * as util from '../../src/util.js' -import type { UploadType } from '../../src/types.js' - -describe('findUnlaunchedSpecs', () => { - it('returns specs that never reached a worker', () => { - const all = ['/p/a.spec.js', '/p/b.spec.js', '/p/c.spec.js'] - expect(findUnlaunchedSpecs(all, new Set(['/p/a.spec.js']))) - .toEqual(['/p/b.spec.js', '/p/c.spec.js']) - }) - - it('matches regardless of file:// prefix or relative form', () => { - const all = ['/p/a.spec.js', '/p/b.spec.js'] - // workers report specs as file:// URLs; the config list does not - expect(findUnlaunchedSpecs(all, new Set(['file:///p/a.spec.js']))) - .toEqual(['/p/b.spec.js']) - }) - - it('returns nothing when every spec ran', () => { - const all = ['/p/a.spec.js'] - expect(findUnlaunchedSpecs(all, new Set(['/p/a.spec.js']))).toEqual([]) - }) - - it('returns everything when no worker ever started', () => { - const all = ['/p/a.spec.js', '/p/b.spec.js'] - expect(findUnlaunchedSpecs(all, new Set())).toEqual(all) - }) -}) - -describe('resolveSpecPatterns', () => { - const fixtures = path.join(process.cwd(), 'tests', 'testOps') - - it('resolves a concrete relative path even when it matches no glob', async () => { - const resolved = await resolveSpecPatterns(['unlaunchedSpecReporter.test.ts'], fixtures) - expect(resolved).toEqual([path.join(fixtures, 'unlaunchedSpecReporter.test.ts')]) - }) - - it('expands glob patterns to absolute paths', async () => { - const resolved = await resolveSpecPatterns(['*.test.ts'], fixtures) - expect(resolved.length).toBeGreaterThan(0) - expect(resolved.every(p => path.isAbsolute(p))).toBe(true) - expect(resolved).toContain(path.join(fixtures, 'unlaunchedSpecReporter.test.ts')) - }) - - it('flattens nested arrays (wdio spec grouping) and de-dupes', async () => { - const resolved = await resolveSpecPatterns( - [['unlaunchedSpecReporter.test.ts'], 'unlaunchedSpecReporter.test.ts'], - fixtures - ) - expect(resolved).toHaveLength(1) - }) - - it('strips the file:// prefix', async () => { - const resolved = await resolveSpecPatterns(['file:///p/a.spec.js'], fixtures) - expect(resolved).toEqual(['/p/a.spec.js']) - }) - - it('ignores non-string entries instead of throwing', async () => { - await expect(resolveSpecPatterns([null, 42, undefined], fixtures)).resolves.toEqual([]) - }) -}) - -describe('applyExcludes', () => { - const root = '/proj' - const specs = ['/proj/tests/a.spec.js', '/proj/tests/flaky-b.spec.js', '/proj/tests/c.spec.js'] - - it('matches exactly when every entry looks like a path', () => { - expect(applyExcludes(specs, ['tests/flaky-b.spec.js'], root)) - .toEqual(['/proj/tests/a.spec.js', '/proj/tests/c.spec.js']) - }) - - it('matches by substring when entries are bare keywords — wdio filterSpecs semantics', () => { - // `exclude: ['flaky']` must drop any spec whose path contains it; resolving it as a - // literal path would match nothing and the spec would be reported as skipped - expect(applyExcludes(specs, ['flaky'], root)) - .toEqual(['/proj/tests/a.spec.js', '/proj/tests/c.spec.js']) - }) - - it('handles glob entries as path-like', () => { - expect(applyExcludes(specs, ['/proj/tests/*.spec.js'], root)).toEqual(specs) - }) - - it('returns specs untouched for an empty or missing exclude list', () => { - expect(applyExcludes(specs, [], root)).toEqual(specs) - expect(applyExcludes(specs, undefined as unknown as string[], root)).toEqual(specs) - }) -}) - -describe('hasPerCapabilitySpecFilters', () => { - // when a capability narrows the spec set, the top-level config no longer describes what - // the run intended to execute — reporting from it would mark excluded specs as skipped - it('is false for plain capabilities', () => { - expect(hasPerCapabilitySpecFilters([{ browserName: 'chrome' }])).toBe(false) - }) - - it('detects wdio:specs and wdio:exclude', () => { - expect(hasPerCapabilitySpecFilters([{ browserName: 'chrome', 'wdio:specs': ['a.js'] }])).toBe(true) - expect(hasPerCapabilitySpecFilters([{ browserName: 'chrome', 'wdio:exclude': ['b.js'] }])).toBe(true) - }) - - it('detects the deprecated specs/exclude spelling', () => { - expect(hasPerCapabilitySpecFilters([{ exclude: ['b.js'] }])).toBe(true) - }) - - it('detects filters nested under multiremote capabilities', () => { - expect(hasPerCapabilitySpecFilters({ - browserA: { capabilities: { browserName: 'chrome', 'wdio:exclude': ['b.js'] } } - })).toBe(true) - }) - - it('is false for multiremote without filters, and for empty input', () => { - expect(hasPerCapabilitySpecFilters({ - browserA: { capabilities: { browserName: 'chrome' } } - })).toBe(false) - expect(hasPerCapabilitySpecFilters([])).toBe(false) - expect(hasPerCapabilitySpecFilters(undefined)).toBe(false) - }) -}) - -describe('reported identity matches getUniqueIdentifier', () => { - // A test reported skipped here and the same test on a later successful run must carry one - // identity, or TM sees two tests and rerun reconciliation breaks. getUniqueIdentifier - // (util.ts) builds `${parent} - ${title}`; mocha's own fullTitle is space-joined and would - // not match. This pins the format. - it('emits bare title for name/identifier and `parent - title` for scope', async () => { - const enqueueTestEvent = vi.fn().mockResolvedValue({ success: true }) - vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => true } as any) - vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) - vi.spyOn(specEnumerator, 'enumerateSpecs').mockResolvedValue([ - { - fullTitle: 'Suite B SB-TC1', - title: 'SB-TC1', - scopes: ['Suite B'], - file: '/proj/tests/b.spec.js' - } - ]) - - await reportUnlaunchedSpecs(['/proj/tests/b.spec.js'], new Set(), '/proj') - - const sent = enqueueTestEvent.mock.calls.map(c => c[1] as { test_run: Record }) - expect(sent).toHaveLength(2) // TestRunStarted + TestRunFinished - for (const event of sent) { - // field-for-field parity with what an executed test emits - expect(event.test_run.name).toBe('SB-TC1') - expect(event.test_run.identifier).toBe('SB-TC1') - expect(event.test_run.scope).toBe('Suite B - SB-TC1') - expect(event.test_run.scopes).toEqual(['Suite B']) - expect(event.test_run.result).toBe('skipped') - } - }) -}) - -describe('emitEvents transport routing', () => { - const events: UploadType[] = [ - { event_type: 'TestRunStarted', test_run: { uuid: 'u1' } }, - { event_type: 'TestRunFinished', test_run: { uuid: 'u1' } } - ] - - beforeEach(() => { - vi.restoreAllMocks() - }) - - it('relays one gRPC call per event when the binary is running', async () => { - // the launcher's TESTHUB_JWT has no build claim on this path, so the collector would - // 401 — events must go through the binary, which holds the build-scoped credential - const enqueueTestEvent = vi.fn().mockResolvedValue({ success: true }) - vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => true } as any) - vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) - const batchSpy = vi.spyOn(util, 'batchAndPostEvents').mockResolvedValue(undefined as any) - - await emitEvents(events) - - // one call per event — the binary's handler takes a single event, not a batch - expect(enqueueTestEvent).toHaveBeenCalledTimes(2) - expect(enqueueTestEvent.mock.calls[0][1]).toEqual(events[0]) - expect(batchSpy).not.toHaveBeenCalled() - }) - - it('falls back to the HTTP batch when no binary is running', async () => { - vi.spyOn(BrowserstackCLI, 'getInstance').mockReturnValue({ isRunning: () => false } as any) - const enqueueTestEvent = vi.fn() - vi.spyOn(GrpcClient, 'getInstance').mockReturnValue({ enqueueTestEvent } as any) - const batchSpy = vi.spyOn(util, 'batchAndPostEvents').mockResolvedValue(undefined as any) - - await emitEvents(events) - - expect(batchSpy).toHaveBeenCalledTimes(1) - expect(batchSpy.mock.calls[0][2]).toEqual(events) - expect(enqueueTestEvent).not.toHaveBeenCalled() - }) -}) From a9a03b02350028e76d51db945c87f6e39256d8b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:11:31 +0000 Subject: [PATCH 5/6] chore(changeset): auto-generate from PR template (minor) --- .changeset/pr-122.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/pr-122.md b/.changeset/pr-122.md index 6067414..64cf117 100644 --- a/.changeset/pr-122.md +++ b/.changeset/pr-122.md @@ -2,4 +2,5 @@ "@wdio/browserstack-service": minor --- -- Tests that never run because Mocha's `bail` halted a spec are now reported as `skipped` instead of being left out of the report — so a Test Run accounts for every test in that spec, including tests in sibling `describe` blocks. +- Tests that never run because of `bail` are now reported as `skipped` instead of being left out of the report — so a Test Run accounts for every test it was meant to execute. +- Covers both `mochaOpts.bail` (the rest of the failing spec) and WebdriverIO's `bail` (spec files that never started). From e0b5c08ccfb3eb699d0fa263ca076c93e8e6a47a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 07:13:07 +0000 Subject: [PATCH 6/6] chore(changeset): auto-generate from PR template (minor) --- .changeset/pr-122.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.changeset/pr-122.md b/.changeset/pr-122.md index 64cf117..9d6aad0 100644 --- a/.changeset/pr-122.md +++ b/.changeset/pr-122.md @@ -2,5 +2,4 @@ "@wdio/browserstack-service": minor --- -- Tests that never run because of `bail` are now reported as `skipped` instead of being left out of the report — so a Test Run accounts for every test it was meant to execute. -- Covers both `mochaOpts.bail` (the rest of the failing spec) and WebdriverIO's `bail` (spec files that never started). +- Tests that never run because Mocha's `bail` halted a spec are now reported as `skipped`, instead of being left out of the report entirely — so a Test Run accounts for every test in that spec.