From b7c404910c87b35c9f363610ad459960f487d130 Mon Sep 17 00:00:00 2001 From: sipayrt Date: Wed, 14 Oct 2026 17:40:15 +0300 Subject: [PATCH 1/4] docs: complete non-mobile command reference Complete missing-commands-plan stages 1-4 and the non-mobile scope of stages 6-7. Add 28 commands in EN/RU and align 14 related pages per locale. Include 41 executable example files, the locked test project, and verification reports. Rechecked 42 Chrome/BiDi tests and 1 Selenium test with zero retries, both locale builds, lint/types, and 86 localized code blocks. Keep mobile documentation deferred. No framework implementation changes. --- docs/basic-guides/selectors.mdx | 4 + .../user-environment-emulation.mdx | 120 +- docs/commands/browser/addInitScript.mdx | 56 + docs/commands/browser/addLocatorStrategy.mdx | 48 + docs/commands/browser/customDollar.mdx | 48 + docs/commands/browser/customDollarDollar.mdx | 51 + docs/commands/browser/downloadFile.mdx | 67 + docs/commands/browser/emulate.mdx | 72 + docs/commands/browser/executeAsync.mdx | 62 +- docs/commands/browser/extendOptions.mdx | 62 + docs/commands/browser/getState.mdx | 84 +- docs/commands/browser/mock.mdx | 174 +- docs/commands/browser/restore.mdx | 51 + docs/commands/browser/setViewport.mdx | 53 + docs/commands/browser/switchFrame.mdx | 57 + docs/commands/browser/throttle.mdx | 80 +- docs/commands/browser/throttleCPU.mdx | 49 + docs/commands/browser/throttleNetwork.mdx | 71 + .../browser/unstable_captureDomSnapshot.mdx | 81 + docs/commands/browser/unstable_getCdp.mdx | 49 + docs/commands/browser/url.mdx | 94 +- docs/commands/element/customDollar.mdx | 50 + docs/commands/element/customDollarDollar.mdx | 51 + docs/commands/element/execute.mdx | 52 + docs/commands/element/executeAsync.mdx | 57 + docs/commands/element/getElement.mdx | 45 + docs/commands/element/isStable.mdx | 57 + .../element/unstable_captureDomSnapshot.mdx | 55 + docs/commands/element/waitForStable.mdx | 54 + docs/commands/mock/abort.mdx | 73 +- docs/commands/mock/abortOnce.mdx | 84 +- docs/commands/mock/clear.mdx | 58 +- docs/commands/mock/redirect.mdx | 60 + docs/commands/mock/redirectOnce.mdx | 61 + docs/commands/mock/request.mdx | 65 + docs/commands/mock/requestOnce.mdx | 65 + docs/commands/mock/reset.mdx | 74 + docs/commands/mock/respond.mdx | 150 +- docs/commands/mock/respondOnce.mdx | 126 +- docs/commands/mock/restore.mdx | 57 +- docs/commands/mock/waitForResponse.mdx | 68 + docs/commands/overview.mdx | 54 +- .../current/basic-guides/selectors.mdx | 4 + .../user-environment-emulation.mdx | 122 +- .../commands/browser/addInitScript.mdx | 56 + .../commands/browser/addLocatorStrategy.mdx | 48 + .../current/commands/browser/customDollar.mdx | 48 + .../commands/browser/customDollarDollar.mdx | 51 + .../current/commands/browser/downloadFile.mdx | 67 + .../current/commands/browser/emulate.mdx | 72 + .../current/commands/browser/executeAsync.mdx | 58 +- .../commands/browser/extendOptions.mdx | 62 + .../current/commands/browser/getState.mdx | 82 +- .../current/commands/browser/mock.mdx | 170 +- .../current/commands/browser/restore.mdx | 51 + .../current/commands/browser/setViewport.mdx | 53 + .../current/commands/browser/switchFrame.mdx | 57 + .../current/commands/browser/throttle.mdx | 80 +- .../current/commands/browser/throttleCPU.mdx | 49 + .../commands/browser/throttleNetwork.mdx | 71 + .../browser/unstable_captureDomSnapshot.mdx | 81 + .../commands/browser/unstable_getCdp.mdx | 49 + .../current/commands/browser/url.mdx | 88 +- .../current/commands/element/customDollar.mdx | 50 + .../commands/element/customDollarDollar.mdx | 51 + .../current/commands/element/execute.mdx | 52 + .../current/commands/element/executeAsync.mdx | 57 + .../current/commands/element/getElement.mdx | 45 + .../current/commands/element/isStable.mdx | 57 + .../element/unstable_captureDomSnapshot.mdx | 55 + .../commands/element/waitForStable.mdx | 54 + .../current/commands/mock/abort.mdx | 71 +- .../current/commands/mock/abortOnce.mdx | 82 +- .../current/commands/mock/clear.mdx | 56 +- .../current/commands/mock/redirect.mdx | 60 + .../current/commands/mock/redirectOnce.mdx | 61 + .../current/commands/mock/request.mdx | 65 + .../current/commands/mock/requestOnce.mdx | 65 + .../current/commands/mock/reset.mdx | 74 + .../current/commands/mock/respond.mdx | 127 +- .../current/commands/mock/respondOnce.mdx | 120 +- .../current/commands/mock/restore.mdx | 55 +- .../current/commands/mock/waitForResponse.mdx | 68 + .../current/commands/overview.mdx | 62 +- vibe/missing-commands-plan-track.md | 59 + vibe/missing-commands-plan.md | 265 + vibe/verified-examples/.gitignore | 5 + vibe/verified-examples/README.md | 37 + .../evidence/baseline-build.txt | 32 + .../evidence/baseline-test.txt | 13 + .../commit-check-20261014/build-en.log | 25 + .../commit-check-20261014/build-ru.log | 15 + .../commit-check-20261014/desktop-run.log | 47 + .../commit-check-20261014/docs-test.log | 13 + .../commit-check-20261014/selenium-run.log | 6 + .../commit-check-20261014/snippets.log | 1 + .../evidence/desktop-run.txt | 47 + .../evidence/docs-build-en.txt | 25 + .../evidence/docs-build-ru.txt | 15 + .../verified-examples/evidence/docs-build.txt | 35 + vibe/verified-examples/evidence/docs-test.txt | 13 + .../evidence/network-cdp-diagnostic.txt | 22 + .../evidence/network-reset-diagnostic.txt | 7 + .../evidence/nonmobile-audit.json | 74 + .../evidence/respond-cookie-green.txt | 2 + .../evidence/respond-cookie-red.txt | 66 + vibe/verified-examples/evidence/results.json | 404 + .../review-fixes-20261014/build-en.log | 25 + .../review-fixes-20261014/build-ru.log | 15 + .../review-fixes-20261014/desktop-run.log | 47 + .../review-fixes-20261014/docs-test.log | 13 + .../review-fixes-20261014/final-docs-test.log | 13 + .../review-fixes-20261014/grid-downloads.json | 14 + .../review-fixes-20261014/html-check.json | 26 + .../review-fixes-20261014/npm-ci-legacy.log | 7 + .../evidence/review-fixes-20261014/npm-ci.log | 39 + .../review-fixes-20261014/scope-check.json | 19 + .../review-fixes-20261014/selenium-run.log | 6 + .../review-fixes-20261014/snippets.log | 1 + vibe/verified-examples/evidence/review.md | 30 + .../evidence/selenium-run.txt | 6 + .../evidence/stage3-initial-failure.txt | 45 + .../evidence/stage6-build-en.txt | 25 + .../evidence/stage6-build-ru.txt | 15 + .../evidence/stage6-docs-test.txt | 13 + .../evidence/stage6-initial-failure.txt | 33 + .../verified-examples/evidence/stage6-run.txt | 15 + .../switchFrame-predicate-diagnostic.txt | 3 + .../evidence/url-network-idle-diagnostic.txt | 20 + .../evidence/visual-check.md | 22 + .../browser/addInitScript.testplane.js | 17 + .../browser/addLocatorStrategy.testplane.js | 11 + .../browser/customDollar.testplane.js | 11 + .../browser/customDollarDollar.testplane.js | 14 + .../examples/browser/emulate.testplane.js | 26 + .../examples/browser/restore.testplane.js | 15 + .../examples/browser/setViewport.testplane.js | 16 + .../examples/browser/switchFrame.testplane.js | 16 + .../examples/browser/throttleCPU.testplane.js | 14 + .../browser/throttleNetwork.testplane.js | 25 + .../element/customDollar.testplane.js | 14 + .../element/customDollarDollar.testplane.js | 15 + .../examples/element/execute.testplane.js | 15 + .../element/executeAsync.testplane.js | 15 + .../examples/element/getElement.testplane.js | 12 + .../examples/element/isStable.testplane.js | 24 + .../element/waitForStable.testplane.js | 15 + .../examples/mock/redirect.testplane.js | 21 + .../examples/mock/redirectOnce.testplane.js | 23 + .../examples/mock/request.testplane.js | 21 + .../examples/mock/requestOnce.testplane.js | 22 + .../examples/mock/reset.testplane.js | 37 + .../mock/waitForResponse.testplane.js | 28 + .../examples/related/abort.testplane.js | 25 + .../examples/related/abortOnce.testplane.js | 27 + .../examples/related/clear.testplane.js | 27 + .../related/executeAsync.testplane.js | 14 + .../examples/related/getState.testplane.js | 21 + .../examples/related/mock.testplane.js | 24 + .../examples/related/overview.testplane.js | 11 + .../examples/related/respond.testplane.js | 43 + .../examples/related/respondOnce.testplane.js | 23 + .../examples/related/restore.testplane.js | 24 + .../examples/related/slowNetwork.testplane.js | 15 + .../examples/related/throttle.testplane.js | 10 + .../examples/related/url.testplane.js | 27 + .../selenium/downloadFile.testplane.js | 29 + .../extendOptions.testplane.js | 27 + .../unstable_captureDomSnapshot.testplane.js | 23 + .../unstable_getCdp.testplane.js | 10 + .../unstable_captureDomSnapshot.testplane.js | 14 + vibe/verified-examples/manifest.json | 223 + vibe/verified-examples/package-lock.json | 9719 +++++++++++++++++ vibe/verified-examples/package.json | 21 + vibe/verified-examples/run-examples.cjs | 55 + vibe/verified-examples/testplane.config.js | 50 + .../testplane.selenium.config.js | 21 + vibe/verified-examples/verify-snippets.cjs | 33 + 178 files changed, 17056 insertions(+), 1222 deletions(-) create mode 100644 docs/commands/browser/addInitScript.mdx create mode 100644 docs/commands/browser/addLocatorStrategy.mdx create mode 100644 docs/commands/browser/customDollar.mdx create mode 100644 docs/commands/browser/customDollarDollar.mdx create mode 100644 docs/commands/browser/downloadFile.mdx create mode 100644 docs/commands/browser/emulate.mdx create mode 100644 docs/commands/browser/extendOptions.mdx create mode 100644 docs/commands/browser/restore.mdx create mode 100644 docs/commands/browser/setViewport.mdx create mode 100644 docs/commands/browser/switchFrame.mdx create mode 100644 docs/commands/browser/throttleCPU.mdx create mode 100644 docs/commands/browser/throttleNetwork.mdx create mode 100644 docs/commands/browser/unstable_captureDomSnapshot.mdx create mode 100644 docs/commands/browser/unstable_getCdp.mdx create mode 100644 docs/commands/element/customDollar.mdx create mode 100644 docs/commands/element/customDollarDollar.mdx create mode 100644 docs/commands/element/execute.mdx create mode 100644 docs/commands/element/executeAsync.mdx create mode 100644 docs/commands/element/getElement.mdx create mode 100644 docs/commands/element/isStable.mdx create mode 100644 docs/commands/element/unstable_captureDomSnapshot.mdx create mode 100644 docs/commands/element/waitForStable.mdx create mode 100644 docs/commands/mock/redirect.mdx create mode 100644 docs/commands/mock/redirectOnce.mdx create mode 100644 docs/commands/mock/request.mdx create mode 100644 docs/commands/mock/requestOnce.mdx create mode 100644 docs/commands/mock/reset.mdx create mode 100644 docs/commands/mock/waitForResponse.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addLocatorStrategy.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollar.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollarDollar.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/downloadFile.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/emulate.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/extendOptions.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/restore.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/switchFrame.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleCPU.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_captureDomSnapshot.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_getCdp.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollar.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollarDollar.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/execute.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/executeAsync.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/getElement.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/isStable.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/unstable_captureDomSnapshot.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/element/waitForStable.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirect.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirectOnce.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/request.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/requestOnce.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/reset.mdx create mode 100644 i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/waitForResponse.mdx create mode 100644 vibe/missing-commands-plan-track.md create mode 100644 vibe/missing-commands-plan.md create mode 100644 vibe/verified-examples/.gitignore create mode 100644 vibe/verified-examples/README.md create mode 100644 vibe/verified-examples/evidence/baseline-build.txt create mode 100644 vibe/verified-examples/evidence/baseline-test.txt create mode 100644 vibe/verified-examples/evidence/commit-check-20261014/build-en.log create mode 100644 vibe/verified-examples/evidence/commit-check-20261014/build-ru.log create mode 100644 vibe/verified-examples/evidence/commit-check-20261014/desktop-run.log create mode 100644 vibe/verified-examples/evidence/commit-check-20261014/docs-test.log create mode 100644 vibe/verified-examples/evidence/commit-check-20261014/selenium-run.log create mode 100644 vibe/verified-examples/evidence/commit-check-20261014/snippets.log create mode 100644 vibe/verified-examples/evidence/desktop-run.txt create mode 100644 vibe/verified-examples/evidence/docs-build-en.txt create mode 100644 vibe/verified-examples/evidence/docs-build-ru.txt create mode 100644 vibe/verified-examples/evidence/docs-build.txt create mode 100644 vibe/verified-examples/evidence/docs-test.txt create mode 100644 vibe/verified-examples/evidence/network-cdp-diagnostic.txt create mode 100644 vibe/verified-examples/evidence/network-reset-diagnostic.txt create mode 100644 vibe/verified-examples/evidence/nonmobile-audit.json create mode 100644 vibe/verified-examples/evidence/respond-cookie-green.txt create mode 100644 vibe/verified-examples/evidence/respond-cookie-red.txt create mode 100644 vibe/verified-examples/evidence/results.json create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/build-en.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/build-ru.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/desktop-run.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/docs-test.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/final-docs-test.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/grid-downloads.json create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/html-check.json create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/npm-ci-legacy.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/npm-ci.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/scope-check.json create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/selenium-run.log create mode 100644 vibe/verified-examples/evidence/review-fixes-20261014/snippets.log create mode 100644 vibe/verified-examples/evidence/review.md create mode 100644 vibe/verified-examples/evidence/selenium-run.txt create mode 100644 vibe/verified-examples/evidence/stage3-initial-failure.txt create mode 100644 vibe/verified-examples/evidence/stage6-build-en.txt create mode 100644 vibe/verified-examples/evidence/stage6-build-ru.txt create mode 100644 vibe/verified-examples/evidence/stage6-docs-test.txt create mode 100644 vibe/verified-examples/evidence/stage6-initial-failure.txt create mode 100644 vibe/verified-examples/evidence/stage6-run.txt create mode 100644 vibe/verified-examples/evidence/switchFrame-predicate-diagnostic.txt create mode 100644 vibe/verified-examples/evidence/url-network-idle-diagnostic.txt create mode 100644 vibe/verified-examples/evidence/visual-check.md create mode 100644 vibe/verified-examples/examples/browser/addInitScript.testplane.js create mode 100644 vibe/verified-examples/examples/browser/addLocatorStrategy.testplane.js create mode 100644 vibe/verified-examples/examples/browser/customDollar.testplane.js create mode 100644 vibe/verified-examples/examples/browser/customDollarDollar.testplane.js create mode 100644 vibe/verified-examples/examples/browser/emulate.testplane.js create mode 100644 vibe/verified-examples/examples/browser/restore.testplane.js create mode 100644 vibe/verified-examples/examples/browser/setViewport.testplane.js create mode 100644 vibe/verified-examples/examples/browser/switchFrame.testplane.js create mode 100644 vibe/verified-examples/examples/browser/throttleCPU.testplane.js create mode 100644 vibe/verified-examples/examples/browser/throttleNetwork.testplane.js create mode 100644 vibe/verified-examples/examples/element/customDollar.testplane.js create mode 100644 vibe/verified-examples/examples/element/customDollarDollar.testplane.js create mode 100644 vibe/verified-examples/examples/element/execute.testplane.js create mode 100644 vibe/verified-examples/examples/element/executeAsync.testplane.js create mode 100644 vibe/verified-examples/examples/element/getElement.testplane.js create mode 100644 vibe/verified-examples/examples/element/isStable.testplane.js create mode 100644 vibe/verified-examples/examples/element/waitForStable.testplane.js create mode 100644 vibe/verified-examples/examples/mock/redirect.testplane.js create mode 100644 vibe/verified-examples/examples/mock/redirectOnce.testplane.js create mode 100644 vibe/verified-examples/examples/mock/request.testplane.js create mode 100644 vibe/verified-examples/examples/mock/requestOnce.testplane.js create mode 100644 vibe/verified-examples/examples/mock/reset.testplane.js create mode 100644 vibe/verified-examples/examples/mock/waitForResponse.testplane.js create mode 100644 vibe/verified-examples/examples/related/abort.testplane.js create mode 100644 vibe/verified-examples/examples/related/abortOnce.testplane.js create mode 100644 vibe/verified-examples/examples/related/clear.testplane.js create mode 100644 vibe/verified-examples/examples/related/executeAsync.testplane.js create mode 100644 vibe/verified-examples/examples/related/getState.testplane.js create mode 100644 vibe/verified-examples/examples/related/mock.testplane.js create mode 100644 vibe/verified-examples/examples/related/overview.testplane.js create mode 100644 vibe/verified-examples/examples/related/respond.testplane.js create mode 100644 vibe/verified-examples/examples/related/respondOnce.testplane.js create mode 100644 vibe/verified-examples/examples/related/restore.testplane.js create mode 100644 vibe/verified-examples/examples/related/slowNetwork.testplane.js create mode 100644 vibe/verified-examples/examples/related/throttle.testplane.js create mode 100644 vibe/verified-examples/examples/related/url.testplane.js create mode 100644 vibe/verified-examples/examples/selenium/downloadFile.testplane.js create mode 100644 vibe/verified-examples/examples/testplane-browser/extendOptions.testplane.js create mode 100644 vibe/verified-examples/examples/testplane-browser/unstable_captureDomSnapshot.testplane.js create mode 100644 vibe/verified-examples/examples/testplane-browser/unstable_getCdp.testplane.js create mode 100644 vibe/verified-examples/examples/testplane-element/unstable_captureDomSnapshot.testplane.js create mode 100644 vibe/verified-examples/manifest.json create mode 100644 vibe/verified-examples/package-lock.json create mode 100644 vibe/verified-examples/package.json create mode 100644 vibe/verified-examples/run-examples.cjs create mode 100644 vibe/verified-examples/testplane.config.js create mode 100644 vibe/verified-examples/testplane.selenium.config.js create mode 100644 vibe/verified-examples/verify-snippets.cjs diff --git a/docs/basic-guides/selectors.mdx b/docs/basic-guides/selectors.mdx index c4828a5b..e6a9f1f4 100644 --- a/docs/basic-guides/selectors.mdx +++ b/docs/basic-guides/selectors.mdx @@ -315,6 +315,10 @@ Use this approach if: - you need access to elements inside the shadow root - you are working with a design system based on Web Components +## Custom locator strategies {/* #custom-locator-strategies */} + +If standard selectors are insufficient, register a strategy with [browser.addLocatorStrategy](../commands/browser/addLocatorStrategy.mdx). Use [browser.custom$](../commands/browser/customDollar.mdx) for one element and [browser.custom$$](../commands/browser/customDollarDollar.mdx) for multiple elements. To search within an element, use [element.custom$](../commands/element/customDollar.mdx) or [element.custom$$](../commands/element/customDollarDollar.mdx). The strategy runs in the browser, so pass data as arguments rather than through Node.js closures. The command pages contain verified examples. + ## Testing Library Testing Library allows you to find elements the way users do — by text, element type, or other attributes that don't depend on your layout details. diff --git a/docs/basic-guides/user-environment-emulation.mdx b/docs/basic-guides/user-environment-emulation.mdx index d401a8df..c1e5ad76 100644 --- a/docs/basic-guides/user-environment-emulation.mdx +++ b/docs/basic-guides/user-environment-emulation.mdx @@ -327,60 +327,65 @@ it("applies styles for the dark color scheme", async ({ browser }) => { }); ``` -## Network +## Network {/* #network */} -### Offline mode +### Offline mode {/* #offline-mode */} -To test how the application works without a network connection, use [`browser.throttleNetwork("offline")`][throttle-network]. +Use [throttleNetwork][throttle-network] to block actual requests. The example requires Chromium with CDP/Puppeteer access, one tab, `isolation: false` and `testsPerSession: 1`: the implementation selects the first Puppeteer page. Navigate before disabling the network. -For example, you can test the error message displayed when a network request fails: +In the verified Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150 combination, the `online` profile did not restore connectivity after `offline`. The example therefore ends the offline session and checks connectivity in a new one rather than promising recovery through that call. Point `baseUrl` to a test HTTP server that returns `200` at the root path. -```typescript -it("shows a message when there is no network connection", async ({ browser }) => { - await browser.url("/"); +```javascript +const assert = require("node:assert/strict"); +// Configure testsPerSession: 1 so this offline session is not reused. +it("throttleNetwork: check a failed request while offline", async ({ browser }) => { + await browser.url("/"); await browser.throttleNetwork("offline"); + const failed = await browser.execute(async () => { + try { + await fetch("/?offline-check", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); +}); - const loadOrders = await browser.getByTestId("load-orders"); - await loadOrders.click(); - - const networkError = await browser.findByTestId("network-error"); - await expect(networkError).toHaveText("No network connection"); - - await browser.throttleNetwork("online"); +it("throttleNetwork: the next session starts online", async ({ browser }) => { + await browser.url("/"); + const status = await browser.execute(async () => { + const response = await fetch("/?online-check", { cache: "no-store" }); + return response.status; + }); + assert.equal(status, 200); }); ``` -Load the page first, then disable the network before the action that sends a request. Unlike `emulate()`, `throttleNetwork()` must be called after navigation. To restore the normal network mode, use the `"online"` profile. +### Slow connection {/* #slow-connection */} -### Slow connection +Pass an object to [throttleNetwork][throttle-network] to limit throughput. The example uses the same isolation requirements. The server at `baseUrl` must serve HTML at `/` and JSON with `source: "original"` at `/api/items`. -To test the interface over a slow connection, pass network parameters to [`browser.throttleNetwork()`][throttle-network]: - -```typescript -it("shows the loading state on a slow network", async ({ browser }) => { - await browser.url("/orders"); +```javascript +const assert = require("node:assert/strict"); +it("slow network: fetch data under custom throughput limits", async ({ browser }) => { + await browser.url("/"); await browser.throttleNetwork({ offline: false, latency: 500, downloadThroughput: (50 * 1024) / 8, uploadThroughput: (20 * 1024) / 8, }); - - const loadOrders = await browser.getByTestId("load-orders"); - await loadOrders.click(); - - const loading = await browser.findByTestId("loading"); - await expect(loading).toBeDisplayed(); - - await browser.throttleNetwork("online"); + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.source, "original"); }); ``` -The object contains four fields: `offline`, the `latency` in milliseconds, and the `downloadThroughput` and `uploadThroughput` rates in bytes per second. - -For common network conditions, you do not have to specify the parameters manually. Pass the name of a predefined profile instead of an object, such as `"Good3G"` or `"offline"`. +`offline` enables offline mode, `latency` is in milliseconds, and `downloadThroughput` and `uploadThroughput` are in bytes per second. You can pass a preset such as `Good3G` instead of an object. The throttled session is not reused by the next test. ### navigator.onLine @@ -400,25 +405,30 @@ The emulation persists until the end of the session, so restore it in the same t `browser.emulate("onLine", false)` only changes the value of `navigator.onLine`; it does not disable the network, and HTTP requests continue to work. -## CPU performance +## CPU performance {/* #cpu-performance */} -To test the interface with limited CPU performance, use [`browser.throttleCPU()`][throttle-cpu]. +[throttleCPU][throttle-cpu] slows down code execution in Chromium through CDP/Puppeteer. As with network throttling, use one tab and `isolation: false`: the command targets the first Puppeteer page. Navigate before calling it. Set `baseUrl` to a test HTML page. -For example, you can test a scenario with 4× CPU slowdown as follows: +The example applies a 2× slowdown and removes it in `finally`. -```typescript -it("works with a throttled CPU", async ({ browser }) => { - await browser.throttleCPU(4); +```javascript +const assert = require("node:assert/strict"); +it("throttleCPU: run a page with a slower CPU", async ({ browser }) => { await browser.url("/"); - - // ... - - await browser.throttleCPU(1); + try { + await browser.throttleCPU(2); + await browser.execute(() => { + document.body.innerHTML = ""; + }); + assert.equal(await browser.$("button").getText(), "Ready"); + } finally { + await browser.throttleCPU(1); + } }); ``` -The higher the slowdown factor, the slower the code runs. A value of `1` disables throttling. +A larger factor slows code execution further. A value of `1` disables throttling. ## Geolocation @@ -551,26 +561,26 @@ describe("dark color scheme", () => { If a setting must remain active for the entire session, create a separate browser configuration for it. This approach is more convenient for setting the browser language, running tests with JavaScript disabled, and setting a fixed window size with [`windowSize`][window-size]. -[emulate]: https://webdriver.io/docs/api/browser/emulate +[emulate]: ../commands/browser/emulate.mdx [webdriver-bidi]: https://w3c.github.io/webdriver-bidi/ -[set-viewport]: https://webdriver.io/docs/api/browser/setViewport +[set-viewport]: ../commands/browser/setViewport.mdx [set-window-size]: ../commands/browser/setWindowSize.mdx [window-size]: ../reference/config/browsers.mdx#window_size [how-to-use-cdp]: ../guides/how-to-use-cdp.mdx -[emulate-device]: https://webdriver.io/docs/emulation#device -[emulate-user-agent]: https://webdriver.io/docs/emulation#user-agent +[emulate-device]: ../commands/browser/emulate.mdx#parameters +[emulate-user-agent]: ../commands/browser/emulate.mdx#parameters [get-puppeteer]: ../commands/browser/getPuppeteer.mdx [page-set-user-agent]: https://pptr.dev/api/puppeteer.page.setuseragent [cdp-set-locale-override]: https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setLocaleOverride [page-emulate-timezone]: https://pptr.dev/api/puppeteer.page.emulatetimezone -[emulate-clock]: https://webdriver.io/docs/emulation#clock -[clock-tick]: https://webdriver.io/docs/api/clock/tick -[emulate-color-scheme]: https://webdriver.io/docs/emulation#color-scheme +[emulate-clock]: ../commands/browser/emulate.mdx#parameters +[clock-tick]: ../commands/browser/emulate.mdx#result +[emulate-color-scheme]: ../commands/browser/emulate.mdx#parameters [cdp-set-emulated-media]: https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setEmulatedMedia -[throttle-network]: https://webdriver.io/docs/api/browser/throttleNetwork -[emulate-online]: https://webdriver.io/docs/emulation#online-property -[throttle-cpu]: https://webdriver.io/docs/api/browser/throttleCPU -[emulate-geolocation]: https://webdriver.io/docs/emulation#geolocation +[throttle-network]: ../commands/browser/throttleNetwork.mdx +[emulate-online]: ../commands/browser/emulate.mdx#parameters +[throttle-cpu]: ../commands/browser/throttleCPU.mdx +[emulate-geolocation]: ../commands/browser/emulate.mdx#parameters [set-permissions]: https://webdriver.io/docs/api/webdriver#setpermissions -[restore]: https://webdriver.io/docs/api/browser/restore +[restore]: ../commands/browser/restore.mdx [testing-library-guide]: ../guides/how-to-add-testing-library.mdx diff --git a/docs/commands/browser/addInitScript.mdx b/docs/commands/browser/addInitScript.mdx new file mode 100644 index 00000000..cdaeac06 --- /dev/null +++ b/docs/commands/browser/addInitScript.mdx @@ -0,0 +1,56 @@ +# addInitScript + +## Overview {/* #overview */} + +Run a function in new documents of the current tab or window before page scripts execute. Use this to prepare browser-side values before the application reads them. The function also runs in newly attached or navigated child frames within that top-level browsing context. + +The script is registered for the tab or window that is current when `addInitScript` is called. It does not automatically apply to other tabs or windows, including those opened later. To use it there, switch to the target tab or window and register it separately before navigation. + +## Usage {/* #usage */} + +`browser.addInitScript(script, ...args)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | ----------- | ----------- | ------------------------------------------------------------------------ | +| `script` | `Function` | Required | A function executed in the browser, without access to Node.js variables. | +| `args` | `unknown[]` | [] | Arguments serialized as JSON and passed to the function. | + +## Result {/* #result */} + +`Promise`: an object with asynchronous `remove()` and `on("data", listener)`. To send data to Node.js, call the `emit` callback appended to the script arguments. + +## Limitations {/* #limitations */} + +Requires a WebDriver BiDi session (`webSocketUrl: true`). Call the command before navigation. `remove()` prevents future injection; it does not undo changes already made in the current document. Remove the script in the same test, even if an assertion fails. In this version the implementation accepts a function, not a string. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("addInitScript: set a value before page scripts", async ({ browser }) => { + const script = await browser.addInitScript(value => { + window.exampleSeed = value; + }, 42); + + try { + await browser.url("/"); + assert.equal(await browser.execute(() => window.exampleSeed), 42); + } finally { + await script.remove(); + } + + await browser.url("/"); + assert.equal(await browser.execute(() => typeof window.exampleSeed), "undefined"); +}); +``` + +## See also {/* #see-also */} + +- [execute](./execute.mdx) +- [url](./url.mdx) +- [emulate](./emulate.mdx) diff --git a/docs/commands/browser/addLocatorStrategy.mdx b/docs/commands/browser/addLocatorStrategy.mdx new file mode 100644 index 00000000..809cf28b --- /dev/null +++ b/docs/commands/browser/addLocatorStrategy.mdx @@ -0,0 +1,48 @@ +# addLocatorStrategy + +## Overview {/* #overview */} + +Register a reusable way to find elements. After registration, call the strategy through `custom$` or `custom$$` on the browser or an element. + +## Usage {/* #usage */} + +`browser.addLocatorStrategy(name, strategy)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | ---------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | `string` | Required | A unique strategy name within the browser instance. | +| `strategy` | `Function` | Required | A browser-side function that returns a DOM element or an array of DOM elements. Convert a NodeList with Array.from before returning it. | + +## Result {/* #result */} + +`void`. Registration is synchronous; it does not find elements by itself. + +## Limitations {/* #limitations */} + +Register the strategy before its first use. Registering an existing name throws an error. The callback runs in the browser and cannot read Node.js closures. For reusable test setup, register it in `prepareBrowser`. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("addLocatorStrategy: find by data attribute", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + browser.addLocatorStrategy("byTest", value => document.querySelector(`[data-test="${value}"]`)); + const button = await browser.custom$("byTest", "save"); + assert.equal(await button.getText(), "Save"); +}); +``` + +## See also {/* #see-also */} + +- [custom$](./customDollar.mdx) +- [custom$$](./customDollarDollar.mdx) +- [prepareBrowser](../../reference/config/prepare-browser.mdx) diff --git a/docs/commands/browser/customDollar.mdx b/docs/commands/browser/customDollar.mdx new file mode 100644 index 00000000..d014aadb --- /dev/null +++ b/docs/commands/browser/customDollar.mdx @@ -0,0 +1,48 @@ +# custom$ + +## Overview {/* #overview */} + +Find one element using a strategy registered with `addLocatorStrategy`. The strategy controls how the element is selected. + +## Usage {/* #usage */} + +`browser.custom$(strategyName, ...strategyArguments)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------- | ----------- | ----------- | ---------------------------------------------- | +| `strategyName` | `string` | Required | The name of a registered strategy. | +| `strategyArguments` | `unknown[]` | [] | Arguments passed to the browser-side strategy. | + +## Result {/* #result */} + +An awaitable element reference resolving to `WebdriverIO.Element`. If the strategy returns several elements, the first is used. + +## Limitations {/* #limitations */} + +An unknown strategy name throws an error. Finding an element does not imply that it is visible or clickable: use the corresponding waits or assertions. If the strategy finds nothing, subsequent element commands can fail. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("custom$: find one element with a custom strategy", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + browser.addLocatorStrategy("byTest", value => document.querySelector(`[data-test="${value}"]`)); + const button = await browser.custom$("byTest", "save"); + assert.equal(await button.getText(), "Save"); +}); +``` + +## See also {/* #see-also */} + +- [addLocatorStrategy](./addLocatorStrategy.mdx) +- [custom$$](./customDollarDollar.mdx) +- [$](./$.mdx) diff --git a/docs/commands/browser/customDollarDollar.mdx b/docs/commands/browser/customDollarDollar.mdx new file mode 100644 index 00000000..74f097e4 --- /dev/null +++ b/docs/commands/browser/customDollarDollar.mdx @@ -0,0 +1,51 @@ +# custom$$ + +## Overview {/* #overview */} + +Find multiple elements with a registered custom strategy. Use `Array.from` in the strategy when a DOM API returns a NodeList. + +## Usage {/* #usage */} + +`browser.custom$$(strategyName, ...strategyArguments)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------- | ----------- | ----------- | ---------------------------------------------- | +| `strategyName` | `string` | Required | The name of a registered strategy. | +| `strategyArguments` | `unknown[]` | [] | Arguments passed to the browser-side strategy. | + +## Result {/* #result */} + +An awaitable collection resolving to an array of `WebdriverIO.Element` objects. No matches produces an empty array. + +## Limitations {/* #limitations */} + +An unknown strategy name throws an error. The command returns the matches found at the time of the query; it does not wait for a required number of elements. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("custom$$: find several elements", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ""; + }); + browser.addLocatorStrategy("byCss", selector => + Array.from(document.querySelectorAll(selector)), + ); + const items = await browser.custom$$("byCss", "li"); + assert.equal(items.length, 2); + assert.equal(await items[1].getText(), "Second"); +}); +``` + +## See also {/* #see-also */} + +- [addLocatorStrategy](./addLocatorStrategy.mdx) +- [custom$](./customDollar.mdx) +- [$$](./$$.mdx) diff --git a/docs/commands/browser/downloadFile.mdx b/docs/commands/browser/downloadFile.mdx new file mode 100644 index 00000000..dafd2291 --- /dev/null +++ b/docs/commands/browser/downloadFile.mdx @@ -0,0 +1,67 @@ +# downloadFile + +## Overview {/* #overview */} + +Copy a file downloaded by the browser on a Selenium Grid node to the Testplane process filesystem. This command does not download an arbitrary URL. + +## Requirements {/* #requirements */} + +Requires Selenium Grid with managed downloads enabled (`--enable-managed-downloads true`) and the capability `se:downloadsEnabled: true`. Chrome, Edge and Firefox are supported. Using `gridUrl: "local"` alone does not provide the Selenium downloads API. See the [Selenium documentation](https://www.selenium.dev/documentation/webdriver/remote_webdriver/#enable-downloads-in-the-grid). + +## Usage {/* #usage */} + +`browser.downloadFile(fileName, targetDirectory)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Description** | +| ----------------- | -------- | ----------------------------------------------------------- | +| `fileName` | `string` | A file name from `getDownloadableFiles().names`, not a URL. | +| `targetDirectory` | `string` | Local destination directory; created automatically. | + +## Result {/* #result */} + +`Promise<{ files: string[] }>`: absolute paths of extracted files. The command also leaves a `.zip` archive in the destination directory. In this version extraction errors are logged and can leave `files` empty; verify the result exists and has the expected contents. + +## Example {/* #examples */} + +Set `gridUrl` to your Grid address and enable `se:downloadsEnabled: true` in `desiredCapabilities`. Set `baseUrl` to a test HTTP site: `/` serves HTML, and `/report.csv` serves `name,value\nexample,42\n` with the header `Content-Disposition: attachment; filename="report.csv"`. + +Wait for the file to appear on the Grid node first: listing files does not wait for a download to finish. Then copy the file and clean up the remote and local directories used by the test. + +```javascript +const assert = require("node:assert/strict"); +const fs = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); + +it("downloadFile: copy a browser download from Selenium Grid", async ({ browser }) => { + const targetDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "testplane-download-")); + try { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = 'Download report'; + }); + await browser.$("a").click(); + await browser.waitUntil( + async () => { + const { names } = await browser.getDownloadableFiles(); + return names.includes("report.csv"); + }, + { timeout: 10000, timeoutMsg: "The CSV was not downloaded on the Grid node" }, + ); + const { files } = await browser.downloadFile("report.csv", targetDirectory); + const localFile = path.join(targetDirectory, "report.csv"); + assert.ok(files.includes(localFile)); + assert.equal(await fs.readFile(localFile, "utf8"), "name,value\nexample,42\n"); + } finally { + await browser.deleteDownloadableFiles(); + await fs.rm(targetDirectory, { recursive: true, force: true }); + } +}); +``` + +## See also {/* #see-also */} + +- [url](./url.mdx) +- [waitUntil](./waitUntil.mdx) diff --git a/docs/commands/browser/emulate.mdx b/docs/commands/browser/emulate.mdx new file mode 100644 index 00000000..22ec35c4 --- /dev/null +++ b/docs/commands/browser/emulate.mdx @@ -0,0 +1,72 @@ +# emulate + +## Overview {/* #overview */} + +Override part of the browser environment, such as the user agent or clock. Use the returned cleanup function or clock object to restore the environment in the same test. + +## Usage {/* #usage */} + +`browser.emulate(scope, options)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | ------------------ | --------------------- | ------------------------------------------------------------------ | +| `scope` | `string` | Required | One of clock, geolocation, userAgent, device, colorScheme, onLine. | +| `options` | `Depends on scope` | Required except clock | Options for the selected scope, described below. | + +Supported scopes: + +- `clock` — Fake-timer options, including `now` and `toFake`; virtual timers advance through `tick`. +- `geolocation` — An object with coordinates for `navigator.geolocation.getCurrentPosition`. +- `userAgent` — A string overriding `navigator.userAgent`, not HTTP request headers. +- `device` — A supported device profile name; changes viewport, DPR and user agent, not engine or touch hardware. +- `colorScheme` — `"light"` or `"dark"`; affects `matchMedia`, not CSS media queries in this implementation. +- `onLine` — A boolean overriding `navigator.onLine`; does not block network requests. + +## Result {/* #result */} + +For `clock`, `Promise`; otherwise, a promise resolving to an asynchronous restore function. Clock methods include `tick(milliseconds)`, `setSystemTime(dateOrTimestamp)`, `restore()` and `install(options)`. + +## Limitations {/* #limitations */} + +Requires WebDriver BiDi (`webSocketUrl: true`). Most scopes affect documents created after the call, so enable emulation before navigation. Clock emulation also affects the current document. Restore changes before the test ends: a later test receives a different browser object. An unsupported scope or invalid option throws an error. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("emulate: control time without waiting", async ({ browser }) => { + await browser.url("/"); + const clock = await browser.emulate("clock", { + now: new Date("2025-01-01T00:00:00.000Z"), + toFake: ["Date", "setTimeout", "clearTimeout"], + }); + try { + await browser.execute(() => { + window.timerFired = false; + setTimeout(() => { + window.timerFired = true; + }, 1000); + }); + assert.equal(await browser.execute(() => window.timerFired), false); + await clock.tick(1000); + assert.equal(await browser.execute(() => window.timerFired), true); + assert.equal( + await browser.execute(() => new Date().toISOString()), + "2025-01-01T00:00:01.000Z", + ); + } finally { + await clock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [restore](./restore.mdx) +- [setViewport](./setViewport.mdx) +- [Environment emulation](../../basic-guides/user-environment-emulation.mdx) diff --git a/docs/commands/browser/executeAsync.mdx b/docs/commands/browser/executeAsync.mdx index c8572404..164d697d 100644 --- a/docs/commands/browser/executeAsync.mdx +++ b/docs/commands/browser/executeAsync.mdx @@ -2,62 +2,48 @@ ## Overview {/* #overview */} -Use the `executeAsync` command to _asynchronously_ execute the specified JavaScript code in the context of the currently selected frame. +Runs JavaScript in the current frame and returns the value passed to a callback. This command is deprecated: prefer [execute](./execute.mdx) with an async function for new code. -The last argument of the command must be a callback that will be called as soon as the script completes its execution. The command passes the script result to the callback as an input parameter. - -The `script` argument defines the script to be executed as the body of a function. The function will be invoked with the provided array of `args`, and access to the values can be obtained through the `arguments` object in the specified order. The last argument must always be a callback function, which will be called once the script has been executed. - -Arguments can be any JSON primitives, arrays, or JSON objects. JSON objects that define a reference to a _WebElement_ will be converted to the corresponding DOM element. Similarly, any _WebElements_ in the script result will be returned to the client as _WebElement JSON_ objects. +The command injects the callback as the last argument of the **browser function**. Do not pass it as a separate argument to `browser.executeAsync`. Call the callback to complete the command. ## Usage {/* #usage */} -```javascript -await browser.executeAsync(script, arguments); -``` +`await browser.executeAsync(script, ...args)` -## Command Parameters {/* #parameters */} +## Command parameters {/* #parameters */} - - - - - - - +- `script`: a function or a string containing a function body. Runs in the browser without Node.js APIs or local closures. +- `args`: serializable arguments passed before the callback; WebElement references become DOM elements. - -
**Name****Type****Description**
scriptString or FunctionThe script to be executed.
argumentsAnyArguments for the script. The last argument must be a callback function, which will be called once the script has been executed.
+Returns a Promise with the value passed to the callback. In Classic, configure the `script` timeout using [setTimeout](./setTimeout.mdx). The BiDi implementation has no separate callback timer; always call it and bound the test with `testTimeout`. -## Usage Examples {/* #examples */} +## Example {/* #examples */} + +Set `baseUrl` to a test HTML page. ```javascript -it("should execute async JavaScript on the page", async ({ browser }) => { - await browser.setTimeout({ script: 5000 }); +const assert = require("node:assert/strict"); +it("browser.executeAsync: return a result through the injected callback", async ({ browser }) => { + await browser.url("/"); + await browser.setTimeout({ script: 5000 }); const result = await browser.executeAsync( - function (a, b, c, d, done) { - // here we are in the browser context: no access to console or client - setTimeout(() => { - done(a + b + c + d); - }, 3000); + (a, b, done) => { + setTimeout(() => done(a + b), 20); }, - 1, - 2, - 3, 4, + 6, ); - - // here we are in the node.js context: access to console and client is available - console.log(result); // outputs: 10 + assert.equal(result, 10); }); ``` -## Related Commands {/* #related */} +## Related commands {/* #related */} -- [execute](../execute) -- [setTimeout](../setTimeout) +- [execute](./execute.mdx) +- [element.executeAsync](../element/executeAsync.mdx) +- [setTimeout](./setTimeout.mdx) -## References +## References {/* #references */} -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/browser/executeAsync), from which we drew some information while writing our version. +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/browser/executeAsync). diff --git a/docs/commands/browser/extendOptions.mdx b/docs/commands/browser/extendOptions.mdx new file mode 100644 index 00000000..219d21ad --- /dev/null +++ b/docs/commands/browser/extendOptions.mdx @@ -0,0 +1,62 @@ +# extendOptions + +## Overview {/* #overview */} + +Shallowly update the current WebdriverIO browser options. Use this for runtime client settings, such as wait timings, not to reconfigure the Testplane runner or renegotiate browser capabilities. + +## Usage {/* #usage */} + +`browser.extendOptions(options)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | ------------------------- | ----------- | ------------------------------------------------ | +| `options` | `Record` | Required | Top-level fields to assign to `browser.options`. | + +## Result {/* #result */} + +`Promise`: the command completes without a return value. + +## Limitations {/* #limitations */} + +Nested objects are replaced, not deeply merged. Existing fields not present in the argument are retained. The method does not validate option names or rebuild the session: changing a value only matters where a command reads that runtime option. It does not change the configuration returned by `getConfig()`. Save and restore modified values in `finally`. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("extendOptions: change WebdriverIO wait settings for this browser", async ({ browser }) => { + const original = { + waitforTimeout: browser.options.waitforTimeout, + waitforInterval: browser.options.waitforInterval, + }; + const configuredTimeout = (await browser.getConfig()).waitTimeout; + assert.equal(typeof configuredTimeout, "number"); + try { + const result = await browser.extendOptions({ waitforTimeout: 1000, waitforInterval: 50 }); + assert.equal(result, undefined); + assert.equal(browser.options.waitforTimeout, 1000); + assert.equal(browser.options.waitforInterval, 50); + assert.equal((await browser.getConfig()).waitTimeout, configuredTimeout); + await browser.url("/"); + await browser.execute(() => { + setTimeout(() => { + document.body.innerHTML = '

Ready

'; + }, 100); + }); + await browser.$("#ready").waitForExist(); + assert.equal(await browser.$("#ready").getText(), "Ready"); + } finally { + await browser.extendOptions(original); + } +}); +``` + +## See also {/* #see-also */} + +- [getConfig](./getConfig.mdx) +- [waitUntil](./waitUntil.mdx) diff --git a/docs/commands/browser/getState.mdx b/docs/commands/browser/getState.mdx index e0c94576..b7eb740e 100644 --- a/docs/commands/browser/getState.mdx +++ b/docs/commands/browser/getState.mdx @@ -6,53 +6,55 @@ import Version from "../../_partials/specs/version.mdx"; ## Overview {/* #overview */} -A browser command that restores the session state from a file which previously saved using the [saveState][saveState] command. +Reads a JSON state file created by [saveState](./saveState.mdx) and returns its contents. It **does not restore browser state**: cookies, storage and the current page stay unchanged. To restore the session, use [restoreState](./restoreState.mdx). ## Usage {/* #usage */} -```typescript -import type { SaveStateData } from "testplane"; +`await browser.getState(options)` -const stateDump: SaveStateData = await browser.getState({ - path: "./stateDump.json", -}); -``` +## Command parameters {/* #parameters */} + +| Parameter | Type | Description | +| -------------- | -------- | ------------------------------------------------------------------------------------------- | +| `options.path` | `string` | Path to the JSON file. Relative paths resolve from the Testplane process working directory. | + +When `options` is omitted, the command uses `browser.config.stateOpts`. An explicit object is not merged with configuration: `getState({})` does not inherit the configured path. + +## Result and errors {/* #result */} + +Returns a Promise with the parsed data. A `saveState` file contains `SaveStateData`: an optional `cookies` array and `framesData`, with `localStorage` and `sessionStorage` grouped by origin. The command does not validate the JSON schema. -## Command Parameters {/* #parameters */} - - - - - - - - - - - - - - - - - - -
**Name****Type****Default****Description**
path`string``-`Path to file with state.
- -## Usage Examples {/* #examples */} - -Save state in file. - -```typescript -it("test", async ({ browser }) => { - const stateData = await browser.getState({ - path: "./stateDump.json", - }); +If no path is set, it logs an error and returns `null`. File read and JSON parsing errors reject the Promise. + +## Example {/* #examples */} + +Set `baseUrl` to a test HTML page. The example saves localStorage to a temporary file, changes the browser value and verifies that reading the file does not restore the session. + +```javascript +const assert = require("node:assert/strict"); +const fs = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); + +it("getState: read a saved file without restoring the session", async ({ browser }) => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "testplane-state-")); + const statePath = path.join(directory, "state.json"); + try { + await browser.url("/"); + const origin = new URL(await browser.getUrl()).origin; + await browser.execute(() => localStorage.setItem("example", "saved")); + await browser.saveState({ path: statePath, cookies: false, localStorage: true }); + await browser.execute(() => localStorage.setItem("example", "current")); + const state = await browser.getState({ path: statePath }); + assert.equal(state.framesData[origin].localStorage.example, "saved"); + assert.equal(await browser.execute(() => localStorage.getItem("example")), "current"); + } finally { + await fs.rm(directory, { recursive: true, force: true }); + } }); ``` -## Related Commands {/* #related */} - -- [saveState][saveState] +## Related commands {/* #related */} -[saveState]: ../saveState +- [saveState](./saveState.mdx) +- [restoreState](./restoreState.mdx) diff --git a/docs/commands/browser/mock.mdx b/docs/commands/browser/mock.mdx index 08955c52..068a0291 100644 --- a/docs/commands/browser/mock.mdx +++ b/docs/commands/browser/mock.mdx @@ -1,139 +1,97 @@ -import Admonition from "@theme/Admonition"; - # mock ## Overview {/* #overview */} -Use the `mock` command to mock the response to a request. +Creates a network interceptor. The returned object can observe requests, replace responses or abort loading. Capabilities depend on the session protocol. -You can enable mocking depending on the URL, headers, or status code. Calling the `mock` method returns a stub object that you can use to modify the response from the web resource. With the stub object, you can return a mock response or abort the request. +- **WebDriver BiDi**: interception through BiDi network events, including request overrides, redirects and rule reset. +- **Classic with CDP/Puppeteer**: the legacy implementation for a compatible browser, not a universal Classic WebDriver capability. +- **Sauce Labs WebDriver interception**: a provider extension with a different API and no events; do not assume the BiDi example works with it. -There are 3 ways to modify the response: +See [CDP setup](../../guides/how-to-use-cdp.mdx) and [request interception](../../guides/how-to-intercept-requests-and-responses.mdx). The differences below describe Testplane 9.3.2 / WDIO 9.6.2. -- return a custom JSON object (e.g., to mock an API response); -- replace the response with a local file (inject a modified JavaScript file); -- redirect to another URL and return the response from there. +## Usage {/* #usage */} - -The _mock_ command only works when using _Chrome DevTools Protocol (CDP)_. +`await browser.mock(url, filters)` -Read more in the section "[How to use Chrome DevTools Protocol in testplane][how-to-use-cdp]". +## Command parameters {/* #parameters */} -Also, see the recipe "[How to intercept requests and responses][how-to-intercept-requests-and-responses]". +| Parameter | BiDi | Classic/CDP | +| ----------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------- | +| `url` | `URLPattern` string: an absolute HTTP(S) pattern or pathname such as `/api/items`. | Glob string or `RegExp`. | +| `method` | HTTP method string or predicate. | Same. | +| `requestHeaders` | Request-header object or predicate. | Same. | +| `responseHeaders` | Response-header object or predicate. | Not applied. | +| `headers` | Do not use; use `responseHeaders` for responses. | Filters **response headers**, not request headers. | +| `postData` | Not applied. | Request body string or predicate. | +| `statusCode` | Response status number or predicate. | Same. | - +Filters are optional. Response filters are evaluated at the response stage: the BiDi `request` event can occur earlier. CDP globs and BiDi `URLPattern` strings are not interchangeable. -## Usage {/* #usage */} +## Result {/* #result */} -```javascript -await browser.mock(url, { method, headers, responseHeaders, postData, statusCode }); -``` +Returns a Promise with a mock object. Shared operations: [respond](../mock/respond.mdx), [respondOnce](../mock/respondOnce.mdx), [abort](../mock/abort.mdx), [abortOnce](../mock/abortOnce.mdx), [waitForResponse](../mock/waitForResponse.mdx), [clear](../mock/clear.mdx) and [restore](../mock/restore.mdx). Signatures can differ between implementations. -## Command Parameters {/* #parameters */} +BiDi only: [request](../mock/request.mdx), [requestOnce](../mock/requestOnce.mdx), [redirect](../mock/redirect.mdx), [redirectOnce](../mock/redirectOnce.mdx) and [reset](../mock/reset.mdx). - - - - - - - - - - - +`mock.calls` stores response history: - -
**Name****Type****Description**
urlStringThe URL of the request to mock.
methodString or FunctionThe HTTP method to filter the resource.
headersObject or FunctionThe request headers to filter the resource.
responseHeadersObject or FunctionThe response headers to filter the resource.
postDataString or FunctionThe request postData to filter the resource.
statusCodeNumber or FunctionThe status code to filter the resource.
+- BiDi: `network.responseStarted` objects, for example `call.request.url` and `call.response.status`. This does not guarantee a fully downloaded body. +- Classic/CDP: entries with `url`, `body`, `headers`, `responseHeaders`, `statusCode`. -## Usage Examples {/* #examples */} +Do not treat one implementation's fields as a shared contract. `clear()` clears history but retains rules. `restore()` removes the interceptor; await it in `finally`. -```javascript -it("should mock network resources", async ({ browser }) => { - // use a static string - const userListMock = await browser.mock("**" + "/users/list"); - - // we can also mock responses based on - // request or response headers, status code, or postData - const strictMock = await browser.mock("**", { - // mock all json responses - statusCode: 200, - headers: { "Content-Type": "application/json" }, - responseHeaders: { "Cache-Control": "no-cache" }, - postData: "foobar", - }); +## Events {/* #events */} - // comparator function - const apiV1Mock = await browser.mock("**" + "/api/v1", { - statusCode: statusCode => statusCode >= 200 && statusCode <= 203, - headers: headers => - headers["Authorization"] && headers["Authorization"].startsWith("Bearer "), - responseHeaders: headers => headers["Impersonation"], - postData: data => typeof data === "string" && data.includes("foo"), - }); -}); +Subscribe with `mock.on(name, listener)`. Listeners run in Node.js, not in the browser. -it("should modify API responses", async ({ browser }) => { - // filter by method - const todoMock = await browser.mock("**" + "/todos", { - method: "get", - }); +| Event | BiDi | Classic/CDP | +| ----------- | -------------------------------------------------------------- | ------------------------------------------ | +| `request` | `network.beforeRequestSent` event: URL at `event.request.url`. | CDP `Fetch.requestPaused` event. | +| `match` | Not emitted in this version, although declared in types. | Matched response entry. | +| `overwrite` | Request or response event that received an override. | Overridden response parameters. | +| `fail` | Aborted request ID. | Object with `requestId` and `errorReason`. | +| `continue` | Continued request ID. | Continued request ID. | - // mock the response of the endpoint with predefined fixture - mock.respond([ - { - title: "Injected Todo", - order: null, - completed: false, - url: "http://todo-backend-express-knex.herokuapp.com/916", - }, - ]); - - // respond with a different status code or header - mock.respond( - [ - { - title: "Injected Todo", - order: null, - completed: false, - url: "http://todo-backend-express-knex.herokuapp.com/916", - }, - ], - { - statusCode: 404, - headers: { - "x-custom-header": "foobar", - }, - }, - ); -}); +An event alone is not proof that the entire network exchange has finished. -it("should modify text assets", async ({ browser }) => { - const scriptMock = await browser.mock("**" + "/script.min.js"); - scriptMock.respond("./tests/fixtures/script.js"); -}); +## Example {/* #examples */} -it("should redirect web resources", async ({ browser }) => { - const headerMock = await browser.mock("**" + "/header.png"); - headerMock.respond("https://media.giphy.com/media/F9hQLAVhWnL56/giphy.gif"); +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. - const pageMock = await browser.mock("https://google.com/"); - pageMock.respond("https://webdriver.io"); - await browser.url("https://google.com"); +```javascript +const assert = require("node:assert/strict"); - console.log(await browser.getTitle()); - // outputs: "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js" +it("browser.mock: observe and replace a BiDi response", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items", { method: "GET", statusCode: 200 }); + const seenUrls = []; + mock.on("request", event => seenUrls.push(event.request.url)); + try { + mock.respond({ source: "mocked" }); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(body.source, "mocked"); + assert.equal(mock.calls.length, 1); + assert.ok(mock.calls[0].request.url.endsWith("/api/items")); + assert.ok(seenUrls.some(url => url.endsWith("/api/items"))); + } finally { + await mock.restore(); + } }); ``` -## Related Commands {/* #related */} - -- [mockClearAll](../mockClearAll) -- [mockRestoreAll](../mockRestoreAll) +## Related commands {/* #related */} -## References +- [mockClearAll](./mockClearAll.mdx) +- [mockRestoreAll](./mockRestoreAll.mdx) +- [respond](../mock/respond.mdx) -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/browser/mock), from which we drew some information while writing our version. +## References {/* #references */} -[how-to-use-cdp]: ../../../guides/how-to-use-cdp -[how-to-intercept-requests-and-responses]: ../../../guides/how-to-intercept-requests-and-responses +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/browser/mock). diff --git a/docs/commands/browser/restore.mdx b/docs/commands/browser/restore.mdx new file mode 100644 index 00000000..2a51fbdf --- /dev/null +++ b/docs/commands/browser/restore.mdx @@ -0,0 +1,51 @@ +# restore + +## Overview {/* #overview */} + +Remove emulation installed by `emulate` on the same browser instance. This command does not restore cookies, storage, network throttling, CPU throttling or mocks. + +## Usage {/* #usage */} + +`browser.restore(scopes)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | -------------------- | --------------------- | ------------------------------------------------------------------------- | +| `scopes` | `string \| string[]` | All registered scopes | One scope or several: clock, geolocation, userAgent, colorScheme, onLine. | + +## Result {/* #result */} + +`Promise`. With no recorded emulation for the browser instance, the command does nothing. + +## Limitations {/* #limitations */} + +Call cleanup in the same test that enabled emulation, preferably in `finally`. For preload-based overrides, reload the page after restoration to observe the original behavior. `device` is not registered as a restore scope: call the function returned by `emulate("device", name)`. That function restores the Desktop Chrome viewport (1280 × 720, DPR 1), not the original viewport. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("restore: remove a user-agent override", async ({ browser }) => { + await browser.url("/"); + const original = await browser.execute(() => navigator.userAgent); + await browser.emulate("userAgent", "ExampleBrowser/1.0"); + try { + await browser.url("/"); + assert.equal(await browser.execute(() => navigator.userAgent), "ExampleBrowser/1.0"); + } finally { + await browser.restore("userAgent"); + } + await browser.url("/"); + assert.equal(await browser.execute(() => navigator.userAgent), original); +}); +``` + +## See also {/* #see-also */} + +- [emulate](./emulate.mdx) +- [restoreState](./restoreState.mdx) +- [mock.restore](../mock/restore.mdx) diff --git a/docs/commands/browser/setViewport.mdx b/docs/commands/browser/setViewport.mdx new file mode 100644 index 00000000..cd275a7d --- /dev/null +++ b/docs/commands/browser/setViewport.mdx @@ -0,0 +1,53 @@ +# setViewport + +## Overview {/* #overview */} + +Set the size of the page viewport and its device pixel ratio. Unlike `setWindowSize`, this changes the area used to render the page, not the outer browser window. + +## Usage {/* #usage */} + +`browser.setViewport({ width, height, devicePixelRatio })` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------ | -------- | ----------- | -------------------------------------------------------- | +| `width` | `number` | Required | Viewport width in CSS pixels. | +| `height` | `number` | Required | Viewport height in CSS pixels. | +| `devicePixelRatio` | `number` | 1 | The device pixel ratio (DPR). Omitting it sets DPR to 1. | + +## Result {/* #result */} + +`Promise`. + +## Limitations {/* #limitations */} + +Requires WebDriver BiDi (`webSocketUrl: true`) and backend support for viewport resizing. In WDIO 9.6.2, the client checks width and height against 0 and `Number.MAX_SAFE_INTEGER`. The backend can impose stricter limits: passing the client check does not guarantee support for that viewport size. Save the previous dimensions if they must be restored. Changing dimensions does not emulate a mobile device, touch input or its browser engine. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("setViewport: change page dimensions", async ({ browser }) => { + await browser.url("/"); + const original = await browser.execute(() => ({ + width: innerWidth, + height: innerHeight, + devicePixelRatio, + })); + try { + await browser.setViewport({ width: 800, height: 600, devicePixelRatio: 1 }); + assert.deepEqual(await browser.execute(() => [innerWidth, innerHeight]), [800, 600]); + } finally { + await browser.setViewport(original); + } +}); +``` + +## See also {/* #see-also */} + +- [setWindowSize](./setWindowSize.mdx) +- [emulate](./emulate.mdx) diff --git a/docs/commands/browser/switchFrame.mdx b/docs/commands/browser/switchFrame.mdx new file mode 100644 index 00000000..3cd3111d --- /dev/null +++ b/docs/commands/browser/switchFrame.mdx @@ -0,0 +1,57 @@ +# switchFrame + +## Overview {/* #overview */} + +Switch subsequent browser commands into an iframe, or return to the top-level document. This changes the browsing context, not the selected window. + +## Usage {/* #usage */} + +`browser.switchFrame(context)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | --------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `context` | `Element \| string \| Function \| null` | Required | An iframe element; for BiDi, also a context ID, URL, URL substring, or predicate executed in candidate frames. null selects the top-level document. | + +## Result {/* #result */} + +`Promise`. In BiDi mode, returns the selected context ID. In Classic mode, the underlying protocol switch may return no value. + +## Limitations {/* #limitations */} + +Classic supports an iframe element and `null`, but rejects string and function selectors. Use `null` in `finally` when the following test steps must run in the main document. A frame that cannot be found causes an error. Do not confuse this command with `switchWindow`. + +:::warning + +In WDIO 9.6.2, the predicate-function branch references a global `browser`. In a standard Testplane test using the `{ browser }` argument, a matching frame is found but the command fails with `browser is not defined`. This was reproduced in a BiDi session. Use an iframe element as shown below; do not introduce a global `browser` to work around the failure. + +::: + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("switchFrame: enter an iframe and return", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Main page

'; + }); + try { + await browser.switchFrame(browser.$("#frame")); + assert.equal(await browser.$("p").getText(), "Frame content"); + } finally { + await browser.switchFrame(null); + } + assert.equal(await browser.$("h1").getText(), "Main page"); +}); +``` + +## See also {/* #see-also */} + +- [switchWindow](./switchWindow.mdx) +- [execute](./execute.mdx) diff --git a/docs/commands/browser/throttle.mdx b/docs/commands/browser/throttle.mdx index 18fc624c..e32dd053 100644 --- a/docs/commands/browser/throttle.mdx +++ b/docs/commands/browser/throttle.mdx @@ -1,73 +1,49 @@ -import Admonition from "@theme/Admonition"; - # throttle ## Overview {/* #overview */} -Use the `throttle` command to simulate different types of network connections for the user. +Deprecated alias of [throttleNetwork](./throttleNetwork.mdx). Limits network throughput through CDP/Puppeteer; it is not equivalent to [emulate("onLine")](./emulate.mdx). Use `throttleNetwork` for new code. -This command allows you to model the behavior of a website or web application under various network bandwidth conditions. +## Usage {/* #usage */} -There are also many presets with ready-made network configuration settings. For example: +`await browser.throttle(profile)` -- offline | online -- GPRS -- Regular2G | Good2G -- Regular3G | Good3G -- Regular4G -- DSL -- WiFi +## Command parameters {/* #parameters */} - -The _throttle_ command only works when using the _Chrome DevTools Protocol (CDP)_. +Parameters match [throttleNetwork](./throttleNetwork.mdx#parameters). Pass a profile name: `offline`, `online`, `GPRS`, `Regular2G`, `Good2G`, `Regular3G`, `Good3G`, `Regular4G`, `DSL`, `WiFi`. Names contain no spaces. -Read more in the section "[How to use Chrome DevTools Protocol in testplane][how-to-use-cdp]". +Or pass an object with `offline` (boolean), `latency` (milliseconds), `downloadThroughput` and `uploadThroughput` (bytes per second, `-1` disables throughput limits). Returns `Promise`. -Also, see the recipe "[How to manage network bandwidth][how-to-manage-network-bandwidth]". +## Limitations {/* #limitations */} - +Requires a browser with CDP/Puppeteer access. The implementation selects the first Puppeteer page: use one tab and `isolation: false` for this example, and navigate first. -## Usage {/* #usage */} +In Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150, the `online` profile did not restore connectivity after `offline`. Set `testsPerSession: 1` to prevent limits leaking into the next test. See [throttleNetwork](./throttleNetwork.mdx#limitations). -```javascript -await browser.throttle({ offline, latency, downloadThroughput, uploadThroughput }); -``` +## Example {/* #examples */} -## Command Parameters {/* #parameters */} - - - - - - - - - - - - -
**Name****Type****Description**
offlineBooleanEnable emulation of a loss of connection.
latencyNumberMinimum latency from the sent request to received response headers, in milliseconds.
downloadThroughputNumberMaximum total download throughput (bytes/sec). _-1_ disables download throttling.
uploadThroughputNumberMaximum total upload throughput (bytes/sec). _-1_ disables upload throttling.
- -## Usage Examples {/* #examples */} +Set `baseUrl` to a test HTTP server that returns `200` at the root path. The example uses a new session for each test. ```javascript -it("should throttle the network", async ({ browser }) => { - // use a preset - await browser.throttle("Regular 3G"); - - // configure the network settings manually - await browser.throttle({ - offline: false, - downloadThroughput: (200 * 1024) / 8, - uploadThroughput: (200 * 1024) / 8, - latency: 20, - }); +const assert = require("node:assert/strict"); + +it("throttle: use the deprecated alias in an isolated session", async ({ browser }) => { + await browser.url("/"); + await browser.throttle("Good3G"); + const status = await browser.execute( + async () => (await fetch("/?throttled", { cache: "no-store" })).status, + ); + assert.equal(status, 200); }); ``` -## References +## Related commands {/* #related */} + +- [throttleNetwork](./throttleNetwork.mdx) +- [throttleCPU](./throttleCPU.mdx) +- [CDP](../../guides/how-to-use-cdp.mdx) +- [Emulation](../../basic-guides/user-environment-emulation.mdx) -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/browser/throttle), from which we drew some information while writing our version. +## References {/* #references */} -[how-to-use-cdp]: ../../../guides/how-to-use-cdp -[how-to-manage-network-bandwidth]: ../../../basic-guides/emulation +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/browser/throttle). diff --git a/docs/commands/browser/throttleCPU.mdx b/docs/commands/browser/throttleCPU.mdx new file mode 100644 index 00000000..ae4b26e2 --- /dev/null +++ b/docs/commands/browser/throttleCPU.mdx @@ -0,0 +1,49 @@ +# throttleCPU + +## Overview {/* #overview */} + +Apply a CPU slowdown through Chrome DevTools Protocol. Use this to exercise a page under reduced processing speed, not to reproduce a particular physical device. + +## Usage {/* #usage */} + +`browser.throttleCPU(factor)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | -------- | ----------- | ---------------------------------------------------------------------- | +| `factor` | `number` | Required | Slowdown factor: 1 disables throttling, 2 requests a twofold slowdown. | + +## Result {/* #result */} + +`Promise`. + +## Limitations {/* #limitations */} + +Requires Chromium and an available Puppeteer/CDP connection. The current implementation targets the first page returned by Puppeteer, not necessarily the active Testplane page. For this example, use one tab, `isolation: false` and `testsPerSession: 1`; a fresh session preserves test isolation. A non-number or an unavailable CDP connection causes an error. Factor 1 requests the normal CPU mode. + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("throttleCPU: run a page with a slower CPU", async ({ browser }) => { + await browser.url("/"); + try { + await browser.throttleCPU(2); + await browser.execute(() => { + document.body.innerHTML = ""; + }); + assert.equal(await browser.$("button").getText(), "Ready"); + } finally { + await browser.throttleCPU(1); + } +}); +``` + +## See also {/* #see-also */} + +- [throttleNetwork](./throttleNetwork.mdx) +- [getPuppeteer](./getPuppeteer.mdx) diff --git a/docs/commands/browser/throttleNetwork.mdx b/docs/commands/browser/throttleNetwork.mdx new file mode 100644 index 00000000..5910b688 --- /dev/null +++ b/docs/commands/browser/throttleNetwork.mdx @@ -0,0 +1,71 @@ +# throttleNetwork + +## Overview {/* #overview */} + +Limit network throughput and latency or put a page offline through CDP. This affects actual requests, unlike `emulate("onLine", false)`, which only changes a JavaScript property. + +## Usage {/* #usage */} + +`browser.throttleNetwork(profileOrOptions)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| -------------------- | ------------------ | ------------------ | -------------------------------------------------------------------------------------------- | +| `profileOrOptions` | `string \| object` | Required | A named profile or an object with offline, latency, downloadThroughput and uploadThroughput. | +| `offline` | `boolean` | Required in object | Whether network access is disabled. | +| `latency` | `number` | Required in object | Minimum latency in milliseconds. | +| `downloadThroughput` | `number` | Required in object | Download bytes per second; -1 removes this limit. | +| `uploadThroughput` | `number` | Required in object | Upload bytes per second; -1 removes this limit. | + +## Result {/* #result */} + +`Promise`. + +## Limitations {/* #limitations */} + +The local CDP path requires Chromium and a Puppeteer connection. It targets the first Puppeteer page. Use one tab, `isolation: false` and `testsPerSession: 1` for the example below. Load the page before disabling the network. Profiles: `offline`, `GPRS`, `Regular2G`, `Good2G`, `Regular3G`, `Good3G`, `Regular4G`, `DSL`, `WiFi`, `online`. Unsupported profile names throw an error. Sauce Labs sessions use a separate backend command. + +:::warning + +With Testplane 9.3.2, WDIO 9.6.2 and Chrome for Testing 150.0.7871.124, a subsequent `"online"` call did not restore networking after `"offline"`. Each call creates a separate CDP session. Do not rely on this reset when reusing a browser: the example ends the offline session after the test and checks networking in a new one. To switch modes within one test, use a single CDP session. + +::: + +## Example {/* #examples */} + +Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +// Configure testsPerSession: 1 so this offline session is not reused. +it("throttleNetwork: check a failed request while offline", async ({ browser }) => { + await browser.url("/"); + await browser.throttleNetwork("offline"); + const failed = await browser.execute(async () => { + try { + await fetch("/?offline-check", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); +}); + +it("throttleNetwork: the next session starts online", async ({ browser }) => { + await browser.url("/"); + const status = await browser.execute(async () => { + const response = await fetch("/?online-check", { cache: "no-store" }); + return response.status; + }); + assert.equal(status, 200); +}); +``` + +## See also {/* #see-also */} + +- [throttleCPU](./throttleCPU.mdx) +- [emulate](./emulate.mdx) +- [getPuppeteer](./getPuppeteer.mdx) diff --git a/docs/commands/browser/unstable_captureDomSnapshot.mdx b/docs/commands/browser/unstable_captureDomSnapshot.mdx new file mode 100644 index 00000000..74c5643a --- /dev/null +++ b/docs/commands/browser/unstable_captureDomSnapshot.mdx @@ -0,0 +1,81 @@ +# unstable_captureDomSnapshot + +## Overview {/* #overview */} + +Capture a compact textual representation of the current document or a selected subtree for diagnostics. To capture the document with options, pass the options object as the only argument. + +## Usage {/* #usage */} + +`browser.unstable_captureDomSnapshot(selector?, options?)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ----------------------- | ---------- | -------------------------- | ----------------------------------------------------------------------------------------- | +| `selector` | `string` | `body` / `documentElement` | Optional CSS selector selecting the snapshot root. Omit it to start at the document body. | +| `options.includeTags` | `string[]` | `[]` | Remove names from the default excluded-tag set; not an allowlist. | +| `options.excludeTags` | `string[]` | `[]` | Exclude these elements with their subtrees; takes precedence over includeTags. | +| `options.includeAttrs` | `string[]` | `[]` | Add names to the set of retained attributes. | +| `options.excludeAttrs` | `string[]` | `[]` | Remove names from the retained-attribute set; takes precedence over includeAttrs. | +| `options.truncateText` | `boolean` | `true` | Truncate direct text nodes. This flag does not disable attribute-value truncation. | +| `options.maxTextLength` | `number` | `100` | Maximum text or attribute length before adding `...`. | + +## Result {/* #result */} + +`Promise` with four fields: + +- `snapshot`: compact, indented text, not HTML. +- `omittedTags`: sorted names of excluded tags encountered during traversal. +- `omittedAttributes`: sorted names of attributes omitted on retained elements. +- `textWasTruncated`: whether text or an attribute was shortened. + +A missing CSS match returns `snapshot: "# Element not found: "`; invalid CSS returns `"# Invalid selector: "`. These cases do not throw a selector error. These lists are diagnostics of this traversal, not a complete report of every omitted descendant. + +`maxTextLength` does not limit `id`, `class` or state-marker values such as `@value`. Excluded `id` and `class` are not listed in `omittedAttributes`. + +## Limitations {/* #limitations */} + +By default, `head`, `link`, `meta`, `noscript`, `script`, `slot`, `style`, `template` and `title` are excluded. Tag names are case-insensitive. Retained attributes include IDs, classes, form attributes, selected ARIA attributes and test attributes. Other attributes can be added with `includeAttrs`. Hidden retained elements are marked `@hidden`; empty uninformative elements and SVG children may be omitted. This is neither the full DOM nor an accessibility tree. + +The snapshot can contain input values, page text and sensitive attributes. `excludeAttrs` is not a general redaction mechanism: current input values are also recorded as `@value`. Use non-sensitive fixture data and inspect output before attaching it to logs or reports. + +:::warning + +This is an unstable Testplane API. Its signature, behavior and result format may change between versions. + +::: + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("browser.unstable_captureDomSnapshot: capture a filtered subtree", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

abcdefghijk

Outside

'; + }); + const result = await browser.unstable_captureDomSnapshot("#panel", { + excludeAttrs: ["data-internal"], + maxTextLength: 6, + }); + assert.match(result.snapshot, /section#panel/); + assert.match(result.snapshot, /abcdef\.\.\./); + assert.doesNotMatch(result.snapshot, /Outside|data-internal/); + assert.ok(result.omittedTags.includes("script")); + assert.ok(result.omittedAttributes.includes("data-internal")); + assert.equal(result.textWasTruncated, true); + const missing = await browser.unstable_captureDomSnapshot("#missing"); + assert.equal(missing.snapshot, "# Element not found: #missing"); + const invalid = await browser.unstable_captureDomSnapshot("["); + assert.equal(invalid.snapshot, "# Invalid selector: ["); +}); +``` + +## See also {/* #see-also */} + +- [element.unstable_captureDomSnapshot](../element/unstable_captureDomSnapshot.mdx) +- [execute](./execute.mdx) diff --git a/docs/commands/browser/unstable_getCdp.mdx b/docs/commands/browser/unstable_getCdp.mdx new file mode 100644 index 00000000..4be8580b --- /dev/null +++ b/docs/commands/browser/unstable_getCdp.mdx @@ -0,0 +1,49 @@ +# unstable_getCdp + +## Overview {/* #overview */} + +Get Testplane’s existing Chrome DevTools Protocol connection for advanced diagnostics. This is Testplane’s `CDP` object, not a Puppeteer browser or a Puppeteer `CDPSession`. + +## Usage {/* #usage */} + +`browser.unstable_getCdp()` + +## Parameters {/* #parameters */} + +This command has no parameters. + +## Result {/* #result */} + +`Promise`. Returns `null` for a non-Chromium session or when Testplane could not establish its CDP connection. + +## Limitations {/* #limitations */} + +Check for `null` before use. `cdp.request(method, params?, cdpSessionId?)` sends a raw CDP request; page-targeted commands may need an attached target session ID. Domain helpers include `target`, `runtime`, `network`, `page`, `dom`, `css`, `fetch`, `debugger` and `profiler`. Their contracts are also unstable. Do not call `cdp.close()`: Testplane owns and uses this shared connection. Avoid changing domains used by the runner unless you understand the interaction. + +:::warning + +This is an unstable Testplane API. Its signature, behavior and result format may change between versions. + +::: + +## Example {/* #examples */} + +Run this test in a Chromium browser with an available CDP connection. It reads browser version metadata without changing the page or closing the shared connection. + +```javascript +const assert = require("node:assert/strict"); + +it("unstable_getCdp: query the connected Chromium browser", async ({ browser }) => { + const cdp = await browser.unstable_getCdp(); + assert.ok(cdp, "This test requires a Chromium session with a CDP connection"); + const version = await cdp.request("Browser.getVersion"); + assert.match(version.product, /Chrome/); + assert.equal(typeof version.protocolVersion, "string"); + // Testplane owns this connection: do not call cdp.close(). +}); +``` + +## See also {/* #see-also */} + +- [getPuppeteer](./getPuppeteer.mdx) +- [execute](./execute.mdx) diff --git a/docs/commands/browser/url.mdx b/docs/commands/browser/url.mdx index 79d45f52..a791c2c2 100644 --- a/docs/commands/browser/url.mdx +++ b/docs/commands/browser/url.mdx @@ -2,62 +2,74 @@ ## Overview {/* #overview */} -Use the `url` command to navigate to the specified URL in the browser. +Opens a URL. In Testplane, omitting the argument or passing an empty string returns the current URL without navigation. Navigating to the same URL again reloads the page. -If [baseUrl][base-url] is specified in the configuration, it will be added to the `url` parameter using the Node.js method [url.resolve()][url-resolve]. - -Calling `browser.url()` with the same URL as before will reload the page. +With [baseUrl](../../reference/config/browsers.mdx#base_url), the address is resolved using `url.resolve(baseUrl, url)`. For `baseUrl` equal to `http://example.com/site/`, `relative` opens `/site/relative`, while `/rootRelative` opens `/rootRelative`. An absolute URL does not depend on `baseUrl`. The trailing `/` in the base address affects relative path resolution. ## Usage {/* #usage */} -```javascript -await browser.url(url); -``` - -## Command Parameters {/* #parameters */} - - - - - - - +`await browser.url(url, options)`; `await browser.url()` - -
**Name****Type****Description**
urlStringThe URL to open.
+## Command parameters {/* #parameters */} -## Usage Examples {/* #examples */} +| Parameter | Description | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `url` | Absolute or relative address string. Omit it to read the current address. | +| `options.wait` | BiDi: `none`, `interactive`, `complete` or `networkIdle`. Defaults to the session page-load strategy; normally `complete`. See the `networkIdle` limitation below. | +| `options.timeout` | BiDi: timeout for `networkIdle` only, default `5000` ms. Does not replace Testplane or page-load timeouts. | +| `options.headers` | BiDi: headers for the navigation request, not persistent headers for every page resource. | +| `options.auth` | BiDi: `{ user, pass }` for HTTP Basic Auth. Replaces `Authorization` from `headers`. Use HTTPS and keep secrets out of test files. | +| `options.onBeforeLoad` | BiDi: function run before the new document scripts. It is serialized and cannot access Node.js closures. | -**url.js** +## Limitations {/* #limitations */} -```javascript -// navigate to URL -await browser.url("https://webdriver.io"); +Options apply only to HTTP(S) navigation in BiDi. The Testplane wrapper ignores the second argument in Classic. After successful navigation, the temporary init script is removed; its changes in the loaded document remain until the next navigation. -// get the URL -console.log(await browser.getUrl()); // outputs: "https://webdriver.io" -``` +In the verified Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150 combination, `networkIdle` fails with `Couldn't find request for navigation with id ...`: the implementation looks up a navigation request using a context ID. Use `complete`, then explicitly wait for the required application state. `complete` alone does not mean that background application requests have finished. -**baseUrlResolutions.js** +## Result {/* #result */} -```javascript -// Let baseUrl = http://example.com/site/ important to add a slash in the end +Without an address, returns the current URL string. Navigation returns `undefined` in Classic; in BiDi it may return navigation request data or `undefined`. To read the address, use `getUrl()` or `url()` without an argument. -// Specifying a full URL results in https://webdriver.io -await browser.url("https://webdriver.io"); +## Example {/* #examples */} -// Specifying a URL without a leading slash -// results in the URL relative to the baseUrl: http://example.com/site/relative -await browser.url("relative"); +The example uses Chrome/BiDi and a test HTTP server in `baseUrl`. At `/navigation`, a page script records `window.initialSeed = window.navigationSeed ?? null`, and the server embeds the received `x-example` header in `window.receivedHeader`. The root path serves a regular HTML page. -// Specifying a URL with a leading slash -// results in the URL relative to the root of baseUrl: http://example.com/rootRelative -await browser.url("/rootRelative"); +```javascript +const assert = require("node:assert/strict"); + +it("url: navigate with BiDi options and read the current URL", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/navigation", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + await browser.url(target, { + wait: "complete", + headers: { "x-example": "navigation" }, + onBeforeLoad: () => { + window.navigationSeed = 42; + }, + }); + assert.equal(await browser.url(), target); + assert.deepEqual(await browser.execute(() => [window.initialSeed, window.receivedHeader]), [ + 42, + "navigation", + ]); + await browser.execute(() => { + window.unsavedValue = "temporary"; + }); + await browser.url(target); + assert.equal(await browser.execute(() => typeof window.unsavedValue), "undefined"); + assert.equal(await browser.execute(() => window.initialSeed), null); +}); ``` -## References +## Related commands {/* #related */} + +- [addInitScript](./addInitScript.mdx) +- [waitUntil](./waitUntil.mdx) -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/browser/url), from which we drew some information while writing our version. +## References {/* #references */} -[base-url]: ../../../reference/config/browsers#base_url -[url-resolve]: https://nodejs.org/api/url.html#urlresolvefrom-to +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/browser/url). diff --git a/docs/commands/element/customDollar.mdx b/docs/commands/element/customDollar.mdx new file mode 100644 index 00000000..28f80274 --- /dev/null +++ b/docs/commands/element/customDollar.mdx @@ -0,0 +1,50 @@ +# custom$ + +## Overview {/* #overview */} + +Find an element with a custom locator strategy, passing the parent element as the final strategy argument. Make the strategy search inside that element to limit its scope. + +## Usage {/* #usage */} + +`element.custom$(strategyName, ...strategyArguments)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------- | ----------- | ----------- | -------------------------------------------------- | +| `strategyName` | `string` | Required | Name registered with `browser.addLocatorStrategy`. | +| `strategyArguments` | `unknown[]` | [] | Arguments passed before the parent DOM element. | + +## Result {/* #result */} + +An awaitable element object. If the strategy returns an array, only its first element is used. A result without a match has no `elementId`. + +## Limitations {/* #limitations */} + +Register the strategy on the same browser instance. The parent must exist; an unknown strategy or missing parent causes an error. The callback runs in the browser and must return a DOM element or an array of DOM elements. Convert a `NodeList` with `Array.from`. Scoping is the callback’s responsibility: using `document.querySelector` still searches the whole document. This command does not wait for a particular number of children or for visibility. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("element.custom$: search inside an element", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '
'; + }); + browser.addLocatorStrategy("byTestWithin", (value, root) => + root.querySelector(`[data-test="${value}"]`), + ); + const button = await browser.$("#panel").custom$("byTestWithin", "save"); + assert.equal(await button.getText(), "Inside"); +}); +``` + +## See also {/* #see-also */} + +- [addLocatorStrategy](../browser/addLocatorStrategy.mdx) +- [browser.custom$](../browser/customDollar.mdx) diff --git a/docs/commands/element/customDollarDollar.mdx b/docs/commands/element/customDollarDollar.mdx new file mode 100644 index 00000000..e205eb11 --- /dev/null +++ b/docs/commands/element/customDollarDollar.mdx @@ -0,0 +1,51 @@ +# custom$$ + +## Overview {/* #overview */} + +Find elements with a custom locator strategy, passing the parent element as the final strategy argument. Make the strategy search inside that element to limit its scope. + +## Usage {/* #usage */} + +`element.custom$$(strategyName, ...strategyArguments)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------- | ----------- | ----------- | -------------------------------------------------- | +| `strategyName` | `string` | Required | Name registered with `browser.addLocatorStrategy`. | +| `strategyArguments` | `unknown[]` | [] | Arguments passed before the parent DOM element. | + +## Result {/* #result */} + +An awaitable array of element objects. If the strategy finds nothing, the array is empty. + +## Limitations {/* #limitations */} + +Register the strategy on the same browser instance. The parent must exist; an unknown strategy or missing parent causes an error. The callback runs in the browser and must return a DOM element or an array of DOM elements. Convert a `NodeList` with `Array.from`. Scoping is the callback’s responsibility: using `document.querySelector` still searches the whole document. This command does not wait for a particular number of children or for visibility. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("element.custom$$: find children with a custom strategy", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Outside

First

Second

'; + }); + browser.addLocatorStrategy("byTestWithin", (value, root) => + Array.from(root.querySelectorAll(`[data-test="${value}"]`)), + ); + const items = await browser.$("#panel").custom$$("byTestWithin", "item"); + assert.equal(items.length, 2); + assert.equal(await items[1].getText(), "Second"); +}); +``` + +## See also {/* #see-also */} + +- [addLocatorStrategy](../browser/addLocatorStrategy.mdx) +- [browser.custom$$](../browser/customDollarDollar.mdx) diff --git a/docs/commands/element/execute.mdx b/docs/commands/element/execute.mdx new file mode 100644 index 00000000..0ed22e9c --- /dev/null +++ b/docs/commands/element/execute.mdx @@ -0,0 +1,52 @@ +# execute + +## Overview {/* #overview */} + +Execute JavaScript in the browser with the selected DOM element as the first argument. Use it to inspect or change DOM properties that have no dedicated command. + +## Usage {/* #usage */} + +`element.execute(script, ...args)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | -------------------- | ----------- | ---------------------------------------------------- | +| `script` | `Function \| string` | Required | Function, or function body, executed in the browser. | +| `args` | `unknown[]` | [] | Additional arguments after the DOM element. | + +## Result {/* #result */} + +A promise with the serialized function result. The BiDi execution path also awaits a promise returned by the function. + +## Limitations {/* #limitations */} + +The command calls `waitForExist` before execution. Browser code cannot access Node.js variables or imports through closures; pass data as arguments. This does not imply visibility or clickability. The asynchronous example below uses WebDriver BiDi. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("element.execute: pass the DOM element to a browser function", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
'; + }); + const result = await browser.$("#counter").execute(async (element, increment) => { + const count = Number(element.dataset.count) + increment; + element.dataset.count = String(count); + return { count, id: element.id }; + }, 3); + assert.deepEqual(result, { count: 7, id: "counter" }); + assert.equal(await browser.$("#counter").getAttribute("data-count"), "7"); +}); +``` + +## See also {/* #see-also */} + +- [browser.execute](../browser/execute.mdx) +- [executeAsync](./executeAsync.mdx) +- [waitForExist](./waitForExist.mdx) diff --git a/docs/commands/element/executeAsync.mdx b/docs/commands/element/executeAsync.mdx new file mode 100644 index 00000000..87093154 --- /dev/null +++ b/docs/commands/element/executeAsync.mdx @@ -0,0 +1,57 @@ +# executeAsync + +## Overview {/* #overview */} + +Execute a callback-based browser script with the DOM element as its first argument. The final argument is a callback that sends one result back to the test. + +## Usage {/* #usage */} + +`element.executeAsync(script, ...args)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | -------------------- | ----------- | ------------------------------------------------------------- | +| `script` | `Function \| string` | Required | Function or function body. Call the final callback to finish. | +| `args` | `unknown[]` | [] | Arguments between the DOM element and callback. | + +## Result {/* #result */} + +A promise with the value passed to the callback. Returning a value from the script is not a substitute for calling the callback. + +## Limitations {/* #limitations */} + +Unlike `element.execute`, this implementation does not explicitly call `waitForExist`; wait for the element yourself. For Classic sessions, the protocol script timeout is set with `browser.setTimeout({ script: milliseconds })`. The BiDi path uses `script.callFunction` and does not implement a separate callback timer: do not rely on the Classic timeout to stop a missing callback there. Always call the callback, and keep a finite Testplane `testTimeout`. + +:::warning + +Deprecated in the bundled WebdriverIO API. For new BiDi tests, prefer `element.execute` with an async function. + +::: + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("element.executeAsync: return data through the final callback", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '

Ready

'; + }); + const element = await browser.$("#message"); + await element.waitForExist(); + await browser.setTimeout({ script: 5000 }); + const result = await element.executeAsync((node, prefix, done) => { + setTimeout(() => done(prefix + node.textContent), 20); + }, "Status: "); + assert.equal(result, "Status: Ready"); +}); +``` + +## See also {/* #see-also */} + +- [execute](./execute.mdx) +- [setTimeout](../browser/setTimeout.mdx) diff --git a/docs/commands/element/getElement.mdx b/docs/commands/element/getElement.mdx new file mode 100644 index 00000000..4784d366 --- /dev/null +++ b/docs/commands/element/getElement.mdx @@ -0,0 +1,45 @@ +# getElement + +## Overview {/* #overview */} + +Resolve a chainable element reference into its element object to read properties such as `selector` and `elementId`. + +## Usage {/* #usage */} + +`element.getElement()` + +## Parameters {/* #parameters */} + +This command has no parameters. + +## Result {/* #result */} + +`Promise`: the current element object, including its methods and metadata. + +## Limitations {/* #limitations */} + +This is not a new protocol search, a DOM snapshot or a wait for visibility. The wrapper returns the current element object; do not assume that an `elementId` remains valid after navigation or DOM replacement. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("getElement: inspect a resolved element object", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + const element = await browser.$("#save").getElement(); + assert.equal(element.selector, "#save"); + assert.equal(typeof element.elementId, "string"); + assert.equal(await element.getText(), "Save"); +}); +``` + +## See also {/* #see-also */} + +- [$](../browser/$.mdx) +- [isExisting](./isExisting.mdx) diff --git a/docs/commands/element/isStable.mdx b/docs/commands/element/isStable.mdx new file mode 100644 index 00000000..8e3d9530 --- /dev/null +++ b/docs/commands/element/isStable.mdx @@ -0,0 +1,57 @@ +# isStable + +## Overview {/* #overview */} + +Check whether the element’s bounding rectangle stays unchanged across two animation frames. Use the result to detect geometric movement, not arbitrary page activity. + +## Usage {/* #usage */} + +`element.isStable()` + +## Parameters {/* #parameters */} + +This command has no parameters. + +## Result {/* #result */} + +`Promise`: `true` when the compared bounding-rectangle properties are equal; otherwise `false`. + +## Limitations {/* #limitations */} + +Use an existing element in the active tab. A hidden document causes an error because animation frames do not run normally there. Opacity, colors and network activity can change without affecting geometry. A `true` result is a measurement at this moment, not a guarantee that all animations have finished. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("isStable: check whether element geometry changes", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
Box
'; + }); + const box = await browser.$("#box"); + await box.waitForExist(); + assert.equal(await box.isStable(), true); + await box.execute(node => { + node.animate([{ transform: "translateX(0)" }, { transform: "translateX(200px)" }], { + duration: 500, + iterations: Infinity, + direction: "alternate", + }); + }); + try { + assert.equal(await box.isStable(), false); + } finally { + await box.execute(node => node.getAnimations().forEach(animation => animation.cancel())); + } + assert.equal(await box.isStable(), true); +}); +``` + +## See also {/* #see-also */} + +- [waitForStable](./waitForStable.mdx) +- [waitForExist](./waitForExist.mdx) diff --git a/docs/commands/element/unstable_captureDomSnapshot.mdx b/docs/commands/element/unstable_captureDomSnapshot.mdx new file mode 100644 index 00000000..f5ad416e --- /dev/null +++ b/docs/commands/element/unstable_captureDomSnapshot.mdx @@ -0,0 +1,55 @@ +# unstable_captureDomSnapshot + +## Overview {/* #overview */} + +Capture a compact DOM snapshot rooted at the selected element. Content outside that element is not included. + +## Usage {/* #usage */} + +`element.unstable_captureDomSnapshot(options?)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | ------------------------ | ----------- | ----------------------------------------------------------------- | +| `options` | `CaptureSnapshotOptions` | `{}` | The same filtering and truncation options as the browser command. | + +## Result {/* #result */} + +`Promise` with `snapshot`, `omittedTags`, `omittedAttributes` and `textWasTruncated`. See the [shared result contract](../browser/unstable_captureDomSnapshot.mdx#result). + +## Limitations {/* #limitations */} + +Uses `element.execute`, including its wait for the element to exist. Filtering, truncation and privacy limitations are the same as for the [browser command](../browser/unstable_captureDomSnapshot.mdx#limitations). In particular, `truncateText: false` does not disable attribute truncation, and input values are not sanitized. This method takes options, not another CSS selector. + +:::warning + +This is an unstable Testplane API. Its signature, behavior and result format may change between versions. + +::: + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("element.unstable_captureDomSnapshot: capture only this element", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Outside

'; + }); + const result = await browser.$("#panel").unstable_captureDomSnapshot({ truncateText: false }); + assert.match(result.snapshot, /section#panel/); + assert.match(result.snapshot, /button\[data-test=save\] "Save"/); + assert.doesNotMatch(result.snapshot, /Outside/); + assert.equal(result.textWasTruncated, false); +}); +``` + +## See also {/* #see-also */} + +- [browser.unstable_captureDomSnapshot](../browser/unstable_captureDomSnapshot.mdx) +- [execute](./execute.mdx) diff --git a/docs/commands/element/waitForStable.mdx b/docs/commands/element/waitForStable.mdx new file mode 100644 index 00000000..e7dd3459 --- /dev/null +++ b/docs/commands/element/waitForStable.mdx @@ -0,0 +1,54 @@ +# waitForStable + +## Overview {/* #overview */} + +Poll `isStable` until the element stops changing geometry, or starts changing when `reverse` is enabled. + +## Usage {/* #usage */} + +`element.waitForStable({ timeout, interval, reverse, timeoutMsg })` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | --------- | ----------------------- | -------------------------------------- | +| `timeout` | `number` | `waitTimeout` (3000 ms) | Maximum wait in milliseconds. | +| `interval` | `number` | `waitInterval` (500 ms) | Polling interval in milliseconds. | +| `reverse` | `boolean` | `false` | Wait for `isStable()` to return false. | +| `timeoutMsg` | `string` | — | Custom timeout error message. | + +## Result {/* #result */} + +`Promise`. Successful completion returns `undefined`, not `true`. A timeout or a stability-check error rejects the promise. + +## Limitations {/* #limitations */} + +The default timings come from the browser’s Testplane configuration. All `isStable` limitations apply, including active-tab and geometry-only checks. Wait for the element to exist first when it may be added later. This command does not wait for pending HTTP requests or non-geometric animations. + +## Example {/* #examples */} + +Set `baseUrl` to a local HTTP test site where `/` serves an HTML page. Run the example in a Testplane test file. + +```javascript +const assert = require("node:assert/strict"); + +it("waitForStable: wait for a size animation to stop", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
Box
'; + }); + const box = await browser.$("#box"); + await box.execute(node => { + node.animate([{ width: "40px" }, { width: "140px" }], { duration: 500, fill: "forwards" }); + }); + const result = await box.waitForStable({ timeout: 3000, interval: 50 }); + assert.equal(result, undefined); + assert.equal(await box.execute(node => node.getBoundingClientRect().width), 140); +}); +``` + +## See also {/* #see-also */} + +- [isStable](./isStable.mdx) +- [waitForExist](./waitForExist.mdx) +- [waitUntil](../browser/waitUntil.mdx) diff --git a/docs/commands/mock/abort.mdx b/docs/commands/mock/abort.mdx index 1d0888fd..6d4b9065 100644 --- a/docs/commands/mock/abort.mdx +++ b/docs/commands/mock/abort.mdx @@ -2,51 +2,60 @@ ## Overview {/* #overview */} -Use the `abort` command to abort a request with a specified error. +Aborts matching requests while the rule is active. BiDi and Classic/CDP signatures differ. Do not pass an error-code string in BiDi: it does not select a network error and may change whether the rule is one-shot. -A request can be aborted with one of the following errors: +## Usage {/* #usage */} -- Failed -- Aborted -- TimedOut -- AccessDenied -- ConnectionClosed | ConnectionReset | ConnectionRefused | ConnectionAborted | ConnectionFailed -- NameNotResolved -- InternetDisconnected -- AddressUnreachable -- BlockedByClient | BlockedByResponse +**BiDi:** `mock.abort(once = false)` -## Usage {/* #usage */} +**Classic/CDP:** `mock.abort(errorReason, sticky = true)` -```javascript -mock.abort(errorCode); -``` +## Parameters and result {/* #parameters */} + +BiDi: `once` is a boolean, default `false`; use [abortOnce](./abortOnce.mdx) for one request. Classic/CDP: `errorReason` selects the error; `sticky` defaults to `true` to retain the rule. -## Command Parameters {/* #parameters */} +Classic/CDP error codes: `Failed`, `Aborted`, `TimedOut`, `AccessDenied`, `ConnectionClosed`, `ConnectionReset`, `ConnectionRefused`, `ConnectionAborted`, `ConnectionFailed`, `NameNotResolved`, `InternetDisconnected`, `AddressUnreachable`, `BlockedByClient`, `BlockedByResponse`. - - - - - - +BiDi synchronously returns the same mock; Classic/CDP has no useful return value. BiDi rules share a queue with [request](./request.mdx) and [redirect](./redirect.mdx). A persistent rule replaces the queue; a one-shot rule retains other one-shot rules and removes the persistent rule. CDP appends rules: an earlier persistent rule can prevent a later rule from being used. - -
**Name****Type****Description**
errorCodeErrorCodeThe response error code. Possible values are listed [above](#overview).
+## Example {/* #examples */} -## Usage Examples {/* #examples */} +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. ```javascript -it("should block Google Analytics from page", async ({ browser }) => { - const mock = await browser.mock("https://www.google-analytics.com/**"); - mock.abort("Failed"); +const assert = require("node:assert/strict"); + +it("mock.abort: block matching BiDi requests", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.abort(); + for (let i = 0; i < 2; i++) { + const failed = await browser.execute(async () => { + try { + await fetch("/api/items", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); + } + } finally { + await mock.restore(); + } }); ``` -## Related Commands {/* #related */} +## Related commands {/* #related */} -- [abortOnce](../abortOnce) +- [abortOnce](./abortOnce.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) -## References +## References {/* #references */} -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/mock/abort), from which we drew some information while writing our version. +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/mock/abort). diff --git a/docs/commands/mock/abortOnce.mdx b/docs/commands/mock/abortOnce.mdx index 03adb03d..c3b2b77d 100644 --- a/docs/commands/mock/abortOnce.mdx +++ b/docs/commands/mock/abortOnce.mdx @@ -2,60 +2,62 @@ ## Overview {/* #overview */} -Use the `abortOnce` command to abort a request once with a specified error. +Aborts one matching request. BiDi and Classic/CDP signatures differ. Do not pass an error-code string in BiDi: it does not select a network error and may change whether the rule is one-shot. -A request can be aborted with one of the following errors: +## Usage {/* #usage */} -- Failed -- Aborted -- TimedOut -- AccessDenied -- ConnectionClosed | ConnectionReset | ConnectionRefused | ConnectionAborted | ConnectionFailed -- NameNotResolved -- InternetDisconnected -- AddressUnreachable -- BlockedByClient | BlockedByResponse +**BiDi:** `mock.abortOnce()` -## Usage {/* #usage */} +**Classic/CDP:** `mock.abortOnce(errorReason)` -```javascript -mock.abortOnce(errorCode); -``` +## Parameters and result {/* #parameters */} + +BiDi takes no arguments. Classic/CDP requires `errorReason`. -## Command Parameters {/* #parameters */} +Classic/CDP error codes: `Failed`, `Aborted`, `TimedOut`, `AccessDenied`, `ConnectionClosed`, `ConnectionReset`, `ConnectionRefused`, `ConnectionAborted`, `ConnectionFailed`, `NameNotResolved`, `InternetDisconnected`, `AddressUnreachable`, `BlockedByClient`, `BlockedByResponse`. - - - - - - +BiDi synchronously returns the same mock; Classic/CDP has no useful return value. BiDi rules share a queue with [request](./request.mdx) and [redirect](./redirect.mdx). A persistent rule replaces the queue; a one-shot rule retains other one-shot rules and removes the persistent rule. CDP appends rules: an earlier persistent rule can prevent a later rule from being used. - -
**Name****Type****Description**
errorCodeErrorCodeThe response error code. Possible values are listed [above](#overview).
+## Example {/* #examples */} -## Usage Examples {/* #examples */} +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. ```javascript -it("should block mock only once", async ({ browser }) => { - const mock = await browser.mock("https://webdriver.io"); - mock.abortOnce("Failed"); - - await browser - .url("https://webdriver.io") - // catch exception as the request to the page will fail - .catch(() => console.log('Failed to get the page "https://webdriver.io"')); - - await browser.url("https://webdriver.io"); - console.log(await browser.getTitle()); - // will output: "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js" +const assert = require("node:assert/strict"); + +it("mock.abortOnce: block one BiDi request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.abortOnce(); + const failed = await browser.execute(async () => { + try { + await fetch("/api/items", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); + const status = await browser.execute( + async () => (await fetch("/api/items", { cache: "no-store" })).status, + ); + assert.equal(status, 200); + } finally { + await mock.restore(); + } }); ``` -## Related Commands {/* #related */} +## Related commands {/* #related */} -- [abort](../abort) +- [abort](./abort.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) -## References +## References {/* #references */} -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/mock/abortOnce), from which we drew some information while writing our version. +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/mock/abortOnce). diff --git a/docs/commands/mock/clear.mdx b/docs/commands/mock/clear.mdx index ec1653da..02e4bbc8 100644 --- a/docs/commands/mock/clear.mdx +++ b/docs/commands/mock/clear.mdx @@ -2,31 +2,57 @@ ## Overview {/* #overview */} -Use the `clear` command to reset all information stored in the `mock.calls` array. +Clears `mock.calls` history but retains the interceptor, override rules and event listeners. The next matching response is added to history again. To remove rules without deleting a BiDi mock, use [reset](./reset.mdx); to remove the interceptor, use [restore](./restore.mdx). ## Usage {/* #usage */} -```javascript -mock.clear(); -``` +`await mock.clear()` -## Usage Examples {/* #examples */} +## Parameters and result {/* #parameters */} -```javascript -it("should clear mock", async ({ browser }) => { - const mock = await browser.mock("https://google.com/"); - await browser.url("https://google.com"); +Takes no arguments. BiDi synchronously returns the same mock; Classic/CDP has no useful return value. The example uses `await`, which also works with asynchronous implementations. Clear history before [waitForResponse](./waitForResponse.mdx) when waiting for a new response rather than a previously recorded one. - console.log(mock.calls.length); // outputs: 1 - mock.clear(); - console.log(mock.calls.length); // outputs: 0 +## Example {/* #examples */} + +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.clear: clear history but retain response rules", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond({ source: "mocked" }); + await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + await mock.clear(); + assert.equal(mock.calls.length, 0); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "mocked"); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + } finally { + await mock.restore(); + } }); ``` -## Related Commands {/* #related */} +## Related commands {/* #related */} -- [restore](../restore) +- [clear](./clear.mdx) +- [reset](./reset.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) -## References +## References {/* #references */} -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/mock/clear), from which we drew some information while writing our version. +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/mock/clear). diff --git a/docs/commands/mock/redirect.mdx b/docs/commands/mock/redirect.mdx new file mode 100644 index 00000000..476f0025 --- /dev/null +++ b/docs/commands/mock/redirect.mdx @@ -0,0 +1,60 @@ +# redirect + +## Overview {/* #overview */} + +Change the destination URL of matching outgoing requests. This is a request URL override, not an HTTP 3xx response or a browser navigation. + +## Usage {/* #usage */} + +`mock.redirect(redirectUrl, once = false)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | --------- | ----------- | ---------------------------------------- | +| `redirectUrl` | `string` | Required | Absolute destination URL. | +| `once` | `boolean` | `false` | Apply only to the next matching request. | + +## Result {/* #result */} + +The same mock object, synchronously. + +## Limitations {/* #limitations */} + +Requires a WebDriver BiDi session (`webSocketUrl: true`); this method is not provided by the Classic/CDP mock implementation. The examples explicitly subscribe to network events in every session; see [the subscription limitation](./waitForResponse.mdx#limitations). Always call `mock.restore()` in `finally` when the mock is no longer needed. + +The destination must be reachable. Redirects use the same queue as `request` and `abort`; installing a persistent redirect replaces that queue, and a one-shot redirect discards a previous persistent rule. After a one-shot rule is consumed, the unmodified request is sent unless another one-shot rule is queued. + +## Example {/* #examples */} + +Use a local HTTP test site as `baseUrl`. `/` serves HTML. `/api/items` returns JSON with `source: "original"`, the request method in `method`, and the `x-example` request header in `header` (`null` when absent). `/api/other` returns the same fields with `source: "redirected"`. The examples use real requests to these endpoints. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.redirect: send matching requests to another endpoint", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/api/other", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.redirect(target); + for (let i = 0; i < 2; i++) { + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.source, "redirected"); + } + } finally { + await mock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [redirectOnce](./redirectOnce.mdx) +- [request](./request.mdx) +- [reset](./reset.mdx) diff --git a/docs/commands/mock/redirectOnce.mdx b/docs/commands/mock/redirectOnce.mdx new file mode 100644 index 00000000..30371cf2 --- /dev/null +++ b/docs/commands/mock/redirectOnce.mdx @@ -0,0 +1,61 @@ +# redirectOnce + +## Overview {/* #overview */} + +Send only the next matching request to another URL. Equivalent to `mock.redirect(redirectUrl, true)`. + +## Usage {/* #usage */} + +`mock.redirectOnce(redirectUrl)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | -------- | ----------- | ------------------------- | +| `redirectUrl` | `string` | Required | Absolute destination URL. | + +## Result {/* #result */} + +The same mock object, synchronously. + +## Limitations {/* #limitations */} + +Requires a WebDriver BiDi session (`webSocketUrl: true`); this method is not provided by the Classic/CDP mock implementation. The examples explicitly subscribe to network events in every session; see [the subscription limitation](./waitForResponse.mdx#limitations). Always call `mock.restore()` in `finally` when the mock is no longer needed. + +The destination must be reachable. Redirects use the same queue as `request` and `abort`; installing a persistent redirect replaces that queue, and a one-shot redirect discards a previous persistent rule. After a one-shot rule is consumed, the unmodified request is sent unless another one-shot rule is queued. + +## Example {/* #examples */} + +Use a local HTTP test site as `baseUrl`. `/` serves HTML. `/api/items` returns JSON with `source: "original"`, the request method in `method`, and the `x-example` request header in `header` (`null` when absent). `/api/other` returns the same fields with `source: "redirected"`. The examples use real requests to these endpoints. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.redirectOnce: redirect only the next request", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/api/other", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.redirectOnce(target); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.source, "redirected"); + assert.equal(second.source, "original"); + } finally { + await mock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [redirect](./redirect.mdx) +- [request](./request.mdx) +- [reset](./reset.mdx) diff --git a/docs/commands/mock/request.mdx b/docs/commands/mock/request.mdx new file mode 100644 index 00000000..516a8839 --- /dev/null +++ b/docs/commands/mock/request.mdx @@ -0,0 +1,65 @@ +# request + +## Overview {/* #overview */} + +Override matching outgoing requests before they reach the server. Use this to change headers, the method, body, cookies or target URL. + +## Usage {/* #usage */} + +`mock.request(overwrite, once = false)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------- | ------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------- | +| `overwrite` | `object` | Required | Request fields to override. | +| `overwrite.body` | `unknown \| Function` | — | Request body; strings are sent as text, objects as JSON. Falsy body values are ignored in this version. | +| `overwrite.cookies` | `Cookie[] \| Function` | — | Cookie list in WebDriver cookie format. | +| `overwrite.headers` | `Record \| Function` | — | Replacement header map, not a merge with the original headers. | +| `overwrite.method` | `string \| Function` | — | HTTP method, for example `POST`. | +| `overwrite.url` | `string \| Function` | — | Absolute replacement URL. | +| `once` | `boolean` | `false` | Apply the rule only to the next matching request. | + +## Result {/* #result */} + +The same mock object, synchronously, for chaining. + +## Limitations {/* #limitations */} + +Requires a WebDriver BiDi session (`webSocketUrl: true`); this method is not provided by the Classic/CDP mock implementation. The examples explicitly subscribe to network events in every session; see [the subscription limitation](./waitForResponse.mdx#limitations). Always call `mock.restore()` in `finally` when the mock is no longer needed. + +The override affects outgoing requests; it does not fabricate responses. Each field may be a value or a synchronous function receiving the BiDi request event (the URL is in `event.request.url`). Request overrides, redirects and aborts share one rule queue. A persistent rule replaces that queue; a one-shot rule keeps existing one-shot rules but removes a persistent rule. Do not expect a persistent rule to resume after a one-shot rule. + +## Example {/* #examples */} + +Use a local HTTP test site as `baseUrl`. `/` serves HTML. `/api/items` returns JSON with `source: "original"`, the request method in `method`, and the `x-example` request header in `header` (`null` when absent). `/api/other` returns the same fields with `source: "redirected"`. The examples use real requests to these endpoints. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.request: change outgoing requests", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.request({ method: "POST", headers: { "x-example": "persistent" } }); + for (let i = 0; i < 2; i++) { + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.method, "POST"); + assert.equal(data.header, "persistent"); + } + } finally { + await mock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [requestOnce](./requestOnce.mdx) +- [respond](./respond.mdx) +- [reset](./reset.mdx) diff --git a/docs/commands/mock/requestOnce.mdx b/docs/commands/mock/requestOnce.mdx new file mode 100644 index 00000000..a80b22a3 --- /dev/null +++ b/docs/commands/mock/requestOnce.mdx @@ -0,0 +1,65 @@ +# requestOnce + +## Overview {/* #overview */} + +Override only the next matching outgoing request. Equivalent to `mock.request(overwrite, true)`. + +## Usage {/* #usage */} + +`mock.requestOnce(overwrite)` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------------- | ------------------------------------ | ----------- | ------------------------------------------------------------------------------------------------------- | +| `overwrite` | `object` | Required | Request fields to override. | +| `overwrite.body` | `unknown \| Function` | — | Request body; strings are sent as text, objects as JSON. Falsy body values are ignored in this version. | +| `overwrite.cookies` | `Cookie[] \| Function` | — | Cookie list in WebDriver cookie format. | +| `overwrite.headers` | `Record \| Function` | — | Replacement header map, not a merge with the original headers. | +| `overwrite.method` | `string \| Function` | — | HTTP method, for example `POST`. | +| `overwrite.url` | `string \| Function` | — | Absolute replacement URL. | + +## Result {/* #result */} + +The same mock object, synchronously, for chaining. + +## Limitations {/* #limitations */} + +Requires a WebDriver BiDi session (`webSocketUrl: true`); this method is not provided by the Classic/CDP mock implementation. The examples explicitly subscribe to network events in every session; see [the subscription limitation](./waitForResponse.mdx#limitations). Always call `mock.restore()` in `finally` when the mock is no longer needed. + +The override affects outgoing requests; it does not fabricate responses. Each field may be a value or a synchronous function receiving the BiDi request event (the URL is in `event.request.url`). Request overrides, redirects and aborts share one rule queue. A persistent rule replaces that queue; a one-shot rule keeps existing one-shot rules but removes a persistent rule. Do not expect a persistent rule to resume after a one-shot rule. + +## Example {/* #examples */} + +Use a local HTTP test site as `baseUrl`. `/` serves HTML. `/api/items` returns JSON with `source: "original"`, the request method in `method`, and the `x-example` request header in `header` (`null` when absent). `/api/other` returns the same fields with `source: "redirected"`. The examples use real requests to these endpoints. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.requestOnce: change only the next request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.requestOnce({ headers: { "x-example": "once" } }); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.header, "once"); + assert.equal(second.header, null); + } finally { + await mock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [request](./request.mdx) +- [respond](./respond.mdx) +- [reset](./reset.mdx) diff --git a/docs/commands/mock/reset.mdx b/docs/commands/mock/reset.mdx new file mode 100644 index 00000000..d6ec1f99 --- /dev/null +++ b/docs/commands/mock/reset.mdx @@ -0,0 +1,74 @@ +# reset + +## Overview {/* #overview */} + +Clear recorded calls and all request and response override rules while keeping the mock’s interception active. Use it to reuse the mock with its original URL pattern and filters. + +## Usage {/* #usage */} + +`mock.reset()` + +## Parameters {/* #parameters */} + +This command has no parameters. + +## Result {/* #result */} + +The same mock object, synchronously. + +## Limitations {/* #limitations */} + +Requires a WebDriver BiDi session (`webSocketUrl: true`); this method is not provided by the Classic/CDP mock implementation. The examples explicitly subscribe to network events in every session; see [the subscription limitation](./waitForResponse.mdx#limitations). Always call `mock.restore()` in `finally` when the mock is no longer needed. + +`clear()` removes only history and preserves rules. `reset()` also removes rules, including redirects and aborts. `restore()` removes interception and makes the mock unusable for new overrides. Reset does not undo a request already sent or a response already delivered. + +## Example {/* #examples */} + +Use a local HTTP test site as `baseUrl`. `/` serves HTML. `/api/items` returns JSON with `source: "original"`, the request method in `method`, and the `x-example` request header in `header` (`null` when absent). `/api/other` returns the same fields with `source: "redirected"`. The examples use real requests to these endpoints. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.reset: remove overrides but keep the mock usable", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.request({ headers: { "x-example": "overridden" } }); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.header, "overridden"); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.reset(), mock); + assert.equal(mock.calls.length, 0); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(second.header, null); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + mock.respond({ source: "mocked" }); + const third = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(third.source, "mocked"); + mock.reset(); + const fourth = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(fourth.source, "original"); + } finally { + await mock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [clear](./clear.mdx) +- [restore](./restore.mdx) +- [request](./request.mdx) +- [respond](./respond.mdx) diff --git a/docs/commands/mock/respond.mdx b/docs/commands/mock/respond.mdx index b403d334..410cab1c 100644 --- a/docs/commands/mock/respond.mdx +++ b/docs/commands/mock/respond.mdx @@ -1,100 +1,94 @@ -import Admonition from "@theme/Admonition"; - # respond ## Overview {/* #overview */} -Use the `respond` command to always reply with the same overwrite. - - - Also read the recipe "[How to Track and Intercept Network Requests and - Responses][how-to-intercept-requests-and-responses]". - +Overrides responses to matching requests while the rule is active. BiDi still sends the original request to the server; this is not a way to prevent request side effects. ## Usage {/* #usage */} -```javascript -mock.respond(overwrites, { header, statusCode, fetchResponse }); -``` +`mock.respond(overwrites, params)` + +## Command parameters {/* #parameters */} + +| Parameter | BiDi | Classic/CDP | +| ---------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| `overwrites` | Body string or JSON object/array. Body callbacks are not supported. | String, JSON or function receiving the request entry and client. The function may return a Promise. | +| `params.headers` | Response-header object or synchronous function of the response event. Replaces the header set. | Object or function. Adds to original headers. | +| `params.statusCode` | Number or synchronous function of the response event. | Number or function. | +| `params.cookies` | WebDriver-format `Cookie[]` or synchronous function of the response event. | Not supported by this implementation. | +| `params.fetchResponse` | Not applied: overrides happen at the response stage, so the request reaches the server. | Boolean, default `true`. `false` overrides before the original body is fetched. | + +For `params.cookies`, pass a string `value` and the `expiry` field (Unix time in seconds), not a BiDi `value` object and `expires`. The library converts the cookies to BiDi itself. + +The parameter is **`headers`**, not `header`. Metadata defaults to the original values when not overridden. + +## Limitations {/* #limitations */} -## Command Parameters {/* #parameters */} +In BiDi, a file path or URL string remains response text. It does not read a file or redirect the request; use [redirect](./redirect.mdx) to change the URL. In Classic/CDP, an existing path is read as a local file, while an HTTP(S) URL string creates a redirect response. - - - - - - - - - +Do not mix persistent and one-shot rules without accounting for the implementation. In BiDi, `respond()` replaces the response queue with a persistent rule. `respondOnce()` removes a persistent rule and appends to other one-shot rules. CDP appends rules, so an earlier persistent rule can mask later ones. - -
**Name****Type****Description**
overwritesMockOverwrite_Payload_ to overwrite the response.
headerObjectOverwrite specific headers.
statusCodeNumberOverwrite the response status code.
fetchResponseBooleanFetch the actual response before replying with fake data.
+## Result {/* #result */} -## Usage Examples {/* #examples */} +BiDi synchronously returns the same mock. Classic/CDP has no useful return value. Call `await mock.restore()` to remove overrides and the interceptor. + +## Example {/* #examples */} + +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. ```javascript -it("should demonstrate response overwrite with static data", async ({ browser }) => { - const mock = await browser.mock("https://todo-backend-express-knex.herokuapp.com/", { - method: "get", - }); +const assert = require("node:assert/strict"); - mock.respond( - [ - { - title: "Injected (non) completed Todo", - order: null, - completed: false, - }, +it("mock.respond: replace response data and metadata in BiDi", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond( + { source: "mocked" }, { - title: "Injected completed Todo", - order: null, - completed: true, + statusCode: 201, + headers: { "content-type": "application/json", "x-example": "response" }, + cookies: [ + { + name: "mock-example", + value: "ready", + domain: new URL(await browser.getUrl()).hostname, + path: "/", + }, + ], }, - ], - { - statusCode: 200, - fetchResponse: true, // default - }, - ); - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - - await browser.$("#todo-list li").waitForExist(); - - const todoElements = await browser.$$("#todo-list li"); - console.log(await Promise.all(todoElements.map(el => el.getText()))); - // will output: "[ 'Injected (non) completed Todo', 'Injected completed Todo' ]" -}); - -it("should demonstrate response overwrite with dynamic data", async ({ browser }) => { - const mock = await browser.mock("https://todo-backend-express-knex.herokuapp.com/"); - - mock.respond( - request => { - if (request.body.username === "test") { - return { ...request.body, foo: "bar" }; - } - return request.body; - }, - { - statusCode: () => 200, - headers: () => ({ foo: "bar" }), - fetchResponse: false, // do not fetch the actual response - }, - ); + ); + for (let i = 0; i < 2; i++) { + const result = await browser.execute(async () => { + const response = await fetch("/api/items", { cache: "no-store" }); + return { + status: response.status, + header: response.headers.get("x-example"), + body: await response.json(), + }; + }); + assert.deepEqual(result, { + status: 201, + header: "response", + body: { source: "mocked" }, + }); + } + } finally { + await mock.restore(); + } }); ``` -## Related Commands {/* #related */} - -- [respondOnce](../respondOnce) +## Related commands {/* #related */} -## References +- [respondOnce](./respondOnce.mdx) +- [redirect](./redirect.mdx) +- [browser.mock](../browser/mock.mdx) +- [Network interception](../../guides/how-to-intercept-requests-and-responses.mdx) -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/mock/respond), from which we drew some information while writing our version. +## References {/* #references */} -[how-to-intercept-requests-and-responses]: ../../../guides/how-to-intercept-requests-and-responses +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/mock/respond). diff --git a/docs/commands/mock/respondOnce.mdx b/docs/commands/mock/respondOnce.mdx index 1bbddbba..f428d9db 100644 --- a/docs/commands/mock/respondOnce.mdx +++ b/docs/commands/mock/respondOnce.mdx @@ -1,112 +1,58 @@ -import Admonition from "@theme/Admonition"; - # respondOnce ## Overview {/* #overview */} -Use the `respondOnce` command to respond once with the specified overwrite. - -You can call `respondOnce` multiple times in succession. The responses will be used in the same order the `respondOnce` commands were called. - -If you use only `respondOnce` and access the resource more times than `respondOnce` was called, then after exhausting the fake data, the request will start returning the original response from the resource. - - - Also read the recipe "[How to Track and Intercept Network Requests and - Responses][how-to-intercept-requests-and-responses]". - +Overrides one response. Repeated calls queue responses in call order. Once the one-shot queue is exhausted, subsequent requests receive original server responses. ## Usage {/* #usage */} -```javascript -mock.respondOnce(overwrites, { header, statusCode, fetchResponse }); -``` +`mock.respondOnce(overwrites, params)` -## Command Parameters {/* #parameters */} +## Command parameters {/* #parameters */} - - - - - - - - - +Parameters and BiDi versus Classic/CDP differences match [respond](./respond.mdx#parameters), including `headers`, not `header`. BiDi accepts a string or JSON body; body callbacks and `fetchResponse: false` are not supported. The request reaches the server. - -
**Name****Type****Description**
overwritesMockOverwrite_Payload_ to overwrite the response.
headerObjectOverwrite specific headers.
statusCodeNumberOverwrite the response status code.
fetchResponseBooleanFetch the actual response before replying with fake data.
+In BiDi, a one-shot rule removes the persistent rule and appends to other one-shot rules. In Classic/CDP, an earlier persistent rule can mask the queue. Do not assume identical behavior when mixing `respond` and `respondOnce` across protocols. -## Usage Examples {/* #examples */} +BiDi synchronously returns the same mock; Classic/CDP has no useful return value. -```javascript -async function getToDos(browser) { - await browser.$("#todo-list li").waitForExist(); +## Example {/* #examples */} - const todoElements = await browser.$$("#todo-list li"); +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. - return Promise.all(todoElements.map(el => el.getText())); -} +```javascript +const assert = require("node:assert/strict"); -it("should demonstrate the respondOnce command", async ({ browser }) => { - const mock = await browser.mock("https://todo-backend-express-knex.herokuapp.com/", { - method: "get", +it("mock.respondOnce: consume BiDi responses in order", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], }); - - mock.respondOnce([ - { - title: "3", - }, - { - title: "2", - }, - { - title: "1", - }, - ]); - - mock.respondOnce([ - { - title: "2", - }, - { - title: "1", - }, - ]); - - mock.respondOnce([ - { - title: "1", - }, - ]); - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // outputs: [ '3', '2', '1' ] - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // outputs: [ '2', '1' ] - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // outputs: [ '1' ] - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // outputs: the actual resource response + const mock = await browser.mock("/api/items"); + try { + mock.respondOnce({ source: "first" }); + mock.respondOnce({ source: "second" }); + const sources = []; + for (let i = 0; i < 3; i++) { + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + sources.push(body.source); + } + assert.deepEqual(sources, ["first", "second", "original"]); + } finally { + await mock.restore(); + } }); ``` -## Related Commands {/* #related */} - -- [respond](../respond) +## Related commands {/* #related */} -## References +- [respond](./respond.mdx) +- [reset](./reset.mdx) +- [restore](./restore.mdx) +- [Network interception](../../guides/how-to-intercept-requests-and-responses.mdx) -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/mock/respondOnce), from which we drew some information while writing our version. +## References {/* #references */} -[how-to-intercept-requests-and-responses]: ../../../guides/how-to-intercept-requests-and-responses +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/mock/respondOnce). diff --git a/docs/commands/mock/restore.mdx b/docs/commands/mock/restore.mdx index ba49bde8..e51867ff 100644 --- a/docs/commands/mock/restore.mdx +++ b/docs/commands/mock/restore.mdx @@ -2,31 +2,56 @@ ## Overview {/* #overview */} -Use the `restore` command to perform all actions that [mock.clear()](../clear) does, as well as remove any fake return values or implementations. +Removes the network interceptor, clears history and disables overrides. Subsequent requests proceed without this mock. This is not [browser.restore](../browser/restore.mdx), which restores emulation. + +Await `restore()` before sending the next request or ending the test. In BiDi, a restored mock cannot be reused: create another with [browser.mock](../browser/mock.mdx). To retain the interceptor, use [reset](./reset.mdx). ## Usage {/* #usage */} -```javascript -mock.restore(); -``` +`await mock.restore()` -## Usage Examples {/* #examples */} +## Parameters and result {/* #parameters */} -```javascript -it("should demonstrate the addValue command", async ({ browser }) => { - const mock = await browser.mock("**/googlelogo_color_272x92dp.png"); - mock.respond("https://webdriver.io/img/webdriverio.png"); +Takes no arguments. Returns a Promise resolving to the same mock in BiDi and no useful value in Classic/CDP. Use it for cleanup in `finally`, including when an assertion fails. - await browser.url("https://google.com"); // will show WebdriverIO logo instead of Google logo - mock.restore(); - await browser.url("https://google.com"); // will show the native Google logo +## Example {/* #examples */} + +The example uses Chrome with WebDriver BiDi. Point `baseUrl` to a test HTTP server: `/` serves an HTML page, and `/api/items` returns JSON with `source: "original"` and status `200`. The explicit event subscription supports new sessions in the same worker in WDIO 9.6.2. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.restore: remove interception before the next request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond({ source: "mocked" }); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "mocked"); + } finally { + await mock.restore(); + } + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "original"); + assert.equal(mock.calls.length, 0); + assert.throws(() => mock.respond({ source: "unused" }), /restored mock/); }); ``` -## Related Commands {/* #related */} +## Related commands {/* #related */} -- [clear](../clear) +- [clear](./clear.mdx) +- [reset](./reset.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) -## References +## References {/* #references */} -We'd like to give credit to the original WebdriverIO docs [article](https://webdriver.io/docs/api/mock/restore), from which we drew some information while writing our version. +This page draws on the [original WebdriverIO article](https://webdriver.io/docs/api/mock/restore). diff --git a/docs/commands/mock/waitForResponse.mdx b/docs/commands/mock/waitForResponse.mdx new file mode 100644 index 00000000..95a61e82 --- /dev/null +++ b/docs/commands/mock/waitForResponse.mdx @@ -0,0 +1,68 @@ +# waitForResponse + +## Overview {/* #overview */} + +Wait until the mock has at least one recorded response in `calls`. This is useful for observing a request started by page code without adding a fixed delay. + +## Usage {/* #usage */} + +`mock.waitForResponse({ timeout, interval, timeoutMsg })` + +## Parameters {/* #parameters */} + +| **Parameter** | **Type** | **Default** | **Description** | +| ------------- | -------- | ----------------------- | ----------------------------------- | +| `timeout` | `number` | `waitTimeout` (3000 ms) | Maximum wait in milliseconds. | +| `interval` | `number` | `waitInterval` (500 ms) | Polling interval in milliseconds. | +| `timeoutMsg` | `string` | — | Custom error message for a timeout. | + +## Result {/* #result */} + +`Promise` resolving to `true` once `calls` is nonempty. Rejects on timeout or a polling error. It does not return the response: inspect `mock.calls` separately. + +## Limitations {/* #limitations */} + +Available in both BiDi and Classic/CDP mock implementations. Old calls satisfy the condition immediately: call `mock.clear()` before triggering the operation you want to observe. In BiDi, the history is populated at `network.responseStarted`; this does not prove that the response body is consumed or the UI is updated. + +In bundled WDIO 9.6.2, automatic network subscription is guarded by a module-level flag. When a worker creates another browser session, response events may no longer be subscribed to. The example calls `sessionSubscribe` explicitly per session, which was verified with Testplane `testsPerSession: 1`. This is a real protocol subscription, not a replacement for the mock. + +## Example {/* #examples */} + +Use a local HTTP test site as `baseUrl`. `/` serves HTML. `/api/items` returns JSON with `source: "original"`, the request method in `method`, and the `x-example` request header in `header` (`null` when absent). `/api/other` returns the same fields with `source: "redirected"`. The examples use real requests to these endpoints. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.waitForResponse: wait for a new recorded response", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + await browser.execute(async () => (await fetch("/api/items")).text()); + await mock.waitForResponse({ timeout: 3000 }); + mock.clear(); + assert.equal(mock.calls.length, 0); + await browser.execute(() => { + window.requestFinished = false; + fetch("/api/items", { cache: "no-store" }) + .then(response => response.text()) + .then(() => { + window.requestFinished = true; + }); + }); + assert.equal(await mock.waitForResponse({ timeout: 3000, interval: 50 }), true); + assert.equal(mock.calls.length, 1); + await browser.waitUntil(() => browser.execute(() => window.requestFinished)); + } finally { + await mock.restore(); + } +}); +``` + +## See also {/* #see-also */} + +- [browser.mock](../browser/mock.mdx) +- [clear](./clear.mdx) +- [restore](./restore.mdx) diff --git a/docs/commands/overview.mdx b/docs/commands/overview.mdx index f62dcea4..785f3579 100644 --- a/docs/commands/overview.mdx +++ b/docs/commands/overview.mdx @@ -2,48 +2,44 @@ sidebar_class_name: hidden --- -import Admonition from "@theme/Admonition"; - # Testplane Commands -## Overview +## Overview {/* #overview */} - - Only commands for the latest version Testplane v8 and WebDriverIO v9 are described. Commands for - older versions should be referenced in the WebDriverIO documentation (example for [WebDriverIO - v8][webdriverio@8-api]). - +This reference covers Testplane v9 and commands from its WebdriverIO v9 dependency. Availability depends on the browser, session protocol and dependency version; each page lists its requirements. For older versions, use the documentation archive and [WebdriverIO v8](https://v8.webdriver.io/docs/api). -Since Testplane is based on [WebDriverIO v9][webdriverio-api], all commands provided by WebDriverIO are available in it. +Testplane does not provide the WDIO runner's implicit global `browser`, `$` and `$$`. Get `browser` from the test argument, or use `this.browser` in a regular function. An arrow function does not receive the test's `this` context. Find elements with `browser.$` and `browser.$$`. -However, the command descriptions on the [WebDriverIO][webdriverio-api] website for version 8 are not quite suitable _as is_ for Testplane users due to a number of reasons: +## Example {/* #examples */} -- In WebDriverIO, the `browser` object exists in the global scope, whereas in Testplane you need to either write `this.browser`: +Both tests open `/` relative to `baseUrl`. The test page must contain an `h1` with the text `Command examples`. ```javascript -it("should test something", async function () { - await this.browser.url("https://testplane.io"); - // test code... -}); -``` +const assert = require("node:assert/strict"); -or get the `browser` object from the function argument (note that the `browser` object is passed inside an object!): +it("commands: use the browser fixture", async ({ browser }) => { + await browser.url("/"); + assert.equal(await browser.$("h1").getText(), "Command examples"); +}); -```javascript -it("should test something", async ({ browser }) => { - await browser.url("https://testplane.io"); - // test code... +it("commands: use this.browser with a regular function", async function () { + await this.browser.url("/"); + assert.equal(await this.browser.$("h1").getText(), "Command examples"); }); ``` -- Similarly, the `browser.$` and `browser.$$` functions in WebDriverIO are available in tests under the names `$` and `$$`, while in Testplane you need to refer to them by their full path: `browser.$` and `browser.$$`; - -- There are no links established between similar commands: no clustering of commands; +## Choose a command {/* #command-groups */} -Nevertheless, our description does not yet include protocol-specific commands. You can find the relevant commands on the WebDriverIO website under the "[Protocols][webdriverio-protocols]" section. +| Task | Commands | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Page setup and emulation | [addInitScript](./browser/addInitScript.mdx), [emulate](./browser/emulate.mdx), [restore](./browser/restore.mdx), [setViewport](./browser/setViewport.mdx) | +| Custom selectors | [addLocatorStrategy](./browser/addLocatorStrategy.mdx), [browser.custom$](./browser/customDollar.mdx), [browser.custom$$](./browser/customDollarDollar.mdx), [element.custom$](./element/customDollar.mdx), [element.custom$$](./element/customDollarDollar.mdx) | +| Frames and downloads | [switchFrame](./browser/switchFrame.mdx), [downloadFile](./browser/downloadFile.mdx) | +| Resource throttling | [throttleCPU](./browser/throttleCPU.mdx), [throttleNetwork](./browser/throttleNetwork.mdx) | +| Element operations | [execute](./element/execute.mdx), [executeAsync](./element/executeAsync.mdx), [getElement](./element/getElement.mdx), [isStable](./element/isStable.mdx), [waitForStable](./element/waitForStable.mdx) | +| Network interception | [mock](./browser/mock.mdx), [waitForResponse](./mock/waitForResponse.mdx), [request](./mock/request.mdx), [requestOnce](./mock/requestOnce.mdx), [redirect](./mock/redirect.mdx), [redirectOnce](./mock/redirectOnce.mdx), [reset](./mock/reset.mdx) | +| Testplane extensions | [extendOptions](./browser/extendOptions.mdx), [browser.unstable_captureDomSnapshot](./browser/unstable_captureDomSnapshot.mdx), [element.unstable_captureDomSnapshot](./element/unstable_captureDomSnapshot.mdx), [unstable_getCdp](./browser/unstable_getCdp.mdx) | -Also, in the descriptions of some commands, links to individual recipes are still not localized and lead to the WebDriverIO website. +## Reference scope {/* #scope */} -[webdriverio@8-api]: https://v8.webdriver.io/docs/api -[webdriverio-api]: https://webdriver.io/docs/api -[webdriverio-protocols]: https://webdriver.io/docs/api/webdriver +Low-level WebDriver, BiDi and Appium commands are not duplicated here: see [WebdriverIO protocols](https://webdriver.io/docs/api/webdriver). Methods of `InitScript` and clock objects are documented with [addInitScript](./browser/addInitScript.mdx) and [emulate](./browser/emulate.mdx), not as separate browser commands. When using external recipes, account for version differences and how Testplane provides `browser`. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx index c304b807..285badbb 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/selectors.mdx @@ -313,6 +313,10 @@ const slotElements = await customElement.shadow$$(".slot-item"); - нужен доступ к элементам внутри shadow root - работаете с дизайн-системой на базе Web Components +## Собственные стратегии поиска {/* #custom-locator-strategies */} + +Если стандартного селектора недостаточно, зарегистрируйте стратегию через [browser.addLocatorStrategy](../commands/browser/addLocatorStrategy.mdx). Используйте [browser.custom$](../commands/browser/customDollar.mdx) для одного элемента и [browser.custom$$](../commands/browser/customDollarDollar.mdx) для нескольких. Для поиска внутри конкретного элемента доступны [element.custom$](../commands/element/customDollar.mdx) и [element.custom$$](../commands/element/customDollarDollar.mdx). Функция стратегии выполняется в браузере, поэтому передавайте ей данные аргументами, а не через замыкания Node.js. Проверенные примеры приведены на страницах команд. + ## Testing Library Testing Library позволяет искать элементы так, как их ищут на странице пользователи — по тексту, типу элемента или другим атрибутам, которые не зависят от деталей вашей верстки. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx index e356eaaf..f3f4f865 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx @@ -327,60 +327,71 @@ it("применяет стили для темной цветовой схем }); ``` -## Сеть +## Сеть {/* #network */} -### Отсутствие сети + -Для проверки работы приложения без сети используйте [`browser.throttleNetwork("offline")`][throttle-network]. +### Отсутствие сети {/* #offline-mode */} -Например, так можно проверить сообщение об ошибке при сетевом запросе: + -```typescript -it("показывает сообщение при отсутствии сети", async ({ browser }) => { - await browser.url("/"); +Для реального отключения запросов используйте [throttleNetwork][throttle-network]. Нужен Chromium с доступным CDP/Puppeteer, одна вкладка, `isolation: false` и `testsPerSession: 1`: реализация выбирает первую страницу Puppeteer. Сначала откройте страницу, затем отключите сеть. - await browser.throttleNetwork("offline"); +В проверенной связке Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150 вызов с профилем `online` не восстановил сеть после `offline`. Поэтому пример заканчивает офлайн-сессию и проверяет соединение в новой, а не обещает восстановление тем же вызовом. `baseUrl` должен указывать на тестовый HTTP-сервер, отвечающий `200` на корневой путь. - const loadOrders = await browser.getByTestId("load-orders"); - await loadOrders.click(); +```javascript +const assert = require("node:assert/strict"); - const networkError = await browser.findByTestId("network-error"); - await expect(networkError).toHaveText("Нет подключения к сети"); +// Configure testsPerSession: 1 so this offline session is not reused. +it("throttleNetwork: check a failed request while offline", async ({ browser }) => { + await browser.url("/"); + await browser.throttleNetwork("offline"); + const failed = await browser.execute(async () => { + try { + await fetch("/?offline-check", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); +}); - await browser.throttleNetwork("online"); +it("throttleNetwork: the next session starts online", async ({ browser }) => { + await browser.url("/"); + const status = await browser.execute(async () => { + const response = await fetch("/?online-check", { cache: "no-store" }); + return response.status; + }); + assert.equal(status, 200); }); ``` -Сначала загрузите страницу, а затем отключите сеть перед действием, которое отправляет запрос. В отличие от `emulate()`, `throttleNetwork()` нужно вызывать после навигации. Чтобы вернуть обычный сетевой режим, используйте профиль `"online"`. +### Медленное соединение {/* #slow-connection */} -### Медленное соединение + -Для проверки интерфейса при медленном соединении передайте параметры сети в [`browser.throttleNetwork()`][throttle-network]: +Для ограничения скорости передайте объект в [throttleNetwork][throttle-network]. Пример использует те же условия изоляции. Сервер по адресу `baseUrl` должен отдавать HTML на `/` и JSON с `source: "original"` на `/api/items`. -```typescript -it("показывает состояние загрузки при медленной сети", async ({ browser }) => { - await browser.url("/orders"); +```javascript +const assert = require("node:assert/strict"); +it("slow network: fetch data under custom throughput limits", async ({ browser }) => { + await browser.url("/"); await browser.throttleNetwork({ offline: false, latency: 500, downloadThroughput: (50 * 1024) / 8, uploadThroughput: (20 * 1024) / 8, }); - - const loadOrders = await browser.getByTestId("load-orders"); - await loadOrders.click(); - - const loading = await browser.findByTestId("loading"); - await expect(loading).toBeDisplayed(); - - await browser.throttleNetwork("online"); + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.source, "original"); }); ``` -Объект содержит четыре поля: `offline`, задержку `latency` в миллисекундах, а также скорости загрузки и отправки данных `downloadThroughput` и `uploadThroughput` в байтах в секунду. - -Для типовых условий параметры можно не задавать вручную. Вместо объекта передайте имя готового профиля, например `"Good3G"` или `"offline"`. +`offline` включает офлайн-режим, `latency` задает задержку в миллисекундах, `downloadThroughput` и `uploadThroughput` — скорость в байтах в секунду. Вместо объекта можно передать профиль, например `Good3G`. Ограниченная сессия не переиспользуется в следующем тесте. ### navigator.onLine @@ -400,21 +411,28 @@ it("показывает офлайн-режим", async ({ browser }) => { `browser.emulate("onLine", false)` только меняет значение `navigator.onLine`, но не отключает сеть: HTTP-запросы продолжают выполняться. -## Производительность CPU - -Для проверки интерфейса при ограниченной производительности процессора используйте [`browser.throttleCPU()`][throttle-cpu]. +## Производительность CPU {/* #cpu-performance */} -Например, так можно проверить сценарий при четырехкратном замедлении CPU: + -```typescript -it("работает при замедленном CPU", async ({ browser }) => { - await browser.throttleCPU(4); +[throttleCPU][throttle-cpu] замедляет выполнение кода в Chromium через CDP/Puppeteer. Как и для сети, нужны одна вкладка и `isolation: false`: команда работает с первой страницей Puppeteer. Откройте страницу до вызова команды. В `baseUrl` укажите адрес тестовой HTML-страницы. - await browser.url("/"); +Пример задает двукратное замедление и отменяет его в `finally`. - // ... +```javascript +const assert = require("node:assert/strict"); - await browser.throttleCPU(1); +it("throttleCPU: run a page with a slower CPU", async ({ browser }) => { + await browser.url("/"); + try { + await browser.throttleCPU(2); + await browser.execute(() => { + document.body.innerHTML = ""; + }); + assert.equal(await browser.$("button").getText(), "Ready"); + } finally { + await browser.throttleCPU(1); + } }); ``` @@ -551,26 +569,26 @@ describe("темная цветовая схема", () => { Если настройка должна действовать всю сессию, создайте для нее отдельную конфигурацию браузера. Так удобнее задавать язык браузера, запускать тесты с отключенным JavaScript и фиксировать размер окна с помощью [`windowSize`][window-size]. -[emulate]: https://webdriver.io/docs/api/browser/emulate +[emulate]: ../commands/browser/emulate.mdx [webdriver-bidi]: https://w3c.github.io/webdriver-bidi/ -[set-viewport]: https://webdriver.io/docs/api/browser/setViewport +[set-viewport]: ../commands/browser/setViewport.mdx [set-window-size]: ../commands/browser/setWindowSize.mdx [window-size]: ../reference/config/browsers.mdx#window_size [how-to-use-cdp]: ../guides/how-to-use-cdp.mdx -[emulate-device]: https://webdriver.io/docs/emulation#device -[emulate-user-agent]: https://webdriver.io/docs/emulation#user-agent +[emulate-device]: ../commands/browser/emulate.mdx#parameters +[emulate-user-agent]: ../commands/browser/emulate.mdx#parameters [get-puppeteer]: ../commands/browser/getPuppeteer.mdx [page-set-user-agent]: https://pptr.dev/api/puppeteer.page.setuseragent [cdp-set-locale-override]: https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setLocaleOverride [page-emulate-timezone]: https://pptr.dev/api/puppeteer.page.emulatetimezone -[emulate-clock]: https://webdriver.io/docs/emulation#clock -[clock-tick]: https://webdriver.io/docs/api/clock/tick -[emulate-color-scheme]: https://webdriver.io/docs/emulation#color-scheme +[emulate-clock]: ../commands/browser/emulate.mdx#parameters +[clock-tick]: ../commands/browser/emulate.mdx#result +[emulate-color-scheme]: ../commands/browser/emulate.mdx#parameters [cdp-set-emulated-media]: https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setEmulatedMedia -[throttle-network]: https://webdriver.io/docs/api/browser/throttleNetwork -[emulate-online]: https://webdriver.io/docs/emulation#online-property -[throttle-cpu]: https://webdriver.io/docs/api/browser/throttleCPU -[emulate-geolocation]: https://webdriver.io/docs/emulation#geolocation +[throttle-network]: ../commands/browser/throttleNetwork.mdx +[emulate-online]: ../commands/browser/emulate.mdx#parameters +[throttle-cpu]: ../commands/browser/throttleCPU.mdx +[emulate-geolocation]: ../commands/browser/emulate.mdx#parameters [set-permissions]: https://webdriver.io/docs/api/webdriver#setpermissions -[restore]: https://webdriver.io/docs/api/browser/restore +[restore]: ../commands/browser/restore.mdx [testing-library-guide]: ../guides/how-to-add-testing-library.mdx diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx new file mode 100644 index 00000000..ba29863b --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx @@ -0,0 +1,56 @@ +# addInitScript + +## Обзор {/* #overview */} + +Выполните функцию в новых документах текущей вкладки или окна до запуска скриптов страницы. Это позволяет подготовить значения до того, как приложение их прочитает. Функция также выполняется в дочерних фреймах этого верхнеуровневого контекста при их добавлении или переходе на другую страницу. + +Скрипт регистрируется для вкладки или окна, текущих в момент вызова `addInitScript`. Он не распространяется автоматически на другие вкладки и окна, в том числе открытые позже. Чтобы использовать его там, переключитесь на нужную вкладку или окно и зарегистрируйте скрипт отдельно до перехода на страницу. + +## Использование {/* #usage */} + +`browser.addInitScript(script, ...args)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | ----------- | ---------------- | -------------------------------------------------------------------- | +| `script` | `Function` | Обязателен | Функция для выполнения в браузере, без доступа к переменным Node.js. | +| `args` | `unknown[]` | [] | Аргументы, сериализуемые в JSON и передаваемые функции. | + +## Результат {/* #result */} + +`Promise`: объект с асинхронным методом `remove()` и подпиской `on("data", listener)`. Для передачи данных в Node.js вызовите callback `emit`, который добавляется последним аргументом функции. + +## Ограничения {/* #limitations */} + +Требуется сессия WebDriver BiDi (`webSocketUrl: true`). Вызовите команду до перехода на страницу. `remove()` отменяет последующее внедрение, но не откатывает изменения текущего документа. Удаляйте скрипт в том же тесте, даже если проверка завершилась ошибкой. Реализация этой версии принимает функцию, а не строку. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("addInitScript: set a value before page scripts", async ({ browser }) => { + const script = await browser.addInitScript(value => { + window.exampleSeed = value; + }, 42); + + try { + await browser.url("/"); + assert.equal(await browser.execute(() => window.exampleSeed), 42); + } finally { + await script.remove(); + } + + await browser.url("/"); + assert.equal(await browser.execute(() => typeof window.exampleSeed), "undefined"); +}); +``` + +## См. также {/* #see-also */} + +- [execute](./execute.mdx) +- [url](./url.mdx) +- [emulate](./emulate.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addLocatorStrategy.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addLocatorStrategy.mdx new file mode 100644 index 00000000..f582ef12 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addLocatorStrategy.mdx @@ -0,0 +1,48 @@ +# addLocatorStrategy + +## Обзор {/* #overview */} + +Зарегистрируйте переиспользуемый способ поиска элементов. Затем вызывайте стратегию через `custom$` или `custom$$` у браузера или элемента. + +## Использование {/* #usage */} + +`browser.addLocatorStrategy(name, strategy)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `name` | `string` | Обязателен | Уникальное имя стратегии в объекте браузера. | +| `strategy` | `Function` | Обязателен | Браузерная функция, возвращающая DOM-элемент или массив DOM-элементов. Перед возвратом преобразуйте NodeList через Array.from. | + +## Результат {/* #result */} + +`void`. Регистрация синхронная и сама по себе не выполняет поиск. + +## Ограничения {/* #limitations */} + +Зарегистрируйте стратегию до первого использования. Повторная регистрация имени вызывает ошибку. Callback выполняется в браузере и не может читать замыкания Node.js. Для общего использования зарегистрируйте стратегию в `prepareBrowser`. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("addLocatorStrategy: find by data attribute", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + browser.addLocatorStrategy("byTest", value => document.querySelector(`[data-test="${value}"]`)); + const button = await browser.custom$("byTest", "save"); + assert.equal(await button.getText(), "Save"); +}); +``` + +## См. также {/* #see-also */} + +- [custom$](./customDollar.mdx) +- [custom$$](./customDollarDollar.mdx) +- [prepareBrowser](../../reference/config/prepare-browser.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollar.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollar.mdx new file mode 100644 index 00000000..f3636bd1 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollar.mdx @@ -0,0 +1,48 @@ +# custom$ + +## Обзор {/* #overview */} + +Найдите один элемент с помощью стратегии, зарегистрированной через `addLocatorStrategy`. Способ выбора элемента определяет сама стратегия. + +## Использование {/* #usage */} + +`browser.custom$(strategyName, ...strategyArguments)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------- | ----------- | ---------------- | --------------------------------------- | +| `strategyName` | `string` | Обязателен | Имя зарегистрированной стратегии. | +| `strategyArguments` | `unknown[]` | [] | Аргументы браузерной функции стратегии. | + +## Результат {/* #result */} + +Ожидаемая ссылка на элемент, разрешающаяся в `WebdriverIO.Element`. Если стратегия вернула несколько элементов, используется первый. + +## Ограничения {/* #limitations */} + +Неизвестное имя стратегии вызывает ошибку. Найденный элемент не обязательно видим или доступен для нажатия: используйте соответствующие ожидания или проверки. Если стратегия ничего не нашла, последующие команды элемента могут завершиться ошибкой. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("custom$: find one element with a custom strategy", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + browser.addLocatorStrategy("byTest", value => document.querySelector(`[data-test="${value}"]`)); + const button = await browser.custom$("byTest", "save"); + assert.equal(await button.getText(), "Save"); +}); +``` + +## См. также {/* #see-also */} + +- [addLocatorStrategy](./addLocatorStrategy.mdx) +- [custom$$](./customDollarDollar.mdx) +- [$](./$.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollarDollar.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollarDollar.mdx new file mode 100644 index 00000000..1557677b --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollarDollar.mdx @@ -0,0 +1,51 @@ +# custom$$ + +## Обзор {/* #overview */} + +Найдите несколько элементов с помощью зарегистрированной стратегии. Если DOM API возвращает NodeList, преобразуйте его через `Array.from` внутри стратегии. + +## Использование {/* #usage */} + +`browser.custom$$(strategyName, ...strategyArguments)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------- | ----------- | ---------------- | --------------------------------------- | +| `strategyName` | `string` | Обязателен | Имя зарегистрированной стратегии. | +| `strategyArguments` | `unknown[]` | [] | Аргументы браузерной функции стратегии. | + +## Результат {/* #result */} + +Ожидаемая коллекция, разрешающаяся в массив объектов `WebdriverIO.Element`. Если совпадений нет, возвращается пустой массив. + +## Ограничения {/* #limitations */} + +Неизвестное имя стратегии вызывает ошибку. Команда возвращает совпадения на момент поиска и не ожидает появления заданного количества элементов. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("custom$$: find several elements", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = "
  • First
  • Second
"; + }); + browser.addLocatorStrategy("byCss", selector => + Array.from(document.querySelectorAll(selector)), + ); + const items = await browser.custom$$("byCss", "li"); + assert.equal(items.length, 2); + assert.equal(await items[1].getText(), "Second"); +}); +``` + +## См. также {/* #see-also */} + +- [addLocatorStrategy](./addLocatorStrategy.mdx) +- [custom$](./customDollar.mdx) +- [$$](./$$.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/downloadFile.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/downloadFile.mdx new file mode 100644 index 00000000..43e04bb0 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/downloadFile.mdx @@ -0,0 +1,67 @@ +# downloadFile + +## Обзор {/* #overview */} + +Копирует файл, скачанный браузером на узле Selenium Grid, в файловую систему процесса Testplane. Метод не скачивает произвольный URL. + +## Условия использования {/* #requirements */} + +Нужен Selenium Grid с включенными управляемыми загрузками (`--enable-managed-downloads true`) и capability `se:downloadsEnabled: true`. Поддерживаются Chrome, Edge и Firefox. Режим `gridUrl: "local"` сам по себе не предоставляет API загрузок Selenium. Подробнее — в [документации Selenium](https://www.selenium.dev/documentation/webdriver/remote_webdriver/#enable-downloads-in-the-grid). + +## Использование {/* #usage */} + +`browser.downloadFile(fileName, targetDirectory)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **Описание** | +| ----------------- | -------- | ------------------------------------------------------ | +| `fileName` | `string` | Имя файла из `getDownloadableFiles().names`, не URL. | +| `targetDirectory` | `string` | Локальный каталог назначения; создается автоматически. | + +## Результат {/* #result */} + +`Promise<{ files: string[] }>`: абсолютные пути распакованных файлов. Метод также оставляет архив `.zip` в каталоге назначения. В этой версии ошибки распаковки записываются в лог и могут привести к пустому массиву `files`; проверяйте наличие и содержимое результата. + +## Пример {/* #examples */} + +Настройте `gridUrl` на адрес своего Grid, включите `se:downloadsEnabled: true` в `desiredCapabilities`. Укажите `baseUrl` тестового HTTP-сайта: `/` возвращает HTML, а `/report.csv` — текст `name,value\nexample,42\n` с заголовком `Content-Disposition: attachment; filename="report.csv"`. + +Сначала дождитесь загрузки файла на узле Grid: получение списка файлов не ожидает окончания загрузки. Затем скопируйте файл и очистите удаленный и локальный каталоги, созданные тестом. + +```javascript +const assert = require("node:assert/strict"); +const fs = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); + +it("downloadFile: copy a browser download from Selenium Grid", async ({ browser }) => { + const targetDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "testplane-download-")); + try { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = 'Download report'; + }); + await browser.$("a").click(); + await browser.waitUntil( + async () => { + const { names } = await browser.getDownloadableFiles(); + return names.includes("report.csv"); + }, + { timeout: 10000, timeoutMsg: "The CSV was not downloaded on the Grid node" }, + ); + const { files } = await browser.downloadFile("report.csv", targetDirectory); + const localFile = path.join(targetDirectory, "report.csv"); + assert.ok(files.includes(localFile)); + assert.equal(await fs.readFile(localFile, "utf8"), "name,value\nexample,42\n"); + } finally { + await browser.deleteDownloadableFiles(); + await fs.rm(targetDirectory, { recursive: true, force: true }); + } +}); +``` + +## См. также {/* #see-also */} + +- [url](./url.mdx) +- [waitUntil](./waitUntil.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/emulate.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/emulate.mdx new file mode 100644 index 00000000..b047bad8 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/emulate.mdx @@ -0,0 +1,72 @@ +# emulate + +## Обзор {/* #overview */} + +Подмените часть браузерного окружения, например user agent или часы. Для восстановления окружения в том же тесте используйте возвращенную функцию очистки или объект часов. + +## Использование {/* #usage */} + +`browser.emulate(scope, options)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | ----------------- | ----------------------- | ---------------------------------------------------------------------------- | +| `scope` | `string` | Обязателен | Один из режимов: clock, geolocation, userAgent, device, colorScheme, onLine. | +| `options` | Зависит от режима | Обязателен, кроме clock | Настройки выбранного режима, описанные ниже. | + +Поддерживаемые режимы: + +- `clock` — Настройки виртуальных часов, включая `now` и `toFake`; виртуальные таймеры продвигаются через `tick`. +- `geolocation` — Объект с координатами для `navigator.geolocation.getCurrentPosition`. +- `userAgent` — Строка для `navigator.userAgent`, а не HTTP-заголовков запросов. +- `device` — Имя поддерживаемого профиля устройства; меняет viewport, DPR и user agent, но не движок и сенсорный ввод. +- `colorScheme` — `"light"` или `"dark"`; в этой реализации влияет на `matchMedia`, но не на CSS-медиазапросы. +- `onLine` — Булево значение для `navigator.onLine`; не блокирует сетевые запросы. + +## Результат {/* #result */} + +Для `clock` — `Promise`, для остальных режимов — промис с асинхронной функцией восстановления. Объект часов предоставляет методы `tick(milliseconds)`, `setSystemTime(dateOrTimestamp)`, `restore()` и `install(options)`. + +## Ограничения {/* #limitations */} + +Требуется WebDriver BiDi (`webSocketUrl: true`). Большинство режимов влияет на документы, созданные после вызова: включайте эмуляцию до перехода на страницу. Эмуляция часов также действует в текущем документе. Восстанавливайте окружение до завершения теста: следующий тест получает другой объект браузера. Неподдерживаемый режим или неверная настройка вызывают ошибку. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("emulate: control time without waiting", async ({ browser }) => { + await browser.url("/"); + const clock = await browser.emulate("clock", { + now: new Date("2025-01-01T00:00:00.000Z"), + toFake: ["Date", "setTimeout", "clearTimeout"], + }); + try { + await browser.execute(() => { + window.timerFired = false; + setTimeout(() => { + window.timerFired = true; + }, 1000); + }); + assert.equal(await browser.execute(() => window.timerFired), false); + await clock.tick(1000); + assert.equal(await browser.execute(() => window.timerFired), true); + assert.equal( + await browser.execute(() => new Date().toISOString()), + "2025-01-01T00:00:01.000Z", + ); + } finally { + await clock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [restore](./restore.mdx) +- [setViewport](./setViewport.mdx) +- [Эмуляция окружения](../../basic-guides/user-environment-emulation.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/executeAsync.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/executeAsync.mdx index 74d58b14..60fee573 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/executeAsync.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/executeAsync.mdx @@ -2,58 +2,48 @@ ## Обзор {/* #overview */} -Используйте команду `executeAsync`, чтобы выполнить _асинхронно_ указанный JavaScript-код на странице текущего выбранного фрейма. +Выполняет JavaScript в текущем фрейме и возвращает значение, переданное в callback. Команда устарела: для нового кода используйте [execute](./execute.mdx) с асинхронной функцией. -Последним аргументом команды должен быть колбэк, который будет вызван, как только скрипт завершит свою работу. В качестве входного параметра команда передает в колбэк результат выполнения скрипта. - -Аргумент `script` определяет сценарий для выполнения в виде тела функции. Функция будет вызвана с предоставленным массивом `args`, и доступ к значениям может быть получен через объект `arguments` в указанном порядке. Последним аргументом всегда должна быть колбэк-функция, которая будет вызвана после того, как скрипт будет выполнен. - -Аргументами могут быть любые JSON-примитивы, массивы или объекты JSON. Объекты JSON, которые определяют ссылку на _WebElement,_ будут преобразованы в соответствующий элемент DOM. Аналогично, любые _WebElements_ в результате скрипта будут возвращены клиенту в виде объектов _WebElement JSON_. +Callback добавляет сама команда как последний аргумент **браузерной функции**. Его не нужно передавать отдельным аргументом `browser.executeAsync`. Вызовите callback, чтобы завершить команду. ## Использование {/* #usage */} -```javascript -await browser.executeAsync(script, arguments); -``` +`await browser.executeAsync(script, ...args)` ## Параметры команды {/* #parameters */} - - - - - - - +- `script` — функция или строка с телом функции. Код выполняется в браузере, без Node.js API и локальных замыканий. +- `args` — сериализуемые аргументы, передаваемые перед callback; ссылки WebElement преобразуются в DOM-элементы. + +Результат — Promise со значением, переданным в callback. В Classic настройте тайм-аут `script` через [setTimeout](./setTimeout.mdx). В BiDi-реализации нет отдельного таймера ожидания callback; всегда вызывайте его и ограничивайте тест через `testTimeout`. - -
**Имя****Тип****Описание**
scriptString или FunctionСкрипт, который необходимо выполнить.
argumentsAnyАргументы для скрипта. Последним аргументом должна быть колбэк-функция, которая будет вызвана после того, как скрипт будет выполнен.
+## Пример {/* #examples */} -## Примеры использования {/* #examples */} +Укажите в `baseUrl` адрес тестовой HTML-страницы. ```javascript -it("should execute async JavaScript on the page", async ({ browser }) => { - await browser.setTimeout({ script: 5000 }); +const assert = require("node:assert/strict"); +it("browser.executeAsync: return a result through the injected callback", async ({ browser }) => { + await browser.url("/"); + await browser.setTimeout({ script: 5000 }); const result = await browser.executeAsync( - function (a, b, c, d, done) { - // здесь мы в browser-контексте: доступа к консоли и к клиенту у нас нет - setTimeout(() => { - done(a + b + c + d); - }, 3000); + (a, b, done) => { + setTimeout(() => done(a + b), 20); }, - 1, - 2, - 3, 4, + 6, ); - - // здесь мы в node.js-контексте: доступ к консоли и к клиенту у нас есть - console.log(result); // выведет: 10 + assert.equal(result, 10); }); ``` ## Связанные команды {/* #related */} -- [execute](../execute) -- [setTimeout](../setTimeout) +- [execute](./execute.mdx) +- [element.executeAsync](../element/executeAsync.mdx) +- [setTimeout](./setTimeout.mdx) + +## Источники {/* #references */} + +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/browser/executeAsync). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/extendOptions.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/extendOptions.mdx new file mode 100644 index 00000000..d065ce6f --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/extendOptions.mdx @@ -0,0 +1,62 @@ +# extendOptions + +## Обзор {/* #overview */} + +Поверхностно обновляет настройки текущего объекта браузера WebdriverIO. Используйте метод для клиентских параметров, например интервалов ожидания, а не для изменения конфигурации раннера Testplane или capabilities сессии. + +## Использование {/* #usage */} + +`browser.extendOptions(options)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | ------------------------- | ---------------- | ---------------------------------------------------- | +| `options` | `Record` | Обязательный | Поля верхнего уровня для записи в `browser.options`. | + +## Результат {/* #result */} + +`Promise`: команда завершается без возвращаемого значения. + +## Ограничения {/* #limitations */} + +Вложенные объекты заменяются, а не объединяются рекурсивно. Поля, которых нет в аргументе, сохраняются. Метод не проверяет имена параметров и не пересоздает сессию: изменение действует только там, где команда читает этот runtime-параметр. Конфигурация, возвращаемая `getConfig()`, не меняется. Сохраните изменяемые значения и восстановите их в `finally`. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("extendOptions: change WebdriverIO wait settings for this browser", async ({ browser }) => { + const original = { + waitforTimeout: browser.options.waitforTimeout, + waitforInterval: browser.options.waitforInterval, + }; + const configuredTimeout = (await browser.getConfig()).waitTimeout; + assert.equal(typeof configuredTimeout, "number"); + try { + const result = await browser.extendOptions({ waitforTimeout: 1000, waitforInterval: 50 }); + assert.equal(result, undefined); + assert.equal(browser.options.waitforTimeout, 1000); + assert.equal(browser.options.waitforInterval, 50); + assert.equal((await browser.getConfig()).waitTimeout, configuredTimeout); + await browser.url("/"); + await browser.execute(() => { + setTimeout(() => { + document.body.innerHTML = '

Ready

'; + }, 100); + }); + await browser.$("#ready").waitForExist(); + assert.equal(await browser.$("#ready").getText(), "Ready"); + } finally { + await browser.extendOptions(original); + } +}); +``` + +## См. также {/* #see-also */} + +- [getConfig](./getConfig.mdx) +- [waitUntil](./waitUntil.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/getState.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/getState.mdx index 80faac68..4f22baf1 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/getState.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/getState.mdx @@ -6,53 +6,55 @@ import Version from "../../_partials/specs/version.mdx"; ## Обзор {/* #overview */} -Эта команда возвращает состояние страницы из файла, ранее сохраненного с помощью команды [saveState][saveState]. +Читает JSON-файл состояния, созданный [saveState](./saveState.mdx), и возвращает его содержимое. Команда **не восстанавливает состояние браузера**: не меняет cookies, хранилища и текущую страницу. Для восстановления используйте [restoreState](./restoreState.mdx). ## Использование {/* #usage */} -```typescript -import type { SaveStateData } from "testplane"; - -const stateDump: SaveStateData = await browser.getState({ - path: "./stateDump.json", -}); -``` +`await browser.getState(options)` ## Параметры команды {/* #parameters */} - - - - - - - - - - - - - - - - - -
**Имя****Тип****Значение по умолчанию****Описание**
path`string``-`Путь к файлу с состоянием.
- -## Usage Examples {/* #examples */} - -Save state in file. - -```typescript -it("test", async ({ browser }) => { - const stateData = await browser.getState({ - path: "./stateDump.json", - }); +| Параметр | Тип | Описание | +| -------------- | -------- | ------------------------------------------------------------------------------------------ | +| `options.path` | `string` | Путь к JSON-файлу. Относительный путь разрешается от рабочего каталога процесса Testplane. | + +Если аргумент `options` пропущен, используются `browser.config.stateOpts`. Явно переданный объект не объединяется с настройками: `getState({})` не подставит путь из конфигурации. + +## Результат и ошибки {/* #result */} + +Возвращает Promise с прочитанными данными. Для файла `saveState` это `SaveStateData`: необязательный массив `cookies` и `framesData`, где состояния `localStorage` и `sessionStorage` сгруппированы по origin. Схема прочитанного JSON не проверяется. + +Если путь не задан, команда пишет сообщение об ошибке в лог и возвращает `null`. Ошибки чтения и разбора JSON отклоняют Promise. + +## Пример {/* #examples */} + +Укажите в `baseUrl` адрес тестовой HTML-страницы. Пример сохраняет localStorage во временный файл, меняет значение в браузере и проверяет, что чтение файла не восстанавливает сессию. + +```javascript +const assert = require("node:assert/strict"); +const fs = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); + +it("getState: read a saved file without restoring the session", async ({ browser }) => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "testplane-state-")); + const statePath = path.join(directory, "state.json"); + try { + await browser.url("/"); + const origin = new URL(await browser.getUrl()).origin; + await browser.execute(() => localStorage.setItem("example", "saved")); + await browser.saveState({ path: statePath, cookies: false, localStorage: true }); + await browser.execute(() => localStorage.setItem("example", "current")); + const state = await browser.getState({ path: statePath }); + assert.equal(state.framesData[origin].localStorage.example, "saved"); + assert.equal(await browser.execute(() => localStorage.getItem("example")), "current"); + } finally { + await fs.rm(directory, { recursive: true, force: true }); + } }); ``` -## Related Commands {/* #related */} - -- [saveState][saveState] +## Связанные команды {/* #related */} -[saveState]: ../saveState +- [saveState](./saveState.mdx) +- [restoreState](./restoreState.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/mock.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/mock.mdx index 26bc37da..8d3c7ab9 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/mock.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/mock.mdx @@ -1,135 +1,97 @@ -import Admonition from "@theme/Admonition"; - # mock ## Обзор {/* #overview */} -Используйте команду `mock`, чтобы имитировать ответ на запрос. +Создает перехватчик сетевых запросов. Возвращаемый объект позволяет наблюдать запросы, заменять ответы или прерывать загрузку. Возможности зависят от протокола сессии. -Вы можете включать имитацию в зависимости от урла, заголовков или статус-кода. Вызов метода `mock` возвращает объект-заглушку _(stub)_, который вы можете использовать, чтобы модифицировать ответ от веб-ресурса. С помощью объекта-заглушки вы можете возвращать ответ-имитацию или ронять запрос. +- **WebDriver BiDi**: перехват через сетевые события BiDi. Дополнительно доступны изменение запроса, перенаправление и сброс правил. +- **Classic с CDP/Puppeteer**: прежняя реализация для совместимого браузера. Это не универсальная возможность Classic WebDriver. +- **WebDriver-перехват Sauce Labs**: отдельное расширение провайдера с другим API и без событий; не переносите на него BiDi-пример без проверки. -Существует 3 способа модифицировать ответ: +См. [подключение CDP](../../guides/how-to-use-cdp.mdx) и [перехват запросов](../../guides/how-to-intercept-requests-and-responses.mdx). Различия ниже относятся к Testplane 9.3.2 / WDIO 9.6.2. -- вернуть кастомный JSON-объект (например, чтобы имитировать ответ на API-запрос); -- заменить ответ локальным файлом (подсунуть модифицированный JavaScript-файл); -- перенаправить на другой URL и подсунуть полученный оттуда ответ. +## Использование {/* #usage */} - -Команда _mock_ работает только при использовании _Chrome DevTools Protocol (CDP)_. +`await browser.mock(url, filters)` -Читайте подробности в разделе «[Как использовать Chrome DevTools Protocol в testplane][how-to-use-cdp]». +## Параметры команды {/* #parameters */} -А также читайте рецепт «[Как отслеживать и перехватывать сетевые запросы и ответы][how-to-intercept-requests-and-responses]». +| Параметр | BiDi | Classic/CDP | +| ----------------- | ----------------------------------------------------------------------------------- | ----------------------------------------- | +| `url` | Строка `URLPattern`: абсолютный HTTP(S)-шаблон или pathname, например `/api/items`. | Glob-строка или `RegExp`. | +| `method` | Строка или функция-фильтр HTTP-метода. | То же. | +| `requestHeaders` | Объект или функция-фильтр заголовков запроса. | То же. | +| `responseHeaders` | Объект или функция-фильтр заголовков ответа. | Не применяется. | +| `headers` | Не используйте; для ответа есть `responseHeaders`. | Фильтр **заголовков ответа**, не запроса. | +| `postData` | Не применяется. | Строка или функция-фильтр тела запроса. | +| `statusCode` | Число или функция-фильтр статуса ответа. | То же. | - +Опции фильтрации необязательны. Фильтры ответа проверяются только на стадии ответа: событие `request` в BiDi может произойти раньше. Glob-строки CDP и `URLPattern` BiDi не взаимозаменяемы. -## Использование {/* #usage */} +## Результат {/* #result */} -```javascript -await browser.mock(url, { method, headers, responseHeaders, postData, statusCode }); -``` +Команда возвращает Promise с объектом мока. Общие операции: [respond](../mock/respond.mdx), [respondOnce](../mock/respondOnce.mdx), [abort](../mock/abort.mdx), [abortOnce](../mock/abortOnce.mdx), [waitForResponse](../mock/waitForResponse.mdx), [clear](../mock/clear.mdx) и [restore](../mock/restore.mdx). Сигнатуры могут различаться между реализациями. -## Параметры команды {/* #parameters */} +Только в BiDi: [request](../mock/request.mdx), [requestOnce](../mock/requestOnce.mdx), [redirect](../mock/redirect.mdx), [redirectOnce](../mock/redirectOnce.mdx) и [reset](../mock/reset.mdx). - - - - - - - - - - - +`mock.calls` хранит историю ответов: - -
**Имя****Тип****Описание**
urlStringURL, запрос которого нужно имитировать.
methodString или Functionhttp-метод, по которому нужно фильтровать ресурс.
headersObject или FunctionЗаголовки запроса, по которым нужно фильтровать ресурс.
responseHeadersObject или FunctionЗаголовки ответа, по которым нужно фильтровать ресурс.
postDataString или FunctionpostData запроса, по которому нужно фильтровать ресурс.
statusCodeNumber или FunctionКод статуса, по которому нужно фильтровать ресурс.
+- BiDi: объекты `network.responseStarted`, например `call.request.url` и `call.response.status`. Это не гарантия полной загрузки тела. +- Classic/CDP: записи с `url`, `body`, `headers`, `responseHeaders`, `statusCode`. -## Примеры использования {/* #examples */} +Не используйте поля одной реализации как общий контракт. `clear()` очищает историю, но сохраняет правила. `restore()` удаляет перехватчик; ожидайте его завершения в `finally`. -```javascript -it("should mock network resources", async ({ browser }) => { - // используем статическую строку - const userListMock = await browser.mock("**" + "/users/list"); - - // также мы можем имитировать ответы в зависимости - // от заголовков запроса или ответа, статус-кода, или postData - const strictMock = await browser.mock("**", { - // мокаем все json-ответы - statusCode: 200, - headers: { "Content-Type": "application/json" }, - responseHeaders: { "Cache-Control": "no-cache" }, - postData: "foobar", - }); +## События {/* #events */} - // функция-компаратор - const apiV1Mock = await browser.mock("**" + "/api/v1", { - statusCode: statusCode => statusCode >= 200 && statusCode <= 203, - headers: headers => - headers["Authorization"] && headers["Authorization"].startsWith("Bearer "), - responseHeaders: headers => headers["Impersonation"], - postData: data => typeof data === "string" && data.includes("foo"), - }); -}); +Подписывайтесь через `mock.on(name, listener)`. Обработчик выполняется в Node.js, а не в браузере. -it("should modify API responses", async ({ browser }) => { - // фильтруем по методу - const todoMock = await browser.mock("**" + "/todos", { - method: "get", - }); +| Событие | BiDi | Classic/CDP | +| ----------- | --------------------------------------------------------------- | ------------------------------------- | +| `request` | Событие `network.beforeRequestSent`: URL в `event.request.url`. | Событие CDP `Fetch.requestPaused`. | +| `match` | В этой версии не испускается, хотя объявлено в типах. | Запись совпавшего ответа. | +| `overwrite` | Событие запроса или ответа, к которому применена подмена. | Параметры подмененного ответа. | +| `fail` | Идентификатор прерванного запроса. | Объект с `requestId` и `errorReason`. | +| `continue` | Идентификатор продолженного запроса. | Идентификатор продолженного запроса. | - // имитируем ответ эндпойнта заготовленной фикстурой - mock.respond([ - { - title: "Injected Todo", - order: null, - completed: false, - url: "http://todo-backend-express-knex.herokuapp.com/916", - }, - ]); - - // отвечаем другим статус-кодом или заголовком - mock.respond( - [ - { - title: "Injected Todo", - order: null, - completed: false, - url: "http://todo-backend-express-knex.herokuapp.com/916", - }, - ], - { - statusCode: 404, - headers: { - "x-custom-header": "foobar", - }, - }, - ); -}); +Не используйте наличие события как доказательство завершения всего сетевого обмена. -it("should modify text assets", async ({ browser }) => { - const scriptMock = await browser.mock("**" + "/script.min.js"); - scriptMock.respond("./tests/fixtures/script.js"); -}); +## Пример {/* #examples */} -it("should redirect web resources", async ({ browser }) => { - const headerMock = await browser.mock("**" + "/header.png"); - headerMock.respond("https://media.giphy.com/media/F9hQLAVhWnL56/giphy.gif"); +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. - const pageMock = await browser.mock("https://google.com/"); - pageMock.respond("https://webdriver.io"); - await browser.url("https://google.com"); +```javascript +const assert = require("node:assert/strict"); - console.log(await browser.getTitle()); - // выведет: "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js" +it("browser.mock: observe and replace a BiDi response", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items", { method: "GET", statusCode: 200 }); + const seenUrls = []; + mock.on("request", event => seenUrls.push(event.request.url)); + try { + mock.respond({ source: "mocked" }); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(body.source, "mocked"); + assert.equal(mock.calls.length, 1); + assert.ok(mock.calls[0].request.url.endsWith("/api/items")); + assert.ok(seenUrls.some(url => url.endsWith("/api/items"))); + } finally { + await mock.restore(); + } }); ``` ## Связанные команды {/* #related */} -- [mockClearAll](../mockClearAll) -- [mockRestoreAll](../mockRestoreAll) +- [mockClearAll](./mockClearAll.mdx) +- [mockRestoreAll](./mockRestoreAll.mdx) +- [respond](../mock/respond.mdx) + +## Источники {/* #references */} -[how-to-use-cdp]: ../../../guides/how-to-use-cdp -[how-to-intercept-requests-and-responses]: ../../../guides/how-to-intercept-requests-and-responses +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/browser/mock). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/restore.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/restore.mdx new file mode 100644 index 00000000..b3f8d307 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/restore.mdx @@ -0,0 +1,51 @@ +# restore + +## Обзор {/* #overview */} + +Отмените эмуляцию, включенную через `emulate` у того же объекта браузера. Команда не восстанавливает cookies, хранилища, ограничение сети или CPU и не отменяет моки. + +## Использование {/* #usage */} + +`browser.restore(scopes)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | -------------------- | ----------------------------- | ------------------------------------------------------------------------------- | +| `scopes` | `string \| string[]` | Все зарегистрированные режимы | Один или несколько режимов: clock, geolocation, userAgent, colorScheme, onLine. | + +## Результат {/* #result */} + +`Promise`. Если для объекта браузера не записана эмуляция, команда ничего не делает. + +## Ограничения {/* #limitations */} + +Выполняйте очистку в том же тесте, где включена эмуляция, предпочтительно в `finally`. Для подмен через предварительные скрипты перезагрузите страницу после восстановления. `device` не регистрируется как режим восстановления: вызовите функцию, возвращенную `emulate("device", name)`. Она устанавливает viewport Desktop Chrome (1280 × 720, DPR 1), а не исходный размер. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("restore: remove a user-agent override", async ({ browser }) => { + await browser.url("/"); + const original = await browser.execute(() => navigator.userAgent); + await browser.emulate("userAgent", "ExampleBrowser/1.0"); + try { + await browser.url("/"); + assert.equal(await browser.execute(() => navigator.userAgent), "ExampleBrowser/1.0"); + } finally { + await browser.restore("userAgent"); + } + await browser.url("/"); + assert.equal(await browser.execute(() => navigator.userAgent), original); +}); +``` + +## См. также {/* #see-also */} + +- [emulate](./emulate.mdx) +- [restoreState](./restoreState.mdx) +- [mock.restore](../mock/restore.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx new file mode 100644 index 00000000..a7fd1b04 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx @@ -0,0 +1,53 @@ +# setViewport + +## Обзор {/* #overview */} + +Задайте размер области отображения страницы и плотность пикселей. В отличие от `setWindowSize`, команда изменяет viewport страницы, а не внешние размеры окна браузера. + +## Использование {/* #usage */} + +`browser.setViewport({ width, height, devicePixelRatio })` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------ | -------- | ---------------- | -------------------------------------------------------------------------- | +| `width` | `number` | Обязателен | Ширина viewport в CSS-пикселях. | +| `height` | `number` | Обязателен | Высота viewport в CSS-пикселях. | +| `devicePixelRatio` | `number` | 1 | Плотность пикселей (DPR). Если параметр не передан, устанавливается DPR 1. | + +## Результат {/* #result */} + +`Promise`. + +## Ограничения {/* #limitations */} + +Требуются WebDriver BiDi (`webSocketUrl: true`) и поддержка изменения viewport на стороне драйвера. В WDIO 9.6.2 клиент проверяет ширину и высоту по границам 0 и `Number.MAX_SAFE_INTEGER`. Драйвер может накладывать более строгие ограничения: прохождение клиентской проверки не гарантирует поддержку такого размера viewport. Сохраните прежние размеры, если их нужно восстановить. Изменение размеров не эмулирует мобильное устройство, сенсорный ввод или его браузерный движок. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("setViewport: change page dimensions", async ({ browser }) => { + await browser.url("/"); + const original = await browser.execute(() => ({ + width: innerWidth, + height: innerHeight, + devicePixelRatio, + })); + try { + await browser.setViewport({ width: 800, height: 600, devicePixelRatio: 1 }); + assert.deepEqual(await browser.execute(() => [innerWidth, innerHeight]), [800, 600]); + } finally { + await browser.setViewport(original); + } +}); +``` + +## См. также {/* #see-also */} + +- [setWindowSize](./setWindowSize.mdx) +- [emulate](./emulate.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/switchFrame.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/switchFrame.mdx new file mode 100644 index 00000000..0463ca4b --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/switchFrame.mdx @@ -0,0 +1,57 @@ +# switchFrame + +## Обзор {/* #overview */} + +Переключите последующие команды браузера в iframe или вернитесь в корневой документ. Команда изменяет контекст фрейма, а не выбранное окно. + +## Использование {/* #usage */} + +`browser.switchFrame(context)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | --------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `context` | `Element \| string \| Function \| null` | Обязателен | Элемент iframe; для BiDi также ID контекста, URL, часть URL или функция-предикат, выполняемая во фреймах. null выбирает корневой документ. | + +## Результат {/* #result */} + +`Promise`. В режиме BiDi возвращается ID выбранного контекста. В Classic протокольное переключение может не возвращать значение. + +## Ограничения {/* #limitations */} + +Classic поддерживает элемент iframe и `null`, но отклоняет строку и функцию. Используйте `null` в `finally`, если последующие шаги должны выполняться в основном документе. Если фрейм не найден, команда завершается ошибкой. Не путайте ее с `switchWindow`. + +:::warning + +В WDIO 9.6.2 ветка с функцией-предикатом обращается к глобальному `browser`. В стандартном тесте Testplane с аргументом `{ browser }` подходящий фрейм найден, но команда завершается ошибкой `browser is not defined`. Это воспроизведено в BiDi-сессии. Используйте элемент iframe, как в примере ниже; не создавайте глобальный `browser` ради обхода ошибки. + +::: + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("switchFrame: enter an iframe and return", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Main page

'; + }); + try { + await browser.switchFrame(browser.$("#frame")); + assert.equal(await browser.$("p").getText(), "Frame content"); + } finally { + await browser.switchFrame(null); + } + assert.equal(await browser.$("h1").getText(), "Main page"); +}); +``` + +## См. также {/* #see-also */} + +- [switchWindow](./switchWindow.mdx) +- [execute](./execute.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttle.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttle.mdx index fde56ecd..d0853469 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttle.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttle.mdx @@ -1,69 +1,49 @@ -import Admonition from "@theme/Admonition"; - # throttle ## Обзор {/* #overview */} -Используйте команду `throttle`, чтобы эмулировать разные типы сетевого соединения у пользователя. +Устаревшее имя [throttleNetwork](./throttleNetwork.mdx). Ограничивает пропускную способность сети через CDP/Puppeteer; не эквивалентно [emulate("onLine")](./emulate.mdx). Для нового кода используйте `throttleNetwork`. -Эта команда позволяет промоделировать поведение сайта или веб-приложения при различной пропускной способности канала связи у пользователя. +## Использование {/* #usage */} -Существует также множество пресетов с готовыми настройками конфигурации сети. Например: +`await browser.throttle(profile)` -- offline | online -- GPRS -- Regular2G | Good2G -- Regular3G | Good3G -- Regular4G -- DSL -- WiFi +## Параметры команды {/* #parameters */} - -Команда _throttle_ работает только при использовании _Chrome DevTools Protocol (CDP)_. +Параметры совпадают с [throttleNetwork](./throttleNetwork.mdx#parameters). Можно передать имя профиля: `offline`, `online`, `GPRS`, `Regular2G`, `Good2G`, `Regular3G`, `Good3G`, `Regular4G`, `DSL`, `WiFi`. Пробелов в именах нет. -Читайте подробности в разделе «[Как использовать Chrome DevTools Protocol в testplane][how-to-use-cdp]». +Или объект с полями `offline` (boolean), `latency` (миллисекунды), `downloadThroughput` и `uploadThroughput` (байты в секунду, `-1` отключает ограничение скорости). Результат — `Promise`. -А также читайте рецепт «[Как управлять пропускной способностью сети][how-to-manage-network-bandwidth]». +## Ограничения {/* #limitations */} - +Нужен браузер с доступным CDP/Puppeteer. Реализация выбирает первую страницу Puppeteer: для примера используйте одну вкладку и `isolation: false`, сначала откройте страницу. -## Использование {/* #usage */} +В Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150 профиль `online` не восстановил сеть после `offline`. Настройте `testsPerSession: 1`, чтобы ограничение не перешло в следующий тест. Подробнее — [throttleNetwork](./throttleNetwork.mdx#limitations). -```javascript -await browser.throttle({ offline, latency, downloadThroughput, uploadThroughput }); -``` +## Пример {/* #examples */} -## Параметры команды {/* #parameters */} - - - - - - - - - - - - -
**Имя****Тип****Описание**
offlineBooleanВключить эмуляцию потери соединения.
latencyNumberМинимальная задержка от отправленного запроса до полученных заголовков ответа, в миллисекундах.
downloadThroughputNumberМаксимальная совокупная пропускная способность загрузки (байт/сек). _-1_ отключает регулирование загрузки.
uploadThroughputNumberМаксимальная совокупная пропускная способность аплоада (байт/сек). _-1_ отключает регулирование аплоада.
- -## Примеры использования {/* #examples */} +Укажите в `baseUrl` адрес тестового HTTP-сервера, который отвечает `200` на корневой путь. Пример использует новую сессию на каждый тест. ```javascript -it("should throttle the network", async ({ browser }) => { - // используем готовый пресет - await browser.throttle("Regular 3G"); - - // а здесь настраиваем конфигурацию сети сами - await browser.throttle({ - offline: false, - downloadThroughput: (200 * 1024) / 8, - uploadThroughput: (200 * 1024) / 8, - latency: 20, - }); +const assert = require("node:assert/strict"); + +it("throttle: use the deprecated alias in an isolated session", async ({ browser }) => { + await browser.url("/"); + await browser.throttle("Good3G"); + const status = await browser.execute( + async () => (await fetch("/?throttled", { cache: "no-store" })).status, + ); + assert.equal(status, 200); }); ``` -[how-to-use-cdp]: ../../../guides/how-to-use-cdp -[how-to-manage-network-bandwidth]: ../../../basic-guides/emulation +## Связанные команды {/* #related */} + +- [throttleNetwork](./throttleNetwork.mdx) +- [throttleCPU](./throttleCPU.mdx) +- [CDP](../../guides/how-to-use-cdp.mdx) +- [Emulation](../../basic-guides/user-environment-emulation.mdx) + +## Источники {/* #references */} + +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/browser/throttle). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleCPU.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleCPU.mdx new file mode 100644 index 00000000..e7ff0da7 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleCPU.mdx @@ -0,0 +1,49 @@ +# throttleCPU + +## Обзор {/* #overview */} + +Замедлите CPU через Chrome DevTools Protocol. Это позволяет проверить страницу при ограниченной производительности, но не воспроизводит конкретное физическое устройство. + +## Использование {/* #usage */} + +`browser.throttleCPU(factor)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | -------- | ---------------- | -------------------------------------------------------------------------------- | +| `factor` | `number` | Обязателен | Коэффициент замедления: 1 отключает ограничение, 2 задает двукратное замедление. | + +## Результат {/* #result */} + +`Promise`. + +## Ограничения {/* #limitations */} + +Требуются Chromium и доступное соединение Puppeteer/CDP. Текущая реализация выбирает первую страницу Puppeteer, которая не обязательно совпадает с активной страницей Testplane. Для примера используйте одну вкладку, `isolation: false` и `testsPerSession: 1`: новая сессия сохраняет независимость тестов. Нечисловой аргумент или недоступное CDP-соединение вызывают ошибку. Коэффициент 1 запрашивает обычный режим CPU. + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("throttleCPU: run a page with a slower CPU", async ({ browser }) => { + await browser.url("/"); + try { + await browser.throttleCPU(2); + await browser.execute(() => { + document.body.innerHTML = ""; + }); + assert.equal(await browser.$("button").getText(), "Ready"); + } finally { + await browser.throttleCPU(1); + } +}); +``` + +## См. также {/* #see-also */} + +- [throttleNetwork](./throttleNetwork.mdx) +- [getPuppeteer](./getPuppeteer.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx new file mode 100644 index 00000000..c04a5a34 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx @@ -0,0 +1,71 @@ +# throttleNetwork + +## Обзор {/* #overview */} + +Ограничьте пропускную способность и задержку сети или переведите страницу в офлайн через CDP. Это влияет на реальные запросы, в отличие от `emulate("onLine", false)`, который меняет только JavaScript-свойство. + +## Использование {/* #usage */} + +`browser.throttleNetwork(profileOrOptions)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| -------------------- | ------------------ | -------------------- | ------------------------------------------------------------------------------------------------ | +| `profileOrOptions` | `string \| object` | Обязателен | Именованный профиль или объект с полями offline, latency, downloadThroughput и uploadThroughput. | +| `offline` | `boolean` | Обязателен в объекте | Отключен ли сетевой доступ. | +| `latency` | `number` | Обязателен в объекте | Минимальная задержка в миллисекундах. | +| `downloadThroughput` | `number` | Обязателен в объекте | Скорость скачивания в байтах в секунду; -1 снимает это ограничение. | +| `uploadThroughput` | `number` | Обязателен в объекте | Скорость отправки в байтах в секунду; -1 снимает это ограничение. | + +## Результат {/* #result */} + +`Promise`. + +## Ограничения {/* #limitations */} + +Локальная CDP-реализация требует Chromium и соединение Puppeteer. Она выбирает первую страницу Puppeteer. Для примера ниже используйте одну вкладку, `isolation: false` и `testsPerSession: 1`. Загрузите страницу до отключения сети. Профили: `offline`, `GPRS`, `Regular2G`, `Good2G`, `Regular3G`, `Good3G`, `Regular4G`, `DSL`, `WiFi`, `online`. Неизвестное имя профиля вызывает ошибку. В сессиях Sauce Labs используется отдельная команда сервиса. + +:::warning + +В проверенной связке Testplane 9.3.2, WDIO 9.6.2 и Chrome for Testing 150.0.7871.124 повторный вызов с `"online"` не восстановил сеть после `"offline"`. Каждый вызов создает отдельную CDP-сессию. Не полагайтесь на такой сброс для повторного использования браузера: пример завершает офлайн-сессию после теста и проверяет сеть в новой. Для переключения в пределах одного теста управляйте режимами через одну CDP-сессию. + +::: + +## Пример {/* #examples */} + +Настройте `baseUrl` на свой локальный тестовый HTTP-сайт, где `/` возвращает HTML-страницу. Пример выполняется внутри обычного файла тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +// Configure testsPerSession: 1 so this offline session is not reused. +it("throttleNetwork: check a failed request while offline", async ({ browser }) => { + await browser.url("/"); + await browser.throttleNetwork("offline"); + const failed = await browser.execute(async () => { + try { + await fetch("/?offline-check", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); +}); + +it("throttleNetwork: the next session starts online", async ({ browser }) => { + await browser.url("/"); + const status = await browser.execute(async () => { + const response = await fetch("/?online-check", { cache: "no-store" }); + return response.status; + }); + assert.equal(status, 200); +}); +``` + +## См. также {/* #see-also */} + +- [throttleCPU](./throttleCPU.mdx) +- [emulate](./emulate.mdx) +- [getPuppeteer](./getPuppeteer.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_captureDomSnapshot.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_captureDomSnapshot.mdx new file mode 100644 index 00000000..85560b93 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_captureDomSnapshot.mdx @@ -0,0 +1,81 @@ +# unstable_captureDomSnapshot + +## Обзор {/* #overview */} + +Создает компактное текстовое представление текущего документа или выбранного поддерева для диагностики. Чтобы снять документ с настройками, передайте объект настроек единственным аргументом. + +## Использование {/* #usage */} + +`browser.unstable_captureDomSnapshot(selector?, options?)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ----------------------- | ---------- | -------------------------- | ------------------------------------------------------------------------------------------- | +| `selector` | `string` | `body` / `documentElement` | Необязательный CSS-селектор корня снимка. Без него обход начинается с body документа. | +| `options.includeTags` | `string[]` | `[]` | Убрать имена из стандартного набора исключенных тегов; это не список разрешенных тегов. | +| `options.excludeTags` | `string[]` | `[]` | Исключить элементы вместе с поддеревьями; приоритет выше includeTags. | +| `options.includeAttrs` | `string[]` | `[]` | Добавить имена в набор сохраняемых атрибутов. | +| `options.excludeAttrs` | `string[]` | `[]` | Убрать имена из набора сохраняемых атрибутов; приоритет выше includeAttrs. | +| `options.truncateText` | `boolean` | `true` | Сокращать непосредственный текст элемента. Флаг не отключает сокращение значений атрибутов. | +| `options.maxTextLength` | `number` | `100` | Максимальная длина текста или значения атрибута до добавления `...`. | + +## Результат {/* #result */} + +`Promise` с четырьмя полями: + +- `snapshot`: компактный текст с отступами, а не HTML. +- `omittedTags`: отсортированные имена исключенных тегов, встретившихся при обходе. +- `omittedAttributes`: отсортированные имена пропущенных атрибутов сохраненных элементов. +- `textWasTruncated`: был ли сокращен текст или значение атрибута. + +Для отсутствующего CSS-совпадения возвращается `snapshot: "# Element not found: "`; для некорректного CSS — `"# Invalid selector: "`. В этих случаях ошибка селектора не выбрасывается. Списки описывают конкретный обход, а не всех пропущенных потомков. + +`maxTextLength` не ограничивает `id`, `class` и значения маркеров состояния, например `@value`. Исключенные `id` и `class` не перечисляются в `omittedAttributes`. + +## Ограничения {/* #limitations */} + +По умолчанию исключены `head`, `link`, `meta`, `noscript`, `script`, `slot`, `style`, `template` и `title`. Регистр имен тегов не учитывается. Сохраняются в том числе идентификаторы, классы, атрибуты форм, часть ARIA-атрибутов и тестовые атрибуты. Остальные добавляются через `includeAttrs`. Скрытые сохраненные элементы помечаются `@hidden`; пустые неинформативные элементы и дочерние узлы SVG могут быть пропущены. Это не полный DOM и не дерево доступности. + +Снимок может содержать значения полей ввода, текст страницы и чувствительные атрибуты. `excludeAttrs` — не универсальное средство удаления секретов: текущее значение input записывается также как `@value`. Используйте нечувствительные тестовые данные и проверяйте снимок перед добавлением в логи или отчеты. + +:::warning + +Это нестабильный API Testplane. Сигнатура, поведение и формат результата могут изменяться между версиями. + +::: + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("browser.unstable_captureDomSnapshot: capture a filtered subtree", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

abcdefghijk

Outside

'; + }); + const result = await browser.unstable_captureDomSnapshot("#panel", { + excludeAttrs: ["data-internal"], + maxTextLength: 6, + }); + assert.match(result.snapshot, /section#panel/); + assert.match(result.snapshot, /abcdef\.\.\./); + assert.doesNotMatch(result.snapshot, /Outside|data-internal/); + assert.ok(result.omittedTags.includes("script")); + assert.ok(result.omittedAttributes.includes("data-internal")); + assert.equal(result.textWasTruncated, true); + const missing = await browser.unstable_captureDomSnapshot("#missing"); + assert.equal(missing.snapshot, "# Element not found: #missing"); + const invalid = await browser.unstable_captureDomSnapshot("["); + assert.equal(invalid.snapshot, "# Invalid selector: ["); +}); +``` + +## См. также {/* #see-also */} + +- [element.unstable_captureDomSnapshot](../element/unstable_captureDomSnapshot.mdx) +- [execute](./execute.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_getCdp.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_getCdp.mdx new file mode 100644 index 00000000..12d3925b --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_getCdp.mdx @@ -0,0 +1,49 @@ +# unstable_getCdp + +## Обзор {/* #overview */} + +Возвращает существующее подключение Testplane к Chrome DevTools Protocol для расширенной диагностики. Это объект `CDP` Testplane, а не браузер Puppeteer или его `CDPSession`. + +## Использование {/* #usage */} + +`browser.unstable_getCdp()` + +## Параметры {/* #parameters */} + +У команды нет параметров. + +## Результат {/* #result */} + +`Promise`. Возвращает `null` для не-Chromium-сессии или если Testplane не смог создать CDP-подключение. + +## Ограничения {/* #limitations */} + +Проверяйте результат на `null`. `cdp.request(method, params?, cdpSessionId?)` отправляет протокольный запрос; командам страницы может потребоваться идентификатор сессии подключенной цели. Доступны вспомогательные объекты `target`, `runtime`, `network`, `page`, `dom`, `css`, `fetch`, `debugger` и `profiler`. Их контракты также нестабильны. Не вызывайте `cdp.close()`: это общее подключение принадлежит Testplane и используется им. Не меняйте состояние доменов, занятых раннером, без понимания взаимодействия. + +:::warning + +Это нестабильный API Testplane. Сигнатура, поведение и формат результата могут изменяться между версиями. + +::: + +## Пример {/* #examples */} + +Запустите тест в Chromium-браузере с доступным CDP-подключением. Он читает данные о версии браузера, не меняет страницу и не закрывает общее подключение. + +```javascript +const assert = require("node:assert/strict"); + +it("unstable_getCdp: query the connected Chromium browser", async ({ browser }) => { + const cdp = await browser.unstable_getCdp(); + assert.ok(cdp, "This test requires a Chromium session with a CDP connection"); + const version = await cdp.request("Browser.getVersion"); + assert.match(version.product, /Chrome/); + assert.equal(typeof version.protocolVersion, "string"); + // Testplane owns this connection: do not call cdp.close(). +}); +``` + +## См. также {/* #see-also */} + +- [getPuppeteer](./getPuppeteer.mdx) +- [execute](./execute.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/url.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/url.mdx index 74b1c782..3f1cdda2 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/url.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/url.mdx @@ -2,58 +2,74 @@ ## Обзор {/* #overview */} -Используйте команду `url`, чтобы перейти по заданному URL-адресу в браузере. +Открывает URL. В Testplane вызов без аргумента или с пустой строкой возвращает текущий URL и не выполняет переход. Повторный переход на тот же адрес перезагружает страницу. -Если в конфигурации указан [baseUrl][base-url], то он будет добавлен к параметру `url` с помощью метода Node.JS [url.resolve()][url-resolve]. - -Вызов `browser.url()` с тем же URL-адресом, что и в прошлый раз, вызовет перезагрузку страницы. +Если задан [baseUrl](../../reference/config/browsers.mdx#base_url), адрес разрешается через `url.resolve(baseUrl, url)`. Например, при `baseUrl` равном `http://example.com/site/` адрес `relative` откроет `/site/relative`, а `/rootRelative` — `/rootRelative`. Полный URL не зависит от `baseUrl`. Завершающий `/` в базовом адресе влияет на разрешение относительного пути. ## Использование {/* #usage */} -```javascript -await browser.url(url); -``` +`await browser.url(url, options)`; `await browser.url()` ## Параметры команды {/* #parameters */} - - - - - - +| Параметр | Описание | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `url` | Строка с полным или относительным адресом. Без аргумента — чтение текущего адреса. | +| `options.wait` | BiDi: `none`, `interactive`, `complete` или `networkIdle`. Без значения используется стратегия загрузки сессии; для обычной стратегии — `complete`. Ограничение `networkIdle` описано ниже. | +| `options.timeout` | BiDi: тайм-аут только ожидания `networkIdle`, по умолчанию `5000` мс. Не заменяет тайм-ауты Testplane и загрузки страницы. | +| `options.headers` | BiDi: объект заголовков для запроса навигации. Не задает постоянные заголовки для всех ресурсов страницы. | +| `options.auth` | BiDi: объект `{ user, pass }` для HTTP Basic Auth. Заменяет `Authorization` из `headers`. Используйте HTTPS и не сохраняйте секреты в тестах. | +| `options.onBeforeLoad` | BiDi: функция, выполняемая до скриптов нового документа. Функция сериализуется и не имеет доступа к замыканию Node.js. | - -
**Имя****Тип****Описание**
urlStringURL, который надо открыть.
+## Ограничения {/* #limitations */} -## Примеры использования {/* #examples */} +Опции применяются только к HTTP(S)-переходам в BiDi. В Classic обертка Testplane игнорирует второй аргумент. После успешного перехода временный init-скрипт удаляется; его изменения в загруженной странице остаются до следующей навигации. -**url.js** +В проверенной связке Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150 режим `networkIdle` падает с `Couldn't find request for navigation with id ...`: реализация ищет запрос навигации по идентификатору контекста. Используйте `complete` и затем явно ожидайте нужное состояние приложения. `complete` само по себе не означает завершения фоновых запросов приложения. -```javascript -// переходим на URL -await browser.url("https://webdriver.io"); +## Результат {/* #result */} -// получаем url -console.log(await browser.getUrl()); // выведет: "https://webdriver.io" -``` +Вызов без адреса возвращает строку текущего URL. Переход в Classic возвращает `undefined`; в BiDi может вернуть данные запроса навигации или `undefined`. Для чтения адреса используйте `getUrl()` или `url()` без аргумента. + +## Пример {/* #examples */} -**baseUrlResolutions.js** +Пример использует Chrome/BiDi и тестовый HTTP-сервер в `baseUrl`. На `/navigation` скрипт страницы записывает `window.initialSeed = window.navigationSeed ?? null`, а сервер встраивает значение заголовка `x-example` в `window.receivedHeader`. Корневой путь отдает обычную HTML-страницу. ```javascript -// Пусть baseUrl = http://example.com/site/ важно указывать слэш в конце +const assert = require("node:assert/strict"); + +it("url: navigate with BiDi options and read the current URL", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/navigation", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + await browser.url(target, { + wait: "complete", + headers: { "x-example": "navigation" }, + onBeforeLoad: () => { + window.navigationSeed = 42; + }, + }); + assert.equal(await browser.url(), target); + assert.deepEqual(await browser.execute(() => [window.initialSeed, window.receivedHeader]), [ + 42, + "navigation", + ]); + await browser.execute(() => { + window.unsavedValue = "temporary"; + }); + await browser.url(target); + assert.equal(await browser.execute(() => typeof window.unsavedValue), "undefined"); + assert.equal(await browser.execute(() => window.initialSeed), null); +}); +``` -// При указании полного URL-адреса итоговый URL будет https://webdriver.io -await browser.url("https://webdriver.io"); +## Связанные команды {/* #related */} -// При указании URL-адреса без начального слэша -// итоговый URL формируется относительно всего baseUrl: http://example.com/site/relative -await browser.url("relative"); +- [addInitScript](./addInitScript.mdx) +- [waitUntil](./waitUntil.mdx) -// При указании URL-адреса с начальным слэшом -// итоговый URL формируется относительно корня baseUrl: http://example.com/rootRelative -await browser.url("/rootRelative"); -``` +## Источники {/* #references */} -[base-url]: ../../../reference/config/browsers#base_url -[url-resolve]: https://nodejs.org/api/url.html#urlresolvefrom-to +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/browser/url). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollar.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollar.mdx new file mode 100644 index 00000000..541e5e6d --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollar.mdx @@ -0,0 +1,50 @@ +# custom$ + +## Обзор {/* #overview */} + +Находит элемент пользовательской стратегией поиска. Родительский элемент передается последним аргументом стратегии. Чтобы ограничить область поиска, используйте его внутри стратегии. + +## Использование {/* #usage */} + +`element.custom$(strategyName, ...strategyArguments)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------- | ----------- | ---------------- | ----------------------------------------------------------- | +| `strategyName` | `string` | Обязательный | Имя, зарегистрированное через `browser.addLocatorStrategy`. | +| `strategyArguments` | `unknown[]` | [] | Аргументы, передаваемые перед родительским DOM-элементом. | + +## Результат {/* #result */} + +Объект элемента, получаемый через `await`. Если стратегия вернула массив, используется первый элемент. У результата без совпадения нет `elementId`. + +## Ограничения {/* #limitations */} + +Зарегистрируйте стратегию у того же объекта браузера. Родитель должен существовать; неизвестная стратегия или отсутствие родителя вызывают ошибку. Функция выполняется в браузере и возвращает DOM-элемент или массив DOM-элементов. Преобразуйте `NodeList` через `Array.from`. Область поиска определяет сама функция: `document.querySelector` продолжает искать по всему документу. Команда не ожидает заданного количества дочерних элементов или их видимости. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("element.custom$: search inside an element", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '
'; + }); + browser.addLocatorStrategy("byTestWithin", (value, root) => + root.querySelector(`[data-test="${value}"]`), + ); + const button = await browser.$("#panel").custom$("byTestWithin", "save"); + assert.equal(await button.getText(), "Inside"); +}); +``` + +## См. также {/* #see-also */} + +- [addLocatorStrategy](../browser/addLocatorStrategy.mdx) +- [browser.custom$](../browser/customDollar.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollarDollar.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollarDollar.mdx new file mode 100644 index 00000000..87bdd47a --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollarDollar.mdx @@ -0,0 +1,51 @@ +# custom$$ + +## Обзор {/* #overview */} + +Находит элементы пользовательской стратегией поиска. Родительский элемент передается последним аргументом стратегии. Чтобы ограничить область поиска, используйте его внутри стратегии. + +## Использование {/* #usage */} + +`element.custom$$(strategyName, ...strategyArguments)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------- | ----------- | ---------------- | ----------------------------------------------------------- | +| `strategyName` | `string` | Обязательный | Имя, зарегистрированное через `browser.addLocatorStrategy`. | +| `strategyArguments` | `unknown[]` | [] | Аргументы, передаваемые перед родительским DOM-элементом. | + +## Результат {/* #result */} + +Массив объектов элементов, получаемый через `await`. Если стратегия ничего не нашла, массив пуст. + +## Ограничения {/* #limitations */} + +Зарегистрируйте стратегию у того же объекта браузера. Родитель должен существовать; неизвестная стратегия или отсутствие родителя вызывают ошибку. Функция выполняется в браузере и возвращает DOM-элемент или массив DOM-элементов. Преобразуйте `NodeList` через `Array.from`. Область поиска определяет сама функция: `document.querySelector` продолжает искать по всему документу. Команда не ожидает заданного количества дочерних элементов или их видимости. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("element.custom$$: find children with a custom strategy", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Outside

First

Second

'; + }); + browser.addLocatorStrategy("byTestWithin", (value, root) => + Array.from(root.querySelectorAll(`[data-test="${value}"]`)), + ); + const items = await browser.$("#panel").custom$$("byTestWithin", "item"); + assert.equal(items.length, 2); + assert.equal(await items[1].getText(), "Second"); +}); +``` + +## См. также {/* #see-also */} + +- [addLocatorStrategy](../browser/addLocatorStrategy.mdx) +- [browser.custom$$](../browser/customDollarDollar.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/execute.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/execute.mdx new file mode 100644 index 00000000..50db35d3 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/execute.mdx @@ -0,0 +1,52 @@ +# execute + +## Обзор {/* #overview */} + +Выполняет JavaScript в браузере и передает выбранный DOM-элемент первым аргументом. Используйте метод для чтения или изменения свойств DOM, для которых нет отдельной команды. + +## Использование {/* #usage */} + +`element.execute(script, ...args)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | -------------------- | ---------------- | --------------------------------------------------- | +| `script` | `Function \| string` | Обязательный | Функция или тело функции для выполнения в браузере. | +| `args` | `unknown[]` | [] | Дополнительные аргументы после DOM-элемента. | + +## Результат {/* #result */} + +Промис с сериализованным результатом функции. В режиме BiDi выполнение также ожидает промис, возвращенный функцией. + +## Ограничения {/* #limitations */} + +Перед выполнением команда вызывает `waitForExist`. Код браузера не имеет доступа к переменным и импортам Node.js через замыкание; передавайте данные аргументами. Ожидание существования не гарантирует видимость или кликабельность. Асинхронный пример ниже использует WebDriver BiDi. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("element.execute: pass the DOM element to a browser function", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
'; + }); + const result = await browser.$("#counter").execute(async (element, increment) => { + const count = Number(element.dataset.count) + increment; + element.dataset.count = String(count); + return { count, id: element.id }; + }, 3); + assert.deepEqual(result, { count: 7, id: "counter" }); + assert.equal(await browser.$("#counter").getAttribute("data-count"), "7"); +}); +``` + +## См. также {/* #see-also */} + +- [browser.execute](../browser/execute.mdx) +- [executeAsync](./executeAsync.mdx) +- [waitForExist](./waitForExist.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/executeAsync.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/executeAsync.mdx new file mode 100644 index 00000000..ace256e8 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/executeAsync.mdx @@ -0,0 +1,57 @@ +# executeAsync + +## Обзор {/* #overview */} + +Выполняет браузерный скрипт с callback. DOM-элемент передается первым аргументом, а callback для передачи результата в тест — последним. + +## Использование {/* #usage */} + +`element.executeAsync(script, ...args)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | -------------------- | ---------------- | -------------------------------------------------------------------------------- | +| `script` | `Function \| string` | Обязательный | Функция или тело функции. Для завершения вызовите последний аргумент — callback. | +| `args` | `unknown[]` | [] | Аргументы между DOM-элементом и callback. | + +## Результат {/* #result */} + +Промис со значением, переданным в callback. Возврат значения из скрипта не заменяет вызов callback. + +## Ограничения {/* #limitations */} + +В отличие от `element.execute`, эта реализация не вызывает `waitForExist` явно; дождитесь элемента самостоятельно. В Classic-сессиях тайм-аут протокольного скрипта задается через `browser.setTimeout({ script: milliseconds })`. Ветка BiDi использует `script.callFunction` без отдельного таймера callback: не полагайтесь на тайм-аут Classic, если callback не был вызван. Всегда вызывайте callback и задавайте конечный `testTimeout` в Testplane. + +:::warning + +В поставляемом API WebdriverIO метод устарел. Для новых BiDi-тестов используйте `element.execute` с асинхронной функцией. + +::: + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("element.executeAsync: return data through the final callback", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '

Ready

'; + }); + const element = await browser.$("#message"); + await element.waitForExist(); + await browser.setTimeout({ script: 5000 }); + const result = await element.executeAsync((node, prefix, done) => { + setTimeout(() => done(prefix + node.textContent), 20); + }, "Status: "); + assert.equal(result, "Status: Ready"); +}); +``` + +## См. также {/* #see-also */} + +- [execute](./execute.mdx) +- [setTimeout](../browser/setTimeout.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/getElement.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/getElement.mdx new file mode 100644 index 00000000..ed17bfdc --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/getElement.mdx @@ -0,0 +1,45 @@ +# getElement + +## Обзор {/* #overview */} + +Получает объект элемента из цепочки команд для чтения свойств `selector`, `elementId` и других данных. + +## Использование {/* #usage */} + +`element.getElement()` + +## Параметры {/* #parameters */} + +У команды нет параметров. + +## Результат {/* #result */} + +`Promise`: текущий объект элемента с методами и метаданными. + +## Ограничения {/* #limitations */} + +Это не новый протокольный поиск, не снимок DOM и не ожидание видимости. Метод возвращает текущий объект элемента. Не считайте, что его `elementId` остается действительным после навигации или замены DOM. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("getElement: inspect a resolved element object", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + const element = await browser.$("#save").getElement(); + assert.equal(element.selector, "#save"); + assert.equal(typeof element.elementId, "string"); + assert.equal(await element.getText(), "Save"); +}); +``` + +## См. также {/* #see-also */} + +- [$](../browser/$.mdx) +- [isExisting](./isExisting.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/isStable.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/isStable.mdx new file mode 100644 index 00000000..89689043 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/isStable.mdx @@ -0,0 +1,57 @@ +# isStable + +## Обзор {/* #overview */} + +Проверяет, остается ли геометрия элемента неизменной между двумя кадрами анимации. Метод обнаруживает изменение положения и размера, а не любую активность страницы. + +## Использование {/* #usage */} + +`element.isStable()` + +## Параметры {/* #parameters */} + +У команды нет параметров. + +## Результат {/* #result */} + +`Promise`: `true`, если сравниваемые свойства прямоугольника совпадают, иначе `false`. + +## Ограничения {/* #limitations */} + +Проверяйте существующий элемент на активной вкладке. Скрытый документ вызывает ошибку: кадры анимации в нем не выполняются обычным образом. Прозрачность, цвет и сетевые запросы могут меняться без изменения геометрии. Результат `true` — измерение в данный момент, а не гарантия окончания всех анимаций. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("isStable: check whether element geometry changes", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
Box
'; + }); + const box = await browser.$("#box"); + await box.waitForExist(); + assert.equal(await box.isStable(), true); + await box.execute(node => { + node.animate([{ transform: "translateX(0)" }, { transform: "translateX(200px)" }], { + duration: 500, + iterations: Infinity, + direction: "alternate", + }); + }); + try { + assert.equal(await box.isStable(), false); + } finally { + await box.execute(node => node.getAnimations().forEach(animation => animation.cancel())); + } + assert.equal(await box.isStable(), true); +}); +``` + +## См. также {/* #see-also */} + +- [waitForStable](./waitForStable.mdx) +- [waitForExist](./waitForExist.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/unstable_captureDomSnapshot.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/unstable_captureDomSnapshot.mdx new file mode 100644 index 00000000..ec469abc --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/unstable_captureDomSnapshot.mdx @@ -0,0 +1,55 @@ +# unstable_captureDomSnapshot + +## Обзор {/* #overview */} + +Создает компактный снимок DOM с выбранным элементом в корне. Содержимое за пределами элемента в снимок не входит. + +## Использование {/* #usage */} + +`element.unstable_captureDomSnapshot(options?)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | ------------------------ | ---------------- | ------------------------------------------------------------------ | +| `options` | `CaptureSnapshotOptions` | `{}` | Те же параметры фильтрации и сокращения, что у браузерной команды. | + +## Результат {/* #result */} + +`Promise` с полями `snapshot`, `omittedTags`, `omittedAttributes` и `textWasTruncated`. См. [общий формат результата](../browser/unstable_captureDomSnapshot.mdx#result). + +## Ограничения {/* #limitations */} + +Использует `element.execute` с ожиданием существования элемента. Фильтрация, сокращение и ограничения обработки чувствительных данных совпадают с [браузерной командой](../browser/unstable_captureDomSnapshot.mdx#limitations). В частности, `truncateText: false` не отключает сокращение атрибутов, а значения input не очищаются от секретов. Метод принимает настройки, а не дополнительный CSS-селектор. + +:::warning + +Это нестабильный API Testplane. Сигнатура, поведение и формат результата могут изменяться между версиями. + +::: + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("element.unstable_captureDomSnapshot: capture only this element", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Outside

'; + }); + const result = await browser.$("#panel").unstable_captureDomSnapshot({ truncateText: false }); + assert.match(result.snapshot, /section#panel/); + assert.match(result.snapshot, /button\[data-test=save\] "Save"/); + assert.doesNotMatch(result.snapshot, /Outside/); + assert.equal(result.textWasTruncated, false); +}); +``` + +## См. также {/* #see-also */} + +- [browser.unstable_captureDomSnapshot](../browser/unstable_captureDomSnapshot.mdx) +- [execute](./execute.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/waitForStable.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/waitForStable.mdx new file mode 100644 index 00000000..987cbbb3 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/element/waitForStable.mdx @@ -0,0 +1,54 @@ +# waitForStable + +## Обзор {/* #overview */} + +Повторяет `isStable`, пока геометрия элемента не станет стабильной. С `reverse` ожидает обратного условия — изменения геометрии. + +## Использование {/* #usage */} + +`element.waitForStable({ timeout, interval, reverse, timeoutMsg })` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | --------- | ----------------------- | ------------------------------------------- | +| `timeout` | `number` | `waitTimeout` (3000 ms) | Предельное время ожидания в миллисекундах. | +| `interval` | `number` | `waitInterval` (500 ms) | Интервал проверок в миллисекундах. | +| `reverse` | `boolean` | `false` | Ожидать результата `false` от `isStable()`. | +| `timeoutMsg` | `string` | — | Сообщение ошибки по тайм-ауту. | + +## Результат {/* #result */} + +`Promise`. Успешное завершение возвращает `undefined`, а не `true`. Тайм-аут или ошибка проверки стабильности отклоняют промис. + +## Ограничения {/* #limitations */} + +Значения тайм-аутов берутся из конфигурации браузера Testplane. Действуют ограничения `isStable`: активная вкладка и проверка только геометрии. Если элемент появится позже, сначала дождитесь его существования. Команда не ожидает окончания HTTP-запросов или анимаций, не затрагивающих геометрию. + +## Пример {/* #examples */} + +Настройте `baseUrl` на локальный HTTP-сайт, где `/` возвращает HTML-страницу. Запустите пример в файле тестов Testplane. + +```javascript +const assert = require("node:assert/strict"); + +it("waitForStable: wait for a size animation to stop", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
Box
'; + }); + const box = await browser.$("#box"); + await box.execute(node => { + node.animate([{ width: "40px" }, { width: "140px" }], { duration: 500, fill: "forwards" }); + }); + const result = await box.waitForStable({ timeout: 3000, interval: 50 }); + assert.equal(result, undefined); + assert.equal(await box.execute(node => node.getBoundingClientRect().width), 140); +}); +``` + +## См. также {/* #see-also */} + +- [isStable](./isStable.mdx) +- [waitForExist](./waitForExist.mdx) +- [waitUntil](../browser/waitUntil.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abort.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abort.mdx index 7c5aeaeb..c460a622 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abort.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abort.mdx @@ -2,47 +2,60 @@ ## Обзор {/* #overview */} -Используйте команду `abort`, чтобы прервать запрос с заданной ошибкой. +Прерывает подходящие запросы, пока правило активно. Сигнатуры BiDi и Classic/CDP различаются. В BiDi не передавайте строку с кодом ошибки: она не задает сетевую ошибку и может изменить однократность правила. -Запрос можно прервать с одной из следующих ошибок: +## Использование {/* #usage */} -- Failed -- Aborted -- TimedOut -- AccessDenied -- ConnectionClosed | ConnectionReset | ConnectionRefused | ConnectionAborted | ConnectionFailed -- NameNotResolved -- InternetDisconnected -- AddressUnreachable -- BlockedByClient | BlockedByResponse +**BiDi:** `mock.abort(once = false)` -## Использование {/* #usage */} +**Classic/CDP:** `mock.abort(errorReason, sticky = true)` -```javascript -mock.abort(errorCode); -``` +## Параметры и результат {/* #parameters */} -## Параметры команды {/* #parameters */} +BiDi: `once` — boolean, по умолчанию `false`; для одного запроса удобнее [abortOnce](./abortOnce.mdx). Classic/CDP: `errorReason` задает ошибку, `sticky` по умолчанию `true` сохраняет правило. - - - - - - +Коды ошибки Classic/CDP: `Failed`, `Aborted`, `TimedOut`, `AccessDenied`, `ConnectionClosed`, `ConnectionReset`, `ConnectionRefused`, `ConnectionAborted`, `ConnectionFailed`, `NameNotResolved`, `InternetDisconnected`, `AddressUnreachable`, `BlockedByClient`, `BlockedByResponse`. - -
**Имя****Тип****Описание**
errorCodeErrorCodeКод ошибки ответа. Возможные значения см. [выше](#overview).
+BiDi возвращает тот же объект мока синхронно; Classic/CDP не возвращает полезного значения. Правила BiDi делят очередь с [request](./request.mdx) и [redirect](./redirect.mdx). Постоянное правило заменяет очередь, однократное сохраняет другие однократные правила и убирает постоянное. В CDP правила добавляются в очередь: ранее добавленное постоянное правило может не дать примениться следующему. -## Примеры использования {/* #examples */} +## Пример {/* #examples */} + +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. ```javascript -it("should block Google Analytics from page", async ({ browser }) => { - const mock = await browser.mock("https://www.google-analytics.com/**"); - mock.abort("Failed"); +const assert = require("node:assert/strict"); + +it("mock.abort: block matching BiDi requests", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.abort(); + for (let i = 0; i < 2; i++) { + const failed = await browser.execute(async () => { + try { + await fetch("/api/items", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); + } + } finally { + await mock.restore(); + } }); ``` ## Связанные команды {/* #related */} -- [abortOnce](../abortOnce) +- [abortOnce](./abortOnce.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) + +## Источники {/* #references */} + +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/mock/abort). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abortOnce.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abortOnce.mdx index c3b10165..01610fc0 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abortOnce.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abortOnce.mdx @@ -2,56 +2,62 @@ ## Обзор {/* #overview */} -Используйте команду `abortOnce`, чтобы прервать запрос один раз с заданной ошибкой. +Прерывает один подходящий запрос. Сигнатуры BiDi и Classic/CDP различаются. В BiDi не передавайте строку с кодом ошибки: она не задает сетевую ошибку и может изменить однократность правила. -Запрос можно прервать с одной из следующих ошибок: +## Использование {/* #usage */} -- Failed -- Aborted -- TimedOut -- AccessDenied -- ConnectionClosed | ConnectionReset | ConnectionRefused | ConnectionAborted | ConnectionFailed -- NameNotResolved -- InternetDisconnected -- AddressUnreachable -- BlockedByClient | BlockedByResponse +**BiDi:** `mock.abortOnce()` -## Использование {/* #usage */} +**Classic/CDP:** `mock.abortOnce(errorReason)` -```javascript -mock.abortOnce(errorCode); -``` +## Параметры и результат {/* #parameters */} -## Параметры команды {/* #parameters */} +BiDi не принимает аргументов. В Classic/CDP нужен `errorReason`. - - - - - - +Коды ошибки Classic/CDP: `Failed`, `Aborted`, `TimedOut`, `AccessDenied`, `ConnectionClosed`, `ConnectionReset`, `ConnectionRefused`, `ConnectionAborted`, `ConnectionFailed`, `NameNotResolved`, `InternetDisconnected`, `AddressUnreachable`, `BlockedByClient`, `BlockedByResponse`. - -
**Имя****Тип****Описание**
errorCodeErrorCodeКод ошибки ответа. Возможные значения см. [выше](#overview).
+BiDi возвращает тот же объект мока синхронно; Classic/CDP не возвращает полезного значения. Правила BiDi делят очередь с [request](./request.mdx) и [redirect](./redirect.mdx). Постоянное правило заменяет очередь, однократное сохраняет другие однократные правила и убирает постоянное. В CDP правила добавляются в очередь: ранее добавленное постоянное правило может не дать примениться следующему. -## Примеры использования {/* #examples */} +## Пример {/* #examples */} + +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. ```javascript -it("should block mock only once", async ({ browser }) => { - const mock = await browser.mock("https://webdriver.io"); - mock.abortOnce("Failed"); - - await browser - .url("https://webdriver.io") - // ловим исключение, так как запрос к странице упадет - .catch(() => console.log('Failed to get the page "https://webdriver.io"')); - - await browser.url("https://webdriver.io"); - console.log(await browser.getTitle()); - // выведет: "WebdriverIO · Next-gen browser and mobile automation test framework for Node.js" +const assert = require("node:assert/strict"); + +it("mock.abortOnce: block one BiDi request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.abortOnce(); + const failed = await browser.execute(async () => { + try { + await fetch("/api/items", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); + const status = await browser.execute( + async () => (await fetch("/api/items", { cache: "no-store" })).status, + ); + assert.equal(status, 200); + } finally { + await mock.restore(); + } }); ``` ## Связанные команды {/* #related */} -- [abort](../abort) +- [abort](./abort.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) + +## Источники {/* #references */} + +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/mock/abortOnce). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/clear.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/clear.mdx index 20a005a9..33c552ef 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/clear.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/clear.mdx @@ -2,27 +2,57 @@ ## Обзор {/* #overview */} -Используйте команду `clear`, чтобы сбросить всю информацию, хранящуюся в массиве `mock.calls`. +Очищает историю `mock.calls`, но сохраняет перехватчик, правила подмены и обработчики событий. Следующий подходящий ответ снова добавится в историю. Для удаления правил без удаления BiDi-мока используйте [reset](./reset.mdx); для полного удаления — [restore](./restore.mdx). ## Использование {/* #usage */} -```javascript -mock.clear(); -``` +`await mock.clear()` -## Примеры использования {/* #examples */} +## Параметры и результат {/* #parameters */} -```javascript -it("should clear mock", async ({ browser }) => { - const mock = await browser.mock("https://google.com/"); - await browser.url("https://google.com"); +Аргументов нет. BiDi синхронно возвращает тот же мок, Classic/CDP не возвращает полезного значения. В примере используется `await`, что также подходит для асинхронных реализаций. Очистите историю перед [waitForResponse](./waitForResponse.mdx), если нужно дождаться нового ответа, а не ранее сохраненного. + +## Пример {/* #examples */} - console.log(mock.calls.length); // выведет: 1 - mock.clear(); - console.log(mock.calls.length); // выведет: 0 +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.clear: clear history but retain response rules", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond({ source: "mocked" }); + await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + await mock.clear(); + assert.equal(mock.calls.length, 0); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "mocked"); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + } finally { + await mock.restore(); + } }); ``` ## Связанные команды {/* #related */} -- [restore](../restore) +- [clear](./clear.mdx) +- [reset](./reset.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) + +## Источники {/* #references */} + +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/mock/clear). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirect.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirect.mdx new file mode 100644 index 00000000..efc66c07 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirect.mdx @@ -0,0 +1,60 @@ +# redirect + +## Обзор {/* #overview */} + +Меняет URL назначения подходящих исходящих запросов. Это переопределение URL, а не HTTP-ответ 3xx или навигация браузера. + +## Использование {/* #usage */} + +`mock.redirect(redirectUrl, once = false)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------- | --------- | ---------------- | -------------------------------------------------- | +| `redirectUrl` | `string` | Обязательный | Абсолютный URL назначения. | +| `once` | `boolean` | `false` | Применить только к следующему подходящему запросу. | + +## Результат {/* #result */} + +Тот же объект мока синхронно. + +## Ограничения {/* #limitations */} + +Нужна WebDriver BiDi-сессия (`webSocketUrl: true`): в Classic/CDP-реализации мока этого метода нет. Примеры явно подписываются на сетевые события в каждой сессии; см. [ограничение подписки](./waitForResponse.mdx#limitations). Когда мок больше не нужен, вызовите `mock.restore()` в `finally`. + +Адрес назначения должен быть доступен. Перенаправления используют общую очередь с `request` и `abort`: постоянное перенаправление заменяет очередь, а однократное удаляет предыдущее постоянное правило. После однократного перенаправления запросы отправляются без изменения, если в очереди нет других однократных правил. + +## Пример {/* #examples */} + +Укажите в `baseUrl` локальный HTTP-сайт. `/` возвращает HTML. `/api/items` возвращает JSON: `source: "original"`, метод запроса в `method` и заголовок запроса `x-example` в `header` (`null`, если заголовка нет). `/api/other` возвращает те же поля с `source: "redirected"`. Примеры выполняют настоящие запросы к этим обработчикам. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.redirect: send matching requests to another endpoint", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/api/other", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.redirect(target); + for (let i = 0; i < 2; i++) { + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.source, "redirected"); + } + } finally { + await mock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [redirectOnce](./redirectOnce.mdx) +- [request](./request.mdx) +- [reset](./reset.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirectOnce.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirectOnce.mdx new file mode 100644 index 00000000..124a331c --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirectOnce.mdx @@ -0,0 +1,61 @@ +# redirectOnce + +## Обзор {/* #overview */} + +Меняет URL только у следующего подходящего запроса. Эквивалент `mock.redirect(redirectUrl, true)`. + +## Использование {/* #usage */} + +`mock.redirectOnce(redirectUrl)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------- | -------- | ---------------- | -------------------------- | +| `redirectUrl` | `string` | Обязательный | Абсолютный URL назначения. | + +## Результат {/* #result */} + +Тот же объект мока синхронно. + +## Ограничения {/* #limitations */} + +Нужна WebDriver BiDi-сессия (`webSocketUrl: true`): в Classic/CDP-реализации мока этого метода нет. Примеры явно подписываются на сетевые события в каждой сессии; см. [ограничение подписки](./waitForResponse.mdx#limitations). Когда мок больше не нужен, вызовите `mock.restore()` в `finally`. + +Адрес назначения должен быть доступен. Перенаправления используют общую очередь с `request` и `abort`: постоянное перенаправление заменяет очередь, а однократное удаляет предыдущее постоянное правило. После однократного перенаправления запросы отправляются без изменения, если в очереди нет других однократных правил. + +## Пример {/* #examples */} + +Укажите в `baseUrl` локальный HTTP-сайт. `/` возвращает HTML. `/api/items` возвращает JSON: `source: "original"`, метод запроса в `method` и заголовок запроса `x-example` в `header` (`null`, если заголовка нет). `/api/other` возвращает те же поля с `source: "redirected"`. Примеры выполняют настоящие запросы к этим обработчикам. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.redirectOnce: redirect only the next request", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/api/other", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.redirectOnce(target); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.source, "redirected"); + assert.equal(second.source, "original"); + } finally { + await mock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [redirect](./redirect.mdx) +- [request](./request.mdx) +- [reset](./reset.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/request.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/request.mdx new file mode 100644 index 00000000..f8af58f5 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/request.mdx @@ -0,0 +1,65 @@ +# request + +## Обзор {/* #overview */} + +Изменяет подходящие исходящие запросы до отправки на сервер: заголовки, метод, тело, cookie или URL. + +## Использование {/* #usage */} + +`mock.request(overwrite, once = false)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------- | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------- | +| `overwrite` | `object` | Обязательный | Поля запроса для переопределения. | +| `overwrite.body` | `unknown \| Function` | — | Тело запроса: строки передаются как текст, объекты — как JSON. Ложные значения тела в этой версии игнорируются. | +| `overwrite.cookies` | `Cookie[] \| Function` | — | Массив cookie в формате WebDriver. | +| `overwrite.headers` | `Record \| Function` | — | Новый набор заголовков, а не дополнение исходного. | +| `overwrite.method` | `string \| Function` | — | HTTP-метод, например `POST`. | +| `overwrite.url` | `string \| Function` | — | Абсолютный URL назначения. | +| `once` | `boolean` | `false` | Применить правило только к следующему подходящему запросу. | + +## Результат {/* #result */} + +Тот же объект мока синхронно, для цепочки вызовов. + +## Ограничения {/* #limitations */} + +Нужна WebDriver BiDi-сессия (`webSocketUrl: true`): в Classic/CDP-реализации мока этого метода нет. Примеры явно подписываются на сетевые события в каждой сессии; см. [ограничение подписки](./waitForResponse.mdx#limitations). Когда мок больше не нужен, вызовите `mock.restore()` в `finally`. + +Переопределение меняет исходящий запрос, а не создает поддельный ответ. Поле может быть значением или синхронной функцией от события BiDi (URL находится в `event.request.url`). Переопределения запросов, перенаправления и прерывания используют общую очередь правил. Постоянное правило заменяет очередь; однократное сохраняет другие однократные правила, но удаляет постоянное. Не рассчитывайте на возврат к постоянному правилу после однократного. + +## Пример {/* #examples */} + +Укажите в `baseUrl` локальный HTTP-сайт. `/` возвращает HTML. `/api/items` возвращает JSON: `source: "original"`, метод запроса в `method` и заголовок запроса `x-example` в `header` (`null`, если заголовка нет). `/api/other` возвращает те же поля с `source: "redirected"`. Примеры выполняют настоящие запросы к этим обработчикам. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.request: change outgoing requests", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.request({ method: "POST", headers: { "x-example": "persistent" } }); + for (let i = 0; i < 2; i++) { + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.method, "POST"); + assert.equal(data.header, "persistent"); + } + } finally { + await mock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [requestOnce](./requestOnce.mdx) +- [respond](./respond.mdx) +- [reset](./reset.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/requestOnce.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/requestOnce.mdx new file mode 100644 index 00000000..078a7b4b --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/requestOnce.mdx @@ -0,0 +1,65 @@ +# requestOnce + +## Обзор {/* #overview */} + +Изменяет только следующий подходящий исходящий запрос. Эквивалент `mock.request(overwrite, true)`. + +## Использование {/* #usage */} + +`mock.requestOnce(overwrite)` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------------- | ------------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------- | +| `overwrite` | `object` | Обязательный | Поля запроса для переопределения. | +| `overwrite.body` | `unknown \| Function` | — | Тело запроса: строки передаются как текст, объекты — как JSON. Ложные значения тела в этой версии игнорируются. | +| `overwrite.cookies` | `Cookie[] \| Function` | — | Массив cookie в формате WebDriver. | +| `overwrite.headers` | `Record \| Function` | — | Новый набор заголовков, а не дополнение исходного. | +| `overwrite.method` | `string \| Function` | — | HTTP-метод, например `POST`. | +| `overwrite.url` | `string \| Function` | — | Абсолютный URL назначения. | + +## Результат {/* #result */} + +Тот же объект мока синхронно, для цепочки вызовов. + +## Ограничения {/* #limitations */} + +Нужна WebDriver BiDi-сессия (`webSocketUrl: true`): в Classic/CDP-реализации мока этого метода нет. Примеры явно подписываются на сетевые события в каждой сессии; см. [ограничение подписки](./waitForResponse.mdx#limitations). Когда мок больше не нужен, вызовите `mock.restore()` в `finally`. + +Переопределение меняет исходящий запрос, а не создает поддельный ответ. Поле может быть значением или синхронной функцией от события BiDi (URL находится в `event.request.url`). Переопределения запросов, перенаправления и прерывания используют общую очередь правил. Постоянное правило заменяет очередь; однократное сохраняет другие однократные правила, но удаляет постоянное. Не рассчитывайте на возврат к постоянному правилу после однократного. + +## Пример {/* #examples */} + +Укажите в `baseUrl` локальный HTTP-сайт. `/` возвращает HTML. `/api/items` возвращает JSON: `source: "original"`, метод запроса в `method` и заголовок запроса `x-example` в `header` (`null`, если заголовка нет). `/api/other` возвращает те же поля с `source: "redirected"`. Примеры выполняют настоящие запросы к этим обработчикам. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.requestOnce: change only the next request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.requestOnce({ headers: { "x-example": "once" } }); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.header, "once"); + assert.equal(second.header, null); + } finally { + await mock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [request](./request.mdx) +- [respond](./respond.mdx) +- [reset](./reset.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/reset.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/reset.mdx new file mode 100644 index 00000000..298c7107 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/reset.mdx @@ -0,0 +1,74 @@ +# reset + +## Обзор {/* #overview */} + +Очищает историю вызовов и все правила изменения запросов и ответов, сохраняя активный перехват. Позволяет повторно использовать мок с прежним шаблоном URL и фильтрами. + +## Использование {/* #usage */} + +`mock.reset()` + +## Параметры {/* #parameters */} + +У команды нет параметров. + +## Результат {/* #result */} + +Тот же объект мока синхронно. + +## Ограничения {/* #limitations */} + +Нужна WebDriver BiDi-сессия (`webSocketUrl: true`): в Classic/CDP-реализации мока этого метода нет. Примеры явно подписываются на сетевые события в каждой сессии; см. [ограничение подписки](./waitForResponse.mdx#limitations). Когда мок больше не нужен, вызовите `mock.restore()` в `finally`. + +`clear()` удаляет только историю и сохраняет правила. `reset()` дополнительно удаляет правила, включая перенаправления и прерывания. `restore()` удаляет перехват, после чего новые переопределения у этого мока недоступны. Сброс не отменяет уже отправленный запрос или доставленный ответ. + +## Пример {/* #examples */} + +Укажите в `baseUrl` локальный HTTP-сайт. `/` возвращает HTML. `/api/items` возвращает JSON: `source: "original"`, метод запроса в `method` и заголовок запроса `x-example` в `header` (`null`, если заголовка нет). `/api/other` возвращает те же поля с `source: "redirected"`. Примеры выполняют настоящие запросы к этим обработчикам. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.reset: remove overrides but keep the mock usable", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.request({ headers: { "x-example": "overridden" } }); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.header, "overridden"); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.reset(), mock); + assert.equal(mock.calls.length, 0); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(second.header, null); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + mock.respond({ source: "mocked" }); + const third = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(third.source, "mocked"); + mock.reset(); + const fourth = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(fourth.source, "original"); + } finally { + await mock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [clear](./clear.mdx) +- [restore](./restore.mdx) +- [request](./request.mdx) +- [respond](./respond.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respond.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respond.mdx index 4645104f..78b6ad0f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respond.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respond.mdx @@ -1,85 +1,94 @@ -import Admonition from "@theme/Admonition"; - # respond ## Обзор {/* #overview */} -Используйте команду `respond`, чтобы всегда отвечать одной и той же перезаписью. - - - Читайте также рецепт «[Как отслеживать и перехватывать сетевые запросы и - ответы][how-to-intercept-requests-and-responses]». - +Подменяет ответы на подходящие запросы, пока правило активно. Исходный запрос в BiDi все равно отправляется на сервер; это не способ запретить побочные эффекты запроса. ## Использование {/* #usage */} -```javascript -mock.respond(overwrites, { header, statusCode, fetchResponse }); -``` +`mock.respond(overwrites, params)` ## Параметры команды {/* #parameters */} - - - - - - - - - +| Параметр | BiDi | Classic/CDP | +| ---------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `overwrites` | Строка тела или JSON-объект/массив. Callback для тела не поддерживается. | Строка, JSON или функция, получающая запись запроса и клиент. Функция может вернуть Promise. | +| `params.headers` | Заголовки ответа: объект или синхронная функция от события ответа. Заменяют набор заголовков. | Объект или функция. Дополняют исходные заголовки. | +| `params.statusCode` | Число или синхронная функция от события ответа. | Число или функция. | +| `params.cookies` | Массив `Cookie[]` в формате WebDriver или синхронная функция от события ответа. | Не поддерживается этой реализацией. | +| `params.fetchResponse` | Не применяется: подмена выполняется на стадии ответа, запрос доходит до сервера. | Boolean, по умолчанию `true`. При `false` ответ подменяется до загрузки оригинального тела. | - -
**Имя****Тип****Описание**
overwritesMockOverwrite_Payload_ для перезаписи ответа.
headerObjectПерезаписать определенные заголовки.
statusCodeNumberПерезаписать код состояния ответа.
fetchResponseBooleanПолучить реальный ответ, прежде чем отвечать с помощью поддельных данных.
+В `params.cookies` передавайте строковое `value` и поле `expiry` (время Unix в секундах), а не BiDi-объект `value` и поле `expires`. Библиотека сама преобразует cookies в формат BiDi. -## Примеры использования {/* #examples */} +Имя параметра — **`headers`**, не `header`. Без переопределения метаданных используются исходные значения. -```javascript -it('should demonstrate response overwrite with static data', async ({ browser }) => { - const mock = await browser.mock('https://todo-backend-express-knex.herokuapp.com/', { - method: 'get' - }); +## Ограничения {/* #limitations */} - mock.respond([{ - title: 'Injected (non) completed Todo', - order: null, - completed: false - }, { - title: 'Injected completed Todo', - order: null, - completed: true - }], { - statusCode: 200, - fetchResponse: true // по умолчанию - }); +В BiDi строка с путем к файлу или URL остается текстом ответа. Команда не читает файл и не перенаправляет запрос; для изменения URL используйте [redirect](./redirect.mdx). В Classic/CDP существующий путь читается как локальный файл, а строка с HTTP(S)-URL задает ответ-перенаправление. - await browser.url('https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/'); +Не смешивайте постоянные и однократные правила без учета реализации. В BiDi `respond()` заменяет очередь ответов постоянным правилом. `respondOnce()` убирает постоянное правило и добавляет однократное к другим однократным. В CDP правила добавляются в очередь, и ранее добавленное постоянное правило может перекрыть последующие. - await browser.$('#todo-list li').waitForExist(); +## Результат {/* #result */} - const todoElements = await browser.$$('#todo-list li'); - console.log(await Promise.all(todoElements.map(el => el.getText()))); - // выведет: "[ 'Injected (non) completed Todo', 'Injected completed Todo' ]" -}); +BiDi синхронно возвращает тот же объект мока. Classic/CDP не возвращает полезного значения. Для удаления подмены и перехватчика вызовите `await mock.restore()`. -it('should demonstrate response overwrite with dynamic data', async ({ browser }) => { - const mock = await browser.mock('https://todo-backend-express-knex.herokuapp.com/'); +## Пример {/* #examples */} - mock.respond((request) => { - if (request.body.username === 'test') { - return { ...request.body, foo: 'bar' }; - } - return request.body; - }, { - statusCode: () => 200, - headers: () => ({ foo: 'bar }), - fetchResponse: false // не получать настоящий ответ +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.respond: replace response data and metadata in BiDi", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], }); + const mock = await browser.mock("/api/items"); + try { + mock.respond( + { source: "mocked" }, + { + statusCode: 201, + headers: { "content-type": "application/json", "x-example": "response" }, + cookies: [ + { + name: "mock-example", + value: "ready", + domain: new URL(await browser.getUrl()).hostname, + path: "/", + }, + ], + }, + ); + for (let i = 0; i < 2; i++) { + const result = await browser.execute(async () => { + const response = await fetch("/api/items", { cache: "no-store" }); + return { + status: response.status, + header: response.headers.get("x-example"), + body: await response.json(), + }; + }); + assert.deepEqual(result, { + status: 201, + header: "response", + body: { source: "mocked" }, + }); + } + } finally { + await mock.restore(); + } }); ``` ## Связанные команды {/* #related */} -- [respondOnce](../respondOnce) +- [respondOnce](./respondOnce.mdx) +- [redirect](./redirect.mdx) +- [browser.mock](../browser/mock.mdx) +- [Network interception](../../guides/how-to-intercept-requests-and-responses.mdx) + +## Источники {/* #references */} -[how-to-intercept-requests-and-responses]: ../../../guides/how-to-intercept-requests-and-responses +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/mock/respond). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respondOnce.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respondOnce.mdx index 3d99842e..878997f3 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respondOnce.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respondOnce.mdx @@ -1,108 +1,58 @@ -import Admonition from "@theme/Admonition"; - # respondOnce ## Обзор {/* #overview */} -Используйте команду `respondOnce`, чтобы ответить один раз заданной перезаписью. - -Вы можете вызвать `respondOnce` несколько раз подряд, тогда при запросах ответы будут использоваться в том же порядке, как были вызваны команды `respondOnce`. - -Если вы используете только `respondOnce` и обратитесь к ресурсу большее количество раз, чем вызывали `respondOnce`, то после исчерпания поддельных данных, запрос начнет возвращать оригинальный ответ от ресурса. - - - Читайте также рецепт «[Как отслеживать и перехватывать сетевые запросы и - ответы][how-to-intercept-requests-and-responses]». - +Подменяет один ответ. Несколько вызовов `respondOnce` добавляют ответы в порядке вызова. Когда очередь однократных ответов исчерпана, следующие запросы получают исходные ответы сервера. ## Использование {/* #usage */} -```javascript -mock.respondOnce(overwrites, { header, statusCode, fetchResponse }); -``` +`mock.respondOnce(overwrites, params)` ## Параметры команды {/* #parameters */} - - - - - - - - - +Параметры и их различия между BiDi и Classic/CDP совпадают с [respond](./respond.mdx#parameters), включая имя `headers`, а не `header`. В BiDi тело — строка или JSON; callback тела и `fetchResponse: false` не поддерживаются. Запрос доходит до сервера. - -
**Имя****Тип****Описание**
overwritesMockOverwrite_Payload_ для перезаписи ответа.
headerObjectПерезаписать определенные заголовки.
statusCodeNumberПерезаписать код состояния ответа.
fetchResponseBooleanПолучить реальный ответ, прежде чем отвечать с помощью поддельных данных.
+В BiDi однократное правило удаляет постоянное правило и добавляется к другим однократным. В Classic/CDP ранее добавленное постоянное правило может перекрыть очередь. Не полагайтесь на одинаковое смешивание `respond` и `respondOnce` между протоколами. -## Примеры использования {/* #examples */} +BiDi синхронно возвращает тот же мок; Classic/CDP не возвращает полезного значения. -```javascript -async function getToDos(browser) { - await browser.$("#todo-list li").waitForExist(); +## Пример {/* #examples */} - const todoElements = await browser.$$("#todo-list li"); +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. - return Promise.all(todoElements.map(el => el.getText())); -} +```javascript +const assert = require("node:assert/strict"); -it("should demonstrate the respondOnce command", async ({ browser }) => { - const mock = await browser.mock("https://todo-backend-express-knex.herokuapp.com/", { - method: "get", +it("mock.respondOnce: consume BiDi responses in order", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], }); - - mock.respondOnce([ - { - title: "3", - }, - { - title: "2", - }, - { - title: "1", - }, - ]); - - mock.respondOnce([ - { - title: "2", - }, - { - title: "1", - }, - ]); - - mock.respondOnce([ - { - title: "1", - }, - ]); - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // выведет: [ '3', '2', '1' ] - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // выведет: [ '2', '1' ] - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // выведет: [ '1' ] - - await browser.url( - "https://todobackend.com/client/index.html?https://todo-backend-express-knex.herokuapp.com/", - ); - console.log(await getToDos(browser)); // выведет: настоящий ответ ресурса + const mock = await browser.mock("/api/items"); + try { + mock.respondOnce({ source: "first" }); + mock.respondOnce({ source: "second" }); + const sources = []; + for (let i = 0; i < 3; i++) { + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + sources.push(body.source); + } + assert.deepEqual(sources, ["first", "second", "original"]); + } finally { + await mock.restore(); + } }); ``` ## Связанные команды {/* #related */} -- [respond](../respond) +- [respond](./respond.mdx) +- [reset](./reset.mdx) +- [restore](./restore.mdx) +- [Network interception](../../guides/how-to-intercept-requests-and-responses.mdx) + +## Источники {/* #references */} -[how-to-intercept-requests-and-responses]: ../../../guides/how-to-intercept-requests-and-responses +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/mock/respondOnce). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/restore.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/restore.mdx index 44e045cb..3619c0df 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/restore.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/restore.mdx @@ -2,27 +2,56 @@ ## Обзор {/* #overview */} -Используйте команду `restore`, чтобы выполнить все то же, что делает [mock.clear()](../clear), а также удалить любые поддельные возвращаемые значения или реализации. +Удаляет сетевой перехватчик, очищает историю и отключает подмены. Следующие запросы идут без этого мока. Это не [browser.restore](../browser/restore.mdx): восстановление эмуляции — другая команда. + +Дождитесь `restore()` перед следующим запросом или завершением теста. В BiDi восстановленный мок нельзя переиспользовать: создайте новый через [browser.mock](../browser/mock.mdx). Для сохранения перехватчика используйте [reset](./reset.mdx). ## Использование {/* #usage */} -```javascript -mock.restore(); -``` +`await mock.restore()` -## Примеры использования {/* #examples */} +## Параметры и результат {/* #parameters */} -```javascript -it("should demonstrate the addValue command", async ({ browser }) => { - const mock = await browser.mock("**/googlelogo_color_272x92dp.png"); - mock.respond("https://webdriver.io/img/webdriverio.png"); +Аргументов нет. Возвращает Promise: в BiDi он разрешается тем же объектом мока, в Classic/CDP — без полезного значения. Используйте команду для очистки в `finally`, в том числе когда проверка теста завершилась ошибкой. + +## Пример {/* #examples */} - await browser.url("https://google.com"); // покажет логотип WebdriverIO вместо логотипа Google - mock.restore(); - await browser.url("https://google.com"); // покажет родной логотип Google +Пример использует Chrome с WebDriver BiDi. Укажите в `baseUrl` адрес тестового HTTP-сервера: `/` отдает HTML-страницу, а `/api/items` — JSON с полем `source: "original"` и статусом `200`. Явная подписка на события нужна для новых сессий в одном воркере в WDIO 9.6.2. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.restore: remove interception before the next request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond({ source: "mocked" }); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "mocked"); + } finally { + await mock.restore(); + } + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "original"); + assert.equal(mock.calls.length, 0); + assert.throws(() => mock.respond({ source: "unused" }), /restored mock/); }); ``` ## Связанные команды {/* #related */} -- [clear](../clear) +- [clear](./clear.mdx) +- [reset](./reset.mdx) +- [restore](./restore.mdx) +- [browser.mock](../browser/mock.mdx) + +## Источники {/* #references */} + +Материал учитывает [исходную статью WebdriverIO](https://webdriver.io/docs/api/mock/restore). diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/waitForResponse.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/waitForResponse.mdx new file mode 100644 index 00000000..14f00093 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/waitForResponse.mdx @@ -0,0 +1,68 @@ +# waitForResponse + +## Обзор {/* #overview */} + +Ожидает хотя бы один зарегистрированный ответ в `calls`. Позволяет дождаться запроса, запущенного кодом страницы, без фиксированной задержки. + +## Использование {/* #usage */} + +`mock.waitForResponse({ timeout, interval, timeoutMsg })` + +## Параметры {/* #parameters */} + +| **Параметр** | **Тип** | **По умолчанию** | **Описание** | +| ------------ | -------- | ----------------------- | ------------------------------------------ | +| `timeout` | `number` | `waitTimeout` (3000 ms) | Предельное время ожидания в миллисекундах. | +| `interval` | `number` | `waitInterval` (500 ms) | Интервал проверок в миллисекундах. | +| `timeoutMsg` | `string` | — | Сообщение ошибки по тайм-ауту. | + +## Результат {/* #result */} + +`Promise` со значением `true`, когда `calls` непуст. При тайм-ауте или ошибке проверки промис отклоняется. Сам ответ не возвращается: читайте `mock.calls` отдельно. + +## Ограничения {/* #limitations */} + +Доступен в BiDi- и Classic/CDP-реализациях мока. Старые вызовы сразу удовлетворяют условию: вызовите `mock.clear()` перед действием, которое нужно проверить. В BiDi история заполняется на событии `network.responseStarted`: это еще не гарантирует чтение тела ответа или обновление интерфейса. + +В поставляемом WDIO 9.6.2 автоматическая подписка на сетевые события защищена флагом уровня модуля. При создании следующей браузерной сессии тем же воркером подписки на ответы может не быть. Пример явно вызывает `sessionSubscribe` в каждой сессии; это проверено с `testsPerSession: 1` в Testplane. Это настоящая протокольная подписка, а не замена мока. + +## Пример {/* #examples */} + +Укажите в `baseUrl` локальный HTTP-сайт. `/` возвращает HTML. `/api/items` возвращает JSON: `source: "original"`, метод запроса в `method` и заголовок запроса `x-example` в `header` (`null`, если заголовка нет). `/api/other` возвращает те же поля с `source: "redirected"`. Примеры выполняют настоящие запросы к этим обработчикам. + +```javascript +const assert = require("node:assert/strict"); + +it("mock.waitForResponse: wait for a new recorded response", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + await browser.execute(async () => (await fetch("/api/items")).text()); + await mock.waitForResponse({ timeout: 3000 }); + mock.clear(); + assert.equal(mock.calls.length, 0); + await browser.execute(() => { + window.requestFinished = false; + fetch("/api/items", { cache: "no-store" }) + .then(response => response.text()) + .then(() => { + window.requestFinished = true; + }); + }); + assert.equal(await mock.waitForResponse({ timeout: 3000, interval: 50 }), true); + assert.equal(mock.calls.length, 1); + await browser.waitUntil(() => browser.execute(() => window.requestFinished)); + } finally { + await mock.restore(); + } +}); +``` + +## См. также {/* #see-also */} + +- [browser.mock](../browser/mock.mdx) +- [clear](./clear.mdx) +- [restore](./restore.mdx) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx index 6f2e4a36..004ae192 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx @@ -2,48 +2,46 @@ sidebar_class_name: hidden --- -import Admonition from "@theme/Admonition"; +# Команды Testplane -# Команды testplane +## Обзор {/* #overview */} -## Обзор + - - Описаны только команды для последней версии Testplane v8 и WebDriverIO v9. Команды для более - старых версий стоит смотреть в документации WebDriverIO (пример для [WebDriverIO - v8][webdriverio@8-api]). - +Справочник описывает Testplane v9 и команды используемой им ветки WebdriverIO v9. Доступность команды зависит от браузера, протокола сессии и версии зависимости; условия указаны на страницах команд. Для старых версий используйте архив документации и [WebdriverIO v8](https://v8.webdriver.io/docs/api). -Так как в основе testplane лежит [WebDriverIO v9][webdriverio-api], то в ней доступны все команды, которые предоставляет WebDriverIO. +В Testplane нет неявных глобальных `browser`, `$` и `$$` из WDIO runner. Получайте `browser` из аргумента теста или через `this.browser` в обычной функции. Стрелочная функция не получает контекст `this` теста. Ищите элементы через `browser.$` и `browser.$$`. -Однако, описания команд на сайте [WebDriverIO][webdriverio-api] для 8-й версии не совсем подходят _as is_ для пользователей testplane из-за целого ряда причин: +## Пример {/* #examples */} -- в WebDriverIO объект `browser` существует в глобальном пространстве, тогда как в testplane нужно либо писать `this.browser`: +Оба теста открывают `/` относительно `baseUrl`. Тестовая страница должна содержать `h1` с текстом `Command examples`. - ```javascript - it("should test something", async () => { - await this.browser.url("https://testplane.io"); - // код теста... - }); - ``` +```javascript +const assert = require("node:assert/strict"); - либо получать объект `browser` из аргумента функции (обратите внимание, что объект `browser` передается внутри объекта!): +it("commands: use the browser fixture", async ({ browser }) => { + await browser.url("/"); + assert.equal(await browser.$("h1").getText(), "Command examples"); +}); - ```javascript - it("should test something", async ({ browser }) => { - await browser.url("https://testplane.io"); - // код теста... - }); - ``` +it("commands: use this.browser with a regular function", async function () { + await this.browser.url("/"); + assert.equal(await this.browser.$("h1").getText(), "Command examples"); +}); +``` -- аналогично функции `browser.$` и `browser.$$` в WebDriverIO доступны в тестах под именами `$` и `$$`, а в testplane к ним нужно обращаться по полному пути: `browser.$` и `browser.$$`; +## Выбор команды {/* #command-groups */} -- не проставлены связи между похожими командами: нет кластеризации команд; +| Задача | Команды | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Подготовка и эмуляция страницы | [addInitScript](./browser/addInitScript.mdx), [emulate](./browser/emulate.mdx), [restore](./browser/restore.mdx), [setViewport](./browser/setViewport.mdx) | +| Собственные селекторы | [addLocatorStrategy](./browser/addLocatorStrategy.mdx), [browser.custom$](./browser/customDollar.mdx), [browser.custom$$](./browser/customDollarDollar.mdx), [element.custom$](./element/customDollar.mdx), [element.custom$$](./element/customDollarDollar.mdx) | +| Фреймы и загрузки | [switchFrame](./browser/switchFrame.mdx), [downloadFile](./browser/downloadFile.mdx) | +| Ограничение ресурсов | [throttleCPU](./browser/throttleCPU.mdx), [throttleNetwork](./browser/throttleNetwork.mdx) | +| Работа с элементом | [execute](./element/execute.mdx), [executeAsync](./element/executeAsync.mdx), [getElement](./element/getElement.mdx), [isStable](./element/isStable.mdx), [waitForStable](./element/waitForStable.mdx) | +| Перехват сети | [mock](./browser/mock.mdx), [waitForResponse](./mock/waitForResponse.mdx), [request](./mock/request.mdx), [requestOnce](./mock/requestOnce.mdx), [redirect](./mock/redirect.mdx), [redirectOnce](./mock/redirectOnce.mdx), [reset](./mock/reset.mdx) | +| Расширения Testplane | [extendOptions](./browser/extendOptions.mdx), [browser.unstable_captureDomSnapshot](./browser/unstable_captureDomSnapshot.mdx), [element.unstable_captureDomSnapshot](./element/unstable_captureDomSnapshot.mdx), [unstable_getCdp](./browser/unstable_getCdp.mdx) | -Тем не менее в наше описание пока ещё не вошли протоколо-специфичные команды. Соответствующие команды вы можете посмотреть на сайте WebDriverIO в разделе "[Protocols][webdriverio-protocols]". +## Границы справочника {/* #scope */} -Также в описаниях некоторых команд ссылки на отдельные рецепты все еще не локализованы и ведут на сайт WebDriverIO. - -[webdriverio@8-api]: https://v8.webdriver.io/docs/api -[webdriverio-api]: https://webdriver.io/docs/api -[webdriverio-protocols]: https://webdriver.io/docs/api/webdriver +Низкоуровневые команды WebDriver, BiDi и Appium не дублируются: см. [протоколы WebdriverIO](https://webdriver.io/docs/api/webdriver). Методы объектов `InitScript` и часов описаны на страницах [addInitScript](./browser/addInitScript.mdx) и [emulate](./browser/emulate.mdx), а не как отдельные команды браузера. Для внешних рецептов учитывайте различия версий и получения объекта `browser`. diff --git a/vibe/missing-commands-plan-track.md b/vibe/missing-commands-plan-track.md new file mode 100644 index 00000000..dbd3c7b2 --- /dev/null +++ b/vibe/missing-commands-plan-track.md @@ -0,0 +1,59 @@ +# Progress tracker for missing-commands-plan.md + +Status format: [ ] - not processed, [X] - completed + +## Execution Stages + +[X] Подготовка: зависимости, исходные проверки, отдельный проект Testplane +[X] Stage 1: Браузерные команды и реальные примеры +[X] Stage 2: Команды элементов и реальные примеры +[X] Stage 3: Сетевые моки и реальные примеры +[X] Stage 4: Расширения Testplane и реальные примеры +[ ] Stage 5: Мобильные команды и реальные примеры — отложены пользователем +[X] Stage 6: Немобильные связанные исправления и навигация +[X] Stage 7: Проверки обеих локалей, сверка примеров, итоговое ревью немобильного объема + +## Notes + +- 2026-10-09: пользователь подтвердил план и потребовал успешный реальный запуск каждого примера. +- Повторные fetch/rebase выполнены, базы трех worktree не изменились. +- В этой задаче изменяется документация, не реализация framework. Тесты примеров характеризуют существующее поведение; намеренно ломать framework ради RED-фазы не требуется. +- Отдельный проект примеров: `/private/tmp/testplane-docs-examples-20261009`. +- Локально отсутствуют Java runtime и Android SDK/Appium, Docker daemon не запущен. Запрошены параметры тестовых стендов или разрешение отложить соответствующие страницы. + +- 2026-10-09: `npm ci`, исходные `npm test` и `npm run build` успешны. В baseline есть предупреждения SSG на testplane-retry-progressive и admonition undefined; это не следствие новых страниц. +- 2026-10-09: проект создан `npm init --yes testplane@latest -- /private/tmp/testplane-docs-examples-20261009 --lang js`; установлен Testplane 9.3.2, WDIO 9.6.2. Первый пример addInitScript прошел в Chrome for Testing 150.0.7871.124 (версия зафиксирована по capabilities, а не установленному Chrome). +- Ruling: проверки примеров вынесены в изолированный HTTP-стенд и свежую Chrome-сессию на каждый тест. Публикуемые фрагменты будут скопированы из прошедших тестов, а не переписаны после проверки. +- Обнаружено: throttleNetwork offline → online воспроизводимо оставляет fetch недоступным. Тот же сценарий через одну CDP-сессию проходит; реализация WDIO создает новую CDP-сессию для каждого вызова. Фреймворк не меняется; пример использует testsPerSession: 1. Диагностические прогоны сохранены отдельно, не замаскированы под успешные. + +- Уточнение: Testplane использует собственный Chrome for Testing 150.0.7871.124, не системный Chrome 154. Первый повторный общий прогон имел отдельный тайм-аут создания сессии; тайм-аут запуска увеличен с 20 до 60 секунд, автоматические retries не включались. + +- Stage 1: созданы 11 страниц × 2 локали. Chrome/BiDi: 11/11 тестов; downloadFile через Selenium Grid 4.50.0: 1/1. Все проверки форматирования и обе сборки прошли. Selenium/JRE распакованы только в /tmp, SHA256 проверены. Для throttleCPU/throttleNetwork подтверждено требование одной вкладки и isolation: false. + +- Stage 2: 7 страниц × 2 локали, реальные тесты 7/7. Prettier и EN/RU build прошли после исправления ссылки на $.mdx. + +- Stage 3: 6 страниц × 2 локали. 6/6 реальных сетевых сценариев прошли, Prettier и обе сборки успешны. Найдена зависимость автоматической BiDi-подписки от глобального флага WDIO; пример с явным sessionSubscribe проверен при пересоздании сессий. Первичный неуспешный прогон сохранен отдельно. + +- Stage 4: 4 страницы × 2 локали, реальные тесты 4/4; форматирование и обе сборки успешны. Stage 5 ожидает рабочую Appium-среду или решение пользователя об изменении объема. Непроверенные мобильные примеры не добавлялись; этапы 6–7 пока не отмечены выполненными. + +- Ревью готовой части: важных незакрытых замечаний к новым материалам нет. Исправлены описание predicate-ветки switchFrame (ошибка воспроизведена реальным тестом) и топология HTTP/Selenium-стенда. Уточнены исключения maxTextLength для id/class/state-маркеров. +- До публикации обязательно выполнить этап 6: старый гайд эмуляции еще обещает восстановление сети через online; старая страница browser.mock еще говорит о поддержке только CDP. Эти противоречия не считаются устраненными созданием новых страниц. +- infradocs-screenshot-producer недоступен в списке навыков сессии. Иллюстрации не создавались; визуальный этап Producer не выполнен. Обычная проверка верстки существующим браузерным инструментом проводится отдельно. + +- Сохранение готовой части: окончательные точные фрагменты — 28/28 desktop + 1/1 Selenium, retries 0. npm test и полная сборка EN/RU успешны после ревью. 56 фрагментов совпадают с 28 тестовыми файлами. Просмотрена репрезентативная выборка в обеих локалях. Подробности: vibe/verified-examples/evidence/. + +- Пользователь выбрал сначала завершить немобильную часть. Этап 5 и мобильные ссылки этапа 6 отложены, не отмечаются завершенными. Повторные fetch/rebase трех worktree не изменили базовые SHA. + +- Stage 6 (немобильный): обновлены 14 существующих страниц в каждой локали; 14 новых runtime-тестов прошли. Полный набор: 42 desktop + 1 Selenium, retries 0. npm test и отдельные EN/RU build успешны. Сверка 86 блоков с 41 тестовым файлом проходит. Исправлена обнаруженная сборкой ссылка на getUrl: это протокольная команда без отдельной страницы. +- url/networkIdle воспроизводимо падает при поиске navigation по context ID; пример с complete прошел. Ограничение описано, диагностический прогон сохранен. + +- Stage 7 (немобильный): финальные 42/42 Chrome/BiDi + 1/1 Selenium, retries 0; npm test, отдельные и общая сборки EN/RU, git diff --check успешны. Проверены 416 относительных ссылок, 86 новых/измененных блоков, парные якоря и автогенерируемая навигация. Просмотрены репрезентативные страницы обоих языков. +- Итоговое независимое ревью: одно важное замечание к cookies в respond исправлено и воспроизведено RED→GREEN. Два небольших уточнения (`setViewport` и область действия `addInitScript`) первоначально отложены; исправлены 2026-10-14, см. запись ниже и evidence/review.md. +- Исходные рабочие каталоги и реализации Testplane/html-reporter не изменены. Ранее существовавший untracked docs/dependency-security.md в исходном testplane сохранен. Коммит, push и публикация не выполнялись. + +- 2026-10-14: закрыты оба оставшихся замечания в EN/RU: фактические клиентские границы `setViewport` и область регистрации `addInitScript`. Изменены только четыре MDX-страницы и отчеты; примеры, ссылки, якоря и lock-файл не менялись. Мобильная часть не затронута. +- Повторная проверка 2026-10-14: 42/42 Chrome/BiDi + 1/1 Selenium, retries 0; npm test, отдельные EN/RU build, сверка 86 блоков / 82 страниц / 41 файла и git diff --check прошли. Визуально проверены четыре страницы; 28 явных якорей найдены в HTML. Стенд восстановлен из сохраненного lock-файла через `npm ci --legacy-peer-deps`: обычный npm ci выявил существующий peer-конфликт proxy-agent; зависимости не обновлялись. Логи: vibe/verified-examples/evidence/review-fixes-20261014/. + +- 2026-10-14: пользователь разрешил локальный коммит готового немобильного объема вместе с примерами и отчетами. Для сохранения свежих логов добавлено исключение `!evidence/**/*.log`; node_modules, debug-артефакты и локальная конфигурация стенда остаются вне коммита. Мобильный этап по-прежнему отложен; push и публикация не запрошены. + +- Перед коммитом повторены npm test, отдельные EN/RU build и все реальные примеры: 42/42 Chrome/BiDi + 1/1 Selenium, retries 0. Все 86 блоков совпадают с сохраненными тестами, SHA256 примеров совпадают с отчетом. Логи: vibe/verified-examples/evidence/commit-check-20261014/. diff --git a/vibe/missing-commands-plan.md b/vibe/missing-commands-plan.md new file mode 100644 index 00000000..351d60a7 --- /dev/null +++ b/vibe/missing-commands-plan.md @@ -0,0 +1,265 @@ +# План дополнения справочника команд Testplane + +## Уточнение текущего объема + +Пользователь подтвердил приоритет немобильной части. Этап 5 и мобильные ссылки этапа 6 (Android-гайд и dragAndDrop) отложены. Текущая последовательность: завершенные этапы 1–4 → немобильная часть этапа 6 → этап 7 для 28 новых методов. Мобильные 11 методов остаются в плане и не считаются выполненными. Финальная проверка getContexts будет выполнена вместе с мобильной частью. + +## Задача + +Дополнить справочник публичных команд актуальной Testplane v9: описать недостающие команды browser, element и mock на английском и русском языках. Создать 39 страниц в каждой локали — всего 78 новых MDX-файлов. Обновить связанные страницы без изменения реализации Testplane и html-reporter. + +Использовать редакционные правила `infradocs-yfm-writer`, сохраняя фактическую платформу Docusaurus/MDX, шаблоны страниц и якоря `{/* #anchor */}`. Не переносить проект на YFM и не создавать `toc.yaml`. Архив v8 и его переводы в этот план не включать. + +## Рабочие каталоги и источники + +- Документация: `/Users/sipayrt/.codex/worktrees/missing-command-docs/testplane-docs`, ветка `codex/missing-command-docs`, база `7e662b60be11ac6813eca9c5d84c5142c043d01f`. +- Testplane: `/Users/sipayrt/.codex/worktrees/missing-command-docs/testplane`, ветка `codex/missing-command-docs`, база `e19510b247523f40c23bf1dcbb719830220d6357`, версия 9.3.2. +- html-reporter: `/Users/sipayrt/.codex/worktrees/missing-command-docs/html-reporter`, ветка `codex/missing-command-docs`, база `d24b6b72418a28f66ff5567f7b14a142b4b8d5e9`. +- Для поведения унаследованных команд использовать `@testplane/webdriverio@9.6.2` из `testplane/package-lock.json`, а не произвольную текущую версию upstream WebdriverIO. +- Закрепленный пакет распакован в `/private/tmp/testplane-command-audit/wdio-9.6.2/package`; SHA-512 архива сверена с lock-файлом. При отсутствии этой временной копии получить ту же версию через `npm pack @testplane/webdriverio@9.6.2 --ignore-scripts`. +- Ниже пути документации считать относительно worktree `testplane-docs`; пути `src/` и `test/` в источниках Testplane — относительно worktree `testplane`. + +## Структура плана + +Выполнить последовательно: браузерные команды, команды элементов, сетевые моки, собственные расширения Testplane, мобильные команды, связанные исправления и навигация, итоговая проверка. + +Для каждого нового файла `docs/` создать русский эквивалент `i18n/ru/docusaurus-plugin-content-docs/current/` с одинаковым API, примерами и якорями. Каждая страница должна содержать назначение, условия применимости, сигнатуру, параметры и значения по умолчанию, возвращаемое значение, ошибки и ограничения, пример Testplane и связанные команды. Использовать `async ({ browser }) => { ... }`, а не глобальный `browser` или `driver` из WDIO runner. + +Не дублировать низкоуровневый справочник WebDriver/CDP/Appium, EventEmitter и методы объектов эмуляции отдельными страницами команд. `getElements()` не объявлять универсальным методом результата `browser.$$()`: учесть переопределение `$$` в `src/browser/existing-browser.ts`. Методы объектов `ClockManager` и `InitScript` описывать в соответствующих страницах `emulate` и `addInitScript`. + +Дополнительное требование пользователя от 2026-10-09: создать отдельный проект через `npm init testplane@latest` и запускать каждый новый или измененный пример как настоящий тест. Публиковать пример только после успешного прогона; проверка синтаксиса, типов и подмена тестируемой команды не заменяют runtime-проверку. Сохранить соответствие «страница → файл теста → результат». Недоступность Selenium Grid или Appium фиксировать как незавершенную работу, а не как успешную проверку. + +Все команды проверки ниже запускать из worktree `testplane-docs`. Перед редактурой установить зависимости через `npm ci` и выполнить базовые `npm test` и `npm run build`; сохранить исходные ошибки отдельно от результатов изменений. + +## Этапы выполнения + +### Этап 1: Браузерные команды — 11 страниц + +**Что добавить:** + +- `addInitScript`: выполнение до скриптов страницы, BiDi, аргументы, возвращаемый объект, `remove()` и события `data`. Не копировать ошибочный пример с `reset()` из комментариев зависимости. +- `addLocatorStrategy`, `custom$`, `custom$$`: регистрация стратегии, параметры, типы результатов, ошибки повторной регистрации и неизвестной стратегии. +- `downloadFile`: выгрузка файла с Selenium Grid, `se:downloadsEnabled`, каталог назначения и связанные протокольные команды. +- `emulate`, `restore`: поддерживаемые режимы, возвращаемые функции восстановления и объект часов; время применения и очистка в пределах того же теста. Отличить `restore()` от `restoreState()` и `mock.restore()`. +- `setViewport`: размеры области страницы и `devicePixelRatio`, отличие от `setWindowSize`, требования BiDi. +- `switchFrame`: формы аргумента, возврат в корневой фрейм через `null`, различия BiDi и Classic; строки и функции не объявлять поддержанными в Classic. +- `throttleCPU`, `throttleNetwork`: требования CDP/Puppeteer, параметры и восстановление нормального режима; не приравнивать сетевое ограничение к `emulate("onLine")`. + +**Файлы создать:** + +- `docs/commands/browser/addInitScript.mdx` +- `docs/commands/browser/addLocatorStrategy.mdx` +- `docs/commands/browser/customDollar.mdx` +- `docs/commands/browser/customDollarDollar.mdx` +- `docs/commands/browser/downloadFile.mdx` +- `docs/commands/browser/emulate.mdx` +- `docs/commands/browser/restore.mdx` +- `docs/commands/browser/setViewport.mdx` +- `docs/commands/browser/switchFrame.mdx` +- `docs/commands/browser/throttleCPU.mdx` +- `docs/commands/browser/throttleNetwork.mdx` +- Русские эквиваленты всех перечисленных файлов. + +**Источники и примеры:** + +- Пакет WDIO: `build/commands/browser/*.d.ts`, `build/types.d.ts`, `build/index.js`, `build/node.js`, `build/clock.d.ts`. +- Testplane: `src/browser/commands/getPuppeteer.js`, `src/browser/existing-browser.ts`. +- Документация: `docs/commands/browser/openAndWait.mdx`, `docs/basic-guides/user-environment-emulation.mdx`. + +**Проверки:** + +- `npx --no-install prettier --check docs/commands/browser i18n/ru/docusaurus-plugin-content-docs/current/commands/browser` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` + +### Этап 2: Команды элементов — 7 страниц + +**Что добавить:** + +- `custom$`, `custom$$`: область поиска относительно элемента, аргументы стратегии и возвращаемые элементы. +- `execute`: элемент как первый аргумент браузерной функции, предварительное ожидание существования, дополнительные аргументы и результат. +- `executeAsync`: callback последним аргументом, тайм-аут скрипта, предупреждение об устаревании и ссылка на `execute`; проверять ожидание элемента по реализации, а не переносить комментарий из WDIO автоматически. +- `getElement`: получение объекта элемента и доступ к его свойствам; отличить от протокольного поиска элемента. +- `isStable`, `waitForStable`: проверка геометрии между кадрами, ограничения неактивной вкладки, параметры ожидания; не обещать завершение любой анимации или сетевой загрузки. Для `waitForStable` описать `Promise` по фактической реализации. + +**Файлы создать:** + +- `docs/commands/element/customDollar.mdx` +- `docs/commands/element/customDollarDollar.mdx` +- `docs/commands/element/execute.mdx` +- `docs/commands/element/executeAsync.mdx` +- `docs/commands/element/getElement.mdx` +- `docs/commands/element/isStable.mdx` +- `docs/commands/element/waitForStable.mdx` +- Русские эквиваленты всех перечисленных файлов. + +**Источники и примеры:** + +- Пакет WDIO: `build/commands/element/*.d.ts`, соответствующие реализации в `build/index.js`, `build/scripts/isElementStable.js`. +- Документация: `docs/commands/element/waitForDisplayed.mdx`, `docs/commands/browser/execute.mdx`. + +**Проверки:** + +- `npx --no-install prettier --check docs/commands/element i18n/ru/docusaurus-plugin-content-docs/current/commands/element` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` + +### Этап 3: API сетевых моков — 6 страниц + +**Что добавить:** + +- `waitForResponse`: ожидание хотя бы одного сохраненного ответа, параметры и ошибка тайм-аута; отдельно пояснить очистку старых вызовов. +- `request`, `requestOnce`: изменение исходящего запроса, доступные поля и однократное применение. +- `redirect`, `redirectOnce`: изменение URL запроса и однократное применение. +- `reset`: очистка истории и правил перехвата без полного удаления мока; отличия от `clear` и `restore`. +- Отметить, что `request`, `requestOnce`, `redirect`, `redirectOnce` и `reset` принадлежат BiDi-реализации мока; не обещать их для CDP и legacy WebDriver-перехвата. + +**Файлы создать:** + +- `docs/commands/mock/waitForResponse.mdx` +- `docs/commands/mock/request.mdx` +- `docs/commands/mock/requestOnce.mdx` +- `docs/commands/mock/redirect.mdx` +- `docs/commands/mock/redirectOnce.mdx` +- `docs/commands/mock/reset.mdx` +- Русские эквиваленты всех перечисленных файлов. + +**Источники и примеры:** + +- Пакет WDIO: `build/utils/interception/{index,bidi,devtools,webdriver,types}.d.ts` и соответствующие реализации в `build/index.js`. +- Документация: `docs/commands/mock/respond.mdx`, `docs/commands/mock/restore.mdx`. + +**Проверки:** + +- `npx --no-install prettier --check docs/commands/mock i18n/ru/docusaurus-plugin-content-docs/current/commands/mock` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` + +### Этап 4: Собственные расширения Testplane — 4 страницы + +**Что добавить:** + +- `browser.extendOptions`: поверхностное изменение `browser.options`; отличить от конфигурации Testplane и capabilities уже созданной сессии. +- `browser.unstable_captureDomSnapshot`: весь документ или CSS-селектор, фильтрация тегов и атрибутов, сокращение текста, поля результата и диагностические строки для некорректного или отсутствующего селектора. +- `element.unstable_captureDomSnapshot`: снимок поддерева конкретного элемента; ссылкой переиспользовать общую семантику настроек и результата без копирования двух полных справочников. +- `browser.unstable_getCdp`: собственный объект `CDP` Testplane либо `null`, ограничения подключения и отличие от `getPuppeteer`. Не предлагать закрывать общее соединение в пользовательском примере. +- Для команд `unstable_*` явно обозначить нестабильный контракт. Не обещать, что DOM-снимок очищен от секретов. + +**Файлы создать:** + +- `docs/commands/browser/extendOptions.mdx` +- `docs/commands/browser/unstable_captureDomSnapshot.mdx` +- `docs/commands/element/unstable_captureDomSnapshot.mdx` +- `docs/commands/browser/unstable_getCdp.mdx` +- Русские эквиваленты всех перечисленных файлов. + +**Источники и примеры:** + +- Testplane: `src/browser/types.ts`, `src/browser/browser.ts`, `src/browser/commands/captureDomSnapshot.ts`, `src/browser/commands/unstable_getCdp.ts`, `src/browser/cdp/index.ts`. +- Тесты: `test/src/browser/commands/captureDomSnapshot.ts`, `test/src/browser/existing-browser.js`. +- Документация: `docs/commands/browser/getConfig.mdx`, `docs/commands/browser/getPuppeteer.mdx`. + +**Проверки:** + +- `npx --no-install prettier --check docs/commands/browser docs/commands/element i18n/ru/docusaurus-plugin-content-docs/current/commands/browser i18n/ru/docusaurus-plugin-content-docs/current/commands/element` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` + +### Этап 5: Мобильные команды — 11 страниц + +**Что добавить:** + +- `browser.swipe`, `browser.tap`: жест и нажатие по координатам, параметры, ограничения платформы и контекста. Для `swipe` закрепленная реализация требует native-контекст, несмотря на более широкое обещание в комментарии WDIO. +- `browser.getContext`, `browser.getContexts`, `browser.switchContext`: обычные и подробные данные, native/webview, платформенные параметры и ожидания Android. +- `browser.relaunchActiveApp`, `browser.deepLink`: перезапуск приложения и открытие ссылки, идентификатор приложения и требования Appium. +- `element.tap`, `element.longPress`, `element.pinch`, `element.zoom`: действия над элементом, параметры жестов и поддерживаемые Appium-драйверы. +- На каждой странице отделить наличие унаследованного метода от требований к мобильной сессии и драйверу. Не представлять эти команды как доступные в обычном desktop-браузере. + +**Файлы создать:** + +- `docs/commands/browser/swipe.mdx` +- `docs/commands/browser/tap.mdx` +- `docs/commands/browser/getContext.mdx` +- `docs/commands/browser/getContexts.mdx` +- `docs/commands/browser/switchContext.mdx` +- `docs/commands/browser/relaunchActiveApp.mdx` +- `docs/commands/browser/deepLink.mdx` +- `docs/commands/element/tap.mdx` +- `docs/commands/element/longPress.mdx` +- `docs/commands/element/pinch.mdx` +- `docs/commands/element/zoom.mdx` +- Русские эквиваленты всех перечисленных файлов. + +**Источники и примеры:** + +- Пакет WDIO: `build/commands/mobile/*.d.ts`, соответствующие функции и проверки среды в `build/index.js`. +- Документация: `docs/guides/android-testing.mdx`, `docs/commands/element/touchAction.mdx`, `docs/commands/element/dragAndDrop.mdx`. + +**Проверки:** + +- `npx --no-install prettier --check docs/commands/browser docs/commands/element i18n/ru/docusaurus-plugin-content-docs/current/commands/browser i18n/ru/docusaurus-plugin-content-docs/current/commands/element` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` + +### Этап 6: Связанные исправления и навигация + +**Что изменить:** + +- Исправить `commands/overview`: актуальная версия v9, группы новых команд и границы справочника относительно протокольного API. +- Исправить `getState`: чтение JSON-файла без восстановления сессии, `stateOpts.path`, возврат `null` без пути, ошибки чтения; выровнять английский и русский текст. +- Дополнить `url`: вызов без URL и параметры BiDi, включая ожидание загрузки, заголовки и `onBeforeLoad`; связать с `addInitScript`. +- Обновить `mock`: требования Classic/CDP и BiDi, доступные методы, `calls` и события. Согласовать `abort`, `abortOnce`, `respond`, `respondOnce`, `clear`, `restore` с обеими реализациями без переписывания не затронутых примеров. +- Добавить ссылки на новые страницы в существующий гайд эмуляции, раздел селекторов, Android-гайд и страницу `dragAndDrop`. +- Для существующих `executeAsync` и `throttle` уточнить статус устаревания по закрепленному пакету и сослаться на рекомендуемые замены. +- Проверить автоматическое включение новых страниц через `autogenerated` в `sidebars.ts`; сохранить текущую структуру. Не создавать ручные записи для каждого нового файла и не менять оглавление v8. + +**Файлы изменить:** + +- `docs/commands/overview.mdx` +- `docs/commands/browser/getState.mdx` +- `docs/commands/browser/url.mdx` +- `docs/commands/browser/mock.mdx` +- `docs/commands/browser/executeAsync.mdx` +- `docs/commands/browser/throttle.mdx` +- `docs/commands/mock/abort.mdx`, `abortOnce.mdx`, `respond.mdx`, `respondOnce.mdx`, `clear.mdx`, `restore.mdx` +- `docs/commands/element/dragAndDrop.mdx` +- `docs/basic-guides/user-environment-emulation.mdx` +- `docs/basic-guides/selectors.mdx` +- `docs/guides/android-testing.mdx` +- Русские эквиваленты всех перечисленных файлов. + +**Источники и примеры:** + +- Testplane: `src/browser/commands/getState.ts`, `src/browser/existing-browser.ts`. +- Пакет WDIO: `build/index.js`, `build/commands/browser/url.d.ts`, `build/commands/browser/executeAsync.d.ts`, `build/commands/browser/throttle.d.ts`. +- Документация: `sidebars.ts`, `docusaurus.config.ts`, `docs/commands/browser/restoreState.mdx`. + +**Проверки:** + +- `npm test` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` + +### Этап 7: Итоговая проверка документации + +**Что проверить:** + +- Повторно сопоставить экспортируемые команды закрепленного WDIO, дополнения из `src/browser/types.ts` и созданные страницы; проверить наличие всех 39 новых страниц в обеих локалях. +- Сверить каждую сигнатуру, значение по умолчанию, возвращаемое значение и ограничение с реализацией. При расхождении комментария или типа и кода не переносить противоречие в текст. +- Проверить синтаксис примеров и отсутствие глобальных `$`, `$$`, `browser`, `driver` вне показанного контекста Testplane. +- Проверить относительные ссылки, одинаковые явные якоря в парных локалях, таблицы, код и предупреждения на собранном сайте. +- Просмотреть в браузере по одной странице каждого типа и наиболее сложные страницы `emulate`, `switchFrame`, `mock`, `unstable_captureDomSnapshot`, `getContexts` в обеих локалях. +- Не объявлять примеры выполненными на реальном Selenium Grid или Appium-устройстве без отдельного успешного прогона в такой среде. В итоговом отчете явно отделить проверку по исходникам и сборку от runtime-проверки. +- Подтвердить, что реализации Testplane/html-reporter, архив v8 и исходные рабочие каталоги не изменены. + +**Команды:** + +- `npm test` +- `npm run build -- --locale en` +- `npm run build -- --locale ru` +- `git diff --check` +- `git status --short` + +**Результат:** + +- Предоставить список новых и дополненных страниц, результаты проверок EN/RU и перечень не выполненных runtime-проверок. Не коммитить и не публиковать изменения без отдельного согласования. diff --git a/vibe/verified-examples/.gitignore b/vibe/verified-examples/.gitignore new file mode 100644 index 00000000..6e87586d --- /dev/null +++ b/vibe/verified-examples/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +validation-environment.json +*.log +!evidence/**/*.log +testplane-debug/ diff --git a/vibe/verified-examples/README.md b/vibe/verified-examples/README.md new file mode 100644 index 00000000..984e42dc --- /dev/null +++ b/vibe/verified-examples/README.md @@ -0,0 +1,37 @@ +# Проверенные примеры команд Testplane + +Проект получен из отдельного проекта, созданного командой `npm init testplane@latest`. Он сохраняет исполняемые примеры для 28 новых команд и связанных исправлений существующих страниц в обеих локалях. Проверяются 86 блоков кода на 82 страницах по 41 тестовому файлу; два файла содержат по два теста. `manifest.json` связывает страницу EN, страницу RU и тест. Скрипт `verify-snippets.cjs` проверяет точное совпадение блоков JavaScript с тестовыми файлами. + +## Окружение + +- Node.js 22.21.1. +- Testplane 9.3.2 и `@testplane/webdriverio` 9.6.2; полный граф зависимостей закреплен в `package-lock.json`. +- Chrome for Testing 150.0.7871.124, WebDriver BiDi. +- Для `downloadFile`: Selenium Grid 4.50.0 с управляемыми загрузками и `se:downloadsEnabled: true`. +- Исходный изолированный проект: `/private/tmp/testplane-docs-examples-20261009`. + +## Запуск + +Из этого каталога установите зависимости через `npm ci --legacy-peer-deps`. Флаг нужен для воспроизведения сохраненного lock-файла: в нем `@puppeteer/browsers@3.0.4` требует peer-зависимость `proxy-agent >=8.0.1`, а закреплена версия `6.5.0`. Без флага `npm ci` в npm 10.9.4 отклоняет этот граф. Зависимости и lock-файл при повторной проверке не обновлялись. `npm test` поднимает HTTP-стенд на свободном порту localhost, запускает браузерные тесты и закрывает HTTP-сервер после завершения. Сервер отдает только синтетические тестовые данные. Для получения Chrome и драйвера при первом запуске может понадобиться сеть. + +`npm run check:snippets` проверяет соответствие тестов страницам; запускайте эту команду из сохраненного в репозитории каталога, а не из отдельной копии проекта. + +`npm run test:selenium` запускает отдельный тест скачивания. Этот стенд рассчитан на **локальный Grid на той же машине**: HTTP-сервер слушает только `127.0.0.1`, и этот адрес передается браузеру в `baseUrl`. Grid по умолчанию ожидается на `http://127.0.0.1:4446`; другой локальный адрес или порт можно передать через `SELENIUM_GRID_URL`. Переменная `EXAMPLE_CHROME_BINARY` позволяет указать путь к браузеру на локальном узле Grid. Простая замена адреса на внешний Grid не сработает: удаленный браузер не видит локальный HTTP-стенд. Для удаленного запуска сначала нужен отдельно настроенный туннель, делающий этот loopback-адрес доступным в браузерной среде; такой запуск здесь не проверялся. Включите управляемые загрузки по [инструкции Selenium](https://www.selenium.dev/documentation/webdriver/remote_webdriver/#enable-downloads-in-the-grid). + +## Изоляция и найденные ограничения + +- `retry: 0`: итоговый успех не скрывает автоматические повторы. +- `testsPerSession: 1`: каждый тест использует новую браузерную сессию. +- `isolation: false` и одна вкладка нужны для проверенных примеров `throttleCPU` и `throttleNetwork`: реализация обращается к первой странице Puppeteer. +- В проверенной связке `throttleNetwork("online")` не восстанавливает сеть после `"offline"`. Пример завершает офлайн-сессию и проверяет доступность сети в следующей. +- В WDIO 9.6.2 флаг автоматической сетевой подписки общий для модуля. Примеры моков явно вызывают `sessionSubscribe`, чтобы получать события после создания новой сессии тем же воркером. +- `url` с `wait: "networkIdle"` в проверенной версии падает при поиске запроса навигации по context ID. Опубликованный пример использует `complete`; неуспешная диагностика сохранена отдельно. +- Мобильные команды отложены пользователем: рабочая Appium-среда пока не предоставлена. Эти примеры и страницы не добавлялись. + +## Результаты + +Финальные результаты и контрольные суммы примеров сохраняются в `evidence/results.json`. Логи этапов и окончательного прогона лежат рядом. Успех сборки MDX не считается доказательством выполнения примеров: в отчетах эти проверки разделены. + +Этапы 1–4 и немобильная часть этапа 6 выполнены. Итоговая проверка немобильного объема (этап 7) выполнена: 42/42 Chrome/BiDi и 1/1 Selenium, retries 0; линтер, типы, EN/RU build и сверка примеров проходят. 2026-10-14 исправлены оба оставшихся уточнения текста (`setViewport` и область действия `addInitScript`) в EN/RU; результаты повторной проверки сохраняются в `evidence/review-fixes-20261014/`. Подробнее — `evidence/review.md`. Этап 5 и мобильные ссылки этапа 6 отложены пользователем: 11 мобильных команд не добавлены и не проверены. Публикация требует отдельного согласования. + +В гайде эмуляции проверены три измененных JavaScript-блока (сеть и CPU). Остальные, не измененные этой задачей примеры гайдов не входят в отчет о runtime-проверке. diff --git a/vibe/verified-examples/evidence/baseline-build.txt b/vibe/verified-examples/evidence/baseline-build.txt new file mode 100644 index 00000000..cecaffb7 --- /dev/null +++ b/vibe/verified-examples/evidence/baseline-build.txt @@ -0,0 +1,32 @@ + +> testplane-docs@0.0.0 build +> docusaurus build + +[INFO] Website will be built for all these locales: +- en +- ru +[INFO] [en] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[WARNING] Docusaurus static site generation process emitted warnings for 2 paths +This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true +Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580 + +- "/docs/v8/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25950,"start":25946}],"span_labels":[]} + +- "/docs/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25423,"start":25419}],"span_labels":[]} + +[SUCCESS] Generated static files in "build". +[INFO] [ru] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[SUCCESS] Generated static files in "build/ru". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/baseline-test.txt b/vibe/verified-examples/evidence/baseline-test.txt new file mode 100644 index 00000000..4afbd2fa --- /dev/null +++ b/vibe/verified-examples/evidence/baseline-test.txt @@ -0,0 +1,13 @@ + +> testplane-docs@0.0.0 test +> npm run lint && npm run typecheck + + +> testplane-docs@0.0.0 lint +> eslint --cache src plugins && prettier --check . + +Checking formatting... +All matched files use Prettier code style! + +> testplane-docs@0.0.0 typecheck +> tsc diff --git a/vibe/verified-examples/evidence/commit-check-20261014/build-en.log b/vibe/verified-examples/evidence/commit-check-20261014/build-en.log new file mode 100644 index 00000000..a9eb2937 --- /dev/null +++ b/vibe/verified-examples/evidence/commit-check-20261014/build-en.log @@ -0,0 +1,25 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale en --out-dir /private/tmp/testplane-docs-review-20261014/precommit/build-en + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [en] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[WARNING] Docusaurus static site generation process emitted warnings for 2 paths +This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true +Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580 + +- "/docs/v8/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25944,"start":25940}],"span_labels":[]} + +- "/docs/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25417,"start":25413}],"span_labels":[]} + +[SUCCESS] Generated static files in "../../../../../../private/tmp/testplane-docs-review-20261014/precommit/build-en". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/commit-check-20261014/build-ru.log b/vibe/verified-examples/evidence/commit-check-20261014/build-ru.log new file mode 100644 index 00000000..82d27cbb --- /dev/null +++ b/vibe/verified-examples/evidence/commit-check-20261014/build-ru.log @@ -0,0 +1,15 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale ru --out-dir /private/tmp/testplane-docs-review-20261014/precommit/build-ru + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [ru] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[SUCCESS] Generated static files in "../../../../../../private/tmp/testplane-docs-review-20261014/precommit/build-ru". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/commit-check-20261014/desktop-run.log b/vibe/verified-examples/evidence/commit-check-20261014/desktop-run.log new file mode 100644 index 00000000..fed906d9 --- /dev/null +++ b/vibe/verified-examples/evidence/commit-check-20261014/desktop-run.log @@ -0,0 +1,47 @@ + +> testplane-docs-examples-20261009@1.0.0 test +> node run-examples.cjs + +[17:37:25 +0300] ✓ addInitScript: set a value before page scripts [chrome:5eb8223a809a212854acf9b9b907c763, pid:73172] - 149ms +[17:37:26 +0300] ✓ addLocatorStrategy: find by data attribute [chrome:611347c8523f1787e215b288a8082606, pid:73172] - 89ms +[17:37:28 +0300] ✓ custom$: find one element with a custom strategy [chrome:c5c007cc700e5527337be8cfbc5d31fd, pid:73172] - 85ms +[17:37:28 +0300] ✓ custom$$: find several elements [chrome:d6735b617008c458e492d7b38c9fa33a, pid:73172] - 74ms +[17:37:29 +0300] ✓ emulate: control time without waiting [chrome:92578f804dfcb4b06199fa8b2cdfa080, pid:73172] - 84ms +[17:37:31 +0300] ✓ restore: remove a user-agent override [chrome:2b3848b29ad163e105d73b3f79498bd9, pid:73172] - 190ms +[17:37:32 +0300] ✓ setViewport: change page dimensions [chrome:b2863d9829088f71cf772b9ae67363ea, pid:73172] - 76ms +[17:37:33 +0300] ✓ switchFrame: enter an iframe and return [chrome:7674684c847182fa3dbd7b08aa819d5d, pid:73172] - 114ms +[17:37:34 +0300] ✓ throttleCPU: run a page with a slower CPU [chrome:45ee9af4a3fdbe0e28aee8609681fa4b, pid:73172] - 120ms +[17:37:35 +0300] ✓ throttleNetwork: check a failed request while offline [chrome:9e14c652b883d763acd7daab751945f3, pid:73172] - 89ms +[17:37:36 +0300] ✓ throttleNetwork: the next session starts online [chrome:6ace7bd5d71932f3c8044fafbe099da1, pid:73172] - 75ms +[17:37:37 +0300] ✓ mock.abort: block matching BiDi requests [chrome:dd8323ea43600619fad0f674d034c681, pid:73172] - 114ms +[17:37:38 +0300] ✓ mock.abortOnce: block one BiDi request [chrome:7f590df8b2cc2938891175f2d823c6f2, pid:73172] - 112ms +[17:37:39 +0300] ✓ mock.clear: clear history but retain response rules [chrome:46f422ff7ba459211bc2a13a980c9c90, pid:73172] - 119ms +[17:37:40 +0300] ✓ browser.executeAsync: return a result through the injected callback [chrome:ae716cd29260ba1fd02bbe751088f3e1, pid:73172] - 103ms +[17:37:41 +0300] ✓ getState: read a saved file without restoring the session [chrome:bee8962111fcc1dc69b67d193c417230, pid:73172] - 83ms +[17:37:42 +0300] ✓ browser.mock: observe and replace a BiDi response [chrome:2e90f1396da6e439d2b1967c6c88f354, pid:73172] - 74ms +[17:37:43 +0300] ✓ commands: use the browser fixture [chrome:583062fa8e0cdd75a58a8c60755fdb2f, pid:73172] - 82ms +[17:37:44 +0300] ✓ commands: use this.browser with a regular function [chrome:fc2a042ac11abeb61d92af79078d731b, pid:73172] - 85ms +[17:37:45 +0300] ✓ mock.respond: replace response data and metadata in BiDi [chrome:27ffbd5caf61f48ea3fc5dd47de0061f, pid:73172] - 148ms +[17:37:46 +0300] ✓ mock.respondOnce: consume BiDi responses in order [chrome:5e2f3c6a2422e316d8830c3f77240e61, pid:73172] - 139ms +[17:37:47 +0300] ✓ mock.restore: remove interception before the next request [chrome:538c322e1bfa1e3140eafab49ef162bc, pid:73172] - 111ms +[17:37:49 +0300] ✓ slow network: fetch data under custom throughput limits [chrome:cbd4ca15876f441b580e7147324e7e7c, pid:73172] - 783ms +[17:37:50 +0300] ✓ throttle: use the deprecated alias in an isolated session [chrome:d254735ff3f8ca9b3a3012f6da272986, pid:73172] - 123ms +[17:37:51 +0300] ✓ url: navigate with BiDi options and read the current URL [chrome:1dee468eff70ec27e2bbf66dcc902bb2, pid:73172] - 151ms +[17:37:52 +0300] ✓ element.custom$: search inside an element [chrome:2f69815466ca5b891c462cdbacb9c7ad, pid:73172] - 78ms +[17:37:53 +0300] ✓ element.custom$$: find children with a custom strategy [chrome:6279ec6b8a7957f9e4cd9f469919bcdf, pid:73172] - 90ms +[17:37:54 +0300] ✓ element.execute: pass the DOM element to a browser function [chrome:a178b6af6ae7a8f880ca3facd5f14932, pid:73172] - 84ms +[17:37:55 +0300] ✓ element.executeAsync: return data through the final callback [chrome:a9d7c1e6bc66d96e53d43c05abe26a2a, pid:73172] - 108ms +[17:37:56 +0300] ✓ getElement: inspect a resolved element object [chrome:0796e602aaaad99078fff3c42fd0c423, pid:73172] - 87ms +[17:37:57 +0300] ✓ isStable: check whether element geometry changes [chrome:140cc0ca3c7038a7479b85ff6c310886, pid:73172] - 168ms +[17:37:59 +0300] ✓ waitForStable: wait for a size animation to stop [chrome:1b336dd42ff3ec04d3d9686432aac8da, pid:73172] - 621ms +[17:38:00 +0300] ✓ mock.redirect: send matching requests to another endpoint [chrome:33055b4ab73fdcc80e2319eca80236f9, pid:73172] - 83ms +[17:38:01 +0300] ✓ mock.redirectOnce: redirect only the next request [chrome:439cfebf06a18368ede84322d3af7ca7, pid:73172] - 128ms +[17:38:02 +0300] ✓ mock.request: change outgoing requests [chrome:6b15bfda38049147b1f4314bcf0d8f2b, pid:73172] - 119ms +[17:38:03 +0300] ✓ mock.requestOnce: change only the next request [chrome:9038fe8f67612799438325274a54c550, pid:73172] - 117ms +[17:38:04 +0300] ✓ mock.reset: remove overrides but keep the mock usable [chrome:bb688a29fae1cab4920f4ada2df5413f, pid:73172] - 132ms +[17:38:05 +0300] ✓ mock.waitForResponse: wait for a new recorded response [chrome:b549519f5d1573a7a98f488f1df85920, pid:73172] - 160ms +[17:38:06 +0300] ✓ extendOptions: change WebdriverIO wait settings for this browser [chrome:db58dffa75a68a6b296f5a5eab01220b, pid:73172] - 193ms +[17:38:07 +0300] ✓ browser.unstable_captureDomSnapshot: capture a filtered subtree [chrome:d4334b73c305de749a955ed95f694d28, pid:73172] - 101ms +[17:38:08 +0300] ✓ unstable_getCdp: query the connected Chromium browser [chrome:091d3215407898176c8b3d6a7eb2b08e, pid:73172] - 19ms +[17:38:09 +0300] ✓ element.unstable_captureDomSnapshot: capture only this element [chrome:0ac204b6897ea882beff2c4209c36025, pid:73172] - 103ms +[17:38:09 +0300] Total: 42 Passed: 42 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/commit-check-20261014/docs-test.log b/vibe/verified-examples/evidence/commit-check-20261014/docs-test.log new file mode 100644 index 00000000..4afbd2fa --- /dev/null +++ b/vibe/verified-examples/evidence/commit-check-20261014/docs-test.log @@ -0,0 +1,13 @@ + +> testplane-docs@0.0.0 test +> npm run lint && npm run typecheck + + +> testplane-docs@0.0.0 lint +> eslint --cache src plugins && prettier --check . + +Checking formatting... +All matched files use Prettier code style! + +> testplane-docs@0.0.0 typecheck +> tsc diff --git a/vibe/verified-examples/evidence/commit-check-20261014/selenium-run.log b/vibe/verified-examples/evidence/commit-check-20261014/selenium-run.log new file mode 100644 index 00000000..77810975 --- /dev/null +++ b/vibe/verified-examples/evidence/commit-check-20261014/selenium-run.log @@ -0,0 +1,6 @@ + +> testplane-docs-examples-20261009@1.0.0 test:selenium +> node run-examples.cjs --config testplane.selenium.config.js + +[17:38:13 +0300] ✓ downloadFile: copy a browser download from Selenium Grid [chrome:06e3889a9391775ad419872e7a2b7f58, pid:75239] - 1193ms +[17:38:13 +0300] Total: 1 Passed: 1 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/commit-check-20261014/snippets.log b/vibe/verified-examples/evidence/commit-check-20261014/snippets.log new file mode 100644 index 00000000..e21afd69 --- /dev/null +++ b/vibe/verified-examples/evidence/commit-check-20261014/snippets.log @@ -0,0 +1 @@ +Verified 86 localized blocks across 82 pages and 41 test files diff --git a/vibe/verified-examples/evidence/desktop-run.txt b/vibe/verified-examples/evidence/desktop-run.txt new file mode 100644 index 00000000..938e58f5 --- /dev/null +++ b/vibe/verified-examples/evidence/desktop-run.txt @@ -0,0 +1,47 @@ + +> testplane-docs-examples-20261009@1.0.0 test +> node run-examples.cjs + +[16:39:18 +0300] ✓ addInitScript: set a value before page scripts [chrome:a53f56edf37ce9c33678bfe627b1877d, pid:21569] - 132ms +[16:39:19 +0300] ✓ addLocatorStrategy: find by data attribute [chrome:ded8200f58a1590d71236f09228d0ec2, pid:21569] - 93ms +[16:39:20 +0300] ✓ custom$: find one element with a custom strategy [chrome:ad8f6b9dca630acb2a25add2dea1b424, pid:21569] - 73ms +[16:39:21 +0300] ✓ custom$$: find several elements [chrome:9cfe7b4ca922363359b59c027a0d6111, pid:21569] - 90ms +[16:39:22 +0300] ✓ emulate: control time without waiting [chrome:2764576d2b3a1ff015a804337d1d007e, pid:21569] - 198ms +[16:39:23 +0300] ✓ restore: remove a user-agent override [chrome:2bbac96df2abad9e5bf861bb2521af02, pid:21569] - 165ms +[16:39:24 +0300] ✓ setViewport: change page dimensions [chrome:ae46b5608b4cf3231fb941832bbde2f2, pid:21569] - 74ms +[16:39:25 +0300] ✓ switchFrame: enter an iframe and return [chrome:6d54769b28de688f565778911a276e34, pid:21569] - 131ms +[16:39:26 +0300] ✓ throttleCPU: run a page with a slower CPU [chrome:a24e2564a110c4e24209a2bcc47762b2, pid:21569] - 96ms +[16:39:27 +0300] ✓ throttleNetwork: check a failed request while offline [chrome:254740b5797acefd0a408dea8e5e3f44, pid:21569] - 88ms +[16:39:28 +0300] ✓ throttleNetwork: the next session starts online [chrome:4ccc4f1a875a813d946daead289a6d84, pid:21569] - 76ms +[16:39:29 +0300] ✓ mock.abort: block matching BiDi requests [chrome:3811560bff35759bd5322554a6518165, pid:21569] - 103ms +[16:39:31 +0300] ✓ mock.abortOnce: block one BiDi request [chrome:d6e14b4833a4ae77c443e224c2f2fce0, pid:21569] - 102ms +[16:39:32 +0300] ✓ mock.clear: clear history but retain response rules [chrome:01ce5a3570ca1686a6b715ca53a20b2c, pid:21569] - 114ms +[16:39:33 +0300] ✓ browser.executeAsync: return a result through the injected callback [chrome:5afe4cb4554eec6c1f9e940d8b31443a, pid:21569] - 101ms +[16:39:34 +0300] ✓ getState: read a saved file without restoring the session [chrome:d1ee99e053149ad70388b948d8ffc0b2, pid:21569] - 99ms +[16:39:35 +0300] ✓ browser.mock: observe and replace a BiDi response [chrome:28dd91fe5bb19ede6476f88f9e793606, pid:21569] - 92ms +[16:39:36 +0300] ✓ commands: use the browser fixture [chrome:be025f152e633fbb5a03f1033b483c0d, pid:21569] - 79ms +[16:39:37 +0300] ✓ commands: use this.browser with a regular function [chrome:607c3eba733df265583e965ca0ba85b7, pid:21569] - 85ms +[16:39:38 +0300] ✓ mock.respond: replace response data and metadata in BiDi [chrome:42b0e201b40b2ee0cb7abb07aec18ce9, pid:21569] - 136ms +[16:39:39 +0300] ✓ mock.respondOnce: consume BiDi responses in order [chrome:62addb6bb51374b2e4fea7874a4d3221, pid:21569] - 121ms +[16:39:40 +0300] ✓ mock.restore: remove interception before the next request [chrome:e5f13b5c4a48ed8b2d41a5954405bdfb, pid:21569] - 112ms +[16:39:41 +0300] ✓ slow network: fetch data under custom throughput limits [chrome:b084a4fd77d97d46d94dd4536282e570, pid:21569] - 779ms +[16:39:42 +0300] ✓ throttle: use the deprecated alias in an isolated session [chrome:b31ed2b79694909b0d9bc36abff78517, pid:21569] - 124ms +[16:39:43 +0300] ✓ url: navigate with BiDi options and read the current URL [chrome:0a78f3f9f0469ebfcb7262afdac15d48, pid:21569] - 209ms +[16:39:44 +0300] ✓ element.custom$: search inside an element [chrome:c5f146bddf3299c0ed7c9078d3892164, pid:21569] - 82ms +[16:39:45 +0300] ✓ element.custom$$: find children with a custom strategy [chrome:66a1bfe2e83cada8c3778dc982bcf6f1, pid:21569] - 94ms +[16:39:46 +0300] ✓ element.execute: pass the DOM element to a browser function [chrome:16ae5e4acd309d161a8ffd0e20abb164, pid:21569] - 82ms +[16:39:47 +0300] ✓ element.executeAsync: return data through the final callback [chrome:7ba0038cd156dba6ede6b3297f2c5dc3, pid:21569] - 109ms +[16:39:48 +0300] ✓ getElement: inspect a resolved element object [chrome:1913cac9e2bae67586b21bb0bc2c678e, pid:21569] - 86ms +[16:39:50 +0300] ✓ isStable: check whether element geometry changes [chrome:eb8a20cb9f6f6839503a01497a6063d8, pid:21569] - 144ms +[16:39:51 +0300] ✓ waitForStable: wait for a size animation to stop [chrome:2471fd38ef2189ede3490206b26ecb98, pid:21569] - 631ms +[16:39:52 +0300] ✓ mock.redirect: send matching requests to another endpoint [chrome:6575b111a2da04ce4ff1407b7e76e6c9, pid:21569] - 134ms +[16:39:53 +0300] ✓ mock.redirectOnce: redirect only the next request [chrome:18af8605085944ae44b1b7990a33c2c4, pid:21569] - 124ms +[16:39:54 +0300] ✓ mock.request: change outgoing requests [chrome:3e96523d89e2c99681f0307ab19c9769, pid:21569] - 114ms +[16:39:55 +0300] ✓ mock.requestOnce: change only the next request [chrome:24edd109c776a402242d18917aac9724, pid:21569] - 110ms +[16:39:56 +0300] ✓ mock.reset: remove overrides but keep the mock usable [chrome:47625225039efb30ea8efa55ae77b9e4, pid:21569] - 179ms +[16:39:57 +0300] ✓ mock.waitForResponse: wait for a new recorded response [chrome:fb3a36d95d37f952911a28be67cf8b54, pid:21569] - 132ms +[16:39:58 +0300] ✓ extendOptions: change WebdriverIO wait settings for this browser [chrome:a64f549a13a5dc0e480311d395f9e000, pid:21569] - 198ms +[16:39:59 +0300] ✓ browser.unstable_captureDomSnapshot: capture a filtered subtree [chrome:536b577b67d621c6257c85aa723f99ae, pid:21569] - 100ms +[16:40:00 +0300] ✓ unstable_getCdp: query the connected Chromium browser [chrome:c296762c472e7686b5bad2d6adde0e97, pid:21569] - 30ms +[16:40:01 +0300] ✓ element.unstable_captureDomSnapshot: capture only this element [chrome:1d7b7e70a68784e048a7b63f125e7839, pid:21569] - 88ms +[16:40:02 +0300] Total: 42 Passed: 42 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/docs-build-en.txt b/vibe/verified-examples/evidence/docs-build-en.txt new file mode 100644 index 00000000..892aca49 --- /dev/null +++ b/vibe/verified-examples/evidence/docs-build-en.txt @@ -0,0 +1,25 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale en + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [en] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[WARNING] Docusaurus static site generation process emitted warnings for 2 paths +This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true +Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580 + +- "/docs/v8/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25944,"start":25940}],"span_labels":[]} + +- "/docs/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25417,"start":25413}],"span_labels":[]} + +[SUCCESS] Generated static files in "build". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/docs-build-ru.txt b/vibe/verified-examples/evidence/docs-build-ru.txt new file mode 100644 index 00000000..48237726 --- /dev/null +++ b/vibe/verified-examples/evidence/docs-build-ru.txt @@ -0,0 +1,15 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale ru + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [ru] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[SUCCESS] Generated static files in "build". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/docs-build.txt b/vibe/verified-examples/evidence/docs-build.txt new file mode 100644 index 00000000..0066d9df --- /dev/null +++ b/vibe/verified-examples/evidence/docs-build.txt @@ -0,0 +1,35 @@ + +> testplane-docs@0.0.0 build +> docusaurus build + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] Website will be built for all these locales: +- en +- ru +[INFO] [en] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[WARNING] Docusaurus static site generation process emitted warnings for 2 paths +This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true +Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580 + +- "/docs/v8/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25950,"start":25946}],"span_labels":[]} + +- "/docs/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25423,"start":25419}],"span_labels":[]} + +[SUCCESS] Generated static files in "build". +[INFO] [ru] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[SUCCESS] Generated static files in "build/ru". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/docs-test.txt b/vibe/verified-examples/evidence/docs-test.txt new file mode 100644 index 00000000..4afbd2fa --- /dev/null +++ b/vibe/verified-examples/evidence/docs-test.txt @@ -0,0 +1,13 @@ + +> testplane-docs@0.0.0 test +> npm run lint && npm run typecheck + + +> testplane-docs@0.0.0 lint +> eslint --cache src plugins && prettier --check . + +Checking formatting... +All matched files use Prettier code style! + +> testplane-docs@0.0.0 typecheck +> tsc diff --git a/vibe/verified-examples/evidence/network-cdp-diagnostic.txt b/vibe/verified-examples/evidence/network-cdp-diagnostic.txt new file mode 100644 index 00000000..515fee5f --- /dev/null +++ b/vibe/verified-examples/evidence/network-cdp-diagnostic.txt @@ -0,0 +1,22 @@ +[13:27:08 +0300] ✓ diagnostic: reset networking using the same CDP session [chrome:e53a1ba2756882e1b69954d372d2a0b3, pid:78172] - 140ms +[13:27:10 +0300] ✘ throttleNetwork: go offline and reconnect [chrome:9eddf3fde881d0787678b476b88988bc, pid:78172] - 816ms +[13:27:10 +0300] Total: 2 Passed: 1 Failed: 1 Skipped: 0 Retries: 0 + +1) throttleNetwork: go offline and reconnect + in file diagnostics/throttleNetwork-original.testplane.js + + chrome + + .. | // diagnostics/throttleNetwork-original.testplane.js + .. | + 17 | await browser.throttleNetwork("online"); + 18 | } +> 19 | const status = await browser.execute(async () => { + | ^ WebdriverBidiExeception: TypeError: Failed to fetch + 20 | const response = await fetch("/?online-check", { cache: "no-store" }); + 21 | return response.status; + 22 | }); + + ✘ WebdriverBidiExeception: TypeError: Failed to fetch + at Object. (/private/tmp/testplane-docs-examples-20261009/diagnostics/throttleNetwork-original.testplane.js:21:34) + at process.processTicksAndRejections (node:internal/process/task_queues:105:5) diff --git a/vibe/verified-examples/evidence/network-reset-diagnostic.txt b/vibe/verified-examples/evidence/network-reset-diagnostic.txt new file mode 100644 index 00000000..6c6c2cf1 --- /dev/null +++ b/vibe/verified-examples/evidence/network-reset-diagnostic.txt @@ -0,0 +1,7 @@ +CURRENT HANDLE 9CEB71E4EE83D8372A278AA28D05018D +PAGE URLS [ 'http://127.0.0.1:64105/' ] +BEFORE 200 +AFTER OFFLINE { error: 'Failed to fetch' } +AFTER ONLINE { error: 'Failed to fetch' } +[14:18:57 +0300] ✓ diagnostic: inspect network target selection [chrome:327321650b13e5360357b3a3a3ad992b, pid:92932] - 186ms +[14:18:57 +0300] Total: 1 Passed: 1 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/nonmobile-audit.json b/vibe/verified-examples/evidence/nonmobile-audit.json new file mode 100644 index 00000000..9da26e7e --- /dev/null +++ b/vibe/verified-examples/evidence/nonmobile-audit.json @@ -0,0 +1,74 @@ +{ + "scope": "nonmobile; mobile stage5 deferred by user", + "localized_new_pages": 56, + "localized_modified_pages": 28, + "new_commands": 28, + "mapped_localized_blocks": 86, + "new_or_modified_blocks_checked": 86, + "test_files": 41, + "relative_file_links_checked": 416, + "export_inventory": { + "browser": { + "export_count": 44, + "missing": [] + }, + "element": { + "export_count": 53, + "missing": [] + }, + "mock": { + "methods": [ + "abort", + "abortOnce", + "clear", + "redirect", + "redirectOnce", + "request", + "requestOnce", + "reset", + "respond", + "respondOnce", + "restore", + "waitForResponse" + ], + "missing": [] + }, + "core_browser": { + "methods": ["addCommand", "overwriteCommand", "addLocatorStrategy"], + "missing": [] + }, + "testplane_augmentations": { + "browser": [ + "addTag", + "assertView", + "clearSession", + "extendOptions", + "getConfig", + "getMeta", + "getState", + "openAndWait", + "overwriteCommand", + "restoreState", + "runStep", + "saveState", + "setMeta", + "switchToRepl", + "unstable_captureDomSnapshot", + "unstable_getCdp", + "waitForStaticToLoad" + ], + "element": ["assertView", "moveCursorTo", "unstable_captureDomSnapshot"] + } + }, + "explicit_anchors": "matched across locale pairs; unique and present in built HTML", + "navigation": "28 new command pages present in EN/RU generated navigation", + "legacy_examples": "unchanged examples in existing guides are out of runtime scope", + "runtime_scope": "42 Chrome/BiDi tests + 1 Selenium test; mobile not executed", + "repository_status": { + "/Users/sipayrt/.codex/worktrees/missing-command-docs/testplane": "clean", + "/Users/sipayrt/.codex/worktrees/missing-command-docs/html-reporter": "clean", + "/Users/sipayrt/work/gemini-testing/testplane-docs": "clean", + "/Users/sipayrt/work/gemini-testing/testplane": "?? docs/dependency-security.md", + "/Users/sipayrt/work/gemini-testing/html-reporter": "clean" + } +} diff --git a/vibe/verified-examples/evidence/respond-cookie-green.txt b/vibe/verified-examples/evidence/respond-cookie-green.txt new file mode 100644 index 00000000..bd47d1b1 --- /dev/null +++ b/vibe/verified-examples/evidence/respond-cookie-green.txt @@ -0,0 +1,2 @@ +[16:37:57 +0300] ✓ mock.respond: replace response data and metadata in BiDi [chrome:58c940d2a38b894fe365dc937156a664, pid:19312] - 151ms +[16:37:57 +0300] Total: 1 Passed: 1 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/respond-cookie-red.txt b/vibe/verified-examples/evidence/respond-cookie-red.txt new file mode 100644 index 00000000..1eaab780 --- /dev/null +++ b/vibe/verified-examples/evidence/respond-cookie-red.txt @@ -0,0 +1,66 @@ +[16:37:14 +0300] Error: Unhandled Rejection in testplane:worker:18549: +[16:37:14 +0300] testplane:worker:18549 terminated unexpectedly with signal: SIGTERM +[16:37:14 +0300] ✘ mock.respond: replace response data and metadata in BiDi [chrome:aedfaa74aa7beade48277319ece7464e] - 146ms +[16:37:14 +0300] Total: 1 Passed: 0 Failed: 1 Skipped: 0 Retries: 0 + +1) mock.respond: replace response data and metadata in BiDi + in file examples/related/respond.testplane.js + + chrome + ✘ This run has been terminated due to an unhandled promise rejection. + + What happened: + - A promise rejected without being handled + What you can do: + - Analyze error below and check your tests for missing "await" statements + - Turn on the @typescript-eslint/no-floating-promises rule to catch + missing "await" statements automatically (works even for js files!) + + The error most likely originated from one of the tests below. + These tests were running or recently ran: + - "mock.respond: replace response data and metadata in BiDi" [chrome] (started at 16:37:14, took 125ms, RUNNING, worker PID: 18549, session: aedfaa74aa7beade48277319ece7464e, retry: 0) + File: /private/tmp/testplane-docs-examples-20261009/examples/related/respond.testplane.js + + Error details: Unhandled Rejection in testplane:worker:18549: + Reason: Error: WebDriver Bidi command "network.provideResponse" failed with error: invalid argument - Invalid input in "cookies"/0/"value". + at BidiHandler.send (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:485:23) + at BidiHandler.networkProvideResponse (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:899:31) + at Browser.value (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:1373:26) + at Browser.wrapCommandFn (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/wdio-utils/build/index.js:925:32) + at Browser. (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/wdio-utils/build/index.js:1027:20) + at #handleResponseStarted (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriverio/build/node.js:4575:28) + at EventEmitter.emit (node:events:519:28) + at EventEmitter.emit (node:domain:489:12) + at WebDriver.prototype. [as emit] (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/wdio-utils/build/index.js:490:29) + at Browser.parseBidiMessage (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:1386:10) + + + +[16:37:14 +0300] Terminating on critical error: This run has been terminated due to an unhandled promise rejection. + +What happened: +- A promise rejected without being handled +What you can do: +- Analyze error below and check your tests for missing "await" statements +- Turn on the @typescript-eslint/no-floating-promises rule to catch + missing "await" statements automatically (works even for js files!) + +The error most likely originated from one of the tests below. +These tests were running or recently ran: + - "mock.respond: replace response data and metadata in BiDi" [chrome] (started at 16:37:14, took 125ms, RUNNING, worker PID: 18549, session: aedfaa74aa7beade48277319ece7464e, retry: 0) + File: /private/tmp/testplane-docs-examples-20261009/examples/related/respond.testplane.js + +Error details: Unhandled Rejection in testplane:worker:18549: +Reason: Error: WebDriver Bidi command "network.provideResponse" failed with error: invalid argument - Invalid input in "cookies"/0/"value". + at BidiHandler.send (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:485:23) + at BidiHandler.networkProvideResponse (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:899:31) + at Browser.value (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:1373:26) + at Browser.wrapCommandFn (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/wdio-utils/build/index.js:925:32) + at Browser. (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/wdio-utils/build/index.js:1027:20) + at #handleResponseStarted (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriverio/build/node.js:4575:28) + at EventEmitter.emit (node:events:519:28) + at EventEmitter.emit (node:domain:489:12) + at WebDriver.prototype. [as emit] (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/wdio-utils/build/index.js:490:29) + at Browser.parseBidiMessage (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriver/build/node.js:1386:10) { + name: 'UnhandledRejectionTerminatedError' +} diff --git a/vibe/verified-examples/evidence/results.json b/vibe/verified-examples/evidence/results.json new file mode 100644 index 00000000..77ea5007 --- /dev/null +++ b/vibe/verified-examples/evidence/results.json @@ -0,0 +1,404 @@ +{ + "verified_at_utc": "2026-10-14T14:38:54.496491+00:00", + "scope": "Nonmobile scope complete; both remaining review wording clarifications fixed in EN/RU on 2026-10-14. Mobile stage 5 and mobile links deferred by user. Publication is not part of this task.", + "versions": { + "node": "22.21.1", + "testplane": "9.3.2", + "webdriverio": "9.6.2", + "chrome_for_testing": "150.0.7871.124", + "selenium": "4.50.0" + }, + "runtime": { + "desktop": { + "total": 42, + "passed": 42, + "failed": 0, + "retries": 0, + "log": "commit-check-20261014/desktop-run.log" + }, + "selenium": { + "total": 1, + "passed": 1, + "failed": 0, + "retries": 0, + "log": "commit-check-20261014/selenium-run.log" + } + }, + "docs": { + "localized_pages": 84, + "commands": 28, + "npm_test": "passed", + "build_en": "passed", + "build_ru": "passed", + "localized_new_pages": 56, + "localized_modified_pages": 28, + "localized_pages_with_verified_blocks": 82, + "verified_blocks": 86, + "combined_build": "passed", + "relative_file_links_checked": 416 + }, + "worktrees": { + "testplane-docs": { + "path": "/Users/sipayrt/.codex/worktrees/missing-command-docs/testplane-docs", + "base_sha": "7e662b60be11ac6813eca9c5d84c5142c043d01f" + }, + "testplane": { + "path": "/Users/sipayrt/.codex/worktrees/missing-command-docs/testplane", + "base_sha": "e19510b247523f40c23bf1dcbb719830220d6357" + }, + "html-reporter": { + "path": "/Users/sipayrt/.codex/worktrees/missing-command-docs/html-reporter", + "base_sha": "d24b6b72418a28f66ff5567f7b14a142b4b8d5e9" + } + }, + "examples": [ + { + "page": "docs/commands/browser/addInitScript.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx", + "test": "examples/browser/addInitScript.testplane.js", + "sha256": "bb1ea8a61cb14b83ade111b0bdc397a1de77ffa6077abe5e20a3a462eea41fa1" + }, + { + "page": "docs/commands/browser/addLocatorStrategy.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addLocatorStrategy.mdx", + "test": "examples/browser/addLocatorStrategy.testplane.js", + "sha256": "e2db1e4acbe31ce56504fd1a05fe59f6af573d3fc0e114c01aa0a47f26641a66" + }, + { + "page": "docs/commands/browser/customDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollar.mdx", + "test": "examples/browser/customDollar.testplane.js", + "sha256": "dbe9d45b25ee31681c746d3efd6f0381e71d4b31101348376fd0c49d9c029d63" + }, + { + "page": "docs/commands/browser/customDollarDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollarDollar.mdx", + "test": "examples/browser/customDollarDollar.testplane.js", + "sha256": "219b07ca4c8090feaad23176483749234a78f312dcbf79733badff189bb3a82c" + }, + { + "page": "docs/commands/browser/downloadFile.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/downloadFile.mdx", + "test": "examples/selenium/downloadFile.testplane.js", + "sha256": "f13c26d07d991df150afee9411045f2a149a6343dc30790e1a7d489c669b35c6" + }, + { + "page": "docs/commands/browser/emulate.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/emulate.mdx", + "test": "examples/browser/emulate.testplane.js", + "sha256": "d525d1ece01bc94c22705f1deff77be9965bdb110b8e3f08de9238d702f4b38a" + }, + { + "page": "docs/commands/browser/extendOptions.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/extendOptions.mdx", + "test": "examples/testplane-browser/extendOptions.testplane.js", + "sha256": "2c1f59fa75d8cd9f3a5f238f1e8a0652d0b3196b49dd84b8ae54103a1ffa5910" + }, + { + "page": "docs/commands/browser/restore.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/restore.mdx", + "test": "examples/browser/restore.testplane.js", + "sha256": "d67d1c0ceec6851dd7d4899e13e6943f2d92991386e76318bc1f7a485ba1d817" + }, + { + "page": "docs/commands/browser/setViewport.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx", + "test": "examples/browser/setViewport.testplane.js", + "sha256": "a9e21cedb776167e6a798568439165abce6c2ee19e871d11b822e49af906ab95" + }, + { + "page": "docs/commands/browser/switchFrame.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/switchFrame.mdx", + "test": "examples/browser/switchFrame.testplane.js", + "sha256": "83e2421201ade409ae8163f29e1f823c8a81e85af31cdf9792228696ce50e6c1" + }, + { + "page": "docs/commands/browser/throttleCPU.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleCPU.mdx", + "test": "examples/browser/throttleCPU.testplane.js", + "sha256": "968fd888a124f5db03f24cface4e50fdb1b3406da3d9388dafc929bc046785cd" + }, + { + "page": "docs/commands/browser/throttleNetwork.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx", + "test": "examples/browser/throttleNetwork.testplane.js", + "sha256": "b3496e94bafd2482a2e032dc325733f2ca9d023a712d33ffbff156a0bb51ade8" + }, + { + "page": "docs/commands/browser/unstable_captureDomSnapshot.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_captureDomSnapshot.mdx", + "test": "examples/testplane-browser/unstable_captureDomSnapshot.testplane.js", + "sha256": "f551df7c8ae0e74f91ad9204cb8115dfa8cf1c726d6821a8f1f512fb1f27417e" + }, + { + "page": "docs/commands/browser/unstable_getCdp.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_getCdp.mdx", + "test": "examples/testplane-browser/unstable_getCdp.testplane.js", + "sha256": "bf5b809d5084250ac88a74fc382d714ffc91e03fd4e117fcb9c1f22dcd278753" + }, + { + "page": "docs/commands/element/customDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollar.mdx", + "test": "examples/element/customDollar.testplane.js", + "sha256": "b3a579688123360632e96756f0936aed58c707e7d1e5234c0a84c464067265f2" + }, + { + "page": "docs/commands/element/customDollarDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollarDollar.mdx", + "test": "examples/element/customDollarDollar.testplane.js", + "sha256": "fa0a81cc4d34ce4a07759adde8d57c2a1e258cfab6e7680c812ea2372056c4c7" + }, + { + "page": "docs/commands/element/execute.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/execute.mdx", + "test": "examples/element/execute.testplane.js", + "sha256": "cc1c8428ffe01e8e0170dfe955218fc22ec6a01f9a2d297f32d7d6b7f8705461" + }, + { + "page": "docs/commands/element/executeAsync.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/executeAsync.mdx", + "test": "examples/element/executeAsync.testplane.js", + "sha256": "266df3b783ddac471147851f8d27bbfb6ee4c4a3bb0d15aff55f8a654c2fc6f4" + }, + { + "page": "docs/commands/element/getElement.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/getElement.mdx", + "test": "examples/element/getElement.testplane.js", + "sha256": "feefbd6979f8493a2fdd5b4870f8024cdd7d27a4f9a292e83bdf3d6b69dd6cb1" + }, + { + "page": "docs/commands/element/isStable.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/isStable.mdx", + "test": "examples/element/isStable.testplane.js", + "sha256": "ee082258ad603829340ba5eac5d485e3304619e7949ab65f8880f7c8baeb633e" + }, + { + "page": "docs/commands/element/unstable_captureDomSnapshot.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/unstable_captureDomSnapshot.mdx", + "test": "examples/testplane-element/unstable_captureDomSnapshot.testplane.js", + "sha256": "be199e34a79a42115aad07225acf10116e5806dc47ddb1d134ade4fda93bed30" + }, + { + "page": "docs/commands/element/waitForStable.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/waitForStable.mdx", + "test": "examples/element/waitForStable.testplane.js", + "sha256": "9066ff7b7cb5a3175e36e932a84abea58ea84ded8978eddce59a1569fb086edf" + }, + { + "page": "docs/commands/mock/redirect.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirect.mdx", + "test": "examples/mock/redirect.testplane.js", + "sha256": "f362a733e4909eb4b58e60bb45cc92ade9bf933ea3723c38bc939b13fce9f42e" + }, + { + "page": "docs/commands/mock/redirectOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirectOnce.mdx", + "test": "examples/mock/redirectOnce.testplane.js", + "sha256": "ed86e4c7146b3484f941e186ac4c49baecd4148bbb358d15e3363a2a622d8ed0" + }, + { + "page": "docs/commands/mock/request.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/request.mdx", + "test": "examples/mock/request.testplane.js", + "sha256": "b8da8bfc95355851d8a01785b798961171b811104e97f4b2eb52a7fad504165c" + }, + { + "page": "docs/commands/mock/requestOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/requestOnce.mdx", + "test": "examples/mock/requestOnce.testplane.js", + "sha256": "e339931f04966a2aa0e87f4176b215c1feb0f333c30de876fcec4b786b95d435" + }, + { + "page": "docs/commands/mock/reset.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/reset.mdx", + "test": "examples/mock/reset.testplane.js", + "sha256": "3fe14483ea475775f8f91baf57058d516a6e25eb004448918f0ec77dae2fec99" + }, + { + "page": "docs/commands/mock/waitForResponse.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/waitForResponse.mdx", + "test": "examples/mock/waitForResponse.testplane.js", + "sha256": "d78605f47c178250380369deff5a11143425e2a0fe310829b905fc9482ae74a8" + }, + { + "page": "docs/commands/browser/getState.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/getState.mdx", + "test": "examples/related/getState.testplane.js", + "sha256": "436875f2534701695a3c767da1fb6234d01fd34d15562f0cc152abefe19f4706" + }, + { + "page": "docs/commands/browser/url.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/url.mdx", + "test": "examples/related/url.testplane.js", + "sha256": "9631e160135bc2e2a60190ea4e417c1b980527eb5bcc022af7ca726939a2ff23" + }, + { + "page": "docs/commands/browser/executeAsync.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/executeAsync.mdx", + "test": "examples/related/executeAsync.testplane.js", + "sha256": "e356a27fa52422114b132925b0677afd048ceb11183aa8f0b9fd3ffcfd1365fb" + }, + { + "page": "docs/commands/browser/throttle.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttle.mdx", + "test": "examples/related/throttle.testplane.js", + "sha256": "8ff3e494546ffb0534d3afe97da31b7a020ac01d7380646c107e988967abcfac" + }, + { + "page": "docs/commands/browser/mock.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/mock.mdx", + "test": "examples/related/mock.testplane.js", + "sha256": "d2c152fdefa34c00f5667c1583f4b75655482216dd3dfa5c13d1958e8762671a" + }, + { + "page": "docs/commands/mock/abort.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abort.mdx", + "test": "examples/related/abort.testplane.js", + "sha256": "aec9e28a6676cf70bf66bdc72e8a466db5ec19cfd759176f1a1257ca34d025e2" + }, + { + "page": "docs/commands/mock/abortOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abortOnce.mdx", + "test": "examples/related/abortOnce.testplane.js", + "sha256": "52bcbe56f930af6174ee870b2e6bd27a352001038d9317018d7f530dacbe598b" + }, + { + "page": "docs/commands/mock/respond.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respond.mdx", + "test": "examples/related/respond.testplane.js", + "sha256": "24ffd149c46e561bd401905eacdd54161b755e047787016bed06ec8a729345e1" + }, + { + "page": "docs/commands/mock/respondOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respondOnce.mdx", + "test": "examples/related/respondOnce.testplane.js", + "sha256": "91b19acf6ff1af404dbe30ef3429503cea39388ddaf755df5a7c473d13b90cf1" + }, + { + "page": "docs/commands/mock/clear.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/clear.mdx", + "test": "examples/related/clear.testplane.js", + "sha256": "492b041e8070ba8d34da0a719e399aea88dc21d2e5a0ebdd3fdf44e30f14c9af" + }, + { + "page": "docs/commands/mock/restore.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/restore.mdx", + "test": "examples/related/restore.testplane.js", + "sha256": "94b75b88a21fe768831cfabed74af9467e6dc93d36affc338c2c715af310b1a0" + }, + { + "page": "docs/commands/overview.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx", + "test": "examples/related/overview.testplane.js", + "sha256": "19de249697daf73acc83d6e417d0e5f7f03d9bde4117bc3fe9542062c515c48f" + }, + { + "page": "docs/basic-guides/user-environment-emulation.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", + "test": "examples/browser/throttleNetwork.testplane.js", + "blockIndex": 0, + "blockCount": 3, + "sha256": "b3496e94bafd2482a2e032dc325733f2ca9d023a712d33ffbff156a0bb51ade8" + }, + { + "page": "docs/basic-guides/user-environment-emulation.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", + "test": "examples/related/slowNetwork.testplane.js", + "blockIndex": 1, + "blockCount": 3, + "sha256": "c7adce91647bcd4fbe23f7547b78f5209b1612dc6a8b2c00d040d543a5ca4008" + }, + { + "page": "docs/basic-guides/user-environment-emulation.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", + "test": "examples/browser/throttleCPU.testplane.js", + "blockIndex": 2, + "blockCount": 3, + "sha256": "968fd888a124f5db03f24cface4e50fdb1b3406da3d9388dafc929bc046785cd" + } + ], + "unverified": [ + "Mobile/Appium commands and associated links (user-deferred stage 5 and mobile part of stage 6)", + "Firefox, broad Classic/CDP, Sauce Labs and external Grid runtime compatibility", + "Unchanged examples in existing guides", + "Illustration workflow: infradocs-screenshot-producer unavailable; no illustrations created" + ], + "log_format": "Captured output; build logs normalized to LF without trailing whitespace.", + "review": { + "important_fixed": [ + "mock.respond accepts WebDriver Cookie[]; real RED→GREEN and final whole suite passed" + ], + "minor_deferred": [], + "report": "review.md", + "independent_runtime_rerun": false, + "minor_fixed": [ + "setViewport: actual WDIO client bounds distinguished from backend limits (EN/RU)", + "addInitScript: registration scoped to current top-level context and child frames; other windows require registration (EN/RU)" + ], + "minor_fixes_verified_at": "2026-10-14", + "independent_review_date": "2026-10-09" + }, + "follow_up_verification": { + "date": "2026-10-14", + "previous_full_verification_at_utc": "2026-10-09T13:42:35.347994+00:00", + "example_project": "/private/tmp/testplane-docs-review-20261014/project", + "dependency_install": { + "command": "npm ci --legacy-peer-deps --no-audit --no-fund", + "result": "passed", + "package_and_lock_unchanged": true, + "plain_npm_ci": "failed: saved proxy-agent 6.5.0 conflicts with @puppeteer/browsers 3.0.4 peer >=8.0.1", + "failure_log": "review-fixes-20261014/npm-ci.log", + "success_log": "review-fixes-20261014/npm-ci-legacy.log" + }, + "checks": { + "npm_test": "passed", + "build_en": "passed", + "build_ru": "passed", + "snippets": { + "blocks": 86, + "pages": 82, + "test_files": 41, + "result": "passed" + }, + "visual_pages": 4, + "explicit_html_anchors": 28, + "code_links_anchors_and_examples_unchanged": true, + "scope_snapshot_files": 1037, + "mobile_changed": false, + "source_worktrees_clean": true + }, + "logs": { + "npm_test": "review-fixes-20261014/final-docs-test.log", + "build_en": "review-fixes-20261014/build-en.log", + "build_ru": "review-fixes-20261014/build-ru.log", + "snippets": "review-fixes-20261014/snippets.log", + "source_and_scope": "review-fixes-20261014/scope-check.json", + "html": "review-fixes-20261014/html-check.json" + }, + "historical_only": [ + "docs.combined_build and docs.relative_file_links_checked refer to 2026-10-09; not rerun for this prose-only follow-up" + ], + "build_warnings": "Existing SWC/Babel, retry-progressive HTML minifier and admonition undefined warnings; builds successful", + "independent_review_rerun": false + }, + "pre_commit_verification": { + "date": "2026-10-14", + "branch": "codex/missing-command-docs", + "scope": "nonmobile only; user authorized local commit, not push or publication", + "npm_test": "passed", + "build_en": "passed", + "build_ru": "passed", + "runtime": { + "desktop_passed": 42, + "selenium_passed": 1, + "failed": 0, + "retries": 0 + }, + "snippet_blocks": 86, + "snippet_pages": 82, + "snippet_test_files": 41, + "saved_test_sha256": "all match", + "logs_directory": "commit-check-20261014", + "mobile_changes": false + } +} diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/build-en.log b/vibe/verified-examples/evidence/review-fixes-20261014/build-en.log new file mode 100644 index 00000000..e5aa3e07 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/build-en.log @@ -0,0 +1,25 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale en --out-dir build-review-en + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [en] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[WARNING] Docusaurus static site generation process emitted warnings for 2 paths +This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true +Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580 + +- "/docs/v8/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25944,"start":25940}],"span_labels":[]} + +- "/docs/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25417,"start":25413}],"span_labels":[]} + +[SUCCESS] Generated static files in "build-review-en". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/build-ru.log b/vibe/verified-examples/evidence/review-fixes-20261014/build-ru.log new file mode 100644 index 00000000..85f509f4 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/build-ru.log @@ -0,0 +1,15 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale ru --out-dir build-review-ru + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [ru] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[SUCCESS] Generated static files in "build-review-ru". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/desktop-run.log b/vibe/verified-examples/evidence/review-fixes-20261014/desktop-run.log new file mode 100644 index 00000000..33ce791a --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/desktop-run.log @@ -0,0 +1,47 @@ + +> testplane-docs-examples-20261009@1.0.0 test +> node run-examples.cjs + +[16:48:23 +0300] ✓ addInitScript: set a value before page scripts [chrome:087c1167e475f3465695d4ddf2ec0dde, pid:43208] - 161ms +[16:48:24 +0300] ✓ addLocatorStrategy: find by data attribute [chrome:b78538faf515d909a54aa3fa7fcac63a, pid:43208] - 89ms +[16:48:25 +0300] ✓ custom$: find one element with a custom strategy [chrome:daee7b966191d03c79560f58e6819aaf, pid:43208] - 90ms +[16:48:26 +0300] ✓ custom$$: find several elements [chrome:8351b4c0689f611bad8868ecee3a6a85, pid:43208] - 90ms +[16:48:27 +0300] ✓ emulate: control time without waiting [chrome:b9dba2509fe7ce57c32df20a11b1e5e9, pid:43208] - 165ms +[16:48:28 +0300] ✓ restore: remove a user-agent override [chrome:7d29c6038be3eb45bd9f06794c64b4c9, pid:43208] - 162ms +[16:48:29 +0300] ✓ setViewport: change page dimensions [chrome:71923e16c1f6b9fecff3ba24a83b0a3e, pid:43208] - 94ms +[16:48:30 +0300] ✓ switchFrame: enter an iframe and return [chrome:00c3e68ddeb356c5a871bbee574b209e, pid:43208] - 115ms +[16:48:31 +0300] ✓ throttleCPU: run a page with a slower CPU [chrome:d7607b7fd2e2b71d3b22425346168971, pid:43208] - 96ms +[16:48:32 +0300] ✓ throttleNetwork: check a failed request while offline [chrome:423d788231aad117c561819380c29b78, pid:43208] - 94ms +[16:48:33 +0300] ✓ throttleNetwork: the next session starts online [chrome:83a96631e61d82e8d99349003994d6ed, pid:43208] - 68ms +[16:48:34 +0300] ✓ mock.abort: block matching BiDi requests [chrome:0dbe1b5a172ff7611006859c20118640, pid:43208] - 104ms +[16:48:35 +0300] ✓ mock.abortOnce: block one BiDi request [chrome:7bbd1ab0dc41fd64c34d2184c25a9b24, pid:43208] - 102ms +[16:48:36 +0300] ✓ mock.clear: clear history but retain response rules [chrome:daedafce27229b57a0d67ef1a7b75c8a, pid:43208] - 116ms +[16:48:37 +0300] ✓ browser.executeAsync: return a result through the injected callback [chrome:98bf7d662e069c217ea868a0b4fb36fb, pid:43208] - 101ms +[16:48:38 +0300] ✓ getState: read a saved file without restoring the session [chrome:0b41f88c3c45ec931241146a1bbc4ee5, pid:43208] - 98ms +[16:48:39 +0300] ✓ browser.mock: observe and replace a BiDi response [chrome:93523193adb970e082806acc4ed872c7, pid:43208] - 111ms +[16:48:40 +0300] ✓ commands: use the browser fixture [chrome:53b373ae7021250a10fbf2e7ae76d535, pid:43208] - 75ms +[16:48:41 +0300] ✓ commands: use this.browser with a regular function [chrome:277f26902f1d1f9fb447b709dd8eb1d1, pid:43208] - 103ms +[16:48:42 +0300] ✓ mock.respond: replace response data and metadata in BiDi [chrome:286f5214a419c0f8f91d02a6d4f03036, pid:43208] - 131ms +[16:48:43 +0300] ✓ mock.respondOnce: consume BiDi responses in order [chrome:b28036d71036b39c0722f58ea900d679, pid:43208] - 105ms +[16:48:44 +0300] ✓ mock.restore: remove interception before the next request [chrome:c7003fb53a3d8e210c3cd3626dd79f2a, pid:43208] - 84ms +[16:48:46 +0300] ✓ slow network: fetch data under custom throughput limits [chrome:8499ee6db63860f19bfb7c195bdab520, pid:43208] - 791ms +[16:48:47 +0300] ✓ throttle: use the deprecated alias in an isolated session [chrome:9cc98652c26f941908989679bc4495d3, pid:43208] - 131ms +[16:48:48 +0300] ✓ url: navigate with BiDi options and read the current URL [chrome:6b1373d305ccc6b6343b19c29236a19d, pid:43208] - 213ms +[16:48:49 +0300] ✓ element.custom$: search inside an element [chrome:22095dda764576849ece8977f8c6bbbc, pid:43208] - 100ms +[16:48:50 +0300] ✓ element.custom$$: find children with a custom strategy [chrome:8ad459f76736cebbc94648f017bad7ab, pid:43208] - 97ms +[16:48:51 +0300] ✓ element.execute: pass the DOM element to a browser function [chrome:b7477a6f8c27d29aa19d8da8e78c20f2, pid:43208] - 97ms +[16:48:52 +0300] ✓ element.executeAsync: return data through the final callback [chrome:f681d76bf939edc3e7c126b704e1d5dd, pid:43208] - 112ms +[16:48:53 +0300] ✓ getElement: inspect a resolved element object [chrome:bdc7a1bc76426a164575ddbc0c42bf3f, pid:43208] - 79ms +[16:48:55 +0300] ✓ isStable: check whether element geometry changes [chrome:75d194fefc36576a38f009acdbfc3569, pid:43208] - 158ms +[16:48:56 +0300] ✓ waitForStable: wait for a size animation to stop [chrome:14017e08800c76bf6f04a12f9d584b09, pid:43208] - 639ms +[16:48:57 +0300] ✓ mock.redirect: send matching requests to another endpoint [chrome:1a562ae6e9f07f10f68e77d4092861fe, pid:43208] - 103ms +[16:48:58 +0300] ✓ mock.redirectOnce: redirect only the next request [chrome:ef51d3b125e9fbe3e8da927ea5e5d1b5, pid:43208] - 151ms +[16:48:59 +0300] ✓ mock.request: change outgoing requests [chrome:35550f7c535e1a2f506a623342d0f442, pid:43208] - 122ms +[16:49:00 +0300] ✓ mock.requestOnce: change only the next request [chrome:a70d9e69ff79a94e9a7fec89169e95f0, pid:43208] - 148ms +[16:49:01 +0300] ✓ mock.reset: remove overrides but keep the mock usable [chrome:601b7daa9ac8293cafa45ef143111246, pid:43208] - 179ms +[16:49:03 +0300] ✓ mock.waitForResponse: wait for a new recorded response [chrome:c99cfebdba78300b0d20d98a6dc6b6a3, pid:43208] - 158ms +[16:49:04 +0300] ✓ extendOptions: change WebdriverIO wait settings for this browser [chrome:6d89527790366e421560ec1043bfc1dd, pid:43208] - 233ms +[16:49:05 +0300] ✓ browser.unstable_captureDomSnapshot: capture a filtered subtree [chrome:64b1355e6c136658bacfa15768ad5d15, pid:43208] - 101ms +[16:49:06 +0300] ✓ unstable_getCdp: query the connected Chromium browser [chrome:575e3ffa8e3dc220c1ba9901c486c21e, pid:43208] - 26ms +[16:49:07 +0300] ✓ element.unstable_captureDomSnapshot: capture only this element [chrome:f722a1acfd61b561c315b90a38bb29ef, pid:43208] - 133ms +[16:49:07 +0300] Total: 42 Passed: 42 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/docs-test.log b/vibe/verified-examples/evidence/review-fixes-20261014/docs-test.log new file mode 100644 index 00000000..4afbd2fa --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/docs-test.log @@ -0,0 +1,13 @@ + +> testplane-docs@0.0.0 test +> npm run lint && npm run typecheck + + +> testplane-docs@0.0.0 lint +> eslint --cache src plugins && prettier --check . + +Checking formatting... +All matched files use Prettier code style! + +> testplane-docs@0.0.0 typecheck +> tsc diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/final-docs-test.log b/vibe/verified-examples/evidence/review-fixes-20261014/final-docs-test.log new file mode 100644 index 00000000..4afbd2fa --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/final-docs-test.log @@ -0,0 +1,13 @@ + +> testplane-docs@0.0.0 test +> npm run lint && npm run typecheck + + +> testplane-docs@0.0.0 lint +> eslint --cache src plugins && prettier --check . + +Checking formatting... +All matched files use Prettier code style! + +> testplane-docs@0.0.0 typecheck +> tsc diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/grid-downloads.json b/vibe/verified-examples/evidence/review-fixes-20261014/grid-downloads.json new file mode 100644 index 00000000..acb7dc65 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/grid-downloads.json @@ -0,0 +1,14 @@ +{ + "java": { + "version": "jdk-21.0.12.1+1", + "url": "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.12.1%2B1/OpenJDK21U-jre_aarch64_mac_hotspot_21.0.12.1_1.tar.gz", + "sha256": "dec50fc6f9fcd4fe3ae8cabf5a5fa68f6afc48841f7698e468e9aa5d54beed84", + "name": "OpenJDK21U-jre_aarch64_mac_hotspot_21.0.12.1_1.tar.gz" + }, + "selenium": { + "version": "4.50.0", + "url": "https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.50.0/selenium-server-4.50.0.jar", + "digest": "sha256:4664ef41d4bf5ee78cd6264ff8dca480ef80628ca91df95ca554c4d66f7831c1", + "name": "selenium-server-4.50.0.jar" + } +} diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/html-check.json b/vibe/verified-examples/evidence/review-fixes-20261014/html-check.json new file mode 100644 index 00000000..3958f070 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/html-check.json @@ -0,0 +1,26 @@ +[ + { + "locale": "en", + "page": "setViewport", + "text": "passed", + "anchors": 7 + }, + { + "locale": "en", + "page": "addInitScript", + "text": "passed", + "anchors": 7 + }, + { + "locale": "ru", + "page": "setViewport", + "text": "passed", + "anchors": 7 + }, + { + "locale": "ru", + "page": "addInitScript", + "text": "passed", + "anchors": 7 + } +] diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/npm-ci-legacy.log b/vibe/verified-examples/evidence/review-fixes-20261014/npm-ci-legacy.log new file mode 100644 index 00000000..6157b7c5 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/npm-ci-legacy.log @@ -0,0 +1,7 @@ +npm warn deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. +npm warn deprecated rimraf@2.2.8: Rimraf versions prior to v4 are no longer supported +npm warn deprecated glob@7.2.0: 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 +npm warn deprecated node-domexception@1.0.0: Use your platform's native DOMException instead +npm warn deprecated glob@10.5.0: 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 + +added 625 packages in 2s diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/npm-ci.log b/vibe/verified-examples/evidence/review-fixes-20261014/npm-ci.log new file mode 100644 index 00000000..0b74b32b --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/npm-ci.log @@ -0,0 +1,39 @@ +npm error code EUSAGE +npm error +npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing. +npm error +npm error Invalid: lock file's proxy-agent@6.5.0 does not satisfy proxy-agent@8.0.2 +npm error Missing: agent-base@9.0.0 from lock file +npm error Missing: http-proxy-agent@9.1.0 from lock file +npm error Missing: https-proxy-agent@9.1.0 from lock file +npm error Missing: pac-proxy-agent@9.1.0 from lock file +npm error Missing: proxy-from-env@2.1.0 from lock file +npm error Missing: socks-proxy-agent@10.1.0 from lock file +npm error Missing: proxy-agent@6.5.0 from lock file +npm error Missing: lru-cache@7.18.3 from lock file +npm error Missing: proxy-agent-negotiate@1.1.0 from lock file +npm error Missing: get-uri@8.0.1 from lock file +npm error Missing: pac-resolver@9.0.1 from lock file +npm error Missing: quickjs-wasi@2.2.0 from lock file +npm error Missing: data-uri-to-buffer@8.0.0 from lock file +npm error Missing: degenerator@7.0.1 from lock file +npm error Missing: ast-types@0.13.4 from lock file +npm error +npm error Clean install a project +npm error +npm error Usage: +npm error npm ci +npm error +npm error Options: +npm error [--install-strategy ] [--legacy-bundling] +npm error [--global-style] [--omit [--omit ...]] +npm error [--include [--include ...]] +npm error [--strict-peer-deps] [--foreground-scripts] [--ignore-scripts] [--no-audit] +npm error [--no-bin-links] [--no-fund] [--dry-run] +npm error [-w|--workspace [-w|--workspace ...]] +npm error [-ws|--workspaces] [--include-workspace-root] [--install-links] +npm error +npm error aliases: clean-install, ic, install-clean, isntall-clean +npm error +npm error Run "npm help ci" for more info +npm error A complete log of this run can be found in: /Users/sipayrt/.npm/_logs/2026-10-14T13_44_44_908Z-debug-0.log diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/scope-check.json b/vibe/verified-examples/evidence/review-fixes-20261014/scope-check.json new file mode 100644 index 00000000..6ac5611a --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/scope-check.json @@ -0,0 +1,19 @@ +{ + "checked_files": [ + "docs/commands/browser/addInitScript.mdx", + "docs/commands/browser/setViewport.mdx", + "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx", + "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx" + ], + "prose_contract": "passed", + "code_fences_links_anchors": "unchanged", + "examples_configs_lock": "unchanged", + "other_mdx_in_snapshot": "unchanged", + "snapshot_files": 1037, + "source_version": "@testplane/webdriverio 9.6.2", + "source_sha256": "1923e38ec895d9ce3842a5d125adcde6f13273342fa54b3c960e3ffe6e017e84", + "source_assertions": [ + "maxWindowSize = Number.MAX_SAFE_INTEGER", + "addInitScript: getWindowHandle and contexts: [context]" + ] +} diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/selenium-run.log b/vibe/verified-examples/evidence/review-fixes-20261014/selenium-run.log new file mode 100644 index 00000000..d9b05030 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/selenium-run.log @@ -0,0 +1,6 @@ + +> testplane-docs-examples-20261009@1.0.0 test:selenium +> node run-examples.cjs --config testplane.selenium.config.js + +[16:57:17 +0300] ✓ downloadFile: copy a browser download from Selenium Grid [chrome:385104efa39aa4e3eef4c1ff08236fde, pid:57734] - 1207ms +[16:57:17 +0300] Total: 1 Passed: 1 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/review-fixes-20261014/snippets.log b/vibe/verified-examples/evidence/review-fixes-20261014/snippets.log new file mode 100644 index 00000000..e21afd69 --- /dev/null +++ b/vibe/verified-examples/evidence/review-fixes-20261014/snippets.log @@ -0,0 +1 @@ +Verified 86 localized blocks across 82 pages and 41 test files diff --git a/vibe/verified-examples/evidence/review.md b/vibe/verified-examples/evidence/review.md new file mode 100644 index 00000000..80f2a2e4 --- /dev/null +++ b/vibe/verified-examples/evidence/review.md @@ -0,0 +1,30 @@ +# Итоговое ревью немобильного объема + +2026-10-09: независимое read-only ревью всех 28 новых команд EN/RU, 14 измененных существующих страниц EN/RU, исполняемых примеров и закрепленных исходников. Проверка включала untracked-файлы: на момент ревью работа еще не была закоммичена. + +## Результат + +Критических дефектов не найдено. Одно важное замечание исправлено: `mock.respond` принимает WebDriver `Cookie[]`, а не готовые BiDi cookies. Входное `value` — строка, время истечения — `expiry`; преобразование в BiDi выполняет библиотека. Уточнение внесено в обе локали. Реальный тест с BiDi-объектом значения завершился ошибкой `network.provideResponse: Invalid input in cookies/0/value`; тот же тест со строкой прошел и проверил установленную cookie через `getCookies`. Логи: `respond-cookie-red.txt`, `respond-cookie-green.txt`. Окончательный общий прогон сохраняется в `results.json`. + +Ревьюер самостоятельно проверил соответствие 86 блоков на 82 страницах 41 тестовому файлу, все 43 названия тестов в успешных логах, ссылки, якоря, изоляцию стенда и чистоту исходников framework. Повторный запуск браузерных тестов выполняет автор изменений, не ревьюер. + +## Уточнения, исправленные 2026-10-14 + +- `setViewport`: в обеих локалях указан фактический верхний порог клиентской проверки WDIO 9.6.2 — `Number.MAX_SAFE_INTEGER`, а не число из сообщения об ошибке. Отдельно указано, что драйвер может накладывать более строгие ограничения: успешная клиентская проверка не гарантирует поддержку размера браузером. +- `addInitScript`: в обеих локалях область действия ограничена текущей при вызове вкладкой или окном и дочерними фреймами. Для других вкладок и окон, включая открытые позже, требуется переключиться в нужный контекст и зарегистрировать скрипт отдельно до навигации. + +Уточнения сверены с установленным `@testplane/webdriverio@9.6.2`: `setViewport` использует `maxWindowSize = Number.MAX_SAFE_INTEGER`, а `addInitScript` передает `contexts: [await getWindowHandle()]` в регистрацию preload-скрипта. Это проверка исходников, а не обещание успешной установки огромного viewport. Код примеров, ссылки и явные якоря не изменены. + +Повторные проверки 2026-10-14 выполняет автор исправлений; отдельное новое независимое ревью не запускалось. Предыдущее независимое ревью датировано 2026-10-09. Свежие логи — в `review-fixes-20261014/`, сводка — в `results.json`. + +## Границы решения + +- Мобильные 11 команд и ссылки отложены по прямому решению пользователя, а не объявлены проверенными. +- Исправление дефектов runtime (predicate switchFrame, networkIdle, сетевые подписки, восстановление сети) не входит в документационную задачу; ограничения воспроизведены и описаны. +- Неизмененные старые примеры гайдов не входят в отчет о новых/измененных примерах. +- Firefox, общий Classic/CDP, Sauce Labs, удаленный Grid и Appium не запускались; проверка исходников не выдается за runtime-проверку этих сред. +- Финальные build, визуальная проверка и обновление отчетов выполняются автором; исторический отчет частичного результата заменен этим отчетом. + +## Предыдущее частичное ревью + +До согласования немобильного приоритета отдельно проверялись этапы 1–4. Тогда исправлены предупреждение о predicate switchFrame, описание локальной топологии Selenium и исключения ограничений DOM snapshot. Противоречия старого browser.mock и гайда сети устранены в этапе 6. Публикация и коммит не выполнялись. diff --git a/vibe/verified-examples/evidence/selenium-run.txt b/vibe/verified-examples/evidence/selenium-run.txt new file mode 100644 index 00000000..0bd88da8 --- /dev/null +++ b/vibe/verified-examples/evidence/selenium-run.txt @@ -0,0 +1,6 @@ + +> testplane-docs-examples-20261009@1.0.0 test:selenium +> node run-examples.cjs --config testplane.selenium.config.js + +[16:40:43 +0300] ✓ downloadFile: copy a browser download from Selenium Grid [chrome:e970bcb9f3d2482c903587e073b86bc4, pid:26444] - 1201ms +[16:40:43 +0300] Total: 1 Passed: 1 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/stage3-initial-failure.txt b/vibe/verified-examples/evidence/stage3-initial-failure.txt new file mode 100644 index 00000000..4eac4640 --- /dev/null +++ b/vibe/verified-examples/evidence/stage3-initial-failure.txt @@ -0,0 +1,45 @@ +[14:33:57 +0300] ✓ mock.redirect: send matching requests to another endpoint [chrome:0edbf996c186007b89c76e90434b3f12, pid:20233] - 165ms +[14:33:58 +0300] ✓ mock.redirectOnce: redirect only the next request [chrome:a8bf90e63cfbf98fc51dac0723b6ea3e, pid:20233] - 99ms +[14:33:59 +0300] ✓ mock.request: change outgoing requests [chrome:a81c75c0fc361eb184eb063b550f5523, pid:20233] - 97ms +[14:34:00 +0300] ✓ mock.requestOnce: change only the next request [chrome:201e2cc83f32ebd11801ff57093870e6, pid:20233] - 120ms +[14:34:05 +0300] ✘ mock.reset: remove overrides but keep the mock usable [chrome:e955b4cfa9a7c17ff61c9935996e918a, pid:20233] - 3805ms +[14:34:10 +0300] ✘ mock.waitForResponse: wait for a new recorded response [chrome:5cd5358202a40f0917fcfb2b48d5d0f6, pid:20233] - 3798ms +[14:34:10 +0300] Total: 6 Passed: 4 Failed: 2 Skipped: 0 Retries: 0 + +1) mock.reset: remove overrides but keep the mock usable + in file examples/mock/reset.testplane.js + + chrome + + . | // examples/mock/reset.testplane.js + . | + 8 | const first = await browser.execute(async () => (await fetch("/api/items", { cache: "no-store" })).json()); + 9 | assert.equal(first.header, "overridden"); +> 10 | await mock.waitForResponse({ timeout: 3000 }); + | ^ Error: waitForResponse timed out after 3000ms + 11 | assert.equal(mock.reset(), mock); + 12 | assert.equal(mock.calls.length, 0); + 13 | const second = await browser.execute(async () => (await fetch("/api/items", { cache: "no-store" })).json()); + + ✘ Error: waitForResponse timed out after 3000ms + at _BidiInterception.waitForResponse (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriverio/build/node.js:4772:26) + at Object. (/private/tmp/testplane-docs-examples-20261009/examples/mock/reset.testplane.js:16:20) + +2) mock.waitForResponse: wait for a new recorded response + in file examples/mock/waitForResponse.testplane.js + + chrome + + . | // examples/mock/waitForResponse.testplane.js + . | + 6 | try { + 7 | await browser.execute(async () => (await fetch("/api/items")).text()); +> 8 | await mock.waitForResponse({ timeout: 3000 }); + | ^ Error: waitForResponse timed out after 3000ms + 9 | mock.clear(); + 10 | assert.equal(mock.calls.length, 0); + 11 | await browser.execute(() => { + + ✘ Error: waitForResponse timed out after 3000ms + at _BidiInterception.waitForResponse (file:///private/tmp/testplane-docs-examples-20261009/node_modules/@testplane/webdriverio/build/node.js:4772:26) + at Object. (/private/tmp/testplane-docs-examples-20261009/examples/mock/waitForResponse.testplane.js:8:20) diff --git a/vibe/verified-examples/evidence/stage6-build-en.txt b/vibe/verified-examples/evidence/stage6-build-en.txt new file mode 100644 index 00000000..892aca49 --- /dev/null +++ b/vibe/verified-examples/evidence/stage6-build-en.txt @@ -0,0 +1,25 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale en + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [en] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[WARNING] Docusaurus static site generation process emitted warnings for 2 paths +This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true +Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580 + +- "/docs/v8/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25944,"start":25940}],"span_labels":[]} + +- "/docs/plugins/testplane-retry-progressive/": + - [HTML minifier diagnostic - error] No "p" element in scope but a "p" end tag seen - {"primary_spans":[{"end":25417,"start":25413}],"span_labels":[]} + +[SUCCESS] Generated static files in "build". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/stage6-build-ru.txt b/vibe/verified-examples/evidence/stage6-build-ru.txt new file mode 100644 index 00000000..48237726 --- /dev/null +++ b/vibe/verified-examples/evidence/stage6-build-ru.txt @@ -0,0 +1,15 @@ + +> testplane-docs@0.0.0 build +> docusaurus build --locale ru + + + ------------------------------------------------------------------------------ Update available 3.10.1 → 3.10.2 To upgrade Docusaurus packages with the latest version, run the following command: `npm i @docusaurus/core@latest @docusaurus/plugin-client-redirects@latest @docusaurus/preset-classic@latest @docusaurus/faster@latest @docusaurus/module-type-aliases@latest @docusaurus/tsconfig@latest @docusaurus/types@latest` ------------------------------------------------------------------------------ + +[INFO] [ru] Creating an optimized production build... +[WARNING] Docusaurus site warnings: +- Your site is using the SWC js loader. You can safely remove the Babel config file at `babel.config.js`. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +No admonition component found for admonition type "undefined". Using Info as fallback. +[SUCCESS] Generated static files in "build". +[INFO] Use `npm run serve` command to test your build locally. diff --git a/vibe/verified-examples/evidence/stage6-docs-test.txt b/vibe/verified-examples/evidence/stage6-docs-test.txt new file mode 100644 index 00000000..4afbd2fa --- /dev/null +++ b/vibe/verified-examples/evidence/stage6-docs-test.txt @@ -0,0 +1,13 @@ + +> testplane-docs@0.0.0 test +> npm run lint && npm run typecheck + + +> testplane-docs@0.0.0 lint +> eslint --cache src plugins && prettier --check . + +Checking formatting... +All matched files use Prettier code style! + +> testplane-docs@0.0.0 typecheck +> tsc diff --git a/vibe/verified-examples/evidence/stage6-initial-failure.txt b/vibe/verified-examples/evidence/stage6-initial-failure.txt new file mode 100644 index 00000000..d37919fa --- /dev/null +++ b/vibe/verified-examples/evidence/stage6-initial-failure.txt @@ -0,0 +1,33 @@ +[16:18:01 +0300] ✓ mock.abort: block matching BiDi requests [chrome:443192c751a04737d6f6acbb8e6ac04a, pid:78894] - 142ms +[16:18:02 +0300] ✓ mock.abortOnce: block one BiDi request [chrome:92b1633eb90df116678d8830218d9195, pid:78894] - 101ms +[16:18:03 +0300] ✓ mock.clear: clear history but retain response rules [chrome:bcdf09618e368c656a49d4a663b348aa, pid:78894] - 140ms +[16:18:04 +0300] ✓ browser.executeAsync: return a result through the injected callback [chrome:279d22df203232c58ee7bf10584753e6, pid:78894] - 99ms +[16:18:05 +0300] ✓ getState: read a saved file without restoring the session [chrome:59231ec4b5e84ea1dbedcb58226b6df3, pid:78894] - 93ms +[16:18:06 +0300] ✓ browser.mock: observe and replace a BiDi response [chrome:36fa0a87147ae66df474b13129e05664, pid:78894] - 86ms +[16:18:07 +0300] ✓ commands: use the browser fixture [chrome:b55fcbdb167d1ca44cc847d084408722, pid:78894] - 85ms +[16:18:08 +0300] ✓ commands: use this.browser with a regular function [chrome:30c780b300f93b1be733e8aab39c0f62, pid:78894] - 66ms +[16:18:09 +0300] ✓ mock.respond: replace response data and metadata in BiDi [chrome:7e5126796905a3c9388109cb4183266a, pid:78894] - 129ms +[16:18:10 +0300] ✓ mock.respondOnce: consume BiDi responses in order [chrome:647c2c13bd33c83a5213b1e05dd3127a, pid:78894] - 130ms +[16:18:11 +0300] ✓ mock.restore: remove interception before the next request [chrome:3f90a1e28c3675601fac74f1892b4330, pid:78894] - 100ms +[16:18:12 +0300] ✓ slow network: fetch data under custom throughput limits [chrome:853c2c19b5ac5df095921f322abfdfb7, pid:78894] - 799ms +[16:18:14 +0300] ✓ throttle: use the deprecated alias in an isolated session [chrome:eb6669518d4ae4f26c0c80681432adaa, pid:78894] - 140ms +[16:18:15 +0300] ✘ url: navigate with BiDi options and read the current URL [chrome:e77b6002ce5e92b2d66a92b2f9b85707, pid:78894] - 688ms +[16:18:15 +0300] Total: 14 Passed: 13 Failed: 1 Skipped: 0 Retries: 0 + +1) url: navigate with BiDi options and read the current URL + in file examples/related/url.testplane.js + + chrome + + . | // examples/related/url.testplane.js + . | + 5 | const target = new URL("/navigation", await browser.getUrl()).href; + 6 | await browser.sessionSubscribe({ events: ["network.beforeRequestSent", "network.responseStarted"] }); +> 7 | await browser.url(target, { + | ^ Error: Couldn't find request for navigation with id 067A4B807D7E7D826D760774AD22CE50 + 8 | wait: "networkIdle", + 9 | timeout: 5000, + 10 | headers: { "x-example": "navigation" }, + + ✘ Error: Couldn't find request for navigation with id 067A4B807D7E7D826D760774AD22CE50 + at Object. (/private/tmp/testplane-docs-examples-20261009/examples/related/url.testplane.js:12:19) diff --git a/vibe/verified-examples/evidence/stage6-run.txt b/vibe/verified-examples/evidence/stage6-run.txt new file mode 100644 index 00000000..68716e4e --- /dev/null +++ b/vibe/verified-examples/evidence/stage6-run.txt @@ -0,0 +1,15 @@ +[16:22:23 +0300] ✓ mock.abort: block matching BiDi requests [chrome:7948bd7a773303090ffcd6718cfd985c, pid:83069] - 131ms +[16:22:24 +0300] ✓ mock.abortOnce: block one BiDi request [chrome:21c3a17f1f1f7565d65e54e04a0a932e, pid:83069] - 81ms +[16:22:25 +0300] ✓ mock.clear: clear history but retain response rules [chrome:833138e3a49c3f5afad4a8b8df457ce0, pid:83069] - 133ms +[16:22:26 +0300] ✓ browser.executeAsync: return a result through the injected callback [chrome:93cb9ad23be2c660e1243a15c1f9f6f8, pid:83069] - 101ms +[16:22:27 +0300] ✓ getState: read a saved file without restoring the session [chrome:f94ec0f3d592c28f6fc0951a931dfc75, pid:83069] - 82ms +[16:22:28 +0300] ✓ browser.mock: observe and replace a BiDi response [chrome:a9d11d625dd4f768678fad0446d8bf65, pid:83069] - 104ms +[16:22:29 +0300] ✓ commands: use the browser fixture [chrome:e22bae4ca0ac6c94a6f8eaa5caa9419f, pid:83069] - 84ms +[16:22:30 +0300] ✓ commands: use this.browser with a regular function [chrome:8f923f37d3879645e8d1e4fc30ca43f3, pid:83069] - 87ms +[16:22:31 +0300] ✓ mock.respond: replace response data and metadata in BiDi [chrome:0b862749c5f6f48c3036d830aa172aba, pid:83069] - 128ms +[16:22:33 +0300] ✓ mock.respondOnce: consume BiDi responses in order [chrome:34d1377eaa4d93cd6e04549795596f7a, pid:83069] - 159ms +[16:22:34 +0300] ✓ mock.restore: remove interception before the next request [chrome:b5a2f14b1f9387ca0eed7a78c1ed9067, pid:83069] - 113ms +[16:22:35 +0300] ✓ slow network: fetch data under custom throughput limits [chrome:71a209c400f6ca0ffd3dba27715f91c9, pid:83069] - 824ms +[16:22:36 +0300] ✓ throttle: use the deprecated alias in an isolated session [chrome:1b5fdb363bc754fa37826a09bc3b6168, pid:83069] - 118ms +[16:22:38 +0300] ✓ url: navigate with BiDi options and read the current URL [chrome:cf6a2a69b79f9168d623d924b4dcac5f, pid:83069] - 201ms +[16:22:38 +0300] Total: 14 Passed: 14 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/switchFrame-predicate-diagnostic.txt b/vibe/verified-examples/evidence/switchFrame-predicate-diagnostic.txt new file mode 100644 index 00000000..0b7aeefb --- /dev/null +++ b/vibe/verified-examples/evidence/switchFrame-predicate-diagnostic.txt @@ -0,0 +1,3 @@ +Confirmed: switchFrame predicate raises browser is not defined +[14:42:36 +0300] ✓ inspect switchFrame predicate failure [chrome:283868807aadc11b308fcf61e635c943, pid:42593] - 172ms +[14:42:36 +0300] Total: 1 Passed: 1 Failed: 0 Skipped: 0 Retries: 0 diff --git a/vibe/verified-examples/evidence/url-network-idle-diagnostic.txt b/vibe/verified-examples/evidence/url-network-idle-diagnostic.txt new file mode 100644 index 00000000..ea8bc4e8 --- /dev/null +++ b/vibe/verified-examples/evidence/url-network-idle-diagnostic.txt @@ -0,0 +1,20 @@ +[16:19:07 +0300] ✘ url: navigate with BiDi options and read the current URL [chrome:e066883aa87a54fa7edae53c4e843c01, pid:80491] - 855ms +[16:19:07 +0300] Total: 1 Passed: 0 Failed: 1 Skipped: 0 Retries: 0 + +1) url: navigate with BiDi options and read the current URL + in file examples/related/url.testplane.js + + chrome + + . | // examples/related/url.testplane.js + . | + 5 | const target = new URL("/navigation", await browser.getUrl()).href; + 6 | await browser.sessionSubscribe({ events: ["network.beforeRequestSent", "network.responseStarted"] }); +> 7 | await browser.url(target, { + | ^ Error: Couldn't find request for navigation with id 236A5651DA461FE7C4D97735C6C60216 + 8 | wait: "networkIdle", + 9 | timeout: 5000, + 10 | headers: { "x-example": "navigation" }, + + ✘ Error: Couldn't find request for navigation with id 236A5651DA461FE7C4D97735C6C60216 + at Object. (/private/tmp/testplane-docs-examples-20261009/examples/related/url.testplane.js:12:19) diff --git a/vibe/verified-examples/evidence/visual-check.md b/vibe/verified-examples/evidence/visual-check.md new file mode 100644 index 00000000..f4346ea5 --- /dev/null +++ b/vibe/verified-examples/evidence/visual-check.md @@ -0,0 +1,22 @@ +# Проверка собранных страниц + +2026-10-09: в браузере Codex открыт полный локальный build EN/RU после этапа 6 и повторной сборки после исправления ревью. Проверены таблицы, блоки кода, предупреждения, боковая навигация и якоря на репрезентативных страницах: + +- emulate — EN и RU; +- browser.mock — EN (параметры) и RU (события); +- mock.respond — EN (исправленная таблица cookies) и RU (точный пример с cookies), после финальной сборки; +- mock.request — RU; обе локали также проверялись при частичном ревью этапов 1–4; +- element.waitForStable — EN и RU; +- browser.unstable_captureDomSnapshot — EN и RU; +- switchFrame — EN и RU, включая предупреждение; +- browser.url — EN, ограничения networkIdle; +- user-environment-emulation — RU, новый офлайн-раздел; +- обзор команд — RU, таблица связанных команд и переходы в справочник. + +Неразрешенных MDX-директив или нарушений верстки на просмотренных фрагментах не обнаружено. Длинные строки кода находятся внутри стандартных прокручиваемых блоков; таблицы переносят текст по ширине колонки. Ссылки и явные якоря дополнительно проверены во всех 84 новых/измененных страницах по собранному HTML. + +Это выборочная проверка верстки существующим браузерным инструментом, не проверка иллюстраций навыком infradocs-screenshot-producer. Этот навык недоступен в текущей сессии; иллюстрации не создавались. Для отдельного этапа иллюстраций требуется подключить навык и начать сессию, в которой он доступен. + +## Повторная проверка 2026-10-14 + +В локальных отдельных сборках EN и RU просмотрены все четыре измененные страницы: `setViewport` (раздел ограничений) и `addInitScript` (обзор и область действия). Проверены переносы абзацев, отображение `Number.MAX_SAFE_INTEGER`, боковая навигация и переход к якорю `limitations`. Финальная формулировка о дочерних фреймах на RU-странице просмотрена после повторной сборки. Необработанных MDX-директив, наложений текста и обрезанных абзацев нет. Примеры кода не менялись. diff --git a/vibe/verified-examples/examples/browser/addInitScript.testplane.js b/vibe/verified-examples/examples/browser/addInitScript.testplane.js new file mode 100644 index 00000000..55fd7c7f --- /dev/null +++ b/vibe/verified-examples/examples/browser/addInitScript.testplane.js @@ -0,0 +1,17 @@ +const assert = require("node:assert/strict"); + +it("addInitScript: set a value before page scripts", async ({ browser }) => { + const script = await browser.addInitScript(value => { + window.exampleSeed = value; + }, 42); + + try { + await browser.url("/"); + assert.equal(await browser.execute(() => window.exampleSeed), 42); + } finally { + await script.remove(); + } + + await browser.url("/"); + assert.equal(await browser.execute(() => typeof window.exampleSeed), "undefined"); +}); diff --git a/vibe/verified-examples/examples/browser/addLocatorStrategy.testplane.js b/vibe/verified-examples/examples/browser/addLocatorStrategy.testplane.js new file mode 100644 index 00000000..8d2378be --- /dev/null +++ b/vibe/verified-examples/examples/browser/addLocatorStrategy.testplane.js @@ -0,0 +1,11 @@ +const assert = require("node:assert/strict"); + +it("addLocatorStrategy: find by data attribute", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + browser.addLocatorStrategy("byTest", value => document.querySelector(`[data-test="${value}"]`)); + const button = await browser.custom$("byTest", "save"); + assert.equal(await button.getText(), "Save"); +}); diff --git a/vibe/verified-examples/examples/browser/customDollar.testplane.js b/vibe/verified-examples/examples/browser/customDollar.testplane.js new file mode 100644 index 00000000..1a5ae81d --- /dev/null +++ b/vibe/verified-examples/examples/browser/customDollar.testplane.js @@ -0,0 +1,11 @@ +const assert = require("node:assert/strict"); + +it("custom$: find one element with a custom strategy", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + browser.addLocatorStrategy("byTest", value => document.querySelector(`[data-test="${value}"]`)); + const button = await browser.custom$("byTest", "save"); + assert.equal(await button.getText(), "Save"); +}); diff --git a/vibe/verified-examples/examples/browser/customDollarDollar.testplane.js b/vibe/verified-examples/examples/browser/customDollarDollar.testplane.js new file mode 100644 index 00000000..bc5163d5 --- /dev/null +++ b/vibe/verified-examples/examples/browser/customDollarDollar.testplane.js @@ -0,0 +1,14 @@ +const assert = require("node:assert/strict"); + +it("custom$$: find several elements", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = "
  • First
  • Second
"; + }); + browser.addLocatorStrategy("byCss", selector => + Array.from(document.querySelectorAll(selector)), + ); + const items = await browser.custom$$("byCss", "li"); + assert.equal(items.length, 2); + assert.equal(await items[1].getText(), "Second"); +}); diff --git a/vibe/verified-examples/examples/browser/emulate.testplane.js b/vibe/verified-examples/examples/browser/emulate.testplane.js new file mode 100644 index 00000000..f096e1c8 --- /dev/null +++ b/vibe/verified-examples/examples/browser/emulate.testplane.js @@ -0,0 +1,26 @@ +const assert = require("node:assert/strict"); + +it("emulate: control time without waiting", async ({ browser }) => { + await browser.url("/"); + const clock = await browser.emulate("clock", { + now: new Date("2025-01-01T00:00:00.000Z"), + toFake: ["Date", "setTimeout", "clearTimeout"], + }); + try { + await browser.execute(() => { + window.timerFired = false; + setTimeout(() => { + window.timerFired = true; + }, 1000); + }); + assert.equal(await browser.execute(() => window.timerFired), false); + await clock.tick(1000); + assert.equal(await browser.execute(() => window.timerFired), true); + assert.equal( + await browser.execute(() => new Date().toISOString()), + "2025-01-01T00:00:01.000Z", + ); + } finally { + await clock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/browser/restore.testplane.js b/vibe/verified-examples/examples/browser/restore.testplane.js new file mode 100644 index 00000000..03351b87 --- /dev/null +++ b/vibe/verified-examples/examples/browser/restore.testplane.js @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); + +it("restore: remove a user-agent override", async ({ browser }) => { + await browser.url("/"); + const original = await browser.execute(() => navigator.userAgent); + await browser.emulate("userAgent", "ExampleBrowser/1.0"); + try { + await browser.url("/"); + assert.equal(await browser.execute(() => navigator.userAgent), "ExampleBrowser/1.0"); + } finally { + await browser.restore("userAgent"); + } + await browser.url("/"); + assert.equal(await browser.execute(() => navigator.userAgent), original); +}); diff --git a/vibe/verified-examples/examples/browser/setViewport.testplane.js b/vibe/verified-examples/examples/browser/setViewport.testplane.js new file mode 100644 index 00000000..7fbd94de --- /dev/null +++ b/vibe/verified-examples/examples/browser/setViewport.testplane.js @@ -0,0 +1,16 @@ +const assert = require("node:assert/strict"); + +it("setViewport: change page dimensions", async ({ browser }) => { + await browser.url("/"); + const original = await browser.execute(() => ({ + width: innerWidth, + height: innerHeight, + devicePixelRatio, + })); + try { + await browser.setViewport({ width: 800, height: 600, devicePixelRatio: 1 }); + assert.deepEqual(await browser.execute(() => [innerWidth, innerHeight]), [800, 600]); + } finally { + await browser.setViewport(original); + } +}); diff --git a/vibe/verified-examples/examples/browser/switchFrame.testplane.js b/vibe/verified-examples/examples/browser/switchFrame.testplane.js new file mode 100644 index 00000000..0a43008e --- /dev/null +++ b/vibe/verified-examples/examples/browser/switchFrame.testplane.js @@ -0,0 +1,16 @@ +const assert = require("node:assert/strict"); + +it("switchFrame: enter an iframe and return", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Main page

'; + }); + try { + await browser.switchFrame(browser.$("#frame")); + assert.equal(await browser.$("p").getText(), "Frame content"); + } finally { + await browser.switchFrame(null); + } + assert.equal(await browser.$("h1").getText(), "Main page"); +}); diff --git a/vibe/verified-examples/examples/browser/throttleCPU.testplane.js b/vibe/verified-examples/examples/browser/throttleCPU.testplane.js new file mode 100644 index 00000000..5f44acf3 --- /dev/null +++ b/vibe/verified-examples/examples/browser/throttleCPU.testplane.js @@ -0,0 +1,14 @@ +const assert = require("node:assert/strict"); + +it("throttleCPU: run a page with a slower CPU", async ({ browser }) => { + await browser.url("/"); + try { + await browser.throttleCPU(2); + await browser.execute(() => { + document.body.innerHTML = ""; + }); + assert.equal(await browser.$("button").getText(), "Ready"); + } finally { + await browser.throttleCPU(1); + } +}); diff --git a/vibe/verified-examples/examples/browser/throttleNetwork.testplane.js b/vibe/verified-examples/examples/browser/throttleNetwork.testplane.js new file mode 100644 index 00000000..7f7641b4 --- /dev/null +++ b/vibe/verified-examples/examples/browser/throttleNetwork.testplane.js @@ -0,0 +1,25 @@ +const assert = require("node:assert/strict"); + +// Configure testsPerSession: 1 so this offline session is not reused. +it("throttleNetwork: check a failed request while offline", async ({ browser }) => { + await browser.url("/"); + await browser.throttleNetwork("offline"); + const failed = await browser.execute(async () => { + try { + await fetch("/?offline-check", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); +}); + +it("throttleNetwork: the next session starts online", async ({ browser }) => { + await browser.url("/"); + const status = await browser.execute(async () => { + const response = await fetch("/?online-check", { cache: "no-store" }); + return response.status; + }); + assert.equal(status, 200); +}); diff --git a/vibe/verified-examples/examples/element/customDollar.testplane.js b/vibe/verified-examples/examples/element/customDollar.testplane.js new file mode 100644 index 00000000..866184f6 --- /dev/null +++ b/vibe/verified-examples/examples/element/customDollar.testplane.js @@ -0,0 +1,14 @@ +const assert = require("node:assert/strict"); + +it("element.custom$: search inside an element", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '
'; + }); + browser.addLocatorStrategy("byTestWithin", (value, root) => + root.querySelector(`[data-test="${value}"]`), + ); + const button = await browser.$("#panel").custom$("byTestWithin", "save"); + assert.equal(await button.getText(), "Inside"); +}); diff --git a/vibe/verified-examples/examples/element/customDollarDollar.testplane.js b/vibe/verified-examples/examples/element/customDollarDollar.testplane.js new file mode 100644 index 00000000..79753462 --- /dev/null +++ b/vibe/verified-examples/examples/element/customDollarDollar.testplane.js @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); + +it("element.custom$$: find children with a custom strategy", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Outside

First

Second

'; + }); + browser.addLocatorStrategy("byTestWithin", (value, root) => + Array.from(root.querySelectorAll(`[data-test="${value}"]`)), + ); + const items = await browser.$("#panel").custom$$("byTestWithin", "item"); + assert.equal(items.length, 2); + assert.equal(await items[1].getText(), "Second"); +}); diff --git a/vibe/verified-examples/examples/element/execute.testplane.js b/vibe/verified-examples/examples/element/execute.testplane.js new file mode 100644 index 00000000..641d4414 --- /dev/null +++ b/vibe/verified-examples/examples/element/execute.testplane.js @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); + +it("element.execute: pass the DOM element to a browser function", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
'; + }); + const result = await browser.$("#counter").execute(async (element, increment) => { + const count = Number(element.dataset.count) + increment; + element.dataset.count = String(count); + return { count, id: element.id }; + }, 3); + assert.deepEqual(result, { count: 7, id: "counter" }); + assert.equal(await browser.$("#counter").getAttribute("data-count"), "7"); +}); diff --git a/vibe/verified-examples/examples/element/executeAsync.testplane.js b/vibe/verified-examples/examples/element/executeAsync.testplane.js new file mode 100644 index 00000000..83bb2d75 --- /dev/null +++ b/vibe/verified-examples/examples/element/executeAsync.testplane.js @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); + +it("element.executeAsync: return data through the final callback", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '

Ready

'; + }); + const element = await browser.$("#message"); + await element.waitForExist(); + await browser.setTimeout({ script: 5000 }); + const result = await element.executeAsync((node, prefix, done) => { + setTimeout(() => done(prefix + node.textContent), 20); + }, "Status: "); + assert.equal(result, "Status: Ready"); +}); diff --git a/vibe/verified-examples/examples/element/getElement.testplane.js b/vibe/verified-examples/examples/element/getElement.testplane.js new file mode 100644 index 00000000..5c41fa64 --- /dev/null +++ b/vibe/verified-examples/examples/element/getElement.testplane.js @@ -0,0 +1,12 @@ +const assert = require("node:assert/strict"); + +it("getElement: inspect a resolved element object", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = ''; + }); + const element = await browser.$("#save").getElement(); + assert.equal(element.selector, "#save"); + assert.equal(typeof element.elementId, "string"); + assert.equal(await element.getText(), "Save"); +}); diff --git a/vibe/verified-examples/examples/element/isStable.testplane.js b/vibe/verified-examples/examples/element/isStable.testplane.js new file mode 100644 index 00000000..2a6d1cf7 --- /dev/null +++ b/vibe/verified-examples/examples/element/isStable.testplane.js @@ -0,0 +1,24 @@ +const assert = require("node:assert/strict"); + +it("isStable: check whether element geometry changes", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
Box
'; + }); + const box = await browser.$("#box"); + await box.waitForExist(); + assert.equal(await box.isStable(), true); + await box.execute(node => { + node.animate([{ transform: "translateX(0)" }, { transform: "translateX(200px)" }], { + duration: 500, + iterations: Infinity, + direction: "alternate", + }); + }); + try { + assert.equal(await box.isStable(), false); + } finally { + await box.execute(node => node.getAnimations().forEach(animation => animation.cancel())); + } + assert.equal(await box.isStable(), true); +}); diff --git a/vibe/verified-examples/examples/element/waitForStable.testplane.js b/vibe/verified-examples/examples/element/waitForStable.testplane.js new file mode 100644 index 00000000..38b437e3 --- /dev/null +++ b/vibe/verified-examples/examples/element/waitForStable.testplane.js @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); + +it("waitForStable: wait for a size animation to stop", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = '
Box
'; + }); + const box = await browser.$("#box"); + await box.execute(node => { + node.animate([{ width: "40px" }, { width: "140px" }], { duration: 500, fill: "forwards" }); + }); + const result = await box.waitForStable({ timeout: 3000, interval: 50 }); + assert.equal(result, undefined); + assert.equal(await box.execute(node => node.getBoundingClientRect().width), 140); +}); diff --git a/vibe/verified-examples/examples/mock/redirect.testplane.js b/vibe/verified-examples/examples/mock/redirect.testplane.js new file mode 100644 index 00000000..cb2f7402 --- /dev/null +++ b/vibe/verified-examples/examples/mock/redirect.testplane.js @@ -0,0 +1,21 @@ +const assert = require("node:assert/strict"); + +it("mock.redirect: send matching requests to another endpoint", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/api/other", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.redirect(target); + for (let i = 0; i < 2; i++) { + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.source, "redirected"); + } + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/mock/redirectOnce.testplane.js b/vibe/verified-examples/examples/mock/redirectOnce.testplane.js new file mode 100644 index 00000000..61aa752a --- /dev/null +++ b/vibe/verified-examples/examples/mock/redirectOnce.testplane.js @@ -0,0 +1,23 @@ +const assert = require("node:assert/strict"); + +it("mock.redirectOnce: redirect only the next request", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/api/other", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.redirectOnce(target); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.source, "redirected"); + assert.equal(second.source, "original"); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/mock/request.testplane.js b/vibe/verified-examples/examples/mock/request.testplane.js new file mode 100644 index 00000000..f83809b6 --- /dev/null +++ b/vibe/verified-examples/examples/mock/request.testplane.js @@ -0,0 +1,21 @@ +const assert = require("node:assert/strict"); + +it("mock.request: change outgoing requests", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.request({ method: "POST", headers: { "x-example": "persistent" } }); + for (let i = 0; i < 2; i++) { + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.method, "POST"); + assert.equal(data.header, "persistent"); + } + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/mock/requestOnce.testplane.js b/vibe/verified-examples/examples/mock/requestOnce.testplane.js new file mode 100644 index 00000000..b3f1ca7b --- /dev/null +++ b/vibe/verified-examples/examples/mock/requestOnce.testplane.js @@ -0,0 +1,22 @@ +const assert = require("node:assert/strict"); + +it("mock.requestOnce: change only the next request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.requestOnce({ headers: { "x-example": "once" } }); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.header, "once"); + assert.equal(second.header, null); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/mock/reset.testplane.js b/vibe/verified-examples/examples/mock/reset.testplane.js new file mode 100644 index 00000000..16cf105e --- /dev/null +++ b/vibe/verified-examples/examples/mock/reset.testplane.js @@ -0,0 +1,37 @@ +const assert = require("node:assert/strict"); + +it("mock.reset: remove overrides but keep the mock usable", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.request({ headers: { "x-example": "overridden" } }); + const first = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(first.header, "overridden"); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.reset(), mock); + assert.equal(mock.calls.length, 0); + const second = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(second.header, null); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + mock.respond({ source: "mocked" }); + const third = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(third.source, "mocked"); + mock.reset(); + const fourth = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(fourth.source, "original"); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/mock/waitForResponse.testplane.js b/vibe/verified-examples/examples/mock/waitForResponse.testplane.js new file mode 100644 index 00000000..b4920873 --- /dev/null +++ b/vibe/verified-examples/examples/mock/waitForResponse.testplane.js @@ -0,0 +1,28 @@ +const assert = require("node:assert/strict"); + +it("mock.waitForResponse: wait for a new recorded response", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + await browser.execute(async () => (await fetch("/api/items")).text()); + await mock.waitForResponse({ timeout: 3000 }); + mock.clear(); + assert.equal(mock.calls.length, 0); + await browser.execute(() => { + window.requestFinished = false; + fetch("/api/items", { cache: "no-store" }) + .then(response => response.text()) + .then(() => { + window.requestFinished = true; + }); + }); + assert.equal(await mock.waitForResponse({ timeout: 3000, interval: 50 }), true); + assert.equal(mock.calls.length, 1); + await browser.waitUntil(() => browser.execute(() => window.requestFinished)); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/abort.testplane.js b/vibe/verified-examples/examples/related/abort.testplane.js new file mode 100644 index 00000000..df7401ba --- /dev/null +++ b/vibe/verified-examples/examples/related/abort.testplane.js @@ -0,0 +1,25 @@ +const assert = require("node:assert/strict"); + +it("mock.abort: block matching BiDi requests", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.abort(); + for (let i = 0; i < 2; i++) { + const failed = await browser.execute(async () => { + try { + await fetch("/api/items", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); + } + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/abortOnce.testplane.js b/vibe/verified-examples/examples/related/abortOnce.testplane.js new file mode 100644 index 00000000..1fc97268 --- /dev/null +++ b/vibe/verified-examples/examples/related/abortOnce.testplane.js @@ -0,0 +1,27 @@ +const assert = require("node:assert/strict"); + +it("mock.abortOnce: block one BiDi request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.abortOnce(); + const failed = await browser.execute(async () => { + try { + await fetch("/api/items", { cache: "no-store" }); + return false; + } catch { + return true; + } + }); + assert.equal(failed, true); + const status = await browser.execute( + async () => (await fetch("/api/items", { cache: "no-store" })).status, + ); + assert.equal(status, 200); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/clear.testplane.js b/vibe/verified-examples/examples/related/clear.testplane.js new file mode 100644 index 00000000..6895c985 --- /dev/null +++ b/vibe/verified-examples/examples/related/clear.testplane.js @@ -0,0 +1,27 @@ +const assert = require("node:assert/strict"); + +it("mock.clear: clear history but retain response rules", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond({ source: "mocked" }); + await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + await mock.clear(); + assert.equal(mock.calls.length, 0); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "mocked"); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(mock.calls.length, 1); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/executeAsync.testplane.js b/vibe/verified-examples/examples/related/executeAsync.testplane.js new file mode 100644 index 00000000..4c581191 --- /dev/null +++ b/vibe/verified-examples/examples/related/executeAsync.testplane.js @@ -0,0 +1,14 @@ +const assert = require("node:assert/strict"); + +it("browser.executeAsync: return a result through the injected callback", async ({ browser }) => { + await browser.url("/"); + await browser.setTimeout({ script: 5000 }); + const result = await browser.executeAsync( + (a, b, done) => { + setTimeout(() => done(a + b), 20); + }, + 4, + 6, + ); + assert.equal(result, 10); +}); diff --git a/vibe/verified-examples/examples/related/getState.testplane.js b/vibe/verified-examples/examples/related/getState.testplane.js new file mode 100644 index 00000000..a3ac5387 --- /dev/null +++ b/vibe/verified-examples/examples/related/getState.testplane.js @@ -0,0 +1,21 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); + +it("getState: read a saved file without restoring the session", async ({ browser }) => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "testplane-state-")); + const statePath = path.join(directory, "state.json"); + try { + await browser.url("/"); + const origin = new URL(await browser.getUrl()).origin; + await browser.execute(() => localStorage.setItem("example", "saved")); + await browser.saveState({ path: statePath, cookies: false, localStorage: true }); + await browser.execute(() => localStorage.setItem("example", "current")); + const state = await browser.getState({ path: statePath }); + assert.equal(state.framesData[origin].localStorage.example, "saved"); + assert.equal(await browser.execute(() => localStorage.getItem("example")), "current"); + } finally { + await fs.rm(directory, { recursive: true, force: true }); + } +}); diff --git a/vibe/verified-examples/examples/related/mock.testplane.js b/vibe/verified-examples/examples/related/mock.testplane.js new file mode 100644 index 00000000..21de4486 --- /dev/null +++ b/vibe/verified-examples/examples/related/mock.testplane.js @@ -0,0 +1,24 @@ +const assert = require("node:assert/strict"); + +it("browser.mock: observe and replace a BiDi response", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items", { method: "GET", statusCode: 200 }); + const seenUrls = []; + mock.on("request", event => seenUrls.push(event.request.url)); + try { + mock.respond({ source: "mocked" }); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + await mock.waitForResponse({ timeout: 3000 }); + assert.equal(body.source, "mocked"); + assert.equal(mock.calls.length, 1); + assert.ok(mock.calls[0].request.url.endsWith("/api/items")); + assert.ok(seenUrls.some(url => url.endsWith("/api/items"))); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/overview.testplane.js b/vibe/verified-examples/examples/related/overview.testplane.js new file mode 100644 index 00000000..25877833 --- /dev/null +++ b/vibe/verified-examples/examples/related/overview.testplane.js @@ -0,0 +1,11 @@ +const assert = require("node:assert/strict"); + +it("commands: use the browser fixture", async ({ browser }) => { + await browser.url("/"); + assert.equal(await browser.$("h1").getText(), "Command examples"); +}); + +it("commands: use this.browser with a regular function", async function () { + await this.browser.url("/"); + assert.equal(await this.browser.$("h1").getText(), "Command examples"); +}); diff --git a/vibe/verified-examples/examples/related/respond.testplane.js b/vibe/verified-examples/examples/related/respond.testplane.js new file mode 100644 index 00000000..9bd003cc --- /dev/null +++ b/vibe/verified-examples/examples/related/respond.testplane.js @@ -0,0 +1,43 @@ +const assert = require("node:assert/strict"); + +it("mock.respond: replace response data and metadata in BiDi", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond( + { source: "mocked" }, + { + statusCode: 201, + headers: { "content-type": "application/json", "x-example": "response" }, + cookies: [ + { + name: "mock-example", + value: "ready", + domain: new URL(await browser.getUrl()).hostname, + path: "/", + }, + ], + }, + ); + for (let i = 0; i < 2; i++) { + const result = await browser.execute(async () => { + const response = await fetch("/api/items", { cache: "no-store" }); + return { + status: response.status, + header: response.headers.get("x-example"), + body: await response.json(), + }; + }); + assert.deepEqual(result, { + status: 201, + header: "response", + body: { source: "mocked" }, + }); + } + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/respondOnce.testplane.js b/vibe/verified-examples/examples/related/respondOnce.testplane.js new file mode 100644 index 00000000..5b982fd6 --- /dev/null +++ b/vibe/verified-examples/examples/related/respondOnce.testplane.js @@ -0,0 +1,23 @@ +const assert = require("node:assert/strict"); + +it("mock.respondOnce: consume BiDi responses in order", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respondOnce({ source: "first" }); + mock.respondOnce({ source: "second" }); + const sources = []; + for (let i = 0; i < 3; i++) { + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + sources.push(body.source); + } + assert.deepEqual(sources, ["first", "second", "original"]); + } finally { + await mock.restore(); + } +}); diff --git a/vibe/verified-examples/examples/related/restore.testplane.js b/vibe/verified-examples/examples/related/restore.testplane.js new file mode 100644 index 00000000..5cccdf71 --- /dev/null +++ b/vibe/verified-examples/examples/related/restore.testplane.js @@ -0,0 +1,24 @@ +const assert = require("node:assert/strict"); + +it("mock.restore: remove interception before the next request", async ({ browser }) => { + await browser.url("/"); + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + const mock = await browser.mock("/api/items"); + try { + mock.respond({ source: "mocked" }); + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "mocked"); + } finally { + await mock.restore(); + } + const body = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(body.source, "original"); + assert.equal(mock.calls.length, 0); + assert.throws(() => mock.respond({ source: "unused" }), /restored mock/); +}); diff --git a/vibe/verified-examples/examples/related/slowNetwork.testplane.js b/vibe/verified-examples/examples/related/slowNetwork.testplane.js new file mode 100644 index 00000000..c7a75fc6 --- /dev/null +++ b/vibe/verified-examples/examples/related/slowNetwork.testplane.js @@ -0,0 +1,15 @@ +const assert = require("node:assert/strict"); + +it("slow network: fetch data under custom throughput limits", async ({ browser }) => { + await browser.url("/"); + await browser.throttleNetwork({ + offline: false, + latency: 500, + downloadThroughput: (50 * 1024) / 8, + uploadThroughput: (20 * 1024) / 8, + }); + const data = await browser.execute(async () => + (await fetch("/api/items", { cache: "no-store" })).json(), + ); + assert.equal(data.source, "original"); +}); diff --git a/vibe/verified-examples/examples/related/throttle.testplane.js b/vibe/verified-examples/examples/related/throttle.testplane.js new file mode 100644 index 00000000..e753297a --- /dev/null +++ b/vibe/verified-examples/examples/related/throttle.testplane.js @@ -0,0 +1,10 @@ +const assert = require("node:assert/strict"); + +it("throttle: use the deprecated alias in an isolated session", async ({ browser }) => { + await browser.url("/"); + await browser.throttle("Good3G"); + const status = await browser.execute( + async () => (await fetch("/?throttled", { cache: "no-store" })).status, + ); + assert.equal(status, 200); +}); diff --git a/vibe/verified-examples/examples/related/url.testplane.js b/vibe/verified-examples/examples/related/url.testplane.js new file mode 100644 index 00000000..92a03eee --- /dev/null +++ b/vibe/verified-examples/examples/related/url.testplane.js @@ -0,0 +1,27 @@ +const assert = require("node:assert/strict"); + +it("url: navigate with BiDi options and read the current URL", async ({ browser }) => { + await browser.url("/"); + const target = new URL("/navigation", await browser.getUrl()).href; + await browser.sessionSubscribe({ + events: ["network.beforeRequestSent", "network.responseStarted"], + }); + await browser.url(target, { + wait: "complete", + headers: { "x-example": "navigation" }, + onBeforeLoad: () => { + window.navigationSeed = 42; + }, + }); + assert.equal(await browser.url(), target); + assert.deepEqual(await browser.execute(() => [window.initialSeed, window.receivedHeader]), [ + 42, + "navigation", + ]); + await browser.execute(() => { + window.unsavedValue = "temporary"; + }); + await browser.url(target); + assert.equal(await browser.execute(() => typeof window.unsavedValue), "undefined"); + assert.equal(await browser.execute(() => window.initialSeed), null); +}); diff --git a/vibe/verified-examples/examples/selenium/downloadFile.testplane.js b/vibe/verified-examples/examples/selenium/downloadFile.testplane.js new file mode 100644 index 00000000..13a6fbdd --- /dev/null +++ b/vibe/verified-examples/examples/selenium/downloadFile.testplane.js @@ -0,0 +1,29 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); + +it("downloadFile: copy a browser download from Selenium Grid", async ({ browser }) => { + const targetDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "testplane-download-")); + try { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = 'Download report'; + }); + await browser.$("a").click(); + await browser.waitUntil( + async () => { + const { names } = await browser.getDownloadableFiles(); + return names.includes("report.csv"); + }, + { timeout: 10000, timeoutMsg: "The CSV was not downloaded on the Grid node" }, + ); + const { files } = await browser.downloadFile("report.csv", targetDirectory); + const localFile = path.join(targetDirectory, "report.csv"); + assert.ok(files.includes(localFile)); + assert.equal(await fs.readFile(localFile, "utf8"), "name,value\nexample,42\n"); + } finally { + await browser.deleteDownloadableFiles(); + await fs.rm(targetDirectory, { recursive: true, force: true }); + } +}); diff --git a/vibe/verified-examples/examples/testplane-browser/extendOptions.testplane.js b/vibe/verified-examples/examples/testplane-browser/extendOptions.testplane.js new file mode 100644 index 00000000..744e1742 --- /dev/null +++ b/vibe/verified-examples/examples/testplane-browser/extendOptions.testplane.js @@ -0,0 +1,27 @@ +const assert = require("node:assert/strict"); + +it("extendOptions: change WebdriverIO wait settings for this browser", async ({ browser }) => { + const original = { + waitforTimeout: browser.options.waitforTimeout, + waitforInterval: browser.options.waitforInterval, + }; + const configuredTimeout = (await browser.getConfig()).waitTimeout; + assert.equal(typeof configuredTimeout, "number"); + try { + const result = await browser.extendOptions({ waitforTimeout: 1000, waitforInterval: 50 }); + assert.equal(result, undefined); + assert.equal(browser.options.waitforTimeout, 1000); + assert.equal(browser.options.waitforInterval, 50); + assert.equal((await browser.getConfig()).waitTimeout, configuredTimeout); + await browser.url("/"); + await browser.execute(() => { + setTimeout(() => { + document.body.innerHTML = '

Ready

'; + }, 100); + }); + await browser.$("#ready").waitForExist(); + assert.equal(await browser.$("#ready").getText(), "Ready"); + } finally { + await browser.extendOptions(original); + } +}); diff --git a/vibe/verified-examples/examples/testplane-browser/unstable_captureDomSnapshot.testplane.js b/vibe/verified-examples/examples/testplane-browser/unstable_captureDomSnapshot.testplane.js new file mode 100644 index 00000000..1120b299 --- /dev/null +++ b/vibe/verified-examples/examples/testplane-browser/unstable_captureDomSnapshot.testplane.js @@ -0,0 +1,23 @@ +const assert = require("node:assert/strict"); + +it("browser.unstable_captureDomSnapshot: capture a filtered subtree", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

abcdefghijk

Outside

'; + }); + const result = await browser.unstable_captureDomSnapshot("#panel", { + excludeAttrs: ["data-internal"], + maxTextLength: 6, + }); + assert.match(result.snapshot, /section#panel/); + assert.match(result.snapshot, /abcdef\.\.\./); + assert.doesNotMatch(result.snapshot, /Outside|data-internal/); + assert.ok(result.omittedTags.includes("script")); + assert.ok(result.omittedAttributes.includes("data-internal")); + assert.equal(result.textWasTruncated, true); + const missing = await browser.unstable_captureDomSnapshot("#missing"); + assert.equal(missing.snapshot, "# Element not found: #missing"); + const invalid = await browser.unstable_captureDomSnapshot("["); + assert.equal(invalid.snapshot, "# Invalid selector: ["); +}); diff --git a/vibe/verified-examples/examples/testplane-browser/unstable_getCdp.testplane.js b/vibe/verified-examples/examples/testplane-browser/unstable_getCdp.testplane.js new file mode 100644 index 00000000..42f703a3 --- /dev/null +++ b/vibe/verified-examples/examples/testplane-browser/unstable_getCdp.testplane.js @@ -0,0 +1,10 @@ +const assert = require("node:assert/strict"); + +it("unstable_getCdp: query the connected Chromium browser", async ({ browser }) => { + const cdp = await browser.unstable_getCdp(); + assert.ok(cdp, "This test requires a Chromium session with a CDP connection"); + const version = await cdp.request("Browser.getVersion"); + assert.match(version.product, /Chrome/); + assert.equal(typeof version.protocolVersion, "string"); + // Testplane owns this connection: do not call cdp.close(). +}); diff --git a/vibe/verified-examples/examples/testplane-element/unstable_captureDomSnapshot.testplane.js b/vibe/verified-examples/examples/testplane-element/unstable_captureDomSnapshot.testplane.js new file mode 100644 index 00000000..a91f9d94 --- /dev/null +++ b/vibe/verified-examples/examples/testplane-element/unstable_captureDomSnapshot.testplane.js @@ -0,0 +1,14 @@ +const assert = require("node:assert/strict"); + +it("element.unstable_captureDomSnapshot: capture only this element", async ({ browser }) => { + await browser.url("/"); + await browser.execute(() => { + document.body.innerHTML = + '

Outside

'; + }); + const result = await browser.$("#panel").unstable_captureDomSnapshot({ truncateText: false }); + assert.match(result.snapshot, /section#panel/); + assert.match(result.snapshot, /button\[data-test=save\] "Save"/); + assert.doesNotMatch(result.snapshot, /Outside/); + assert.equal(result.textWasTruncated, false); +}); diff --git a/vibe/verified-examples/manifest.json b/vibe/verified-examples/manifest.json new file mode 100644 index 00000000..ac3ae0e7 --- /dev/null +++ b/vibe/verified-examples/manifest.json @@ -0,0 +1,223 @@ +[ + { + "page": "docs/commands/browser/addInitScript.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addInitScript.mdx", + "test": "examples/browser/addInitScript.testplane.js" + }, + { + "page": "docs/commands/browser/addLocatorStrategy.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/addLocatorStrategy.mdx", + "test": "examples/browser/addLocatorStrategy.testplane.js" + }, + { + "page": "docs/commands/browser/customDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollar.mdx", + "test": "examples/browser/customDollar.testplane.js" + }, + { + "page": "docs/commands/browser/customDollarDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/customDollarDollar.mdx", + "test": "examples/browser/customDollarDollar.testplane.js" + }, + { + "page": "docs/commands/browser/downloadFile.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/downloadFile.mdx", + "test": "examples/selenium/downloadFile.testplane.js" + }, + { + "page": "docs/commands/browser/emulate.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/emulate.mdx", + "test": "examples/browser/emulate.testplane.js" + }, + { + "page": "docs/commands/browser/extendOptions.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/extendOptions.mdx", + "test": "examples/testplane-browser/extendOptions.testplane.js" + }, + { + "page": "docs/commands/browser/restore.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/restore.mdx", + "test": "examples/browser/restore.testplane.js" + }, + { + "page": "docs/commands/browser/setViewport.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/setViewport.mdx", + "test": "examples/browser/setViewport.testplane.js" + }, + { + "page": "docs/commands/browser/switchFrame.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/switchFrame.mdx", + "test": "examples/browser/switchFrame.testplane.js" + }, + { + "page": "docs/commands/browser/throttleCPU.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleCPU.mdx", + "test": "examples/browser/throttleCPU.testplane.js" + }, + { + "page": "docs/commands/browser/throttleNetwork.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx", + "test": "examples/browser/throttleNetwork.testplane.js" + }, + { + "page": "docs/commands/browser/unstable_captureDomSnapshot.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_captureDomSnapshot.mdx", + "test": "examples/testplane-browser/unstable_captureDomSnapshot.testplane.js" + }, + { + "page": "docs/commands/browser/unstable_getCdp.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/unstable_getCdp.mdx", + "test": "examples/testplane-browser/unstable_getCdp.testplane.js" + }, + { + "page": "docs/commands/element/customDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollar.mdx", + "test": "examples/element/customDollar.testplane.js" + }, + { + "page": "docs/commands/element/customDollarDollar.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/customDollarDollar.mdx", + "test": "examples/element/customDollarDollar.testplane.js" + }, + { + "page": "docs/commands/element/execute.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/execute.mdx", + "test": "examples/element/execute.testplane.js" + }, + { + "page": "docs/commands/element/executeAsync.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/executeAsync.mdx", + "test": "examples/element/executeAsync.testplane.js" + }, + { + "page": "docs/commands/element/getElement.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/getElement.mdx", + "test": "examples/element/getElement.testplane.js" + }, + { + "page": "docs/commands/element/isStable.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/isStable.mdx", + "test": "examples/element/isStable.testplane.js" + }, + { + "page": "docs/commands/element/unstable_captureDomSnapshot.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/unstable_captureDomSnapshot.mdx", + "test": "examples/testplane-element/unstable_captureDomSnapshot.testplane.js" + }, + { + "page": "docs/commands/element/waitForStable.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/waitForStable.mdx", + "test": "examples/element/waitForStable.testplane.js" + }, + { + "page": "docs/commands/mock/redirect.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirect.mdx", + "test": "examples/mock/redirect.testplane.js" + }, + { + "page": "docs/commands/mock/redirectOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/redirectOnce.mdx", + "test": "examples/mock/redirectOnce.testplane.js" + }, + { + "page": "docs/commands/mock/request.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/request.mdx", + "test": "examples/mock/request.testplane.js" + }, + { + "page": "docs/commands/mock/requestOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/requestOnce.mdx", + "test": "examples/mock/requestOnce.testplane.js" + }, + { + "page": "docs/commands/mock/reset.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/reset.mdx", + "test": "examples/mock/reset.testplane.js" + }, + { + "page": "docs/commands/mock/waitForResponse.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/waitForResponse.mdx", + "test": "examples/mock/waitForResponse.testplane.js" + }, + { + "page": "docs/commands/browser/getState.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/getState.mdx", + "test": "examples/related/getState.testplane.js" + }, + { + "page": "docs/commands/browser/url.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/url.mdx", + "test": "examples/related/url.testplane.js" + }, + { + "page": "docs/commands/browser/executeAsync.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/executeAsync.mdx", + "test": "examples/related/executeAsync.testplane.js" + }, + { + "page": "docs/commands/browser/throttle.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttle.mdx", + "test": "examples/related/throttle.testplane.js" + }, + { + "page": "docs/commands/browser/mock.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/mock.mdx", + "test": "examples/related/mock.testplane.js" + }, + { + "page": "docs/commands/mock/abort.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abort.mdx", + "test": "examples/related/abort.testplane.js" + }, + { + "page": "docs/commands/mock/abortOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/abortOnce.mdx", + "test": "examples/related/abortOnce.testplane.js" + }, + { + "page": "docs/commands/mock/respond.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respond.mdx", + "test": "examples/related/respond.testplane.js" + }, + { + "page": "docs/commands/mock/respondOnce.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/respondOnce.mdx", + "test": "examples/related/respondOnce.testplane.js" + }, + { + "page": "docs/commands/mock/clear.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/clear.mdx", + "test": "examples/related/clear.testplane.js" + }, + { + "page": "docs/commands/mock/restore.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/mock/restore.mdx", + "test": "examples/related/restore.testplane.js" + }, + { + "page": "docs/commands/overview.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx", + "test": "examples/related/overview.testplane.js" + }, + { + "page": "docs/basic-guides/user-environment-emulation.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", + "test": "examples/browser/throttleNetwork.testplane.js", + "blockIndex": 0, + "blockCount": 3 + }, + { + "page": "docs/basic-guides/user-environment-emulation.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", + "test": "examples/related/slowNetwork.testplane.js", + "blockIndex": 1, + "blockCount": 3 + }, + { + "page": "docs/basic-guides/user-environment-emulation.mdx", + "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", + "test": "examples/browser/throttleCPU.testplane.js", + "blockIndex": 2, + "blockCount": 3 + } +] diff --git a/vibe/verified-examples/package-lock.json b/vibe/verified-examples/package-lock.json new file mode 100644 index 00000000..98d5389d --- /dev/null +++ b/vibe/verified-examples/package-lock.json @@ -0,0 +1,9719 @@ +{ + "name": "testplane-docs-examples-20261009", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "testplane-docs-examples-20261009", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@swc/core": "^1.16.13", + "@testplane/globals": "^1.0.1", + "@testplane/testing-library": "^1.0.5", + "html-reporter": "^11.17.2", + "testplane": "^9.3.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", + "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gemini-testing/commander": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/@gemini-testing/commander/-/commander-2.15.4.tgz", + "integrity": "sha512-GIvIknEbJccKMv2KCgYOOZPy4QgR3/8csvds/WCUGEJPkghHz6VrziG7cBaB4n91PsFEpOwU+uJqXun5sEBpwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@gemini-testing/sql.js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@gemini-testing/sql.js/-/sql.js-3.0.0.tgz", + "integrity": "sha512-q/g1XuUdTc5v/eBspc7KJYS3RHeyIPmGN5K9N9YZRO+Op01GF4kZIC5RpjYIE8g61JAc23zOLlYMVLJB7SuIKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^28.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jspm/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@jspm/core/-/core-2.0.1.tgz", + "integrity": "sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==", + "dev": true + }, + "node_modules/@jsquash/png": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jsquash/png/-/png-3.1.1.tgz", + "integrity": "sha512-C10pc+0H6j0h8fENOfnGOvkXCmvpSQTDGlfGd0sHphZhPSGTyLjIrHba0FaZZdsKqA/wlmhYicUHb92vfZphaw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@nodable/entities": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-3.1.0.tgz", + "integrity": "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/nodable" + } + ], + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@promptbook/utils": { + "version": "0.69.5", + "resolved": "https://registry.npmjs.org/@promptbook/utils/-/utils-0.69.5.tgz", + "integrity": "sha512-xm5Ti/Hp3o4xHrsK9Yy3MS6KbDxYbq485hDsFvxqaNA7equHLPdo8H8faTitTeb14QCDfLW4iwCxdVYu5sn6YQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/webgptorg/promptbook/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "CC-BY-4.0", + "dependencies": { + "spacetrim": "0.11.59" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-3.0.4.tgz", + "integrity": "sha512-HGM8iAmGTf+Y7t0373szVbTmt3d7vPkYL/1bpOkOFO0YUYLgSeuYBCzESklogNPvOBnZ/MRD5f07OkpqH1trtA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "modern-tar": "^0.7.6", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/main-cli.js" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "proxy-agent": ">=8.0.1" + }, + "peerDependenciesMeta": { + "proxy-agent": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.3.tgz", + "integrity": "sha512-a31GFbL70GlGUGhn8goVjRLtdDG1urLCJFWLZ7JcxdHK9pPvCohLFtYLgJDdNbtrNZSrJl1u9bK05ISsSC2EpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.3.tgz", + "integrity": "sha512-IRv757RLiPGlNxAX5JXU0eiUlUPvKiwFRbkqLd+gywptLK4uSEg1qkl6en6r0cXMazSnWUWOBABv+QfuLV3n5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.3.tgz", + "integrity": "sha512-UkzI35Fnz6+MJNATdnjnZ3TpdG/rXab8h7oDOD9R0zDVIFmuuYrvgUj8V8mh+A//+LR4TlEQo4ZW4RhyRtWv3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.3.tgz", + "integrity": "sha512-C3AyMtAnKQ/jSkZ0chIoWbPXRbFz8JnPLhl1ULecEhcaOGkT19xt7KmO8BulQxt9S/XideHRXvsVQUbGj4JCOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.3.tgz", + "integrity": "sha512-M8yAdBMyHh14JLeXDNGvEkljzuHdcoLBQ399O4Fp4nvX51lueuEY/5U2G9Ci0D6/bQlBp/3JLiBQH55STwwbwg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.3.tgz", + "integrity": "sha512-WQjuOKGh2FN98ZK6i0uM6M6YqnPEds85p3oRkrxiEeTQo/p7RpuU8c9MwQ88LfgwVAzohumlzbQQSikz4Zw9rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.3.tgz", + "integrity": "sha512-5tDLZG5s9WyVKoEHgwos6FpKrgpotl61EYeHSLSVkQzMzTsaClwEr8mok9rm3y734zjiGInG4dfw/56ebWVvEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.3.tgz", + "integrity": "sha512-Mh5KbLeplM2HX6lQI18jsxSsqvPW8d+Hyk8HtY6Zl93woMitRSCZ92rBEzq6v8bfZGlpyMgj4P/sKW4KCjhH3A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.3.tgz", + "integrity": "sha512-QZrrwHnznTv6y4iiHtCqDN+ZsF4qNFQObMOLgPrbxhh9O5o4rgoUe5OnkqG/ceCObMrAjhQt+eQ6HSDzBFgVMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.3.tgz", + "integrity": "sha512-4vLehpXSwlkltnvk5ETB+Hz+d3xayZS3NamZ5Vq4bSo+IUoIALovEJF1YgWPMjHwN7R5o/R4BpP9h3q8EK1xAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.3.tgz", + "integrity": "sha512-SEN7TTTOdXJKZmsWKOnQeVfgy3Dje/08ISXWwFhTggQ3SxjuIaChvmOB5ZDZqJNlBjaXrV6nAHraqEAvdLGqcw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.3.tgz", + "integrity": "sha512-1LDsQMRMCEdGF9Dih5qyh6fF1LK3NnA+pAtE2+9AgZwUdXyd19TOBVC9crSB63iZBHR4vOG7h8gN/LcUH3J7xw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.3.tgz", + "integrity": "sha512-v3D2tAfRriqGOi+5Ac5CiYbBo2NXtLVxJlludTvUZRj502lVMFeVIuvvWbz4qCrotn3e3Uvpvh7FcMpG2QxxXQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.3.tgz", + "integrity": "sha512-db5q6dP189V2+JkMpsXBl8ym6bC0x8ydZ47fUJZ9YdM6T2OF5uqNzX9HaAZHRP/lqjA9bJeNwi0qw1/2/37dUA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.3.tgz", + "integrity": "sha512-nArAVDmrL5ix9UoipcKEsYJw4kMUQCIPYRWuYF2POOC7jd9rmN95d/48ewsYFC/OdtwEx6VYYWYxrp8FbMOU2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.3.tgz", + "integrity": "sha512-2dc7LElu7EVSvpQT8epNnBLa02wlqilNYqHN0FA/RaKqCr9U9ZFYQ0Eh4sfgSzLneLDuJsJRjcezR/l2UyYGXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.3.tgz", + "integrity": "sha512-9PYq2IEzSwXcvQLERcT9LKXRzw8J05GFTbJHA2anJR2WmlRjA1esgXVYhanp+bmjKsDVoZeU2L7Z2pRClLcZTw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.3.tgz", + "integrity": "sha512-vMh+mt0cw1BHldd8cqaU6A5s1W4EUKl6+8MRsv86mj954L4xyruKD7qPXMUI2icImadAZARK10h0onaGTLmCVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.3.tgz", + "integrity": "sha512-PpiKU+sWbqDGc0U0su8oXiyeyqm0UQjmaaPKHktX+DaLe6YKJwW2YVD0f2AXHdVvblZbRqpwRVClWP2F5HCqrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.3.tgz", + "integrity": "sha512-/3enY/AmaIt8IS0fWjX4ztYVPBqkj49cQTy/hCryhBFaEzDfJwwEolq9sxvyWAM2qmsmVuPdeRg5JXSrmeqvJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.3.tgz", + "integrity": "sha512-zsEP/QDuet52hhwvi5T1VPg0/Tkms0m3zkVv7CPsznvhdO4pL4oJTMz26EJsiCe18OeLKPgpTu3XVO3C1eq3ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.3.tgz", + "integrity": "sha512-4ESknW4Db+kqvazVOfsReYJaHxcIsOqcgwjKV2J1tG5Bn4RVf5ocTQKSGuq2HWBJcOLuyim8IWX8DuH6nKmELA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.3.tgz", + "integrity": "sha512-3B8VCQyIyv7FQxYYhb3HpR3bIpronR20txjMSffSoz/sm/DDs3/49CFpEueOsvq9qW9sWoI/6pi6mtciD9NXFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.3.tgz", + "integrity": "sha512-YOoDkKh2oQxOxRB5D9AR8sj3qpMShpmyTkjojiBjMGigz3caV7UvCnXWWUQC0TBmrZ5zuHe2pWl8S+cMfvXzRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.3.tgz", + "integrity": "sha512-qTv4bG+eKJKifzxNJevMUh8L6CjJrz9/Kn1IbP2F5bo0tBzNIR7HMg4vni6XmDzcKUl00lHHY+J5ZgjbGiujOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.52.tgz", + "integrity": "sha512-DNKwjDaMLKWXvjs/zCkSzA3rBzZVv4tuLCEV8ArRyHAqCtPtbUcQj0XS4h7y/CqL9pI6VpCmcH5A6oPOBAD9+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/core": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.16.13.tgz", + "integrity": "sha512-k4uUsIMS0IwOKVsrQs48IZK75O4roV3FnuJEdL7ES+DFESftdgvz73QBqN3VUAbUmUr78We4tzG0kgYj36M22Q==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.28" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.16.13", + "@swc/core-darwin-x64": "1.16.13", + "@swc/core-linux-arm-gnueabihf": "1.16.13", + "@swc/core-linux-arm64-gnu": "1.16.13", + "@swc/core-linux-arm64-musl": "1.16.13", + "@swc/core-linux-ppc64-gnu": "1.16.13", + "@swc/core-linux-s390x-gnu": "1.16.13", + "@swc/core-linux-x64-gnu": "1.16.13", + "@swc/core-linux-x64-musl": "1.16.13", + "@swc/core-win32-arm64-msvc": "1.16.13", + "@swc/core-win32-ia32-msvc": "1.16.13", + "@swc/core-win32-x64-msvc": "1.16.13" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.13.tgz", + "integrity": "sha512-JrMH2h7ohd4mOIneNRx/diqTmCDSdWfp9uOdQ8VJ2V2CA7jOJ1jWb+qp8RoZLqgiVmwDDC7eySKB1kbvZ+jVXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.13.tgz", + "integrity": "sha512-HauZMRxpOImRxgY6GXjw5gbD8ts2NcPZuLMM8htY8Mic7j1Izt/lxkXrKYUwqyU56YLwQZ4dHRT+6jFHkztcag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.13.tgz", + "integrity": "sha512-tUSojGIythnHJKrElDM9OvuhBnK+FF39eM9iIBgj9kdfbAm6PhAM7dKbaSDcnX2gOLG2slkasjD0Z8m/YUlzOA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.13.tgz", + "integrity": "sha512-qS3bk/ezRrLXGn1b764S/EG3/oKj6uRhnTNRAALpnuu8d9I0YwSiUkQghnX7lOH9X/yRr+qrFI3R0wAruO47Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.13.tgz", + "integrity": "sha512-aeoYoG6fQsOS3mFOuTKvnJo1aaaxYNI0aRRlhdHaGP4q1YQx4XOc5Hkd35IFduWCBG3eZl3fPTjRuYvxpl7eTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.13.tgz", + "integrity": "sha512-eEFoueZXWZ2S1lsqL8Ve2C8RjhzH9+Ktj8U4IGTUQL6CNG04+VhQiYQWJOf5xfVBU6I+PbH80yvsYegqZOOSZQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.13.tgz", + "integrity": "sha512-K0q1ReVXL3d5bzBgZxS+yJq5q2xRjg4k2XXGj9yiCEpE5ZqJGQ4Gtk1sRrk8W2lo3KY0DCJD3Myt6x2SMvGArQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.13.tgz", + "integrity": "sha512-Qvpihw30irhIjy2f91BR1pamcJBCgf8hmr9XWenztAX1YLkneRkkTB/PFGWQubhaBqQhY2uZKViMj/xV61uMBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.13.tgz", + "integrity": "sha512-iCGxHbh3Cq5vqRvCPlw3XkkDz4RlkPSyQwfFKbOKTMj5tiEuojY/rLmf4BMfI9Z8yPJTf9VB07t3NbKInoC97g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.13.tgz", + "integrity": "sha512-iOabn6cd+hg3cnoKlAC8j3Yufv8nQHBVU5OIDX8dCPA95PjAZ0B0TlDn9sWivk8ezo/cLJux7p9n0B9vRej+1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.13.tgz", + "integrity": "sha512-+1b/3xBNuVbpnJIZUCAaSdscOPYRWq85eohKx44y/3qbPp56UMiCJWBiWtyr7K5F1nf1JENXm2IBSEJaLgWq6Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.16.13", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.16.13.tgz", + "integrity": "sha512-6kEJhYshk7OVoVIWryiHqjCNuwZeOlPTS6et+bKV/WhtHs8fLA01wyTLrQGi+4ultgRe/o0M4b25K/KKdvl0dQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.28.tgz", + "integrity": "sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.2.tgz", + "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testplane/edgedriver": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@testplane/edgedriver/-/edgedriver-6.1.4.tgz", + "integrity": "sha512-4koRBT84dzso/231E3X4KIH7aC7DVTVf1R8XltahPBQDOUMOgwa6WCRztu9jHkALmsUn8BjBVnF8a3hmK4IfzQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@testplane/wdio-logger": "^9.4.4", + "@zip.js/zip.js": "^2.7.53", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "fast-xml-parser": "^5.0.8", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "which": "^4.0.0" + }, + "bin": { + "edgedriver": "bin/edgedriver.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/edgedriver/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@testplane/edgedriver/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@testplane/geckodriver": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@testplane/geckodriver/-/geckodriver-5.0.2.tgz", + "integrity": "sha512-ax6E0jkCa/N3sMXXOQcA6wu+v8B5wtDGwJe9Y7e2TByt7z/srOKh7iTuNfzeIgW9pKubenkivIKG29DMiFC/2w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@testplane/wdio-logger": "^9.4.4", + "@zip.js/zip.js": "^2.7.54", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.6" + }, + "bin": { + "geckodriver": "bin/geckodriver.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/globals": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@testplane/globals/-/globals-1.0.1.tgz", + "integrity": "sha512-LCyGB4uOeFoSi9i00lNEJCyv2HloVfBpCiipAkVIyhpiq4tAdSwYwsX+rWxF6GNlU47Bj6cxBwZtwOhZvdU4pQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "testplane": ">=9.0.0" + }, + "peerDependenciesMeta": { + "testplane": { + "optional": true + } + } + }, + "node_modules/@testplane/testing-library": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@testplane/testing-library/-/testing-library-1.0.5.tgz", + "integrity": "sha512-hlRaa8hJwKmgsiXDXdJoGARgY00hbJoTuwLOf0n+VPunf4G5yd868OkaE1LaK40fONFUReQI9r1CYnHESfdKFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "^10.4.0" + }, + "peerDependencies": { + "testplane": ">=8.10.0" + } + }, + "node_modules/@testplane/wdio-config": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@testplane/wdio-config/-/wdio-config-9.5.5.tgz", + "integrity": "sha512-BDfkcTQDXkQtJO2v+8Jc3VOn7nGS7VD3gKq55Ax/AjnI+nG9JoevK3u6N7qkX7alrQu4S/2X0+JckHCVAbOTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testplane/wdio-logger": "9.4.6", + "@testplane/wdio-types": "9.5.5", + "@testplane/wdio-utils": "9.5.5", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/wdio-logger": { + "version": "9.4.6", + "resolved": "https://registry.npmjs.org/@testplane/wdio-logger/-/wdio-logger-9.4.6.tgz", + "integrity": "sha512-ZtOnihuxOWxNpIrctRKrtmI7hapR8xCBie5fmo8ojNhiDG5+vouXbhTfYGIdRMJTfH0Fpmz3tVRHJcSjBOwkzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/wdio-protocols": { + "version": "9.4.7", + "resolved": "https://registry.npmjs.org/@testplane/wdio-protocols/-/wdio-protocols-9.4.7.tgz", + "integrity": "sha512-SQb69YKa/G5062GUrBb04OpY6BgyXOdNzacwcnZ/HMJ1jVRlImvcAU0eBZGdiJQIk9H+HotaUgdDpWrl/2b0Cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testplane/wdio-repl": { + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/@testplane/wdio-repl/-/wdio-repl-9.4.4.tgz", + "integrity": "sha512-h0+yQ9iV7U55pt4RDFKH7DnJ8J1SnPbZ/UgCz/oZ0u7N6HG2EN6tGldwZUZjQadwVE38BgQk6l+BwVHuLqEIZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/wdio-types": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@testplane/wdio-types/-/wdio-types-9.5.5.tgz", + "integrity": "sha512-g1vFPN8lfojGHWqQhSMSIfjdL/qiPgk3FGjmvSpTjnkbsNKxoVgRz7O6TeBXBy0h+hEIfXAjWX0utwz2eHjK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/wdio-utils": { + "version": "9.5.5", + "resolved": "https://registry.npmjs.org/@testplane/wdio-utils/-/wdio-utils-9.5.5.tgz", + "integrity": "sha512-FMcoj8xOD1oQ+fdFg0IwVMEoyLYBEO1HV90ivQGSCCmMFQEA62jVFcIB5p0Z5jdH9oHHPLC6IFRXM+Yu0h7qAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@testplane/edgedriver": "^6.1.4", + "@testplane/geckodriver": "^5.0.2", + "@testplane/wdio-logger": "9.4.6", + "@testplane/wdio-types": "9.5.5", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^1.0.0", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/wdio-utils/node_modules/@puppeteer/browsers": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.4", + "tar-fs": "^3.1.1", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testplane/wdio-utils/node_modules/get-port": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-7.2.0.tgz", + "integrity": "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@testplane/webdriver": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@testplane/webdriver/-/webdriver-9.6.0.tgz", + "integrity": "sha512-uYLd9gjhqyevi4tSGFPlzngabwlR+5F52hz3pKweZ13ScsJzCXYE+vVoA9LUyhwJhDwTfg7rCyNV+AhV+vwAdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testplane/wdio-config": "9.5.5", + "@testplane/wdio-logger": "9.4.6", + "@testplane/wdio-protocols": "9.4.6", + "@testplane/wdio-types": "9.5.5", + "@testplane/wdio-utils": "9.5.5", + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "deepmerge-ts": "^7.0.3", + "got": "12.6.1", + "ky": "0.33.0", + "undici": "6.12.0", + "ws": "^8.8.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@testplane/webdriver/node_modules/@testplane/wdio-protocols": { + "version": "9.4.6", + "resolved": "https://registry.npmjs.org/@testplane/wdio-protocols/-/wdio-protocols-9.4.6.tgz", + "integrity": "sha512-r++AmapEhXpShtNsYbqhX71iZSyUp7YyZV+RoamWVyvgP3ljpeU9cLksz2imyZJ5fCas1qcx9xElPKHRF/zcxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testplane/webdriverio": { + "version": "9.6.2", + "resolved": "https://registry.npmjs.org/@testplane/webdriverio/-/webdriverio-9.6.2.tgz", + "integrity": "sha512-vvhTREqPtj402kR7Mr/+J1UD7Xf4vBQ6yUF4ntggOO+Vy6BX+1/bXsdTYlcnoXJ82u8MyHj6T4QH6BqewB2HSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testplane/wdio-config": "9.5.5", + "@testplane/wdio-logger": "9.4.6", + "@testplane/wdio-protocols": "9.4.6", + "@testplane/wdio-repl": "9.4.4", + "@testplane/wdio-types": "9.5.5", + "@testplane/wdio-utils": "9.5.5", + "@testplane/webdriver": "9.6.0", + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "devtools-protocol": "^0.0.1302984", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.6.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "9.0.0", + "puppeteer-core": "^20.9.0", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@testplane/devtools": "^8.32.1" + }, + "peerDependenciesMeta": { + "@testplane/devtools": { + "optional": true + } + } + }, + "node_modules/@testplane/webdriverio/node_modules/@testplane/wdio-protocols": { + "version": "9.4.6", + "resolved": "https://registry.npmjs.org/@testplane/wdio-protocols/-/wdio-protocols-9.4.6.tgz", + "integrity": "sha512-r++AmapEhXpShtNsYbqhX71iZSyUp7YyZV+RoamWVyvgP3ljpeU9cLksz2imyZJ5fCas1qcx9xElPKHRF/zcxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "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==", + "dev": 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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", + "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.4.tgz", + "integrity": "sha512-4JOxa+UAizJgpZfaCPKK2smq9d8mmjZVPMt2kOsg/R8QkoRzydHH1qHxIYNvr1zlEaFj4SXiaaJWxq/LPLKaLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@wdio/logger": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.38.0.tgz", + "integrity": "sha512-kcHL86RmNbcQP+Gq/vQUGlArfU6IIcbbnNp32rRIraitomZow+iEoc519rdQmSVusDozMS5DZthkgDdxK+vz6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/logger/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/logger/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@zip.js/zip.js": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.23.0.tgz", + "integrity": "sha512-K5obAqfrO7zhycnCupM9aaoZ9kx0SA0kv7+Z2oOUsdpWquv4s/Z4zyD7AdTqxrK3pgPM+ozaUi8kdRQbCWaIrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=18.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anynum": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", + "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ast-types": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.3.tgz", + "integrity": "sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", + "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "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/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.3.tgz", + "integrity": "sha512-KQi9ShyPS/nOCtKpma7At8wbq4ORSNUmD67hsZrPcaWJcEmL8PHyS2xVWT308y8/LvHFOUTytTTDsD8dInx6bA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz", + "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.8", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.16.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true, + "license": "ISC" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha512-UJiE1otjXPF5/x+T3zTnSFiTOEmJoGTD9HmBoxnCUwho61a2eSNn/VwtwuIBDAo2SEOv1AJ7ARI5gCmohFLu/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha512-Zv4Dns9IbXXmPkgRRUjAaJQgfN4xX5p6+RQFhWUqscdvvK2xK/ZL8b3IXIJsj+4sD+f24NwnWy2BY8AJ82JB0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/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/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clear-require": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/clear-require/-/clear-require-1.0.1.tgz", + "integrity": "sha512-CmocmREIWAY0uKBGb+5rl9pBYfAP8t1hXkSqM/uGdAzxjkBcJei1BJFjBel0xtOwVeOKbLTy/5q4ogKZGLltCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/color-diff/-/color-diff-1.4.0.tgz", + "integrity": "sha512-4oDB/o78lNdppbaqrg0HjOp7pHmUc+dfCxWKWFnQg6AB/1dkjtBDop3RZht5386cq9xBUDRvDvSCA7WUlM9Jqw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-shorthand-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.2.tgz", + "integrity": "sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.1.tgz", + "integrity": "sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deepmerge-ts": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.6.tgz", + "integrity": "sha512-gQhL1ksGBLQbHeAo47YU6cs2ahd3Pv+8PFYoWogNZbQnNwQyOh9Ad5kbeHFiWwbKdeWQJ5Z7Y7mwb9ew2hXBLw==", + "dev": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/deepmerge-ts" + } + ], + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/degenerator/node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1302984", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1302984.tgz", + "integrity": "sha512-Rgh2Sk5fUSCtEx4QGH9iwTyECdFPySG2nlz5J8guGh2Wlha6uzSOCq/DCEC8faHlLaMPZJMuZ4ovgcX4LvOkKA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "28.1.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", + "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/which": "^2.0.1", + "which": "^2.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" + } + }, + "node_modules/edge-paths/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/edgedriver": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.1.tgz", + "integrity": "sha512-3Ve9cd5ziLByUdigw6zovVeWJjVs8QHVmqOB0sJ0WNeVPcwf4p18GnxMmVvlFmYRloUwf5suNuorea4QzwBIOA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@wdio/logger": "^8.38.0", + "@zip.js/zip.js": "^2.7.48", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "fast-xml-parser": "^4.4.1", + "node-fetch": "^3.3.2", + "which": "^4.0.0" + }, + "bin": { + "edgedriver": "bin/edgedriver.js" + } + }, + "node_modules/edgedriver/node_modules/fast-xml-parser": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.7.tgz", + "integrity": "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/edgedriver/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/edgedriver/node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/edgedriver/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "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/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^28.1.3", + "jest-get-type": "^28.0.2", + "jest-matcher-utils": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/expect-webdriverio": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-3.6.0.tgz", + "integrity": "sha512-8HuVToXDVzkKgUKIUzW/v3bP4ZoMDEwCjX9QmlRlMIvjt3HOSzSIBnRMv8lpeVTUKoR9DZNr/lSuKH4Amx4BBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^28.1.0", + "jest-matcher-utils": "^28.1.0" + } + }, + "node_modules/express": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.3.tgz", + "integrity": "sha512-Bdcs4+3qlpVlx2NRn6fgX2Ue2/gGRaPeawebgclM0ERSCqDpA+owF1fdPwjJUTAJWMTuAaxjDf+hzb0/4eKvvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.13", + "proxy-addr": "~2.0.7", + "qs": "~6.16.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-xml-builder": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.3.1.tgz", + "integrity": "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.6.2", + "xml-naming": "^0.3.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.2.tgz", + "integrity": "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "@nodable/entities": "^3.0.1", + "fast-xml-builder": "^1.2.0", + "is-unsafe": "^2.0.0", + "path-expression-matcher": "^1.6.2", + "strnum": "^2.4.2", + "xml-naming": "^0.3.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "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/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/follow-redirects": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz", + "integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/geckodriver": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.5.0.tgz", + "integrity": "sha512-EnBCT9kJ5oEoP3DaJKjzxAhm7bbNNK6k2q7oCkCT58OIOOiE6Hsr+nVDHflsNaR68HMGtBKOLSZ+YvCDHecScw==", + "dev": true, + "hasInstallScript": true, + "license": "MPL-2.0", + "dependencies": { + "@wdio/logger": "^9.0.0", + "@zip.js/zip.js": "^2.7.48", + "decamelize": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", + "node-fetch": "^3.3.2", + "tar-fs": "^3.0.6", + "which": "^4.0.0" + }, + "bin": { + "geckodriver": "bin/geckodriver.js" + }, + "engines": { + "node": "^16.13 || >=18 || >=20" + } + }, + "node_modules/geckodriver/node_modules/@wdio/logger": { + "version": "9.29.1", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.29.1.tgz", + "integrity": "sha512-0ZAEIo6PNyMIJPlOGkIgyOJUjcd0pC8/QHlVAAe1c91/IcjZ1X+k0yidXHaboJdN7dq1XPUacmhRdtua0U5EZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "safe-regex2": "^5.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/geckodriver/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/geckodriver/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/geckodriver/node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/geckodriver/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/geckodriver/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/gemini-configparser": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/gemini-configparser/-/gemini-configparser-1.4.2.tgz", + "integrity": "sha512-UgCSMUdWpordb1UgbUuJCMCv8fqdkOhu23k8QyaTdUzp4urREwjMCzCRBJtFR7B/sPtBxsJSef/b3CZJNUuBvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/get-uri/node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "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/html-reporter": { + "version": "11.17.2", + "resolved": "https://registry.npmjs.org/html-reporter/-/html-reporter-11.17.2.tgz", + "integrity": "sha512-4loDQXpHz6HvdAQcTEplYNeDO7Lv+9IsOLxeFiKmjhYh3lJkQvui5rPFmtABCaECOBdS3bdJ6QeuDw4AI18geQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "test/func/fixtures/*", + "test/func/packages/*", + "test/func/tests", + "test/component" + ], + "dependencies": { + "@gemini-testing/commander": "^2.15.3", + "@gemini-testing/sql.js": "^3.0.0", + "@inquirer/confirm": "^5.1.15", + "ansi-html-community": "^0.0.8", + "axios": "1.6.3", + "bluebird": "^3.5.3", + "body-parser": "^1.18.2", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "debug": "^4.1.1", + "escape-html": "^1.0.3", + "eventemitter2": "6.4.7", + "express": "^4.16.2", + "fast-glob": "^3.2.12", + "filesize": "^8.0.6", + "fs-extra": "^7.0.1", + "gemini-configparser": "^1.4.2", + "http-codes": "1.0.0", + "image-size": "^1.0.2", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.4", + "looks-same": "^10.0.1", + "nested-error-stacks": "^2.1.0", + "npm-which": "^3.0.1", + "open": "^8.4.2", + "ora": "^5.4.1", + "p-queue": "^5.0.0", + "qs": "^6.9.1", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "url-join": "^4.0.1", + "worker-farm": "^1.7.0", + "yazl": "^3.3.1" + }, + "bin": { + "html-reporter": "bin/html-reporter" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "hermione": ">=8.0.0", + "jest": "*", + "playwright": "*", + "testplane": "*" + }, + "peerDependenciesMeta": { + "hermione": { + "optional": true + }, + "jest": { + "optional": true + }, + "playwright": { + "optional": true + }, + "testplane": { + "optional": true + } + } + }, + "node_modules/htmlfy": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.6.7.tgz", + "integrity": "sha512-r8hRd+oIM10lufovN+zr3VKPTYEIvIwqXGucidh2XQufmiw6sbUXFUFjWlfjo3AnefIDTyzykVzQ8IUVuT1peQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-codes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/http-codes/-/http-codes-1.0.0.tgz", + "integrity": "sha512-yPS0/Sp66fmRSpZ6rbeFinytku7KnBiLa/CaDxHbcr0gAZuQqeGjgyfElyQ5xPiK21e4oL4iqooHLRcr2s7LoA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-meta-resolve": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", + "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/is-unsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-2.0.2.tgz", + "integrity": "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-diff": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", + "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^28.1.1", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-diff/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-get-type": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", + "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", + "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^28.1.3", + "jest-get-type": "^28.0.2", + "pretty-format": "^28.1.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-message-util/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jszip": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.2.tgz", + "integrity": "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/ky": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.0.tgz", + "integrity": "sha512-peKzuOlN/q3Q3jOgi4t0cp6DOgif5rVnmiSIsjsmkiOcdnSjkrKSUqQmRWYCTqjUtR9b3xQQr8aj7KwSW1r49A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-app": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/locate-app/-/locate-app-2.5.0.tgz", + "integrity": "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/locate-app/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@promptbook/utils": "0.69.5", + "type-fest": "4.26.0", + "userhome": "1.0.1" + } + }, + "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/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.zip": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz", + "integrity": "sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg==", + "dev": true, + "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/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/looks-same": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/looks-same/-/looks-same-10.0.1.tgz", + "integrity": "sha512-MHTGwSbifNjQIKkjAr9aOCDxg+E06wtdLBdSmjJSnFxl7u5rkQqTfx+FYK+Fv6sK2MKJFlBaTYN6WOLdm5JSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsquash/png": "^3.1.1", + "buffer-crc32": "^1.0.0", + "color-diff": "^1.1.0", + "nested-error-stacks": "^2.1.0", + "parse-color": "^1.0.0" + }, + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", + "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "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/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "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": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "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/modern-tar": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz", + "integrity": "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nested-error-stacks": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz", + "integrity": "sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==", + "dev": true, + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-path": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", + "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which": "^1.2.10" + }, + "bin": { + "npm-path": "bin/npm-path" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm-which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", + "integrity": "sha512-CM8vMpeFQ7MAPin0U3wzDhSGV0hMHNwHU0wjo402IVizPDrs45jSfSuoC+wThevY88LQti8VvaAnqYAeVy3I1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^2.9.0", + "npm-path": "^2.0.2", + "which": "^1.2.10" + }, + "bin": { + "npm-which": "bin/npm-which.js" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "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/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/p-queue": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-5.0.0.tgz", + "integrity": "sha512-6QfeouDf236N+MAxHch0CVIy8o/KBnmhttKjxZoOkUlzqU+u9rZgEyXH3OdckhTgawbqf5rpzmyR+07+Lv0+zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-color": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz", + "integrity": "sha512-fuDHYgFHJGbpGMgw9skY/bj3HL/Jrn4l/5rSspy00DoT4RyLnDcRvPxdZ+r6OFwIsgAuhDh4I09tAId4mI12bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "~0.5.0" + } + }, + "node_modules/parse-color/node_modules/color-convert": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz", + "integrity": "sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", + "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "dev": true, + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/plugins-loader": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/plugins-loader/-/plugins-loader-1.3.4.tgz", + "integrity": "sha512-JOt9iotacEz9QwvQm7PWk8d71jsigf/ko4UAwkPZKCFGWRzYAuSZ89eJpg1Jp3CAVLOLA5Rhp7rf885ANIEdTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.16.4" + } + }, + "node_modules/png-validator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/png-validator/-/png-validator-1.1.0.tgz", + "integrity": "sha512-MlRLyPI1p3/dJbsjVH+4xOPucycrz8T3EvO0BzCXaNtrUhZkZROtzib9J6mnC81AJO8eBIwiDZwTFel2cMmSuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", + "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "1.4.6", + "chromium-bidi": "0.4.16", + "cross-fetch": "4.0.0", + "debug": "4.3.4", + "devtools-protocol": "0.0.1147663", + "ws": "8.13.0" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1147663", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", + "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/puppeteer-core/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/puppeteer-core/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/puppeteer-core/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/puppeteer-core/node_modules/ws": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/puppeteer-core/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/puppeteer-core/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/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/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/recast": { + "version": "0.23.6", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.6.tgz", + "integrity": "sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha512-kT10v4dhrlLNcnO084hEjvXCI1wUG9qZLoz2RogxqDQQYy7IxjI/iMUkOtQTNEh6rzHxvdQWHsJyel1pKOVCxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", + "integrity": "sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rgb2hex": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.2.5.tgz", + "integrity": "sha512-22MOP1Rh7sAo1BZpDG6R5RFYzR2lYEgwq7HEmyW2qcsOqR2lQKmn+O//xV3YG/0rrhMC6KVX2hU+ZXuaw9a5bw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "MIT", + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rollup": { + "version": "4.64.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.3.tgz", + "integrity": "sha512-iIMWbptLYsZdGTJMH8ngApXdsCatE6bCMv2eqGIfGE1++kwh7TCWBdWKK0FKC0yx++196vGK5VdagzRXSLMa8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.3", + "@rollup/rollup-android-arm64": "4.64.3", + "@rollup/rollup-darwin-arm64": "4.64.3", + "@rollup/rollup-darwin-x64": "4.64.3", + "@rollup/rollup-freebsd-arm64": "4.64.3", + "@rollup/rollup-freebsd-x64": "4.64.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.3", + "@rollup/rollup-linux-arm-musleabihf": "4.64.3", + "@rollup/rollup-linux-arm64-gnu": "4.64.3", + "@rollup/rollup-linux-arm64-musl": "4.64.3", + "@rollup/rollup-linux-loong64-gnu": "4.64.3", + "@rollup/rollup-linux-loong64-musl": "4.64.3", + "@rollup/rollup-linux-ppc64-gnu": "4.64.3", + "@rollup/rollup-linux-ppc64-musl": "4.64.3", + "@rollup/rollup-linux-riscv64-gnu": "4.64.3", + "@rollup/rollup-linux-riscv64-musl": "4.64.3", + "@rollup/rollup-linux-s390x-gnu": "4.64.3", + "@rollup/rollup-linux-x64-gnu": "4.64.3", + "@rollup/rollup-linux-x64-musl": "4.64.3", + "@rollup/rollup-openbsd-x64": "4.64.3", + "@rollup/rollup-openharmony-arm64": "4.64.3", + "@rollup/rollup-win32-arm64-msvc": "4.64.3", + "@rollup/rollup-win32-ia32-msvc": "4.64.3", + "@rollup/rollup-win32-x64-gnu": "4.64.3", + "@rollup/rollup-win32-x64-msvc": "4.64.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safaridriver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safaridriver/-/safaridriver-1.0.1.tgz", + "integrity": "sha512-jkg4434cYgtrIF2AeY/X0Wmd2W73cK5qIEFE3hDrrQenJH/2SDJIXGvPAigfvQTcE9+H31zkiNHbUqcihEiMRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serialize-error": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^2.12.2" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sizzle": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/sizzle/-/sizzle-2.3.6.tgz", + "integrity": "sha512-abtd95IkbcMAaYk1Lux4k9Xz6wnQqyLy2aco9HGJ8jVaCDEcc+ug0hW8RdV6aIre3ycWXxPdcX0u7QL/1UaSoA==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spacetrim": { + "version": "0.11.59", + "resolved": "https://registry.npmjs.org/spacetrim/-/spacetrim-0.11.59.tgz", + "integrity": "sha512-lLYsktklSRKprreOm7NXReW8YiX2VBjbgmXYEziOoGf/qsJqAEACaDvoTtUOycwjpaSh+bT8eu0KrJn7UNxiCg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://buymeacoffee.com/hejny" + }, + { + "type": "github", + "url": "https://github.com/hejny/spacetrim/blob/main/README.md#%EF%B8%8F-contributing" + } + ], + "license": "Apache-2.0" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/strftime": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/strftime/-/strftime-0.10.2.tgz", + "integrity": "sha512-Y6IZaTVM80chcMe7j65Gl/0nmlNdtt+KWPle5YeCAjmsBfw+id2qdaJ5MDrxUq+OmHKab+jHe7mUjU/aNMSZZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.2.tgz", + "integrity": "sha512-rDG3Ah4TV0k1hWvLSzkZtMmLN9+eS+h3knq4MP6A42Y3Yh5qGNnOUs1jJkoSr8FG5dsL28c7KgkIBzSEykqtuw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "anynum": "^1.0.1" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.2.tgz", + "integrity": "sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.9.0", + "bare-fs": "^4.8.2", + "fast-fifo": "^1.3.2", + "streamx": "^2.28.1" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/temp": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", + "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", + "dev": true, + "engines": [ + "node >=0.8.0" + ], + "license": "MIT", + "dependencies": { + "os-tmpdir": "^1.0.0", + "rimraf": "~2.2.6" + } + }, + "node_modules/testplane": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/testplane/-/testplane-9.3.2.tgz", + "integrity": "sha512-8aJhJFpK9wyrQ4ZqQ64qNuqau0w7hWODCXkcSb6M0GV0Z+/C21wwxarucDuK3QucNHjSt3al4+80k0X+crRl5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "7.24.2", + "@gemini-testing/commander": "2.15.4", + "@jridgewell/trace-mapping": "0.3.31", + "@jspm/core": "2.0.1", + "@jsquash/png": "3.1.1", + "@puppeteer/browsers": "3.0.4", + "@testplane/wdio-protocols": "9.4.7", + "@testplane/wdio-utils": "9.5.5", + "@testplane/webdriverio": "9.6.2", + "@vitest/spy": "2.1.4", + "buffer-crc32": "1.0.0", + "chalk": "2.4.2", + "clear-require": "1.0.1", + "cli-progress": "3.12.0", + "debug": "2.6.9", + "edgedriver": "5.6.1", + "error-stack-parser": "2.1.4", + "esbuild": "0.25.8", + "expect-webdriverio": "3.6.0", + "fs-extra": "7.0.1", + "geckodriver": "4.5.0", + "gemini-configparser": "1.4.2", + "get-port": "5.1.1", + "import-meta-resolve": "4.0.0", + "local-pkg": "0.4.3", + "lodash": "4.17.21", + "looks-same": "10.0.1", + "micromatch": "4.0.5", + "mocha": "10.2.0", + "p-limit": "3.1.0", + "pirates": "4.0.7", + "plugins-loader": "1.3.4", + "png-validator": "1.1.0", + "proper-lockfile": "4.1.2", + "recast": "0.23.6", + "resolve.exports": "2.0.2", + "sizzle": "2.3.6", + "socket.io": "4.7.5", + "socket.io-client": "4.7.5", + "strftime": "0.10.2", + "strip-ansi": "6.0.1", + "temp": "0.8.3", + "tinyspy": "3.0.2", + "urijs": "1.19.11", + "url-join": "4.0.1", + "vite": "5.1.6", + "wait-port": "1.1.0", + "worker-farm": "1.7.0", + "ws": "8.18.3", + "yallist": "3.1.1" + }, + "bin": { + "hermione": "bin/hermione", + "testplane": "bin/testplane" + }, + "engines": { + "node": ">= 22" + }, + "peerDependencies": { + "@babel/parser": ">=7.0.0", + "@cspotcode/source-map-support": ">=0.7.0", + "@swc/core": ">=1.3.96", + "ts-node": ">=10.5.0" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@cspotcode/source-map-support": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/testplane/node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/testplane/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/testplane/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/testplane/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/testplane/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/testplane/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/testplane/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/testplane/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/testplane/node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/testplane/node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/testplane/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/testplane/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz", + "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.12.0.tgz", + "integrity": "sha512-d87yk8lqSFUYtR5fTFe2frpkMIrUEz+lgoJmhcL+J3StVl+8fj8ytE4lLnJOTPCE12YbumNGzf4LYsQyusdV5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/userhome": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/userhome/-/userhome-1.0.1.tgz", + "integrity": "sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/wait-port": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-1.1.0.tgz", + "integrity": "sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^9.3.0", + "debug": "^4.3.4" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-port/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "errno": "~0.1.7" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-naming": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.3.0.tgz", + "integrity": "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "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/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yauzl/node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/yazl": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-3.3.1.tgz", + "integrity": "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "^1.0.0" + } + }, + "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/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.8.0.tgz", + "integrity": "sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + } + } +} diff --git a/vibe/verified-examples/package.json b/vibe/verified-examples/package.json new file mode 100644 index 00000000..bf7c44b0 --- /dev/null +++ b/vibe/verified-examples/package.json @@ -0,0 +1,21 @@ +{ + "name": "testplane-docs-examples-20261009", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "node run-examples.cjs", + "test:selenium": "node run-examples.cjs --config testplane.selenium.config.js", + "check:snippets": "node verify-snippets.cjs" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@swc/core": "^1.16.13", + "@testplane/globals": "^1.0.1", + "@testplane/testing-library": "^1.0.5", + "html-reporter": "^11.17.2", + "testplane": "^9.3.2" + } +} diff --git a/vibe/verified-examples/run-examples.cjs b/vibe/verified-examples/run-examples.cjs new file mode 100644 index 00000000..94b0a404 --- /dev/null +++ b/vibe/verified-examples/run-examples.cjs @@ -0,0 +1,55 @@ +const http = require("node:http"); +const { spawn } = require("node:child_process"); +const server = http.createServer((req, res) => { + const path = new URL(req.url, "http://localhost").pathname; + if (path === "/navigation") { + res.setHeader("Content-Type", "text/html"); + res.end( + `

Navigation

`, + ); + } else if (path === "/report.csv") { + res.setHeader("Content-Type", "text/csv"); + res.setHeader("Content-Disposition", 'attachment; filename="report.csv"'); + res.end("name,value\nexample,42\n"); + } else if (path === "/api/items" || path === "/api/other") { + res.setHeader("Content-Type", "application/json"); + res.end( + JSON.stringify({ + source: path === "/api/items" ? "original" : "redirected", + method: req.method, + header: req.headers["x-example"] || null, + }), + ); + } else if (path === "/frame") { + res.setHeader("Content-Type", "text/html"); + res.end("Frame

Frame content

"); + } else { + res.setHeader("Content-Type", "text/html"); + res.end( + "Command examples

Command examples

", + ); + } +}); +server.listen(0, "127.0.0.1", () => { + const baseUrl = `http://127.0.0.1:${server.address().port}`; + const child = spawn( + process.execPath, + [ + require("node:path").join(__dirname, "node_modules/testplane/bin/testplane"), + ...process.argv.slice(2), + ], + { + env: { ...process.env, EXAMPLE_BASE_URL: baseUrl }, + stdio: "inherit", + }, + ); + child.on("error", error => { + console.error(error.message); + server.close(); + process.exitCode = 1; + }); + child.on("exit", code => { + server.close(); + process.exitCode = code ?? 1; + }); +}); diff --git a/vibe/verified-examples/testplane.config.js b/vibe/verified-examples/testplane.config.js new file mode 100644 index 00000000..54b91eb4 --- /dev/null +++ b/vibe/verified-examples/testplane.config.js @@ -0,0 +1,50 @@ +module.exports = { + gridUrl: "local", + baseUrl: process.env.EXAMPLE_BASE_URL, + headless: true, + resetCursor: false, + isolation: false, + sessionsPerBrowser: 1, + testsPerSession: 1, + retry: 0, + testTimeout: 30000, + httpTimeout: 60000, + pageLoadTimeout: 15000, + waitTimeout: 5000, + prepareBrowser(browser) { + require("node:fs").writeFileSync( + "validation-environment.json", + JSON.stringify( + { + browserName: browser.capabilities.browserName, + browserVersion: browser.capabilities.browserVersion, + isBidi: browser.isBidi, + }, + null, + 2, + ) + "\n", + ); + }, + sets: { + examples: { + files: [ + "examples/browser/**/*.testplane.js", + "examples/related/**/*.testplane.js", + "examples/element/**/*.testplane.js", + "examples/mock/**/*.testplane.js", + "examples/testplane-browser/**/*.testplane.js", + "examples/testplane-element/**/*.testplane.js", + ], + browsers: ["chrome"], + }, + }, + browsers: { + chrome: { + desiredCapabilities: { + browserName: "chrome", + browserVersion: "150.0.7871.124", + webSocketUrl: true, + }, + }, + }, +}; diff --git a/vibe/verified-examples/testplane.selenium.config.js b/vibe/verified-examples/testplane.selenium.config.js new file mode 100644 index 00000000..a129bd58 --- /dev/null +++ b/vibe/verified-examples/testplane.selenium.config.js @@ -0,0 +1,21 @@ +const config = require("./testplane.config"); +module.exports = { + ...config, + gridUrl: process.env.SELENIUM_GRID_URL || "http://127.0.0.1:4446", + sets: { selenium: { files: "examples/selenium/**/*.testplane.js", browsers: ["chrome"] } }, + browsers: { + chrome: { + desiredCapabilities: { + browserName: "chrome", + "se:downloadsEnabled": true, + webSocketUrl: true, + "goog:chromeOptions": { + ...(process.env.EXAMPLE_CHROME_BINARY + ? { binary: process.env.EXAMPLE_CHROME_BINARY } + : {}), + args: ["--headless=new"], + }, + }, + }, + }, +}; diff --git a/vibe/verified-examples/verify-snippets.cjs b/vibe/verified-examples/verify-snippets.cjs new file mode 100644 index 00000000..d13d9367 --- /dev/null +++ b/vibe/verified-examples/verify-snippets.cjs @@ -0,0 +1,33 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const manifest = require("./manifest.json"); +const root = path.resolve(__dirname, "../.."); +const verified = new Map(); +for (const entry of manifest) { + const expected = fs.readFileSync(path.join(__dirname, entry.test), "utf8"); + for (const page of [entry.page, entry.translation]) { + const source = fs.readFileSync(path.join(root, page), "utf8"); + const blocks = [...source.matchAll(/^```javascript\n([\s\S]*?)^```/gm)]; + const index = entry.blockIndex ?? 0; + assert.equal(blocks.length, entry.blockCount ?? 1, page); + assert.ok(blocks[index], `${page}: missing block ${index}`); + const indexes = verified.get(page) ?? new Set(); + assert.ok(!indexes.has(index), `${page}: duplicate block ${index}`); + indexes.add(index); + verified.set(page, indexes); + assert.equal( + blocks[index][1], + expected, + `${page} block ${index} differs from ${entry.test}`, + ); + } +} +for (const [page, indexes] of verified) { + const count = [...fs.readFileSync(path.join(root, page), "utf8").matchAll(/^```javascript\n/gm)] + .length; + assert.equal(indexes.size, count, `${page}: not all JavaScript blocks have tests`); +} +console.log( + `Verified ${manifest.length * 2} localized blocks across ${verified.size} pages and ${new Set(manifest.map(entry => entry.test)).size} test files`, +); From e5df81b15b1ac8fe59228642d7e073b3432b6a70 Mon Sep 17 00:00:00 2001 From: sipayrt Date: Thu, 15 Oct 2026 18:44:13 +0300 Subject: [PATCH 2/4] ci: validate documentation examples in pull requests --- .github/workflows/doc-examples.yml | 111 +++++++ .prettierignore | 6 + package-lock.json | 3 + package.json | 11 +- vibe/verified-examples/.gitignore | 3 + vibe/verified-examples/README.md | 72 +++++ vibe/verified-examples/audit-snippets.mjs | 288 ++++++++++++++++++ .../verified-examples/evidence/ci-20261015.md | 36 +++ vibe/verified-examples/manifest.json | 9 +- vibe/verified-examples/non-executable.json | 1 + vibe/verified-examples/package.json | 2 +- vibe/verified-examples/run-ci.cjs | 172 +++++++++++ vibe/verified-examples/run-examples.cjs | 10 + vibe/verified-examples/suite-files.cjs | 18 ++ vibe/verified-examples/testplane.config.js | 9 +- .../testplane.selenium.config.js | 7 +- .../verified-examples/tests/coverage.test.mjs | 263 ++++++++++++++++ vibe/verified-examples/tests/runtime.test.cjs | 62 ++++ vibe/verified-examples/verify-runtime.cjs | 53 ++++ vibe/verified-examples/verify-snippets.cjs | 39 +-- 20 files changed, 1126 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/doc-examples.yml create mode 100644 vibe/verified-examples/audit-snippets.mjs create mode 100644 vibe/verified-examples/evidence/ci-20261015.md create mode 100644 vibe/verified-examples/non-executable.json create mode 100644 vibe/verified-examples/run-ci.cjs create mode 100644 vibe/verified-examples/suite-files.cjs create mode 100644 vibe/verified-examples/tests/coverage.test.mjs create mode 100644 vibe/verified-examples/tests/runtime.test.cjs create mode 100644 vibe/verified-examples/verify-runtime.cjs diff --git a/.github/workflows/doc-examples.yml b/.github/workflows/doc-examples.yml new file mode 100644 index 00000000..858afa48 --- /dev/null +++ b/.github/workflows/doc-examples.yml @@ -0,0 +1,111 @@ +name: Documentation examples + +on: + pull_request: + branches: [master] + +permissions: + contents: read + +concurrency: + group: doc-examples-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + coverage: + name: Examples coverage + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: 22.21.1 + cache: npm + - run: npm ci + - run: npm run test:examples:unit + - name: Check new and changed examples against the PR base + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: npm run check:examples -- --base "$BASE_SHA" + - name: Publish coverage summary + if: ${{ always() }} + run: | + if [ -f tmp/doc-examples/summary.md ]; then + cat tmp/doc-examples/summary.md >> "$GITHUB_STEP_SUMMARY" + fi + - name: Upload full inventory including legacy debt + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: doc-examples-coverage + path: tmp/doc-examples/ + if-no-files-found: warn + retention-days: 14 + + runtime: + name: Examples runtime (${{ matrix.suite }}) + runs-on: ubuntu-24.04 + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + suite: [chrome, selenium] + defaults: + run: + working-directory: vibe/verified-examples + shell: bash + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: 22.21.1 + cache: npm + cache-dependency-path: vibe/verified-examples/package-lock.json + - uses: actions/setup-java@v4 + if: ${{ matrix.suite == 'selenium' }} + with: + distribution: temurin + java-version: "21" + - name: Cache Testplane browsers and drivers + uses: actions/cache@v4 + with: + path: ~/.testplane + key: doc-examples-${{ runner.os }}-${{ runner.arch }}-chrome150-${{ hashFiles('vibe/verified-examples/package-lock.json', 'vibe/verified-examples/testplane.config.js', 'vibe/verified-examples/run-ci.cjs') }} + - run: npm ci --legacy-peer-deps + - name: Download pinned Selenium Grid + if: ${{ matrix.suite == 'selenium' }} + run: | + mkdir -p artifacts + curl --fail --location --retry 3 \ + https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.50.0/selenium-server-4.50.0.jar \ + --output "$RUNNER_TEMP/selenium-server.jar" + echo "SELENIUM_JAR=$RUNNER_TEMP/selenium-server.jar" >> "$GITHUB_ENV" + - name: Run every declared example and verify runtime results + env: + SUITE: ${{ matrix.suite }} + run: | + mkdir -p artifacts + node run-ci.cjs "$SUITE" 2>&1 | tee artifacts/testplane.log + - name: Publish runtime summary + if: ${{ always() }} + run: | + if [ -f artifacts/runtime-summary.md ]; then + cat artifacts/runtime-summary.md >> "$GITHUB_STEP_SUMMARY" + fi + - name: Upload results even on failure + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: doc-examples-${{ matrix.suite }} + path: | + vibe/verified-examples/artifacts/ + vibe/verified-examples/validation-environment.json + vibe/verified-examples/testplane-debug/ + if-no-files-found: warn + retention-days: 14 diff --git a/.prettierignore b/.prettierignore index a3f4dbf2..349ec956 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,9 @@ package-lock.json storybook-static build tmp + +# Generated example-validation output +vibe/verified-examples/artifacts/ +vibe/verified-examples/validation-environment.json +vibe/verified-examples/testplane-debug/ +vibe/verified-examples/.testplane/ diff --git a/package-lock.json b/package-lock.json index 3c7c0523..8bded7cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,12 +49,15 @@ "prettier": "3.2.5", "prettier-plugin-tailwindcss": "0.6.14", "react-spring": "9.7.3", + "remark-mdx": "3.0.1", + "remark-parse": "11.0.0", "sass": "1.72.0", "storybook": "8.0.5", "tailwindcss": "4.1.13", "typed.js": "2.1.0", "typescript": "5.4.3", "typescript-eslint": "7.4.0", + "unified": "11.0.4", "zustand": "4.5.2" }, "engines": { diff --git a/package.json b/package.json index eda511d8..644ad570 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "start": "docusaurus start", "lint": "eslint --cache src plugins && prettier --check .", "reformat": "eslint --cache --fix src plugins && prettier --write .", - "test": "npm run lint && npm run typecheck", + "test": "npm run lint && npm run typecheck && npm run test:examples:unit", "typecheck": "tsc", "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" + "build-storybook": "storybook build", + "check:examples": "node vibe/verified-examples/verify-snippets.cjs", + "test:examples:unit": "node --test vibe/verified-examples/tests/*.test.*" }, "dependencies": { "@docusaurus/core": "3.10.1", @@ -60,7 +62,10 @@ "typed.js": "2.1.0", "typescript": "5.4.3", "typescript-eslint": "7.4.0", - "zustand": "4.5.2" + "zustand": "4.5.2", + "unified": "11.0.4", + "remark-parse": "11.0.0", + "remark-mdx": "3.0.1" }, "browserslist": { "production": [ diff --git a/vibe/verified-examples/.gitignore b/vibe/verified-examples/.gitignore index 6e87586d..bb5b722a 100644 --- a/vibe/verified-examples/.gitignore +++ b/vibe/verified-examples/.gitignore @@ -3,3 +3,6 @@ validation-environment.json *.log !evidence/**/*.log testplane-debug/ + +artifacts/ +.testplane/ diff --git a/vibe/verified-examples/README.md b/vibe/verified-examples/README.md index 984e42dc..2196894d 100644 --- a/vibe/verified-examples/README.md +++ b/vibe/verified-examples/README.md @@ -35,3 +35,75 @@ Этапы 1–4 и немобильная часть этапа 6 выполнены. Итоговая проверка немобильного объема (этап 7) выполнена: 42/42 Chrome/BiDi и 1/1 Selenium, retries 0; линтер, типы, EN/RU build и сверка примеров проходят. 2026-10-14 исправлены оба оставшихся уточнения текста (`setViewport` и область действия `addInitScript`) в EN/RU; результаты повторной проверки сохраняются в `evidence/review-fixes-20261014/`. Подробнее — `evidence/review.md`. Этап 5 и мобильные ссылки этапа 6 отложены пользователем: 11 мобильных команд не добавлены и не проверены. Публикация требует отдельного согласования. В гайде эмуляции проверены три измененных JavaScript-блока (сеть и CPU). Остальные, не измененные этой задачей примеры гайдов не входят в отчет о runtime-проверке. + +## Проверка примеров в pull request + +Workflow `Documentation examples` запускается на каждом PR в `master`. Он не использует секреты, S3 или права записи в репозиторий. Проверки разделены: + +- `Examples coverage` проверяет соответствие опубликованного кода тестам и контролирует новые и измененные блоки. +- `Examples runtime (chrome)` выполняет основной набор в Chrome/BiDi. +- `Examples runtime (selenium)` выполняет `downloadFile` в локальном Selenium Grid с управляемыми загрузками. + +Чтобы эти проверки блокировали слияние, добавьте все три имени в обязательные status checks правила защиты `master`. Сам YAML не изменяет настройки защиты ветки. + +### Что считается покрытым + +`manifest.json` связывает блок документации с полным тестовым файлом. Содержимое блока должно совпадать с файлом, включая пробелы и завершающий перевод строки. Обе локали проверяются независимо. Конфигурации Testplane получают список файлов из этого же manifest: отдельный список исполняемых примеров поддерживать не нужно. + +Поля записи: + +- `page` — путь страницы относительно корня репозитория. +- `translation` — путь перевода; необязателен для примера только в одной локали. +- `test` — путь файла относительно этого проекта, например `examples/browser/addInitScript.testplane.js`. Допустимые каталоги определены в `suite-files.cjs`; `selenium` запускается в отдельном наборе. +- `language` — язык блока, по умолчанию `javascript`. +- `blockIndex` — индекс среди блоков **этого языка** на странице, начиная с нуля; по умолчанию `0`. +- `blockCount` — необязательное ожидаемое число блоков этого языка на странице. +- `testCount` — число тестов в файле, по умолчанию `1`. Для всех ссылок на один файл значение должно совпадать. + +После запуска CI читает JSONL-результаты Testplane. Каждый заявленный файл должен дать ровно `testCount` успешных результатов. Пропуски, повторы, отсутствующие тесты и посторонние файлы завершают проверку ошибкой. Одного успешного exit code недостаточно. + +### Новые примеры и накопленный долг + +Инвентаризация использует AST Markdown/MDX, а не поиск только по метке `javascript`. Она учитывает блоки с любым языком и без языка, в том числе внутри вкладок. JSX-элементы `CodeBlock`, `pre` и `code` учитываются как `jsx-code`, но автоматически не исполняются. Inline-код, произвольные пользовательские React-компоненты и примеры за внешними ссылками не входят в эту проверку. + +В CI базовая версия берется из `pull_request.base.sha`. Для каждой страницы сравниваются язык и SHA-256 содержимого блоков; число одинаковых вхождений тоже учитывается. Изменение обычного текста, номера строки или `title` у блока не требует новой проверки. Новый, измененный, переименованный или дополнительно скопированный пример требует покрытия. Без доступной базовой Git-ревизии проверка завершается ошибкой. Редактируемого списка «старых» примеров нет. + +Неизмененные непокрытые блоки получают статус `legacy`: они видны в отчете, но не блокируют PR. Это долг покрытия, **не доказательство работоспособности и не список ошибок**. Удалить покрытие сохранившегося ранее проверенного примера нельзя: CI покажет `regression`. + +Из инвентаризации исключены версии документации, включая v8, и явный список мобильных страниц в `audit-snippets.mjs`. Текущий список исключенных страниц входит в отчет. Новые мобильные страницы нужно отдельно включать в этот список при согласованном расширении мобильного раздела; запускать их на desktop-стенде не следует. + +Если новый блок заведомо не является исполняемым примером — например, показывает вывод программы или сигнатуру, — добавьте точечную запись в `non-executable.json`. Укажите `page`, `language`, `blockIndex`, `sha256` и содержательный `reason`. Хеш берите из `coverage.json`: это SHA-256 UTF-8 значения AST-блока без перевода строки перед закрывающей оградой. Запись привязана к конкретному содержимому и устаревает при изменении кода. Ее нужно согласовать на ревью. Не используйте такую классификацию вместо теста для исполняемого примера. В отчете она отделена от успешного runtime-покрытия. Сейчас список пуст. + +### Как добавить или изменить пример + +1. Подготовьте полный тест в соответствующем каталоге `examples/`. Используйте локальный HTTP-стенд из `run-examples.cjs`, без внешних сайтов и секретов. +1. Выполните тест и перенесите тот же код в блок документации. При необходимости измените обе локали. +1. Добавьте или обновите запись `manifest.json`. Для нового типа примеров, не поддерживаемого этим Testplane-стендом, сначала добавьте исполняемый адаптер и его проверку, а не исключение из покрытия. +1. Из корня репозитория выполните: + + ```bash + npm ci + npm run test:examples:unit + npm run check:examples -- --base origin/master + ``` + +1. Из этого каталога выполните полный основной набор: + + ```bash + npm ci --legacy-peer-deps + node run-ci.cjs chrome + ``` + +Для Selenium установите Java 21, скачайте `selenium-server-4.50.0.jar` из [официального релиза Selenium](https://github.com/SeleniumHQ/selenium/releases/tag/selenium-4.50.0) и укажите абсолютный путь в `SELENIUM_JAR`. Затем выполните `node run-ci.cjs selenium`. Скрипт проверяет SHA-256 JAR, устанавливает браузер и драйвер, поднимает свой Grid на `127.0.0.1:4446`, дожидается готовности и останавливает его после теста. Порт должен быть свободен. `JAVA_BINARY` позволяет указать отдельный Java executable. Существующий внешний Grid этот CI-runner не использует. + +`run-ci.cjs` опирается на внутренний установщик закрепленной версии Testplane, как standalone-workflow самого фреймворка. При обновлении Testplane проверяйте совместимость этого адаптера. Chrome закреплен в `testplane.config.js`, зависимости — в lock-файле. Эта итерация проверяет воспроизводимость на закрепленном окружении, а не совместимость со всеми браузерами или последним npm-релизом. + +### Где смотреть результаты + +В GitHub Actions доступны краткие Job Summary и три артефакта со сроком хранения 14 дней: + +- `doc-examples-coverage`: `coverage.json`, полный `coverage.md` со всеми старыми непокрытыми блоками, короткий `summary.md`. +- `doc-examples-chrome`: лог Testplane, JSONL-результаты, сведения об окружении и итог runtime-проверки. +- `doc-examples-selenium`: те же файлы и лог Grid. + +Артефакты загружаются и при падении проверки. Ошибка установки окружения не считается успешным прогоном. Локальные результаты по умолчанию лежат в `tmp/doc-examples/` от корня репозитория и `artifacts/` этого проекта. Для разделения прогонов можно задать `EXAMPLE_ARTIFACTS`. Эти каталоги не коммитятся. Файлы в `evidence/` сохраняют историю прежней ручной валидации и не используются как подтверждение текущего CI-прогона. diff --git a/vibe/verified-examples/audit-snippets.mjs b/vibe/verified-examples/audit-snippets.mjs new file mode 100644 index 00000000..49469eba --- /dev/null +++ b/vibe/verified-examples/audit-snippets.mjs @@ -0,0 +1,288 @@ +import { execFileSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkMdx from "remark-mdx"; +import { suiteFor } from "./suite-files.cjs"; + +const roots = ["docs/", "i18n/ru/docusaurus-plugin-content-docs/current/"]; +const harness = "vibe/verified-examples/"; +const mobile = new Set([ + "guides/android-testing", + ...[ + "setOrientation", + "touchAction", + "swipe", + "tap", + "getContext", + "getContexts", + "switchContext", + "relaunchActiveApp", + "deepLink", + ].map(name => `commands/browser/${name}`), + ...["touchAction", "tap", "longPress", "pinch", "zoom"].map(name => `commands/element/${name}`), +]); +const parser = unified().use(remarkParse).use(remarkMdx); +const hash = value => createHash("sha256").update(value).digest("hex"); +const key = block => `${block.page}\0${block.language}\0${block.sha256}`; +const id = block => `${block.page}\0${block.language}\0${block.blockIndex}`; +const isDoc = name => roots.some(root => name.startsWith(root)) && /\.mdx?$/.test(name); +const isMobile = name => + mobile.has(name.slice(roots.find(root => name.startsWith(root)).length).replace(/\.mdx?$/, "")); +const escape = value => String(value).replace(/[&<>|`\r\n]/g, char => `&#${char.charCodeAt(0)};`); + +function blocks(page, source) { + const result = []; + const indexes = new Map(); + const visit = node => { + const jsxCode = + /^mdxJsx/.test(node.type) && ["CodeBlock", "pre", "code"].includes(node.name); + if (node.type === "code" || jsxCode) { + const language = jsxCode ? "jsx-code" : node.lang || ""; + const value = jsxCode + ? source.slice(node.position.start.offset, node.position.end.offset) + : node.value; + const blockIndex = indexes.get(language) || 0; + indexes.set(language, blockIndex + 1); + result.push({ + page, + language, + blockIndex, + line: node.position.start.line, + sha256: hash(value), + value, + }); + if (jsxCode) return; + } + for (const child of node.children || []) visit(child); + }; + visit(parser.parse(source)); + return result; +} + +// Public CLI: compare the working tree with an immutable Git base, never a PR-editable debt list. +export function main(args = process.argv.slice(2)) { + const { values } = parseArgs({ + args, + options: { + root: { type: "string", default: fileURLToPath(new URL("../../", import.meta.url)) }, + base: { type: "string" }, + "report-dir": { type: "string" }, + }, + }); + const root = fs.realpathSync(values.root); + const reportDir = path.resolve(values["report-dir"] || path.join(root, "tmp/doc-examples")); + const report = { base: values.base, counts: {}, errors: [], excluded: [], blocks: [] }; + const git = (...args) => + execFileSync("git", args, { + cwd: root, + encoding: "utf8", + maxBuffer: 32 * 1024 * 1024, + stdio: ["ignore", "pipe", "pipe"], + }); + const read = name => { + const resolved = fs.realpathSync(path.join(root, name)); + if (!resolved.startsWith(root + path.sep)) + throw new Error(`Path outside repository: ${name}`); + return fs.readFileSync(resolved, "utf8"); + }; + try { + if (!values.base) throw new Error("An explicit --base Git revision is required"); + report.base = git( + "rev-parse", + "--verify", + "--end-of-options", + `${values.base}^{commit}`, + ).trim(); + const baseNames = git("ls-tree", "-rz", "--name-only", report.base) + .split("\0") + .filter(Boolean); + const currentNames = [ + ...new Set( + git("ls-files", "-z", "--cached", "--others", "--exclude-standard").split("\0"), + ), + ].filter(name => name && fs.existsSync(path.join(root, name))); + const inventory = (names, get) => + names.filter(isDoc).flatMap(name => { + if (isMobile(name)) return []; + try { + return blocks(name, get(name)); + } catch (error) { + report.errors.push(`${name}: ${error.message}`); + return []; + } + }); + report.excluded = currentNames.filter(name => isDoc(name) && isMobile(name)); + report.excludedRoots = [ + "versioned_docs/", + "i18n/*/docusaurus-plugin-content-docs/version-*/", + ]; + const baseRead = name => git("show", `${report.base}:${name}`); + const before = inventory(baseNames, baseRead); + const current = inventory(currentNames, read); + const manifest = JSON.parse(read(`${harness}manifest.json`)); + const oldManifest = baseNames.includes(`${harness}manifest.json`) + ? JSON.parse(baseRead(`${harness}manifest.json`)) + : []; + const covered = new Map(); + for (const entry of manifest) { + try { + suiteFor(entry.test); + if (!Number.isInteger(entry.testCount ?? 1) || (entry.testCount ?? 1) < 1) + throw new Error(`Invalid testCount: ${entry.test}`); + const testCode = read(`${harness}${entry.test}`); + for (const page of [entry.page, entry.translation].filter(Boolean)) { + const candidates = current.filter( + block => + block.page === page && + block.language === (entry.language ?? "javascript"), + ); + const block = candidates[entry.blockIndex ?? 0]; + if (!block) + throw new Error( + `Missing or excluded block: ${page} #${entry.blockIndex ?? 0}`, + ); + if (entry.blockCount !== undefined && candidates.length !== entry.blockCount) + throw new Error(`Unexpected block count: ${page}`); + if (covered.has(id(block))) + throw new Error(`Duplicate manifest entry: ${page} #${block.blockIndex}`); + if (testCode !== block.value + "\n") + throw new Error(`${page}:${block.line} differs from ${entry.test}`); + covered.set(id(block), entry.test); + } + } catch (error) { + report.errors.push(error.message); + } + } + const exemptions = new Map(); + const exemptionFile = `${harness}non-executable.json`; + const entries = fs.existsSync(path.join(root, exemptionFile)) + ? JSON.parse(read(exemptionFile)) + : []; + for (const entry of entries) { + try { + const block = current.find( + block => + block.page === entry.page && + block.language === entry.language && + block.blockIndex === entry.blockIndex, + ); + if (!block || block.sha256 !== entry.sha256 || !entry.reason?.trim()) + throw new Error( + `Stale or incomplete non-executable classification: ${entry.page}`, + ); + if (covered.has(id(block)) || exemptions.has(id(block))) + throw new Error(`Duplicate classification: ${entry.page}`); + exemptions.set(id(block), entry.reason.trim()); + } catch (error) { + report.errors.push(error.message); + } + } + // Do not let deleting a mapping downgrade a retained, previously covered block to debt/exemption. + const previouslyCovered = new Map(); + for (const entry of oldManifest) { + for (const page of [entry.page, entry.translation].filter(Boolean)) { + const block = before.filter( + block => + block.page === page && block.language === (entry.language ?? "javascript"), + )[entry.blockIndex ?? 0]; + if (block) + previouslyCovered.set(key(block), (previouslyCovered.get(key(block)) || 0) + 1); + } + } + const available = new Map(); + for (const block of before) available.set(key(block), (available.get(key(block)) || 0) + 1); + // Only baseline debt can pay for unclassified occurrences. Covered/exempt + // copies must neither consume debt credits nor create new ones by reordering. + for (const [fingerprint, count] of previouslyCovered) { + available.set(fingerprint, Math.max(0, (available.get(fingerprint) || 0) - count)); + } + const oldExemptions = baseNames.includes(exemptionFile) + ? JSON.parse(baseRead(exemptionFile)) + : []; + for (const entry of oldExemptions) { + const block = before.find( + block => id(block) === id(entry) && block.sha256 === entry.sha256, + ); + if (block) available.set(key(block), Math.max(0, (available.get(key(block)) || 0) - 1)); + } + for (const block of current) { + const remaining = available.get(key(block)) || 0; + if (remaining && !covered.has(id(block)) && !exemptions.has(id(block))) { + available.set(key(block), remaining - 1); + } + const status = covered.has(id(block)) + ? "covered" + : exemptions.has(id(block)) + ? "non-executable" + : remaining + ? "legacy" + : "uncovered"; + const { value, ...location } = block; + report.blocks.push({ + ...location, + status, + test: covered.get(id(block)), + reason: exemptions.get(id(block)), + }); + } + for (const [fingerprint, count] of previouslyCovered) { + const retained = report.blocks.filter(block => key(block) === fingerprint); + const missing = + Math.min(count, retained.length) - + retained.filter(block => block.status === "covered").length; + for (const block of retained + .filter(block => block.status !== "covered") + .slice(0, Math.max(0, missing))) + block.status = "regression"; + } + } catch (error) { + report.errors.push(error.message); + } + for (const status of ["covered", "legacy", "non-executable", "uncovered", "regression"]) + report.counts[status] = report.blocks.filter(block => block.status === status).length; + report.ok = !report.errors.length && !report.counts.uncovered && !report.counts.regression; + const summary = [ + "# Documentation examples", + "", + `Base: ${escape(report.base || "missing")}`, + "", + `Gate: **${report.ok ? "PASS" : "FAIL"}**. Runtime results are checked separately.`, + "", + "| Classification | Blocks |", + "| --- | ---: |", + ...Object.entries(report.counts).map(([status, count]) => `| ${status} | ${count} |`), + "", + "Covered means linked to an exact test source, not a successful browser run.", + "", + "Mobile pages and versioned documentation (including v8) are excluded.", + "", + ...report.errors.map(error => `- ${escape(error)}`), + "", + ].join("\n"); + const rows = report.blocks.map( + block => + `| ${escape(block.page)}:${block.line} | ${escape(block.language || "unlabelled")} #${block.blockIndex} | ${block.status} | ${escape(block.test || block.reason || "—")} |`, + ); + fs.mkdirSync(reportDir, { recursive: true }); + fs.writeFileSync(path.join(reportDir, "coverage.json"), JSON.stringify(report, null, 2) + "\n"); + fs.writeFileSync(path.join(reportDir, "summary.md"), summary); + fs.writeFileSync( + path.join(reportDir, "coverage.md"), + `${summary}\n## Inventory\n\n| Source | Block | Classification | Test / reason |\n| --- | --- | --- | --- |\n${rows.join("\n")}\n\n## Excluded mobile pages\n\n${report.excluded.map(page => `- ${escape(page)}`).join("\n")}\n`, + ); + console.log(summary); + for (const block of report.blocks.filter(block => + ["uncovered", "regression"].includes(block.status), + )) + console.error( + `${block.page}:${block.line}: ${block.status} (${block.language} #${block.blockIndex}, sha256=${block.sha256})`, + ); + for (const error of report.errors) + console.error(`Coverage error (base ${values.base}): ${error}`); + process.exitCode = report.ok ? 0 : 1; +} diff --git a/vibe/verified-examples/evidence/ci-20261015.md b/vibe/verified-examples/evidence/ci-20261015.md new file mode 100644 index 00000000..d3006bf0 --- /dev/null +++ b/vibe/verified-examples/evidence/ci-20261015.md @@ -0,0 +1,36 @@ +# Проверка первого этапа CI — 2026-10-15 + +## Объем + +Добавлены workflow `Documentation examples`, AST-инвентаризация текущих EN/RU-страниц, контроль новых и измененных примеров относительно базы PR, запуск прежнего проверенного набора и валидация фактических JSONL-результатов. Мобильные страницы и версии документации, включая v8, исключены из контроля примеров. Исходники опубликованных страниц и сами тестовые примеры в этой итерации не изменялись. + +База PR для локальной проверки: `7e662b60be11ac6813eca9c5d84c5142c043d01f`. Изменения CI проверены в worktree ветки `sp.commands-doc` поверх `b7c404910c87b35c9f363610ad459960f487d130`. + +## Результаты + +| Проверка | Результат | +| ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| Чистая установка корневого проекта: `npm ci --no-audit --no-fund` | Успешно | +| Установка стенда: `npm ci --legacy-peer-deps` | Успешно | +| `npm test` | Линтер, Prettier, TypeScript и 40 unit/integration-тестов проверки примеров прошли | +| `npm run check:examples -- --base origin/master` | 86 covered, 1980 legacy, 0 uncovered, 0 regression, 0 non-executable | +| `npm run check:snippets` из стенда | Успешно; локальная база HEAD | +| `node run-ci.cjs chrome` | 42/42 теста в 40 файлах, без пропусков и повторов | +| `node run-ci.cjs selenium` | 1/1 тест в одном файле, без пропусков и повторов | +| `npm run build` | EN и RU собраны; предупреждения HTML-minifier/admonition вне изменений CI не устранялись | +| actionlint 1.7.7 для нового workflow | Успешно | +| `git diff --check` | Успешно | + +Реальное локальное окружение: macOS arm64, Node.js 22.21.1, Testplane 9.3.2, Chrome for Testing 150.0.7871.124. Selenium-прогон выполнен с Java 21.0.12.1 и Selenium Server 4.50.0. CI-runner проверил SHA-256 JAR, самостоятельно запустил Grid на localhost, дождался готовности и остановил его после теста. + +## Отрицательные сценарии + +Тесты проверки покрытия выполняются на временных настоящих Git-репозиториях. Они проверяют новые страницы, разные языки блоков, измененный код, дубли, перевод, отсутствие базы, регресс покрытия, недопустимые пути тестов, ошибочный MDX, вложенные вкладки, явные классификации и их устаревание. Runtime-проверка отклоняет skipped, retry, fail, дубли результатов, отсутствующие файлы/тесты и посторонние браузеры. + +Первый набор тестов воспроизвел недостатки старой проверки: 18 падений из 20. Отдельно воспроизведены потеря инвентаризации при ошибке manifest и зависимость учета одинаковых блоков от их порядка. Последний случай найден на независимом read-only ревью: новый непокрытый дубликат мог ошибочно получить статус legacy. Исправление резервирует прежнее покрытие и расходует только объем прежнего непокрытого кода. Все четыре сценария порядка вставки включены в окончательный набор из 40 успешных тестов. После исправлений открытых замечаний ревью не осталось. + +## Ограничения и следующий шаг + +Workflow еще не запускался на GitHub Actions Ubuntu 24.04: изменения не отправлены в удаленную ветку. Локальный macOS-прогон не подтверждает установку и запуск на Ubuntu. После push требуется проверить все три GitHub checks; для обязательного блокирования слияния владелец репозитория должен добавить их в branch protection, как описано в [README](../README.md). + +Полный актуальный список долга генерируется, а не хранится здесь: локально это `tmp/doc-examples/coverage.md` и `coverage.json` от корня репозитория. Локальные логи и JSONL лежат в `artifacts/chrome/` и `artifacts/selenium/` стенда. В GitHub Actions эти данные загружаются как артефакты. Число legacy включает все виды блоков кода, в том числе конфигурацию и вывод; это не число заведомо исполняемых JavaScript-тестов. Исторический отчет не заменяет новый CI-прогон. diff --git a/vibe/verified-examples/manifest.json b/vibe/verified-examples/manifest.json index ac3ae0e7..442e4dae 100644 --- a/vibe/verified-examples/manifest.json +++ b/vibe/verified-examples/manifest.json @@ -57,7 +57,8 @@ { "page": "docs/commands/browser/throttleNetwork.mdx", "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/browser/throttleNetwork.mdx", - "test": "examples/browser/throttleNetwork.testplane.js" + "test": "examples/browser/throttleNetwork.testplane.js", + "testCount": 2 }, { "page": "docs/commands/browser/unstable_captureDomSnapshot.mdx", @@ -197,14 +198,16 @@ { "page": "docs/commands/overview.mdx", "translation": "i18n/ru/docusaurus-plugin-content-docs/current/commands/overview.mdx", - "test": "examples/related/overview.testplane.js" + "test": "examples/related/overview.testplane.js", + "testCount": 2 }, { "page": "docs/basic-guides/user-environment-emulation.mdx", "translation": "i18n/ru/docusaurus-plugin-content-docs/current/basic-guides/user-environment-emulation.mdx", "test": "examples/browser/throttleNetwork.testplane.js", "blockIndex": 0, - "blockCount": 3 + "blockCount": 3, + "testCount": 2 }, { "page": "docs/basic-guides/user-environment-emulation.mdx", diff --git a/vibe/verified-examples/non-executable.json b/vibe/verified-examples/non-executable.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/vibe/verified-examples/non-executable.json @@ -0,0 +1 @@ +[] diff --git a/vibe/verified-examples/package.json b/vibe/verified-examples/package.json index bf7c44b0..8621dd72 100644 --- a/vibe/verified-examples/package.json +++ b/vibe/verified-examples/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "node run-examples.cjs", "test:selenium": "node run-examples.cjs --config testplane.selenium.config.js", - "check:snippets": "node verify-snippets.cjs" + "check:snippets": "node verify-snippets.cjs --base HEAD" }, "keywords": [], "author": "", diff --git a/vibe/verified-examples/run-ci.cjs b/vibe/verified-examples/run-ci.cjs new file mode 100644 index 00000000..75666afe --- /dev/null +++ b/vibe/verified-examples/run-ci.cjs @@ -0,0 +1,172 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { spawn } = require("node:child_process"); +const { once } = require("node:events"); +const { setTimeout: delay } = require("node:timers/promises"); +const { createHash } = require("node:crypto"); +const { verifyRuntime } = require("./verify-runtime.cjs"); + +const SELENIUM_SHA256 = "4664ef41d4bf5ee78cd6264ff8dca480ef80628ca91df95ca554c4d66f7831c1"; +const suite = process.argv[2]; +const artifacts = path.resolve(process.env.EXAMPLE_ARTIFACTS || "artifacts"); +const children = new Set(); + +async function stop(child) { + if (!child?.pid || child.exitCode !== null || child.signalCode) return; + const exited = once(child, "exit"); + child.kill("SIGTERM"); + const timeout = setTimeout(() => child.kill("SIGKILL"), 5000); + try { + await exited; + } finally { + clearTimeout(timeout); + } +} + +function launch(command, args, options) { + const child = spawn(command, args, options); + children.add(child); + child.once("exit", () => children.delete(child)); + return child; +} + +async function main() { + if (!["chrome", "selenium"].includes(suite)) + throw new Error("Usage: node run-ci.cjs chrome|selenium"); + fs.mkdirSync(artifacts, { recursive: true }); + const resultFile = path.join(artifacts, `${suite}.jsonl`); + fs.rmSync(resultFile, { force: true }); + // Same installer as Testplane's standalone CI, tied to the locked Testplane version. + const testplaneSource = path.dirname(require.resolve("testplane")); + const { installBrowser } = require(path.join(testplaneSource, "browser-installer/install")); + const { installChromeDriver } = require( + path.join(testplaneSource, "browser-installer/chrome/driver"), + ); + const { isUbuntu, getUbuntuLinkerEnv } = require( + path.join(testplaneSource, "browser-installer/ubuntu-packages"), + ); + const version = + require("./testplane.config").browsers.chrome.desiredCapabilities.browserVersion; + const binary = await installBrowser("chrome", version, { + shouldInstallWebDriver: true, + shouldInstallUbuntuPackages: true, + }); + const driver = await installChromeDriver(version); + const linkerEnv = (await isUbuntu()) ? await getUbuntuLinkerEnv() : {}; + const env = { ...process.env, ...linkerEnv, EXAMPLE_CHROME_BINARY: binary }; + fs.writeFileSync( + path.join(artifacts, "environment.json"), + JSON.stringify( + { + node: process.version, + platform: process.platform, + arch: process.arch, + testplane: require(path.join(testplaneSource, "../../package.json")).version, + browserVersion: version, + binary, + driver, + selenium: suite === "selenium" ? "4.50.0" : undefined, + }, + null, + 2, + ) + "\n", + ); + let grid; + let gridLog; + try { + if (suite === "selenium") { + const jar = process.env.SELENIUM_JAR; + if (!jar) throw new Error("SELENIUM_JAR must point to selenium-server-4.50.0.jar"); + const digest = createHash("sha256").update(fs.readFileSync(jar)).digest("hex"); + if (digest !== SELENIUM_SHA256) throw new Error("Selenium jar checksum mismatch"); + gridLog = fs.openSync(path.join(artifacts, "selenium.log"), "w"); + const port = 4446; + const url = `http://127.0.0.1:${port}`; + // Refuse to reuse someone else's Grid (managed downloads and Chrome must be ours). + const existing = await fetch(`${url}/status`, { + signal: AbortSignal.timeout(1000), + }).catch(() => null); + if (existing) + throw new Error(`Port ${port} already serves HTTP; stop the existing Grid first`); + grid = launch( + process.env.JAVA_BINARY || "java", + [ + `-Dwebdriver.chrome.driver=${driver}`, + "-jar", + jar, + "standalone", + "--host", + "127.0.0.1", + "--port", + String(port), + "--enable-managed-downloads", + "true", + "--max-sessions", + "1", + "--detect-drivers", + "false", + "--driver-configuration", + "display-name=chrome", + `stereotype=${JSON.stringify({ browserName: "chrome", "goog:chromeOptions": { binary } })}`, + ], + { env, stdio: ["ignore", gridLog, gridLog] }, + ); + let spawnError; + grid.on("error", error => { + spawnError = error; + }); + const deadline = Date.now() + 60000; + while (true) { + if (spawnError) throw spawnError; + if (grid.exitCode !== null || grid.signalCode) + throw new Error("Selenium exited before becoming ready; see selenium.log"); + const response = await fetch(`${url}/status`, { signal: AbortSignal.timeout(1000) }) + .then(res => res.json()) + .catch(() => null); + if (response?.value?.ready) break; + if (Date.now() > deadline) + throw new Error("Selenium readiness timed out; see selenium.log"); + await delay(250); + } + env.SELENIUM_GRID_URL = url; + } + const args = [ + path.join(__dirname, "run-examples.cjs"), + "--reporter", + "flat", + "--reporter", + JSON.stringify({ type: "jsonl", path: resultFile }), + ]; + if (suite === "selenium") args.push("--config", "testplane.selenium.config.js"); + const child = launch(process.execPath, args, { cwd: __dirname, env, stdio: "inherit" }); + const [code, signal] = await once(child, "exit"); + if (code !== 0) throw new Error(`Testplane exited with ${code ?? signal}`); + const results = fs + .readFileSync(resultFile, "utf8") + .split("\n") + .filter(Boolean) + .map(line => JSON.parse(line)); + const counts = verifyRuntime(results, require("./manifest.json"), suite); + const summary = `## ${suite}\n\n${counts.passed} tests passed in ${counts.files} files. No skipped tests or retries.\n`; + fs.writeFileSync(path.join(artifacts, "runtime-summary.md"), summary); + console.log(summary); + } finally { + await stop(grid); + if (gridLog !== undefined) fs.closeSync(gridLog); + } +} + +for (const signal of ["SIGINT", "SIGTERM"]) + process.once(signal, async () => { + await Promise.all([...children].map(stop)); + process.exit(signal === "SIGINT" ? 130 : 143); + }); +main().catch(error => { + fs.mkdirSync(artifacts, { recursive: true }); + fs.writeFileSync( + path.join(artifacts, "runtime-summary.md"), + `## ${suite}\n\nFAIL. See test output and artifacts for details.\n`, + ); + console.error(error); + process.exitCode = 1; +}); diff --git a/vibe/verified-examples/run-examples.cjs b/vibe/verified-examples/run-examples.cjs index 94b0a404..e60680ef 100644 --- a/vibe/verified-examples/run-examples.cjs +++ b/vibe/verified-examples/run-examples.cjs @@ -43,12 +43,22 @@ server.listen(0, "127.0.0.1", () => { stdio: "inherit", }, ); + const terminate = () => { + child.kill("SIGTERM"); + const timeout = setTimeout(() => child.kill("SIGKILL"), 5000); + timeout.unref(); + }; + process.once("SIGTERM", terminate); + process.once("SIGINT", terminate); child.on("error", error => { console.error(error.message); server.close(); process.exitCode = 1; }); child.on("exit", code => { + process.removeListener("SIGTERM", terminate); + process.removeListener("SIGINT", terminate); + server.closeAllConnections(); server.close(); process.exitCode = code ?? 1; }); diff --git a/vibe/verified-examples/suite-files.cjs b/vibe/verified-examples/suite-files.cjs new file mode 100644 index 00000000..aed2bc97 --- /dev/null +++ b/vibe/verified-examples/suite-files.cjs @@ -0,0 +1,18 @@ +function suiteFor(file) { + if ( + typeof file !== "string" || + !/^examples\/(browser|related|element|mock|testplane-browser|testplane-element|selenium)\/[\w-]+\.testplane\.js$/.test( + file, + ) + ) { + throw new Error(`Unsupported test path: ${file}`); + } + return file.startsWith("examples/selenium/") ? "selenium" : "chrome"; +} + +function suiteFiles(suite, manifest = require("./manifest.json")) { + if (!["chrome", "selenium"].includes(suite)) throw new Error(`Unknown suite: ${suite}`); + return [...new Set(manifest.map(entry => entry.test))].filter(file => suiteFor(file) === suite); +} + +module.exports = { suiteFor, suiteFiles }; diff --git a/vibe/verified-examples/testplane.config.js b/vibe/verified-examples/testplane.config.js index 54b91eb4..9e0ee5ea 100644 --- a/vibe/verified-examples/testplane.config.js +++ b/vibe/verified-examples/testplane.config.js @@ -27,14 +27,7 @@ module.exports = { }, sets: { examples: { - files: [ - "examples/browser/**/*.testplane.js", - "examples/related/**/*.testplane.js", - "examples/element/**/*.testplane.js", - "examples/mock/**/*.testplane.js", - "examples/testplane-browser/**/*.testplane.js", - "examples/testplane-element/**/*.testplane.js", - ], + files: require("./suite-files.cjs").suiteFiles("chrome"), browsers: ["chrome"], }, }, diff --git a/vibe/verified-examples/testplane.selenium.config.js b/vibe/verified-examples/testplane.selenium.config.js index a129bd58..eb6575c8 100644 --- a/vibe/verified-examples/testplane.selenium.config.js +++ b/vibe/verified-examples/testplane.selenium.config.js @@ -2,7 +2,12 @@ const config = require("./testplane.config"); module.exports = { ...config, gridUrl: process.env.SELENIUM_GRID_URL || "http://127.0.0.1:4446", - sets: { selenium: { files: "examples/selenium/**/*.testplane.js", browsers: ["chrome"] } }, + sets: { + selenium: { + files: require("./suite-files.cjs").suiteFiles("selenium"), + browsers: ["chrome"], + }, + }, browsers: { chrome: { desiredCapabilities: { diff --git a/vibe/verified-examples/tests/coverage.test.mjs b/vibe/verified-examples/tests/coverage.test.mjs new file mode 100644 index 00000000..4f69f154 --- /dev/null +++ b/vibe/verified-examples/tests/coverage.test.mjs @@ -0,0 +1,263 @@ +import assert from "node:assert/strict"; +import { execFileSync, spawnSync } from "node:child_process"; +import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "node:test"; + +const checker = fileURLToPath(new URL("../verify-snippets.cjs", import.meta.url)); +const harness = "vibe/verified-examples"; +const page = "docs/commands/example.mdx"; +const translation = "i18n/ru/docusaurus-plugin-content-docs/current/commands/example.mdx"; +const code = 'it("works", async () => {});\n'; +const fence = (value = code, lang = "javascript") => `\n\`\`\`${lang}\n${value}\`\`\`\n`; + +function fixture(t, covered = false) { + const root = mkdtempSync(path.join(tmpdir(), "doc-examples-")); + t.after(() => rmSync(root, { recursive: true, force: true })); + const write = (name, value) => { + mkdirSync(path.dirname(path.join(root, name)), { recursive: true }); + writeFileSync( + path.join(root, name), + typeof value === "string" ? value : JSON.stringify(value), + ); + }; + const git = (...args) => + execFileSync("git", args, { + cwd: root, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }).trim(); + const manifest = [{ page, translation, test: "examples/browser/example.testplane.js" }]; + write(page, fence()); + write(translation, fence()); + write(`${harness}/manifest.json`, covered ? manifest : []); + write(`${harness}/non-executable.json`, []); + write(`${harness}/examples/browser/example.testplane.js`, code); + git("init", "--quiet"); + git("add", "."); + git( + "-c", + "user.name=Fixture", + "-c", + "user.email=fixture@example.invalid", + "commit", + "--quiet", + "-m", + "base", + ); + const base = git("rev-parse", "HEAD"); + const check = (ref = base) => { + const result = spawnSync( + process.execPath, + [checker, "--root", root, "--base", ref, "--report-dir", path.join(root, "report")], + { encoding: "utf8" }, + ); + let report; + try { + report = JSON.parse(readFileSync(path.join(root, "report/coverage.json"), "utf8")); + } catch {} + return { ...result, report }; + }; + return { root, write, git, manifest, check }; +} + +test("unchanged uncovered examples are debt, not runtime successes", t => { + const f = fixture(t); + const result = f.check(); + assert.equal(result.status, 0, result.stderr); + assert.equal(result.report.counts.legacy, 2); + assert.equal(result.report.counts.covered, 0); +}); + +for (const lang of ["javascript", "js", "typescript", "ts", "tsx", "bash", ""]) { + test(`a new ${lang || "unlabelled"} example cannot silently join legacy debt`, t => { + const f = fixture(t); + f.write("docs/new.mdx", fence(code, lang)); + const result = f.check(); + assert.equal(result.status, 1, result.stdout); + assert.equal(result.report.counts.uncovered, 1); + }); +} + +test("changing legacy code fails even when the filename is unchanged", t => { + const f = fixture(t); + f.write(page, fence('it("changed", async () => {});\n')); + assert.equal(f.check().status, 1); +}); + +test("prose and fence metadata do not create new examples", t => { + const f = fixture(t); + f.write( + page, + `# New heading\n${fence().replace("```javascript", '```javascript title="example.js"')}`, + ); + assert.equal(f.check().status, 0); +}); + +test("duplicating a legacy example still requires coverage for the new occurrence", t => { + const f = fixture(t); + f.write(page, fence() + fence()); + assert.equal(f.check().report.counts.uncovered, 1); +}); + +test("localized published blocks must match the runnable test exactly", t => { + const f = fixture(t); + f.write(`${harness}/manifest.json`, f.manifest); + assert.equal(f.check().report.counts.covered, 2); + f.write(translation, fence('it("different", async () => {});\n')); + assert.equal(f.check().status, 1); +}); + +test("removing coverage for a retained example is a regression", t => { + const f = fixture(t, true); + f.write(`${harness}/manifest.json`, []); + const result = f.check(); + assert.equal(result.status, 1); + assert.equal(result.report.counts.regression, 2); +}); + +test("deleting both a page and its manifest mapping is allowed", t => { + const f = fixture(t, true); + rmSync(path.join(f.root, page)); + rmSync(path.join(f.root, translation)); + f.write(`${harness}/manifest.json`, []); + assert.equal(f.check().status, 0); +}); + +test("a missing Git base fails closed rather than accepting current examples as legacy", t => { + const f = fixture(t); + const result = f.check("not-a-real-ref"); + assert.equal(result.status, 1); + assert.match(result.stderr, /base|revision/i); +}); + +test("mobile and v8 are outside the gate while desktop pages are not", t => { + const f = fixture(t); + for (const name of [ + "docs/guides/android-testing.mdx", + "docs/commands/browser/tap.mdx", + "i18n/ru/docusaurus-plugin-content-docs/current/commands/element/pinch.mdx", + "versioned_docs/version-v8/new.mdx", + "i18n/ru/docusaurus-plugin-content-docs/version-v8/new.mdx", + ]) + f.write(name, fence("changed();\n")); + assert.equal(f.check().status, 0); + f.write("docs/commands/browser/executionContext.mdx", fence("changed();\n")); + assert.equal(f.check().status, 1); +}); + +test("manifest cannot map code to a non-test file outside the suites", t => { + const f = fixture(t); + f.write(`${harness}/manifest.json`, [{ ...f.manifest[0], test: "README.md" }]); + f.write(`${harness}/README.md`, code); + assert.equal(f.check().status, 1); +}); + +test("duplicate manifest entries cannot inflate covered count", t => { + const f = fixture(t); + f.write(`${harness}/manifest.json`, [...f.manifest, ...f.manifest]); + assert.equal(f.check().status, 1); +}); + +test("invalid MDX fails instead of dropping examples from inventory", t => { + const f = fixture(t); + f.write("docs/broken.mdx", " { + const f = fixture(t); + f.write("docs/jsx.mdx", '{`something();`}'); + assert.equal(f.check().status, 1); +}); + +test("non-executable classification is explicit, reported, and invalidated by code changes", async t => { + const { createHash } = await import("node:crypto"); + const f = fixture(t); + f.write("docs/output.mdx", fence("expected output\n", "text")); + assert.equal(f.check().status, 1); + f.write(`${harness}/non-executable.json`, [ + { + page: "docs/output.mdx", + language: "text", + blockIndex: 0, + sha256: createHash("sha256").update("expected output").digest("hex"), + reason: "Expected terminal output, not executable input.", + }, + ]); + assert.equal(f.check().report.counts["non-executable"], 1); + assert.equal(f.check().status, 0); + f.write("docs/output.mdx", fence("new output\n", "text")); + assert.equal(f.check().status, 1); +}); + +test("an exemption cannot downgrade previously verified code", async t => { + const { createHash } = await import("node:crypto"); + const f = fixture(t, true); + f.write(`${harness}/manifest.json`, []); + f.write( + `${harness}/non-executable.json`, + [page, translation].map(page => ({ + page, + language: "javascript", + blockIndex: 0, + sha256: createHash("sha256").update(code.trimEnd()).digest("hex"), + reason: "Cannot run this anymore.", + })), + ); + assert.equal(f.check().report.counts.regression, 2); + assert.equal(f.check().status, 1); +}); + +test("fences nested in MDX tabs remain in the inventory", t => { + const f = fixture(t); + f.write("docs/tabs.mdx", `\n\n${fence()}\n\n\n`); + assert.equal(f.check().report.counts.uncovered, 1); +}); + +test("renaming a page requires updating its coverage rather than erasing the debt", t => { + const f = fixture(t); + f.write("docs/renamed.mdx", fence()); + rmSync(path.join(f.root, page)); + assert.equal(f.check().report.counts.uncovered, 1); +}); + +test("a broken mapping still publishes the legacy inventory for review", t => { + const f = fixture(t); + f.write(`${harness}/manifest.json`, f.manifest); + f.write( + `${harness}/examples/browser/example.testplane.js`, + 'it("out of sync", async () => {});\n', + ); + const result = f.check(); + assert.equal(result.status, 1); + assert.equal(result.report.counts.legacy, 2); + assert.equal(result.report.errors.length, 1); +}); + +for (const blockIndex of [0, 1]) { + test(`a new covered copy of legacy code does not steal its debt allowance (index ${blockIndex})`, t => { + const f = fixture(t); + f.write(page, fence() + fence()); + f.write(`${harness}/manifest.json`, [{ page, test: f.manifest[0].test, blockIndex }]); + const result = f.check(); + assert.equal(result.status, 0); + assert.equal(result.report.counts.covered, 1); + assert.equal(result.report.counts.legacy, 2); + }); + + test(`a new uncovered copy of verified code cannot become legacy (covered index ${blockIndex})`, t => { + const f = fixture(t, true); + f.write(page, fence() + fence()); + f.write(`${harness}/manifest.json`, [ + { page, test: f.manifest[0].test, blockIndex }, + { page: translation, test: f.manifest[0].test }, + ]); + const result = f.check(); + assert.equal(result.status, 1); + assert.equal(result.report.counts.uncovered, 1); + assert.equal(result.report.counts.legacy, 0); + }); +} diff --git a/vibe/verified-examples/tests/runtime.test.cjs b/vibe/verified-examples/tests/runtime.test.cjs new file mode 100644 index 00000000..0ede0f0b --- /dev/null +++ b/vibe/verified-examples/tests/runtime.test.cjs @@ -0,0 +1,62 @@ +const assert = require("node:assert/strict"); +const { test } = require("node:test"); +const { verifyRuntime } = require("../verify-runtime.cjs"); +const { suiteFiles } = require("../suite-files.cjs"); +const file = "examples/browser/example.testplane.js"; +const download = "examples/selenium/download.testplane.js"; +const manifest = [{ test: file, testCount: 2 }, { test: download }]; +const result = (fullTitle, status = "success") => ({ + file, + fullTitle, + status, + browserId: "chrome", +}); + +test("every declared test must pass in the selected suite", () => { + assert.deepEqual(verifyRuntime([result("first"), result("second")], manifest, "chrome"), { + files: 1, + passed: 2, + }); +}); +for (const status of ["fail", "skipped", "retry", "unknown"]) { + test(`${status} cannot be reported as a successful example`, () => { + assert.throws(() => + verifyRuntime([result("first"), result("second", status)], manifest, "chrome"), + ); + }); +} +test("it.only or missing files cannot produce a green partial run", () => { + assert.throws(() => verifyRuntime([result("first")], manifest, "chrome")); + assert.throws(() => verifyRuntime([], manifest, "chrome")); +}); +test("duplicate passes cannot mask an omitted test", () => { + assert.throws(() => verifyRuntime([result("first"), result("first")], manifest, "chrome")); +}); +test("results from an unrelated file or browser cannot satisfy coverage", () => { + assert.throws(() => + verifyRuntime([{ ...result("first"), file: download }], manifest, "chrome"), + ); + assert.throws(() => + verifyRuntime( + [{ ...result("first"), browserId: "firefox" }, result("second")], + manifest, + "chrome", + ), + ); +}); +test("a suite with no declared tests fails closed", () => { + assert.throws(() => verifyRuntime([], [], "chrome")); +}); +test("a mapped test is selected once even when referenced by several pages", () => { + assert.deepEqual(suiteFiles("chrome", [...manifest, manifest[0]]), [file]); + assert.deepEqual(suiteFiles("selenium", manifest), [download]); +}); +test("conflicting expected counts for one file are rejected", () => { + assert.throws(() => + verifyRuntime( + [result("first"), result("second")], + [...manifest, { test: file, testCount: 1 }], + "chrome", + ), + ); +}); diff --git a/vibe/verified-examples/verify-runtime.cjs b/vibe/verified-examples/verify-runtime.cjs new file mode 100644 index 00000000..3db3950b --- /dev/null +++ b/vibe/verified-examples/verify-runtime.cjs @@ -0,0 +1,53 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const { suiteFiles } = require("./suite-files.cjs"); + +function verifyRuntime(results, manifest, suite) { + const files = suiteFiles(suite, manifest); + assert.ok(files.length, `${suite}: no declared test files`); + const expected = new Map(); + for (const entry of manifest.filter(entry => files.includes(entry.test))) { + const count = entry.testCount ?? 1; + assert.ok(Number.isInteger(count) && count > 0, `Invalid testCount: ${entry.test}`); + assert.ok( + !expected.has(entry.test) || expected.get(entry.test) === count, + `Conflicting testCount: ${entry.test}`, + ); + expected.set(entry.test, count); + } + const seen = new Set(); + const passed = new Map(); + for (const result of results) { + assert.ok(expected.has(result.file), `Unexpected test file: ${result.file}`); + assert.equal(result.browserId, "chrome", `Unexpected browser: ${result.file}`); + assert.equal( + result.status, + "success", + `${result.file}: ${result.fullTitle}: ${result.status}`, + ); + assert.ok(result.fullTitle, `Missing test title: ${result.file}`); + const identity = `${result.file}\0${result.fullTitle}`; + assert.ok(!seen.has(identity), `Duplicate result: ${result.fullTitle}`); + seen.add(identity); + passed.set(result.file, (passed.get(result.file) || 0) + 1); + } + for (const [file, count] of expected) + assert.equal(passed.get(file) || 0, count, `Incomplete execution: ${file}`); + return { files: files.length, passed: results.length }; +} + +module.exports = { verifyRuntime }; +if (require.main === module) { + try { + const [suite, filename] = process.argv.slice(2); + const results = fs + .readFileSync(filename, "utf8") + .split("\n") + .filter(Boolean) + .map(line => JSON.parse(line)); + console.log(JSON.stringify(verifyRuntime(results, require("./manifest.json"), suite))); + } catch (error) { + console.error(error.message); + process.exitCode = 1; + } +} diff --git a/vibe/verified-examples/verify-snippets.cjs b/vibe/verified-examples/verify-snippets.cjs index d13d9367..9bc1ada8 100644 --- a/vibe/verified-examples/verify-snippets.cjs +++ b/vibe/verified-examples/verify-snippets.cjs @@ -1,33 +1,6 @@ -const assert = require("node:assert/strict"); -const fs = require("node:fs"); -const path = require("node:path"); -const manifest = require("./manifest.json"); -const root = path.resolve(__dirname, "../.."); -const verified = new Map(); -for (const entry of manifest) { - const expected = fs.readFileSync(path.join(__dirname, entry.test), "utf8"); - for (const page of [entry.page, entry.translation]) { - const source = fs.readFileSync(path.join(root, page), "utf8"); - const blocks = [...source.matchAll(/^```javascript\n([\s\S]*?)^```/gm)]; - const index = entry.blockIndex ?? 0; - assert.equal(blocks.length, entry.blockCount ?? 1, page); - assert.ok(blocks[index], `${page}: missing block ${index}`); - const indexes = verified.get(page) ?? new Set(); - assert.ok(!indexes.has(index), `${page}: duplicate block ${index}`); - indexes.add(index); - verified.set(page, indexes); - assert.equal( - blocks[index][1], - expected, - `${page} block ${index} differs from ${entry.test}`, - ); - } -} -for (const [page, indexes] of verified) { - const count = [...fs.readFileSync(path.join(root, page), "utf8").matchAll(/^```javascript\n/gm)] - .length; - assert.equal(indexes.size, count, `${page}: not all JavaScript blocks have tests`); -} -console.log( - `Verified ${manifest.length * 2} localized blocks across ${verified.size} pages and ${new Set(manifest.map(entry => entry.test)).size} test files`, -); +import("./audit-snippets.mjs") + .then(({ main }) => main()) + .catch(error => { + console.error(error); + process.exitCode = 1; + }); From c70e9e738411f2e081ee4667de0219d593013212 Mon Sep 17 00:00:00 2001 From: sipayrt Date: Thu, 15 Oct 2026 18:49:12 +0300 Subject: [PATCH 3/4] fix(ci): launch Chrome on GitHub Linux runners --- vibe/verified-examples/README.md | 2 + vibe/verified-examples/ci-chrome-args.cjs | 6 +++ vibe/verified-examples/testplane.config.js | 1 + .../testplane.selenium.config.js | 2 +- .../tests/browser-config.test.cjs | 37 +++++++++++++++++++ 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 vibe/verified-examples/ci-chrome-args.cjs create mode 100644 vibe/verified-examples/tests/browser-config.test.cjs diff --git a/vibe/verified-examples/README.md b/vibe/verified-examples/README.md index 2196894d..3be9eb72 100644 --- a/vibe/verified-examples/README.md +++ b/vibe/verified-examples/README.md @@ -46,6 +46,8 @@ Workflow `Documentation examples` запускается на каждом PR в Чтобы эти проверки блокировали слияние, добавьте все три имени в обязательные status checks правила защиты `master`. Сам YAML не изменяет настройки защиты ветки. +На одноразовых Linux-runner GitHub Actions браузер запускается с `--no-sandbox` и `--disable-dev-shm-usage`, как в [standalone-наборе Testplane](https://github.com/gemini-testing/testplane/blob/master/test/integration/standalone/constants.ts). Это обходит ограничения sandbox для скачанного Chrome for Testing на Ubuntu. Условия задаются совместно: `GITHUB_ACTIONS=true` и `RUNNER_OS=Linux`. В обычном локальном запуске и на других платформах sandbox браузера не отключается. Не переносите эту настройку в окружение с секретами или на постоянный сервер; CI-стенд использует синтетические локальные страницы. + ### Что считается покрытым `manifest.json` связывает блок документации с полным тестовым файлом. Содержимое блока должно совпадать с файлом, включая пробелы и завершающий перевод строки. Обе локали проверяются независимо. Конфигурации Testplane получают список файлов из этого же manifest: отдельный список исполняемых примеров поддерживать не нужно. diff --git a/vibe/verified-examples/ci-chrome-args.cjs b/vibe/verified-examples/ci-chrome-args.cjs new file mode 100644 index 00000000..3b38f558 --- /dev/null +++ b/vibe/verified-examples/ci-chrome-args.cjs @@ -0,0 +1,6 @@ +// Match Testplane's standalone CI on disposable GitHub Linux runners. +// Keep the browser sandbox enabled for ordinary local runs and other platforms. +module.exports = + process.env.GITHUB_ACTIONS === "true" && process.env.RUNNER_OS === "Linux" + ? ["--no-sandbox", "--disable-dev-shm-usage"] + : []; diff --git a/vibe/verified-examples/testplane.config.js b/vibe/verified-examples/testplane.config.js index 9e0ee5ea..5707bdb0 100644 --- a/vibe/verified-examples/testplane.config.js +++ b/vibe/verified-examples/testplane.config.js @@ -37,6 +37,7 @@ module.exports = { browserName: "chrome", browserVersion: "150.0.7871.124", webSocketUrl: true, + "goog:chromeOptions": { args: require("./ci-chrome-args.cjs") }, }, }, }, diff --git a/vibe/verified-examples/testplane.selenium.config.js b/vibe/verified-examples/testplane.selenium.config.js index eb6575c8..340ae977 100644 --- a/vibe/verified-examples/testplane.selenium.config.js +++ b/vibe/verified-examples/testplane.selenium.config.js @@ -18,7 +18,7 @@ module.exports = { ...(process.env.EXAMPLE_CHROME_BINARY ? { binary: process.env.EXAMPLE_CHROME_BINARY } : {}), - args: ["--headless=new"], + args: ["--headless=new", ...require("./ci-chrome-args.cjs")], }, }, }, diff --git a/vibe/verified-examples/tests/browser-config.test.cjs b/vibe/verified-examples/tests/browser-config.test.cjs new file mode 100644 index 00000000..e305574a --- /dev/null +++ b/vibe/verified-examples/tests/browser-config.test.cjs @@ -0,0 +1,37 @@ +const assert = require("node:assert/strict"); +const { execFileSync } = require("node:child_process"); +const path = require("node:path"); +const { test } = require("node:test"); + +for (const config of ["testplane.config.js", "testplane.selenium.config.js"]) { + for (const [name, githubActions, runnerOs, sandboxDisabled] of [ + ["GitHub Linux", "true", "Linux", true], + ["local Linux", "", "Linux", false], + ["GitHub macOS", "true", "macOS", false], + ["GitHub Windows", "true", "Windows", false], + ]) { + test(`${config}: sandbox workaround is scoped to ${name}`, () => { + const filename = path.resolve(__dirname, "..", config); + const script = `console.log(JSON.stringify(require(${JSON.stringify(filename)}).browsers.chrome.desiredCapabilities))`; + const capabilities = JSON.parse( + execFileSync(process.execPath, ["-e", script], { + encoding: "utf8", + env: { + ...process.env, + GITHUB_ACTIONS: githubActions, + RUNNER_OS: runnerOs, + EXAMPLE_CHROME_BINARY: "/fixture/chrome", + }, + }), + ); + const options = capabilities["goog:chromeOptions"] || {}; + assert.equal((options.args || []).includes("--no-sandbox"), sandboxDisabled); + assert.equal((options.args || []).includes("--disable-dev-shm-usage"), sandboxDisabled); + if (config.includes("selenium")) { + assert.ok(options.args.includes("--headless=new")); + assert.equal(options.binary, "/fixture/chrome"); + assert.equal(capabilities["se:downloadsEnabled"], true); + } + }); + } +} From c23b3f070c64dcafaa7ef436c264364952bc4a92 Mon Sep 17 00:00:00 2001 From: sipayrt Date: Thu, 15 Oct 2026 18:53:03 +0300 Subject: [PATCH 4/4] docs: record successful GitHub examples validation --- vibe/verified-examples/evidence/ci-20261015.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/vibe/verified-examples/evidence/ci-20261015.md b/vibe/verified-examples/evidence/ci-20261015.md index d3006bf0..071d3cf1 100644 --- a/vibe/verified-examples/evidence/ci-20261015.md +++ b/vibe/verified-examples/evidence/ci-20261015.md @@ -29,8 +29,24 @@ Первый набор тестов воспроизвел недостатки старой проверки: 18 падений из 20. Отдельно воспроизведены потеря инвентаризации при ошибке manifest и зависимость учета одинаковых блоков от их порядка. Последний случай найден на независимом read-only ревью: новый непокрытый дубликат мог ошибочно получить статус legacy. Исправление резервирует прежнее покрытие и расходует только объем прежнего непокрытого кода. Все четыре сценария порядка вставки включены в окончательный набор из 40 успешных тестов. После исправлений открытых замечаний ревью не осталось. +## Проверка на GitHub Actions + +CI отправлен в PR #142 коммитом `e5df81b15b1ac8fe59228642d7e073b3432b6a70`. В [первом прогоне](https://github.com/gemini-testing/testplane-docs/actions/runs/38762965341) проверка покрытия прошла, а оба runtime-задания упали при создании Chrome-сессии на Ubuntu: браузер завершался до выполнения тестов. Версии скачанных Chrome и ChromeDriver совпадали: 150.0.7871.124; Grid успешно запускался. + +Коммит `c70e9e738411f2e081ee4667de0219d593013212` добавил параметры `--no-sandbox` и `--disable-dev-shm-usage` из standalone-конфигурации CI Testplane. Они включаются только при одновременных `GITHUB_ACTIONS=true` и `RUNNER_OS=Linux`, без изменения обычных локальных запусков и других платформ. Восемь дополнительных тестов проверяют ограничение настройки и сохранение Selenium capabilities; до исправления два соответствующих теста падали, после — все восемь проходят. + +[Повторный прогон на GitHub Actions Ubuntu 24.04](https://github.com/gemini-testing/testplane-docs/actions/runs/38763691122) для коммита `c70e9e738411f2e081ee4667de0219d593013212` завершился успешно: + +| Проверка | Подтвержденный результат | +| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | +| [Examples coverage](https://github.com/gemini-testing/testplane-docs/actions/runs/38763691122/job/116521276107) | 48/48 служебных тестов; 86 covered, 1980 legacy, 0 uncovered, 0 regression | +| [Examples runtime (chrome)](https://github.com/gemini-testing/testplane-docs/actions/runs/38763691122/job/116521276791) | 42/42 теста в 40 файлах; skipped 0, retries 0 | +| [Examples runtime (selenium)](https://github.com/gemini-testing/testplane-docs/actions/runs/38763691122/job/116521276879) | 1/1 тест; skipped 0, retries 0 | + +Проверены не только итоговые статусы jobs, но и логи с количеством выполненных тестов. Все три артефакта (`doc-examples-coverage`, `doc-examples-chrome`, `doc-examples-selenium`) успешно загружены. Этот раздел фиксирует указанный прогон; актуальные статусы последующих коммитов нужно смотреть в PR. + ## Ограничения и следующий шаг -Workflow еще не запускался на GitHub Actions Ubuntu 24.04: изменения не отправлены в удаленную ветку. Локальный macOS-прогон не подтверждает установку и запуск на Ubuntu. После push требуется проверить все три GitHub checks; для обязательного блокирования слияния владелец репозитория должен добавить их в branch protection, как описано в [README](../README.md). +Для обязательного блокирования слияния владелец репозитория должен добавить три проверки в branch protection, как описано в [README](../README.md). Настройки защиты ветки не изменялись. Мобильные страницы и v8 в этой итерации не изменялись и не включались в проверку примеров. Полный актуальный список долга генерируется, а не хранится здесь: локально это `tmp/doc-examples/coverage.md` и `coverage.json` от корня репозитория. Локальные логи и JSONL лежат в `artifacts/chrome/` и `artifacts/selenium/` стенда. В GitHub Actions эти данные загружаются как артефакты. Число legacy включает все виды блоков кода, в том числе конфигурацию и вывод; это не число заведомо исполняемых JavaScript-тестов. Исторический отчет не заменяет новый CI-прогон.