diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index b437b0d023..b3d5b0ab2a 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -100,6 +100,7 @@ jobs: id: findPr - name: Post or Update Coverage Summary Comment + if: ${{ steps.findPr.outputs.number != '' }} uses: actions/github-script@v7 with: script: | diff --git a/.github/workflows/tests-e2e-approve.yml b/.github/workflows/tests-e2e-approve.yml new file mode 100644 index 0000000000..8976a56040 --- /dev/null +++ b/.github/workflows/tests-e2e-approve.yml @@ -0,0 +1,15 @@ +name: ✅ E2E Approve + +on: + pull_request_review: + types: [submitted] + +jobs: + e2e-approve: + runs-on: ubuntu-latest + if: github.event.review.state == 'approved' + steps: + - name: Add "e2e-approved" Label + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: e2e-approved diff --git a/.github/workflows/tests-e2e-docker.yml b/.github/workflows/tests-e2e-docker.yml index ece239ee49..b0b0e72297 100644 --- a/.github/workflows/tests-e2e-docker.yml +++ b/.github/workflows/tests-e2e-docker.yml @@ -83,7 +83,7 @@ jobs: TEST_BIG_DB_DUMP=$TEST_BIG_DB_DUMP \ RI_SERVER_TLS_CERT="$RI_SERVER_TLS_CERT" \ RI_SERVER_TLS_KEY="$RI_SERVER_TLS_KEY" \ - docker compose \ + docker compose --profile e2e \ -f tests/e2e/rte.docker-compose.yml \ -f tests/e2e/docker.web.docker-compose.yml \ up --abort-on-container-exit --force-recreate diff --git a/.github/workflows/tests-e2e-playwright.yml b/.github/workflows/tests-e2e-playwright.yml new file mode 100644 index 0000000000..972e3cce62 --- /dev/null +++ b/.github/workflows/tests-e2e-playwright.yml @@ -0,0 +1,100 @@ +name: Playwright E2E Tests +on: + workflow_call: + inputs: + debug: + description: SSH Debug + default: false + type: boolean +env: + E2E_CLOUD_DATABASE_USERNAME: ${{ secrets.E2E_CLOUD_DATABASE_USERNAME }} + E2E_CLOUD_DATABASE_PASSWORD: ${{ secrets.E2E_CLOUD_DATABASE_PASSWORD }} + E2E_CLOUD_API_ACCESS_KEY: ${{ secrets.E2E_CLOUD_API_ACCESS_KEY }} + E2E_CLOUD_DATABASE_HOST: ${{ secrets.E2E_CLOUD_DATABASE_HOST }} + E2E_CLOUD_DATABASE_PORT: ${{ secrets.E2E_CLOUD_DATABASE_PORT }} + E2E_CLOUD_DATABASE_NAME: ${{ secrets.E2E_CLOUD_DATABASE_NAME }} + E2E_CLOUD_API_SECRET_KEY: ${{ secrets.E2E_CLOUD_API_SECRET_KEY }} + + E2E_RI_ENCRYPTION_KEY: ${{ secrets.E2E_RI_ENCRYPTION_KEY }} + RI_ENCRYPTION_KEY: ${{ secrets.RI_ENCRYPTION_KEY }} + RI_SERVER_TLS_CERT: ${{ secrets.RI_SERVER_TLS_CERT }} + RI_SERVER_TLS_KEY: ${{ secrets.RI_SERVER_TLS_KEY }} + TEST_BIG_DB_DUMP: ${{ secrets.TEST_BIG_DB_DUMP }} + E2E_VOLUME_PATH: '/usr/src/app' + +jobs: + e2e-playwright-chromium-docker: + name: E2E Playwright Chromium Docker Build Tests + timeout-minutes: 60 + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies for Playwright tests + uses: ./.github/actions/install-deps + with: + dir-path: './tests/playwright' + + - name: Install Playwright Browsers + working-directory: ./tests/playwright + run: yarn playwright install --with-deps + + - name: Download Docker Artifacts + uses: actions/download-artifact@v4 + with: + name: docker-builds + path: ./release + + - name: Load built docker image from workspace + run: | + docker image load -i ./release/docker/docker-linux-alpine.amd64.tar + + - name: Set up redis test environments + run: | + TEST_BIG_DB_DUMP=$TEST_BIG_DB_DUMP \ + docker compose -p e2e-rte \ + -f tests/e2e/rte.docker-compose.yml \ + up --detach --force-recreate + + - name: Set up RI docker image + run: | + E2E_RI_ENCRYPTION_KEY="$E2E_RI_ENCRYPTION_KEY" \ + RI_SERVER_TLS_CERT="$RI_SERVER_TLS_CERT" \ + RI_SERVER_TLS_KEY="$RI_SERVER_TLS_KEY" \ + docker compose -p e2e-ri-docker \ + -f tests/e2e/docker.web.docker-compose.yml \ + up --detach --force-recreate + sleep 30 + + - name: Run Playwright tests + timeout-minutes: 80 + working-directory: ./tests/playwright + if: ${{ !cancelled() }} + run: | + yarn test:chromium:docker + + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: | + ./tests/playwright/test-results + ./tests/playwright/allure-results + ./tests/playwright/playwright-report + retention-days: 10 + + - name: Clean up redis test environments + if: always() + run: | + docker compose -p e2e-rte \ + -f tests/e2e/rte.docker-compose.yml \ + down --volumes --remove-orphans + + - name: Clean up RI docker image + if: always() + run: | + docker compose -p e2e-ri-docker \ + -f tests/e2e/docker.web.docker-compose.yml \ + down --volumes --remove-orphans diff --git a/.github/workflows/tests-e2e.yml b/.github/workflows/tests-e2e.yml new file mode 100644 index 0000000000..00e293b03c --- /dev/null +++ b/.github/workflows/tests-e2e.yml @@ -0,0 +1,83 @@ +name: ✅ E2E Tests + +on: + pull_request: + types: [labeled] + + workflow_dispatch: + inputs: + debug: + description: Enable SSH Debug (IT and E2E) + default: false + type: boolean + +# Cancel a previous run workflow +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-e2e + cancel-in-progress: true + +jobs: + # E2E Approve + e2e-approve: + runs-on: ubuntu-latest + if: github.event.action == 'labeled' && contains(github.event.label.name, 'e2e-approved') || github.event_name == 'workflow_dispatch' + name: Approve E2E tests + steps: + - name: Add "e2e-approved" Label + uses: actions-ecosystem/action-add-labels@v1 + with: + labels: e2e-approved + + # E2E Docker + build-docker: + uses: ./.github/workflows/pipeline-build-docker.yml + needs: e2e-approve + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + for_e2e_tests: true + + e2e-docker-tests: + needs: build-docker + uses: ./.github/workflows/tests-e2e-docker.yml + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + + tests-e2e-playwright: + needs: build-docker + uses: ./.github/workflows/tests-e2e-playwright.yml + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + + # E2E AppImage + build-appimage: + uses: ./.github/workflows/pipeline-build-linux.yml + needs: e2e-approve + secrets: inherit + with: + target: build_linux_appimage_x64 + debug: ${{ inputs.debug || false }} + + e2e-appimage-tests: + needs: build-appimage + uses: ./.github/workflows/tests-e2e-appimage.yml + secrets: inherit + with: + debug: ${{ inputs.debug || false }} + + clean: + uses: ./.github/workflows/clean-deployments.yml + if: always() + needs: [e2e-docker-tests, e2e-appimage-tests, tests-e2e-playwright] + + # Remove artifacts from github actions + remove-artifacts: + name: Remove artifacts + needs: [e2e-docker-tests, e2e-appimage-tests, tests-e2e-playwright] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Remove all artifacts + uses: ./.github/actions/remove-artifacts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7040d652c8..e65264f9ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,22 +6,12 @@ on: - 'fe/**' - 'be/**' - 'fe-be/**' - - 'e2e/**' - 'feature/**' - 'bugfix/**' - 'ric/**' workflow_dispatch: inputs: - group_tests: - description: Run group of tests - default: 'all' - type: choice - options: - - all - - without_e2e - - only_e2e - redis_client: description: Library to use for redis connection default: 'ioredis' @@ -42,10 +32,6 @@ on: workflow_call: inputs: - group_tests: - description: Run group of tests - type: string - default: 'without_e2e' short_rte_list: description: Use short rte list type: boolean @@ -71,7 +57,6 @@ jobs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} desktop: ${{ steps.filter.outputs.desktop }} - e2e: ${{ steps.filter.outputs.e2e }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3.0.2 @@ -85,12 +70,10 @@ jobs: - 'redisinsight/api/**' desktop: - 'redisinsight/desktop/**' - e2e: - - 'tests/e2e/**' frontend-tests: needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'fe/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') + if: startsWith(github.ref_name, 'fe/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-frontend.yml secrets: inherit @@ -104,7 +87,7 @@ jobs: backend-tests: needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') + if: startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-backend.yml secrets: inherit @@ -118,7 +101,7 @@ jobs: integration-tests: needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'without_e2e' || startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') + if: startsWith(github.ref_name, 'be/') || startsWith(github.ref_name, 'fe-be/') || startsWith(github.ref_name, 'feature/') || startsWith(github.ref_name, 'bugfix/') || startsWith(github.ref_name, 'ric/') uses: ./.github/workflows/tests-integration.yml secrets: inherit with: @@ -134,49 +117,6 @@ jobs: resource_name: integration-coverage type: integration - # # E2E Approve - e2e-approve: - runs-on: ubuntu-latest - needs: changes - if: inputs.group_tests == 'all' || inputs.group_tests == 'only_e2e' || startsWith(github.ref_name, 'e2e/') - timeout-minutes: 60 - environment: ${{ startsWith(github.ref_name, 'e2e/') && 'e2e-approve' || 'staging' }} - name: Approve E2E tests - steps: - - uses: actions/checkout@v4 - - # E2E Docker - build-docker: - uses: ./.github/workflows/pipeline-build-docker.yml - needs: e2e-approve - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - for_e2e_tests: true - - e2e-docker-tests: - needs: build-docker - uses: ./.github/workflows/tests-e2e-docker.yml - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - - # E2E AppImage - build-appimage: - uses: ./.github/workflows/pipeline-build-linux.yml - needs: e2e-approve - secrets: inherit - with: - target: build_linux_appimage_x64 - debug: ${{ inputs.debug || false }} - - e2e-appimage-tests: - needs: build-appimage - uses: ./.github/workflows/tests-e2e-appimage.yml - secrets: inherit - with: - debug: ${{ inputs.debug || false }} - clean: uses: ./.github/workflows/clean-deployments.yml if: always() @@ -185,8 +125,6 @@ jobs: frontend-tests, backend-tests, integration-tests, - e2e-docker-tests, - e2e-appimage-tests, ] # Remove artifacts from github actions @@ -197,8 +135,6 @@ jobs: frontend-tests, backend-tests, integration-tests, - e2e-docker-tests, - e2e-appimage-tests, ] runs-on: ubuntu-latest steps: diff --git a/package.json b/package.json index db35cc1c65..112f1fa018 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "private": true, "scripts": { "dev:ui": "cross-env yarn --cwd redisinsight/ui dev", + "dev:ui:coverage": "cross-env COLLECT_COVERAGE=true yarn --cwd redisinsight/ui dev", "dev:api": "cross-env yarn --cwd redisinsight/api start:dev", "dev:electron:ui": "cross-env RI_APP_PORT=8080 RI_APP_TYPE=ELECTRON NODE_ENV=development yarn --cwd redisinsight/ui dev", "dev:electron:api": "cross-env RI_APP_PORT=5540 RI_APP_TYPE=ELECTRON NODE_ENV=development USE_TCP_CLOUD_AUTH=true yarn --cwd redisinsight/api start:dev", @@ -222,6 +223,7 @@ "vite-plugin-ejs": "^1.7.0", "vite-plugin-electron": "^0.28.6", "vite-plugin-electron-renderer": "^0.14.5", + "vite-plugin-istanbul": "^7.1.0", "vite-plugin-react-click-to-component": "^3.0.0", "vite-plugin-svgr": "^4.2.0", "webpack": "^5.95.0", diff --git a/redisinsight/ui/vite.config.mjs b/redisinsight/ui/vite.config.mjs index 6047445739..81c5a3f6ac 100644 --- a/redisinsight/ui/vite.config.mjs +++ b/redisinsight/ui/vite.config.mjs @@ -5,6 +5,7 @@ import svgr from 'vite-plugin-svgr'; import fixReactVirtualized from 'esbuild-plugin-react-virtualized'; import { reactClickToComponent } from 'vite-plugin-react-click-to-component'; import { ViteEjsPlugin } from 'vite-plugin-ejs'; +import istanbul from 'vite-plugin-istanbul'; // import { compression } from 'vite-plugin-compression2' import { fileURLToPath, URL } from 'url'; import path from 'path'; @@ -47,8 +48,26 @@ export default defineConfig({ })};`; return html.replace(//, `\n ${script}`); - } - } + }, + }, + // Add istanbul plugin for coverage collection when COLLECT_COVERAGE is true + ...(process.env.COLLECT_COVERAGE === 'true' + ? [ + istanbul({ + include: 'src/**/*', + exclude: [ + 'node_modules', + 'test/', + '**/*.spec.ts', + '**/*.spec.tsx', + '**/*.test.ts', + '**/*.test.tsx', + ], + extension: ['.js', '.ts', '.tsx'], + requireEnv: false, + }), + ] + : []), // !isElectron && compression({ // include: [/\.(js)$/, /\.(css)$/], // deleteOriginalAssets: true diff --git a/tests/e2e/.env b/tests/e2e/.env index 9242dec68e..21c248e7a3 100644 --- a/tests/e2e/.env +++ b/tests/e2e/.env @@ -6,3 +6,4 @@ RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tes RI_NOTIFICATION_SYNC_INTERVAL=30000 RI_FEATURES_CONFIG_URL=http://static-server:5551/remote/features-config.json RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 +TEST_BIG_DB_DUMP=https://s3.amazonaws.com/redisinsight.test/public/rte/dump/big/dump.tar.gz diff --git a/tests/e2e/docker.web.docker-compose.yml b/tests/e2e/docker.web.docker-compose.yml index be7bdc84b3..1aa501ad44 100644 --- a/tests/e2e/docker.web.docker-compose.yml +++ b/tests/e2e/docker.web.docker-compose.yml @@ -2,6 +2,8 @@ version: "3.4" services: e2e: + profiles: + - e2e build: context: . dockerfile: e2e.Dockerfile @@ -37,6 +39,8 @@ services: # Built image app: + extra_hosts: + - "host.docker.internal:host-gateway" logging: driver: none image: redisinsight:amd64 @@ -51,6 +55,8 @@ services: - rihomedir:/data - tmp:/tmp - ./test-data:/test-data + ports: + - 5540:5540 volumes: tmp: diff --git a/tests/e2e/local.web.docker-compose.yml b/tests/e2e/local.web.docker-compose.yml index 788a88304e..b889d935d0 100644 --- a/tests/e2e/local.web.docker-compose.yml +++ b/tests/e2e/local.web.docker-compose.yml @@ -2,6 +2,8 @@ version: "3.4" services: e2e: + profiles: + - e2e build: context: . dockerfile: e2e.Dockerfile diff --git a/tests/playwright/.gitignore b/tests/playwright/.gitignore new file mode 100644 index 0000000000..ee4b77c6b7 --- /dev/null +++ b/tests/playwright/.gitignore @@ -0,0 +1,10 @@ + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/allure-results/ +/.nyc_output/ +/coverage/ diff --git a/tests/playwright/.nycrc.json b/tests/playwright/.nycrc.json new file mode 100644 index 0000000000..553d10ef43 --- /dev/null +++ b/tests/playwright/.nycrc.json @@ -0,0 +1,19 @@ +{ + "all": true, + "cwd": "../../", + "include": ["redisinsight/ui/src/**/*.{ts,tsx,js,jsx}"], + "exclude": [ + "redisinsight/ui/src/**/*.{test,spec}.{ts,tsx,js,jsx}", + "redisinsight/ui/src/**/__tests__/**", + "redisinsight/ui/src/**/__mocks__/**", + "redisinsight/ui/src/**/*.d.ts", + "redisinsight/ui/src/**/node_modules/**" + ], + "reporter": ["text", "html", "lcov"], + "report-dir": "tests/playwright/coverage", + "temp-dir": "tests/playwright/.nyc_output", + "cache": false, + "check-coverage": false, + "skip-full": false, + "skip-empty": false +} diff --git a/tests/playwright/README.md b/tests/playwright/README.md new file mode 100644 index 0000000000..19897b5276 --- /dev/null +++ b/tests/playwright/README.md @@ -0,0 +1,202 @@ +# RedisInsight Playwright Tests + +This project contains end-to-end tests for RedisInsight using [Playwright](https://playwright.dev/). It supports running tests against three different RedisInsight builds: + +- **Docker Build** +- **Electron Build** +- **Local Web Build** (built directly from the source code) + +--- + +## Installation + +> _Note: All commands below should be run from the `tests/playwright` directory._ + +Before running any tests, make sure you have the dependencies installed: + +1. Install Node dependencies: + + ```shell + yarn install + ``` + +2. Install Playwright browsers: + + ```shell + yarn playwright install + ``` + +3. Install Playwright OS dependencies (Linux only): + + ```shell + sudo yarn playwright install-deps + ``` + +## Prerequisites + +- Docker installed and running. +- Redis test environment and RedisInsight configurations from the `tests/e2e` project. + +## Environment-Specific Setup and Test Execution + +For more details, refer to the [Playwright documentation](https://playwright.dev/docs/running-tests). + +### Start Redis Test Environment (Required for All Builds) + +Navigate to the `tests/e2e` directory and run: + +```shell +docker compose -p test-docker -f rte.docker-compose.yml up --force-recreate --detach +``` + +### Docker Build + +- Build the Docker image locally or trigger a [GitHub Action](https://github.com/RedisInsight/RedisInsight/actions/workflows/manual-build.yml) to build and download the artifact (`docker-linux-alpine.amd64.tar`). +- Load the image: + ```shell + docker load -i docker-linux-alpine.amd64.tar + ``` +- Ensure the following environment variables are set in `tests/e2e/.env`: + - `RI_ENCRYPTION_KEY` + - `RI_SERVER_TLS_CERT` + - `RI_SERVER_TLS_KEY` +- Navigate to the `tests/e2e` directory and start the container: + ```shell + docker compose -p e2e-ri-docker -f docker.web.docker-compose.yml up --detach --force-recreate + ``` +- Validate app is running at: `https://localhost:5540`. + +#### Run Playwright Tests + +_Note: Make sure to run the commands bellow from the `e2e/playwright` directory._ + +Run all tests: + +```shell +yarn test:chromium:docker +``` + +Run in debug mode: + +```shell +yarn test:chromium:docker:debug +``` + +Run a specific spec file: + +```shell +yarn test:chromium:docker basic-navigation +``` + +--- + +### Electron Build + +- Build the project from the root directory: + ```shell + yarn package:prod + ``` +- Update `ELECTRON_EXECUTABLE_PATH` in `tests/playwright/env/.desktop.env` to point to the generated executable file (MacOS by default). + +#### Run Playwright Tests + +_Note: Make sure to run the commands bellow from the `e2e/playwright` directory._ + +```shell +yarn test:electron +``` + +--- + +### Local Web Build + +- Make sure you don't have anything (docker container, local server, etc.) running on port 5540. +- Start the UI and API servers: + ```shell + yarn dev:ui + yarn dev:api + ``` +- Access the app at: `http://localhost:8080`. + +#### Run Playwright Tests + +_Note: Make sure to run the command bellow from the `e2e/playwright` directory._ + +```shell +yarn test:chromium:local-web +``` + +## Folder structure + +- `/env` - contains env configs for the 3 types of builds. +- `/tests` - Contains the actual tests. +- `/helpers/api` - ported some api helpers from the tests/e2e project. They are used for setting up data. +- `/pageObjects` - ported page element locators and logic from the tests/e2e project. + +## Extra Tooling + +### Auto-Generate Tests + +Use Playwright's Codegen to auto-generate tests: + +```shell +yarn playwright codegen +``` + +### Interactive UI Mode + +Start Playwright's interactive UI mode: + +```shell +yarn playwright test --ui +``` + +## Reports + +### Allure Reports + +- Ensure `JAVA_HOME` is set and JDK version 8 to 11 is installed. +- Generate a report with history: + ```shell + yarn test:allureHistoryReport + ``` +- For more details, refer to the [Allure documentation](https://allurereport.org/docs/playwright-reference/). + +### Execution Time Comparison + +| Test Name | Framework | Browser | Duration | +| --------------------------------- | ---------- | -------- | -------- | +| Verify that user can add Hash Key | TestCafe | Chromium | 27s | +| Verify that user can add Hash Key | Playwright | Chromium | 10s | +| Verify that user can add Hash Key | TestCafe | Electron | 30s | +| Verify that user can add Hash Key | Playwright | Electron | 18s | + +## Code Coverage + +### Overview + +The Playwright tests can collect code coverage for the React frontend application. This helps track which parts of the UI code are being exercised by the end-to-end tests. + +### Quick Start + +# Start the UI with instrumentation for collecting code coverage + +Ensure UI app is running with `COLLECT_COVERAGE=true` env variable, or simply run the following helper from the root folder + +```shell +yarn dev:ui:coverage +``` + +# Run tests with coverage and generate both text and HTML reports + +```shell +cd tests/playwright +yarn test:coverage +``` + +### Coverage Reports Location + +After running coverage tests, reports are generated in: + +- **HTML Report**: `tests/playwright/coverage/index.html` - Interactive, browsable coverage report +- **LCOV Report**: `tests/playwright/coverage/lcov.info` - For CI/CD integration diff --git a/tests/playwright/env/.desktop.env b/tests/playwright/env/.desktop.env new file mode 100644 index 0000000000..84513a228b --- /dev/null +++ b/tests/playwright/env/.desktop.env @@ -0,0 +1,56 @@ +COMMON_URL= +API_URL=http://localhost:5530/api +ELECTRON_EXECUTABLE_PATH=../../release/mac-arm64/Redis Insight.app/Contents/MacOS/Redis Insight + +RI_APP_FOLDER_NAME=.redis-insight-stage + +OSS_STANDALONE_HOST=localhost +OSS_STANDALONE_PORT=8100 + +OSS_STANDALONE_V5_HOST=localhost +OSS_STANDALONE_V5_PORT=8101 + +OSS_STANDALONE_V7_HOST=localhost +OSS_STANDALONE_V7_PORT=8108 + +OSS_STANDALONE_V8_HOST=localhost +OSS_STANDALONE_V8_PORT=8109 + +OSS_STANDALONE_REDISEARCH_HOST=localhost +OSS_STANDALONE_REDISEARCH_PORT=8102 + +OSS_STANDALONE_BIG_HOST=localhost +OSS_STANDALONE_BIG_PORT=8103 + +OSS_STANDALONE_TLS_HOST=localhost +OSS_STANDALONE_TLS_PORT=8104 + +OSS_STANDALONE_EMPTY_HOST=localhost +OSS_STANDALONE_EMPTY_PORT=8105 + +OSS_STANDALONE_REDISGEARS_HOST=localhost +OSS_STANDALONE_REDISGEARS_PORT=8106 + +OSS_STANDALONE_NOPERM_HOST=localhost +OSS_STANDALONE_NOPERM_PORT=8100 + +OSS_CLUSTER_REDISGEARS_2_HOST=localhost +OSS_CLUSTER_REDISGEARS_2_PORT=8107 + +OSS_CLUSTER_HOST=localhost +OSS_CLUSTER_PORT=8200 + +OSS_SENTINEL_HOST=localhost +OSS_SENTINEL_PORT=28100 +OSS_SENTINEL_PASSWORD=password + +RE_CLUSTER_HOST=localhost +RE_CLUSTER_PORT=19443 + +RI_NOTIFICATION_UPDATE_URL=https://s3.amazonaws.com/redisinsight.test/public/tests/e2e/notifications.json +RI_NOTIFICATION_SYNC_INTERVAL=30000 + +RI_FEATURES_CONFIG_URL=http://localhost:5551/remote/features-config.json +RI_FEATURES_CONFIG_SYNC_INTERVAL=50000 + +REMOTE_FOLDER_PATH=/home/runner/work/RedisInsight/RedisInsight/tests/e2e/remote diff --git a/tests/playwright/env/.docker.env b/tests/playwright/env/.docker.env new file mode 100644 index 0000000000..4cdc9c6d5a --- /dev/null +++ b/tests/playwright/env/.docker.env @@ -0,0 +1,47 @@ +COMMON_URL=https://localhost:5540 +API_URL=https://localhost:5540/api + +RI_APP_FOLDER_NAME=.redis-insight + +OSS_STANDALONE_HOST=host.docker.internal +OSS_STANDALONE_PORT=8100 + +OSS_STANDALONE_V5_HOST=host.docker.internal +OSS_STANDALONE_V5_PORT=8101 + +OSS_STANDALONE_V7_HOST=host.docker.internal +OSS_STANDALONE_V7_PORT=8108 + +OSS_STANDALONE_V8_HOST=host.docker.internal +OSS_STANDALONE_V8_PORT=8109 + +OSS_STANDALONE_REDISEARCH_HOST=host.docker.internal +OSS_STANDALONE_REDISEARCH_PORT=8102 + +OSS_STANDALONE_BIG_HOST=host.docker.internal +OSS_STANDALONE_BIG_PORT=8103 + +OSS_STANDALONE_TLS_HOST=host.docker.internal +OSS_STANDALONE_TLS_PORT=8104 + +OSS_STANDALONE_EMPTY_HOST=host.docker.internal +OSS_STANDALONE_EMPTY_PORT=8105 + +OSS_STANDALONE_REDISGEARS_HOST=host.docker.internal +OSS_STANDALONE_REDISGEARS_PORT=8106 + +OSS_STANDALONE_NOPERM_HOST=host.docker.internal +OSS_STANDALONE_NOPERM_PORT=8100 + +OSS_CLUSTER_REDISGEARS_2_HOST=host.docker.internal +OSS_CLUSTER_REDISGEARS_2_PORT=8107 + +OSS_CLUSTER_HOST=host.docker.internal +OSS_CLUSTER_PORT=8200 + +OSS_SENTINEL_HOST=host.docker.internal +OSS_SENTINEL_PORT=28100 +OSS_SENTINEL_PASSWORD=password + +RE_CLUSTER_HOST=host.docker.internal +RE_CLUSTER_PORT=19443 diff --git a/tests/playwright/env/.local-web.env b/tests/playwright/env/.local-web.env new file mode 100644 index 0000000000..d7baae78ee --- /dev/null +++ b/tests/playwright/env/.local-web.env @@ -0,0 +1,47 @@ +COMMON_URL=http://localhost:8080 +API_URL=http://localhost:5540/api + +RI_APP_FOLDER_NAME=.redis-insight + +OSS_STANDALONE_HOST=localhost +OSS_STANDALONE_PORT=8100 + +OSS_STANDALONE_V5_HOST=localhost +OSS_STANDALONE_V5_PORT=8101 + +OSS_STANDALONE_V7_HOST=localhost +OSS_STANDALONE_V7_PORT=8108 + +OSS_STANDALONE_V8_HOST=localhost +OSS_STANDALONE_V8_PORT=8109 + +OSS_STANDALONE_REDISEARCH_HOST=localhost +OSS_STANDALONE_REDISEARCH_PORT=8102 + +OSS_STANDALONE_BIG_HOST=localhost +OSS_STANDALONE_BIG_PORT=8103 + +OSS_STANDALONE_TLS_HOST=localhost +OSS_STANDALONE_TLS_PORT=8104 + +OSS_STANDALONE_EMPTY_HOST=localhost +OSS_STANDALONE_EMPTY_PORT=8105 + +OSS_STANDALONE_REDISGEARS_HOST=localhost +OSS_STANDALONE_REDISGEARS_PORT=8106 + +OSS_STANDALONE_NOPERM_HOST=localhost +OSS_STANDALONE_NOPERM_PORT=8100 + +OSS_CLUSTER_REDISGEARS_2_HOST=localhost +OSS_CLUSTER_REDISGEARS_2_PORT=8107 + +OSS_CLUSTER_HOST=localhost +OSS_CLUSTER_PORT=8200 + +OSS_SENTINEL_HOST=localhost +OSS_SENTINEL_PORT=28100 +OSS_SENTINEL_PASSWORD=password + +RE_CLUSTER_HOST=localhost +RE_CLUSTER_PORT=19443 diff --git a/tests/playwright/fixtures/test.ts b/tests/playwright/fixtures/test.ts new file mode 100644 index 0000000000..f604033b7b --- /dev/null +++ b/tests/playwright/fixtures/test.ts @@ -0,0 +1,168 @@ +/* eslint-disable no-empty-pattern */ +import { test as base, expect } from '@playwright/test' +import { + BrowserContext, + ElectronApplication, + Page, + _electron as electron, +} from 'playwright' +import log from 'node-color-log' +import { AxiosInstance } from 'axios' +import * as crypto from 'crypto' +import fs from 'fs' +import path from 'path' + +import { apiUrl, isElectron, electronExecutablePath } from '../helpers/conf' +import { generateApiClient } from '../helpers/api/http-client' +import { APIKeyRequests } from '../helpers/api/api-keys' +import { DatabaseAPIRequests } from '../helpers/api/api-databases' +import { UserAgreementDialog } from '../pageObjects' + +// Coverage type declaration +declare global { + interface Window { + // eslint-disable-next-line no-underscore-dangle + __coverage__: any + } +} + +export function generateUUID(): string { + return crypto.randomBytes(16).toString('hex') +} + +type CommonFixtures = { + forEachTest: void + api: { + apiClient: AxiosInstance + keyService: APIKeyRequests + databaseService: DatabaseAPIRequests + } +} + +const commonTest = base.extend({ + // Simple context setup for coverage + context: async ({ context }, use) => { + if (process.env.COLLECT_COVERAGE === 'true') { + const outputDir = path.join(process.cwd(), '.nyc_output') + await fs.promises.mkdir(outputDir, { recursive: true }) + + // Expose coverage collection function + await context.exposeFunction( + 'collectIstanbulCoverage', + (coverageJSON: string) => { + if (coverageJSON) { + fs.writeFileSync( + path.join( + outputDir, + `playwright_coverage_${generateUUID()}.json`, + ), + coverageJSON, + ) + } + }, + ) + } + + await use(context) + }, + + api: async ({ page }, use) => { + const windowId = await page.evaluate(() => window.windowId) + + const apiClient = generateApiClient(apiUrl, windowId) + const databaseService = new DatabaseAPIRequests(apiClient) + const keyService = new APIKeyRequests(apiClient, databaseService) + + await use({ apiClient, keyService, databaseService }) + }, + forEachTest: [ + async ({ page }, use) => { + // before each test: + if (!isElectron) { + await page.goto('/') + } else { + await page.locator('[data-testid="home-tab-databases"]').click() + } + + const userAgreementDialog = new UserAgreementDialog(page) + await userAgreementDialog.acceptLicenseTerms() + + const skipTourElement = page.locator('button', { + hasText: 'Skip tour', + }) + if (await skipTourElement.isVisible()) { + skipTourElement.click() + } + + await use() + + // Collect coverage after each test + if (process.env.COLLECT_COVERAGE === 'true') { + await page + .evaluate(() => { + if ( + typeof window !== 'undefined' && + // eslint-disable-next-line no-underscore-dangle + window.__coverage__ + ) { + ;(window as any).collectIstanbulCoverage( + // eslint-disable-next-line no-underscore-dangle + JSON.stringify(window.__coverage__), + ) + } + }) + .catch(() => { + // Ignore errors - page might be closed + }) + } + }, + { auto: true }, + ], +}) + +const electronTest = commonTest.extend<{ + electronApp: ElectronApplication | null + page: Page + context: BrowserContext +}>({ + electronApp: async ({}, use) => { + const electronApp = await electron.launch({ + executablePath: electronExecutablePath, + args: ['index.html'], + timeout: 60000, + }) + electronApp.on('console', (msg) => { + log.info(`Electron Log: ${msg.type()} - ${msg.text()}`) + }) + + // Wait for window startup + await new Promise((resolve) => setTimeout(resolve, 2000)) + + await use(electronApp) + + log.info('Closing Electron app...') + await electronApp.close() + }, + page: async ({ electronApp }, use) => { + if (!electronApp) { + throw new Error('Electron app is not initialized') + } + + const electronPage = await electronApp.firstWindow() + + await use(electronPage) + }, + context: async ({ electronApp }, use) => { + if (!electronApp) { + throw new Error('Electron app is not initialized') + } + + const electronContext = electronApp.context() + + await use(electronContext) + }, +}) + +const test = isElectron ? electronTest : commonTest + +export { test, expect, isElectron } diff --git a/tests/playwright/helpers/api/api-databases.ts b/tests/playwright/helpers/api/api-databases.ts new file mode 100644 index 0000000000..af233a4dce --- /dev/null +++ b/tests/playwright/helpers/api/api-databases.ts @@ -0,0 +1,98 @@ +import { faker } from '@faker-js/faker' +import { AxiosInstance } from 'axios' +import { AddNewDatabaseParameters, DatabaseInstance } from '../../types' +import { ResourcePath } from '../constants' + +export class DatabaseAPIRequests { + constructor(private apiClient: AxiosInstance) {} + + async addNewStandaloneDatabaseApi( + databaseParameters: AddNewDatabaseParameters, + isCloud = false, + ): Promise { + const uniqueId = faker.string.alphanumeric({ length: 10 }) + const uniqueIdNumber = faker.number.int({ min: 1, max: 1000 }) + const requestBody: any = { + name: databaseParameters.databaseName, + host: databaseParameters.host, + port: Number(databaseParameters.port), + } + + if (databaseParameters.databaseUsername) { + requestBody.username = databaseParameters.databaseUsername + } + + if (databaseParameters.databasePassword) { + requestBody.password = databaseParameters.databasePassword + } + + if (databaseParameters.caCert) { + requestBody.tls = true + requestBody.verifyServerCert = false + requestBody.caCert = { + name: `ca-${uniqueId}`, + certificate: databaseParameters.caCert.certificate, + } + requestBody.clientCert = { + name: `client-${uniqueId}`, + certificate: databaseParameters.clientCert!.certificate, + key: databaseParameters.clientCert!.key, + } + } + + if (isCloud) { + requestBody.cloudDetails = { + cloudId: uniqueIdNumber, + subscriptionType: 'fixed', + planMemoryLimit: 30, + memoryLimitMeasurementUnit: 'mb', + free: true, + } + } + + const response = await this.apiClient.post( + ResourcePath.Databases, + requestBody, + ) + if (response.status !== 201) + throw new Error( + `Database creation failed for ${databaseParameters.databaseName}`, + ) + } + + async getAllDatabases(): Promise { + const response = await this.apiClient.get(ResourcePath.Databases) + if (response.status !== 200) + throw new Error('Failed to retrieve databases') + return response.data + } + + async getDatabaseIdByName(databaseName?: string): Promise { + if (!databaseName) throw new Error('Error: Missing databaseName') + + const allDatabases = await this.getAllDatabases() + const foundDb = allDatabases.find((item) => item.name === databaseName) + + if (!foundDb) throw new Error(`Database ${databaseName} not found`) + + return foundDb.id + } + + async deleteStandaloneDatabaseApi( + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.getDatabaseIdByName( + databaseParameters.databaseName, + ) + if (!databaseId) throw new Error('Error: Missing databaseId') + + const requestBody = { ids: [databaseId] } + const response = await this.apiClient.delete(ResourcePath.Databases, { + data: requestBody, + }) + if (response.status !== 200) + throw new Error( + `Failed to delete database ${databaseParameters.databaseName}`, + ) + } +} diff --git a/tests/playwright/helpers/api/api-keys.ts b/tests/playwright/helpers/api/api-keys.ts new file mode 100755 index 0000000000..b1d1809fdd --- /dev/null +++ b/tests/playwright/helpers/api/api-keys.ts @@ -0,0 +1,130 @@ +/* eslint-disable max-len */ +import { AxiosInstance } from 'axios' +import { DatabaseAPIRequests } from './api-databases' +import { + AddNewDatabaseParameters, + HashKeyParameters, + SetKeyParameters, + StreamKeyParameters, +} from '../../types' + +const bufferPathMask = '/databases/databaseId/keys?encoding=buffer' +export class APIKeyRequests { + constructor( + private apiClient: AxiosInstance, + private databaseAPIRequests: DatabaseAPIRequests, + ) {} + + async addHashKeyApi( + keyParameters: HashKeyParameters, + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseParameters.databaseName, + ) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + fields: keyParameters.fields.map((fields) => ({ + ...fields, + field: Buffer.from(fields.field, 'utf-8'), + value: Buffer.from(fields.value, 'utf-8'), + })), + } + const response = await this.apiClient.post( + `/databases/${databaseId}/hash?encoding=buffer`, + requestBody, + ) + if (response.status !== 201) + throw new Error('The creation of new Hash key request failed') + } + + async addStreamKeyApi( + keyParameters: StreamKeyParameters, + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseParameters.databaseName, + ) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + entries: keyParameters.entries.map((member) => ({ + ...member, + fields: member.fields.map(({ name, value }) => ({ + name: Buffer.from(name, 'utf-8'), + value: Buffer.from(value, 'utf-8'), + })), + })), + } + const response = await this.apiClient.post( + `/databases/${databaseId}/streams?encoding=buffer`, + requestBody, + ) + if (response.status !== 201) + throw new Error('The creation of new Stream key request failed') + } + + async addSetKeyApi( + keyParameters: SetKeyParameters, + databaseParameters: AddNewDatabaseParameters, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseParameters.databaseName, + ) + const requestBody = { + keyName: Buffer.from(keyParameters.keyName, 'utf-8'), + members: keyParameters.members.map((member) => + Buffer.from(member, 'utf-8'), + ), + } + const response = await this.apiClient.post( + `/databases/${databaseId}/set?encoding=buffer`, + requestBody, + ) + if (response.status !== 201) + throw new Error('The creation of new Set key request failed') + } + + async searchKeyByNameApi( + keyName: string, + databaseName: string, + ): Promise { + const requestBody = { + cursor: '0', + match: keyName, + } + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseName, + ) + const response = await this.apiClient.post( + bufferPathMask.replace('databaseId', databaseId), + requestBody, + ) + if (response.status !== 200) + throw new Error('Getting key request failed') + return response.data[0].keys + } + + async deleteKeyByNameApi( + keyName: string, + databaseName: string, + ): Promise { + const databaseId = await this.databaseAPIRequests.getDatabaseIdByName( + databaseName, + ) + const doesKeyExist = await this.searchKeyByNameApi( + keyName, + databaseName, + ) + if (doesKeyExist.length > 0) { + const requestBody = { keyNames: [Buffer.from(keyName, 'utf-8')] } + const response = await this.apiClient.delete( + bufferPathMask.replace('databaseId', databaseId), + { + data: requestBody, + }, + ) + if (response.status !== 200) + throw new Error('The deletion of the key request failed') + } + } +} diff --git a/tests/playwright/helpers/api/http-client.ts b/tests/playwright/helpers/api/http-client.ts new file mode 100644 index 0000000000..24d352e454 --- /dev/null +++ b/tests/playwright/helpers/api/http-client.ts @@ -0,0 +1,34 @@ +import axios, { AxiosInstance } from 'axios' +import https from 'https' + +export function generateApiClient(apiUrl: string, windowId?: string): AxiosInstance { + const apiClient = axios.create({ + baseURL: apiUrl, + headers: { + 'X-Window-Id': windowId, + }, + httpsAgent: new https.Agent({ + rejectUnauthorized: false, // Allows self-signed/invalid SSL certs + }), + }) + + // Enable logging if DEBUG is set + if (process.env.DEBUG) { + this.apiClient.interceptors.request.use((request) => { + console.log('Starting Request', request) + return request + }) + this.apiClient.interceptors.response.use( + (response) => { + console.log('Response:', response) + return response + }, + (error) => { + console.error('Error Response:', error.response) + return Promise.reject(error) + }, + ) + } + + return apiClient +} diff --git a/tests/playwright/helpers/conf.ts b/tests/playwright/helpers/conf.ts new file mode 100644 index 0000000000..306d687069 --- /dev/null +++ b/tests/playwright/helpers/conf.ts @@ -0,0 +1,209 @@ +import { faker } from '@faker-js/faker' +import * as os from 'os' +import * as fs from 'fs' +import { join as joinPath } from 'path' +import * as path from 'path' + +// Urls for using in the tests +export const commonUrl = process.env.COMMON_URL || 'https://localhost:5540' +export const apiUrl = process.env.API_URL || 'https://localhost:5540/api' +export const electronExecutablePath = process.env.ELECTRON_EXECUTABLE_PATH +export const isElectron = electronExecutablePath !== undefined +export const googleUser = process.env.GOOGLE_USER || '' +export const googleUserPassword = process.env.GOOGLE_USER_PASSWORD || '' +export const samlUser = process.env.E2E_SSO_EMAIL || '' +export const samlUserPassword = process.env.E2E_SSO_PASSWORD || '' + +export const workingDirectory = + process.env.RI_APP_FOLDER_ABSOLUTE_PATH || + joinPath(os.homedir(), process.env.RI_APP_FOLDER_NAME || '.redis-insight') +export const fileDownloadPath = joinPath(os.homedir(), 'Downloads') +const uniqueId = faker.string.alphanumeric({ length: 10 }) + +export const ossStandaloneConfig = { + host: process.env.OSS_STANDALONE_HOST!, + port: process.env.OSS_STANDALONE_PORT!, + databaseName: `${process.env.OSS_STANDALONE_DATABASE_NAME || 'test_standalone'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_USERNAME, + databasePassword: process.env.OSS_STANDALONE_PASSWORD, +} + +export const ossStandaloneConfigEmpty = { + host: process.env.OSS_STANDALONE_EMPTY_HOST, + port: process.env.OSS_STANDALONE_EMPTY_PORT, + databaseName: `${process.env.OSS_STANDALONE_EMPTY_DATABASE_NAME || 'test_standalone_empty'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_EMPTY_USERNAME, + databasePassword: process.env.OSS_STANDALONE_EMPTY_PASSWORD, +} + +export const ossStandaloneV5Config = { + host: process.env.OSS_STANDALONE_V5_HOST, + port: process.env.OSS_STANDALONE_V5_PORT, + databaseName: `${process.env.OSS_STANDALONE_V5_DATABASE_NAME || 'test_standalone-v5'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_V5_USERNAME, + databasePassword: process.env.OSS_STANDALONE_V5_PASSWORD, +} + +export const ossStandaloneV7Config = { + host: process.env.OSS_STANDALONE_V7_HOST, + port: process.env.OSS_STANDALONE_V7_PORT, + databaseName: `${process.env.OSS_STANDALONE_V7_DATABASE_NAME || 'test_standalone-v7'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_V7_USERNAME, + databasePassword: process.env.OSS_STANDALONE_V7_PASSWORD, +} + +export const ossStandaloneV6Config = { + host: process.env.OSS_STANDALONE_V8_HOST, + port: process.env.OSS_STANDALONE_V8_PORT, + databaseName: `${process.env.OSS_STANDALONE_V8_DATABASE_NAME || 'test_standalone-v6'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_V8_USERNAME, + databasePassword: process.env.OSS_STANDALONE_V8_PASSWORD, +} + +export const ossStandaloneRedisearch = { + host: process.env.OSS_STANDALONE_REDISEARCH_HOST, + port: process.env.OSS_STANDALONE_REDISEARCH_PORT, + databaseName: `${process.env.OSS_STANDALONE_REDISEARCH_DATABASE_NAME || 'test_standalone-redisearch'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_REDISEARCH_USERNAME, + databasePassword: process.env.OSS_STANDALONE_REDISEARCH_PASSWORD, +} + +export const ossClusterConfig = { + ossClusterHost: process.env.OSS_CLUSTER_HOST, + ossClusterPort: process.env.OSS_CLUSTER_PORT, + ossClusterDatabaseName: `${process.env.OSS_CLUSTER_DATABASE_NAME || 'test_cluster'}-${uniqueId}`, +} + +export const ossSentinelConfig = { + sentinelHost: process.env.OSS_SENTINEL_HOST, + sentinelPort: process.env.OSS_SENTINEL_PORT, + sentinelPassword: process.env.OSS_SENTINEL_PASSWORD, + masters: [ + { + alias: `primary-group-1}-${uniqueId}`, + db: '0', + name: 'primary-group-1', + password: 'defaultpass', + }, + { + alias: `primary-group-2}-${uniqueId}`, + db: '0', + name: 'primary-group-2', + password: 'defaultpass', + }, + ], + name: ['primary-group-1', 'primary-group-2'], +} + +export const redisEnterpriseClusterConfig = { + host: process.env.RE_CLUSTER_HOST, + port: process.env.RE_CLUSTER_PORT, + databaseName: process.env.RE_CLUSTER_DATABASE_NAME || 'test-re-standalone', + databaseUsername: process.env.RE_CLUSTER_ADMIN_USER || 'demo@redislabs.com', + databasePassword: process.env.RE_CLUSTER_ADMIN_PASSWORD || '123456', +} + +export const invalidOssStandaloneConfig = { + host: 'oss-standalone-invalid', + port: '1010', + databaseName: `${process.env.OSS_STANDALONE_INVALID_DATABASE_NAME || 'test_standalone-invalid'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_INVALID_USERNAME, + databasePassword: process.env.OSS_STANDALONE_INVALID_PASSWORD, +} + +export const ossStandaloneBigConfig = { + host: process.env.OSS_STANDALONE_BIG_HOST, + port: process.env.OSS_STANDALONE_BIG_PORT, + databaseName: `${process.env.OSS_STANDALONE_BIG_DATABASE_NAME || 'test_standalone_big'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_BIG_USERNAME, + databasePassword: process.env.OSS_STANDALONE_BIG_PASSWORD, +} + +export const cloudDatabaseConfig = { + host: process.env.E2E_CLOUD_DATABASE_HOST || '', + port: process.env.E2E_CLOUD_DATABASE_PORT || '', + databaseName: `${process.env.E2E_CLOUD_DATABASE_NAME || 'cloud-database'}-${uniqueId}`, + databaseUsername: process.env.E2E_CLOUD_DATABASE_USERNAME, + databasePassword: process.env.E2E_CLOUD_DATABASE_PASSWORD, + accessKey: process.env.E2E_CLOUD_API_ACCESS_KEY || '', + secretKey: process.env.E2E_CLOUD_API_SECRET_KEY || '', +} + +export const ossStandaloneNoPermissionsConfig = { + host: process.env.OSS_STANDALONE_NOPERM_HOST, + port: process.env.OSS_STANDALONE_NOPERM_PORT, + databaseName: `${process.env.OSS_STANDALONE_NOPERM_DATABASE_NAME || 'oss-standalone-no-permissions'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_NOPERM_USERNAME || 'noperm', + databasePassword: process.env.OSS_STANDALONE_NOPERM_PASSWORD, +} + +export const ossStandaloneForSSHConfig = { + host: process.env.OSS_STANDALONE_SSH_HOST || '172.33.100.111', + port: process.env.OSS_STANDALONE_SSH_PORT || '6379', + databaseName: `${process.env.OSS_STANDALONE_SSH_DATABASE_NAME || 'oss-standalone-for-ssh'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_SSH_USERNAME, + databasePassword: process.env.OSS_STANDALONE_SSH_PASSWORD, +} + +export const ossClusterForSSHConfig = { + host: process.env.OSS_CLUSTER_SSH_HOST || '172.31.100.211', + port: process.env.OSS_CLUSTER_SSH_PORT || '6379', + databaseName: `${process.env.OSS_CLUSTER_SSH_DATABASE_NAME || 'oss-cluster-for-ssh'}-${uniqueId}`, + databaseUsername: process.env.OSS_CLUSTER_SSH_USERNAME, + databasePassword: process.env.OSS_CLUSTER_SSH_PASSWORD, +} + +export const ossStandaloneTlsConfig = { + host: process.env.OSS_STANDALONE_TLS_HOST, + port: process.env.OSS_STANDALONE_TLS_PORT, + databaseName: `${process.env.OSS_STANDALONE_TLS_DATABASE_NAME || 'test_standalone_tls'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_TLS_USERNAME, + databasePassword: process.env.OSS_STANDALONE_TLS_PASSWORD, + caCert: { + name: `ca}-${uniqueId}`, + certificate: + process.env.E2E_CA_CRT || + fs.readFileSync( + path.resolve( + __dirname, + '../../e2e/rte/oss-standalone-tls/certs/redisCA.crt', + ), + 'utf-8', + ), + }, + clientCert: { + name: `client}-${uniqueId}`, + certificate: + process.env.E2E_CLIENT_CRT || + fs.readFileSync( + path.resolve( + __dirname, + '../../e2e/rte/oss-standalone-tls/certs/redis.crt', + ), + 'utf-8', + ), + key: + process.env.E2E_CLIENT_KEY || + fs.readFileSync( + path.resolve( + __dirname, + '../../e2e/rte/oss-standalone-tls/certs/redis.key', + ), + 'utf-8', + ), + }, +} + +export const ossStandaloneRedisGears = { + host: process.env.OSS_STANDALONE_REDISGEARS_HOST, + port: process.env.OSS_STANDALONE_REDISGEARS_PORT, + databaseName: `${process.env.OSS_STANDALONE_REDISGEARS_DATABASE_NAME || 'test_standalone_redisgears'}-${uniqueId}`, + databaseUsername: process.env.OSS_STANDALONE_REDISGEARS_USERNAME, + databasePassword: process.env.OSS_STANDALONE_REDISGEARS_PASSWORD, +} + +export const ossClusterRedisGears = { + ossClusterHost: process.env.OSS_CLUSTER_REDISGEARS_2_HOST, + ossClusterPort: process.env.OSS_CLUSTER_REDISGEARS_2_PORT, + ossClusterDatabaseName: `${process.env.OSS_CLUSTER_REDISGEARS_2_NAME || 'test_cluster-gears-2.0'}-${uniqueId}`, +} diff --git a/tests/playwright/helpers/constants.ts b/tests/playwright/helpers/constants.ts new file mode 100644 index 0000000000..af4dc6199d --- /dev/null +++ b/tests/playwright/helpers/constants.ts @@ -0,0 +1,133 @@ +export enum KeyTypesTexts { + Hash = 'Hash', + List = 'List', + Set = 'Set', + ZSet = 'Sorted Set', + String = 'String', + ReJSON = 'JSON', + Stream = 'Stream', + Graph = 'Graph', + TimeSeries = 'Time Series', +} +export const keyLength = 50 + +export const COMMANDS_TO_CREATE_KEY = Object.freeze({ + [KeyTypesTexts.Hash]: (key: string, value: string | number = 'value', field: string | number = 'field') => `HSET ${key} '${field}' '${value}'`, + [KeyTypesTexts.List]: (key: string, element: string | number = 'element') => `LPUSH ${key} '${element}'`, + [KeyTypesTexts.Set]: (key: string, member = 'member') => `SADD ${key} '${member}'`, + [KeyTypesTexts.ZSet]: (key: string, member = 'member', score = 1) => `ZADD ${key} ${score} '${member}'`, + [KeyTypesTexts.String]: (key: string, value = 'val') => `SET ${key} '${value}'`, + [KeyTypesTexts.ReJSON]: (key: string, json = '"val"') => `JSON.SET ${key} . '${json}'`, + [KeyTypesTexts.Stream]: (key: string, value: string | number = 'value', field: string | number = 'field') => `XADD ${key} * '${field}' '${value}'`, + [KeyTypesTexts.Graph]: (key: string) => `GRAPH.QUERY ${key} "CREATE ()"`, + [KeyTypesTexts.TimeSeries]: (key: string) => `TS.CREATE ${key}` +}) + +export enum RTE { + none = 'none', + standalone = 'standalone', + sentinel = 'sentinel', + ossCluster = 'oss-cluster', + reCluster = 're-cluster', + reCloud = 're-cloud' +} + +export enum ENV { + web = 'web', + desktop = 'desktop' +} + +export enum RecommendationIds { + redisVersion = 'redisVersion', + searchVisualization = 'searchVisualization', + setPassword = 'setPassword', + optimizeTimeSeries = 'RTS', + luaScript = 'luaScript', + useSmallerKeys = 'useSmallerKeys', + avoidLogicalDatabases = 'avoidLogicalDatabases', + searchJson = 'searchJSON', + rdi = 'tryRDI' +} + +export enum LibrariesSections { + Functions = 'Functions', + KeyspaceTriggers = 'Keyspace', + ClusterFunctions = 'Cluster', + StreamFunctions= 'Stream', +} + +export enum FunctionsSections { + General = 'General', + Flag = 'Flag', +} + +export enum MonacoEditorInputs { + // add library fields + Code = 'code-value', + Configuration = 'configuration-value', + // added library fields + Library = 'library-code', + LibraryConfiguration = 'library-configuration', +} + +export enum ResourcePath { + Databases = '/databases', + RedisSentinel = '/redis-sentinel', + ClusterDetails = '/cluster-details', + SyncFeatures = '/features/sync', + Rdi = '/rdi' +} + +export enum ExploreTabs { + Tutorials = 'Tutorials', + Tips = 'Tips', +} + +export enum Compatibility { + SearchAndQuery = 'search', + Json = 'json', + TimeSeries = 'time-series' +} + +export enum ChatBotTabs { + General = 'General', + Database = 'Database', +} + +export enum RedisOverviewPage { + DataBase = 'Redis Databases', + Rdi = 'My RDI instances', +} + +export enum TextConnectionSection { + Success = 'success', + Failed = 'failed', +} + +export enum RdiTemplatePipelineType { + Ingest = 'ingest', + WriteBehind = 'write-behind', +} + +export enum RdiTemplateDatabaseType { + SqlServer = 'sql', + Oracle = 'oracle', + MySql = 'mysql', +} + +export enum RdiPopoverOptions { + Server = 'server', + File = 'file', + Pipeline = 'empty', +} + +export enum TlsCertificates { + CA = 'ca', + Client = 'client', +} + +export enum AddElementInList { + Head , + Tail, +} + diff --git a/tests/playwright/helpers/utils.ts b/tests/playwright/helpers/utils.ts new file mode 100644 index 0000000000..4012dc8cfa --- /dev/null +++ b/tests/playwright/helpers/utils.ts @@ -0,0 +1,34 @@ +import { expect, Page } from '@playwright/test' + +import { DatabaseAPIRequests } from './api/api-databases' +import { ossStandaloneConfig } from './conf' + +export async function addStandaloneInstanceAndNavigateToIt( + page: Page, + databaseService: DatabaseAPIRequests, +): Promise<() => Promise> { + // Add a new standalone database + databaseService.addNewStandaloneDatabaseApi(ossStandaloneConfig) + + page.reload() + + return async function cleanup() { + try { + await databaseService.deleteStandaloneDatabaseApi( + ossStandaloneConfig, + ) + } catch (error) { + console.warn('Error during cleanup:', error) + } + } +} + +export async function navigateToStandaloneInstance(page: Page): Promise { + // Click on the added database + const dbItems = page.locator('[data-testid^="instance-name"]') + const db = dbItems.filter({ + hasText: ossStandaloneConfig.databaseName.trim(), + }) + await expect(db).toBeVisible({ timeout: 5000 }) + await db.first().click() +} diff --git a/tests/playwright/package.json b/tests/playwright/package.json new file mode 100644 index 0000000000..2eae9e1a7a --- /dev/null +++ b/tests/playwright/package.json @@ -0,0 +1,44 @@ +{ + "name": "playwright", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "devDependencies": { + "@faker-js/faker": "^9.6.0", + "@playwright/test": "^1.52.0", + "@types/node": "^22.15.29", + "allure-commandline": "^2.33.0", + "allure-js-commons": "^3.2.0", + "allure-playwright": "^3.2.0", + "cross-env": "^7.0.3", + "nyc": "^17.1.0" + }, + "scripts": { + "removeReportDirs": "rm -rf allure-results playwright-report test-results", + "allTests": "playwright test", + "generateReports": "allure generate --clean", + "test:chromium:docker": "cross-env envPath=env/.docker.env yarn playwright test --project=Chromium", + "test:chromium:docker:debug": "yarn test:chromium:docker --debug", + "test:chromium:local-web": "cross-env envPath=env/.local-web.env yarn playwright test --project=Chromium", + "test:chromium:local-web:debug": "yarn test:chromium:local-web --debug", + "test:electron": "cross-env envPath=env/.desktop.env yarn playwright test --project=Chromium", + "test:electron:debug": "yarn test:electron --debug", + "test:coverage": "cross-env COLLECT_COVERAGE=true yarn playwright test; yarn coverage", + "coverage": "npx nyc report --reporter=html --reporter=lcov --reporter=text", + "coverage:clean": "rm -rf .nyc_output coverage", + "clean:results": "rm -rf allure-results", + "prep:history": "if [ -d allure-report/history ]; then cp -R allure-report/history allure-results; fi", + "test:allureHistoryReport": "yarn run prep:history && yarn allTests && yarn allure generate --clean -o allure-report allure-results", + "test:electron:allureHistoryReport": "yarn run prep:history && yarn test:electron && yarn allure generate --clean -o allure-report allure-results", + "generateAndShowReports": "allure serve allure-results", + "test:autogen": "playwright codegen" + }, + "dependencies": { + "axios": "^1.9.0", + "dotenv": "^16.4.7", + "dotenv-cli": "^8.0.0", + "fs-extra": "^11.3.0", + "node-color-log": "^12.0.1", + "sqlite3": "^5.1.7" + } +} diff --git a/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts b/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts new file mode 100755 index 0000000000..9acbeb34c0 --- /dev/null +++ b/tests/playwright/pageObjects/auto-discover-redis-enterprise-databases.ts @@ -0,0 +1,36 @@ +import { Page, Locator } from '@playwright/test' +import { BasePage } from './base-page' + +export class AutoDiscoverREDatabases extends BasePage { + // BUTTONS + readonly addSelectedDatabases: Locator + + readonly databaseCheckbox: Locator + + readonly search: Locator + + readonly viewDatabasesButton: Locator + + // TEXT INPUTS + readonly title: Locator + + readonly databaseName: Locator + + constructor(page: Page) { + super(page) + this.page = page + this.addSelectedDatabases = page.getByTestId('btn-add-databases') + this.databaseCheckbox = page.locator( + '[data-test-subj^="checkboxSelectRow"]', + ) + this.search = page.getByTestId('search') + this.viewDatabasesButton = page.getByTestId('btn-view-databases') + this.title = page.getByTestId('title') + this.databaseName = page.locator('[data-testid^="db_name_"]') + } + + // Get databases name + async getDatabaseName(): Promise { + return this.databaseName.textContent() + } +} diff --git a/tests/playwright/pageObjects/base-overview-page.ts b/tests/playwright/pageObjects/base-overview-page.ts new file mode 100644 index 0000000000..3fb7401355 --- /dev/null +++ b/tests/playwright/pageObjects/base-overview-page.ts @@ -0,0 +1,180 @@ +/* eslint-disable no-await-in-loop */ +/* eslint-disable no-restricted-syntax */ +import { expect, Locator, Page } from '@playwright/test' +import { Toast } from './components/common/toast' +import { BasePage } from './base-page' +import { RedisOverviewPage } from '../helpers/constants' +import { DatabasesForImport } from '../types' + +export class BaseOverviewPage extends BasePage { + // Component instance used in methods + toast: Toast + + // BUTTONS & ACTION SELECTORS + readonly deleteRowButton: Locator + + readonly confirmDeleteButton: Locator + + readonly confirmDeleteAllDbButton: Locator + + // TABLE / LIST SELECTORS + readonly instanceRow: Locator + + readonly selectAllCheckbox: Locator + + readonly deleteButtonInPopover: Locator + + dbNameList: Locator + + readonly tableRowContent: Locator + + readonly editDatabaseButton: Locator + + // NAVIGATION SELECTORS + readonly databasePageLink: Locator + + readonly rdiPageLink: Locator + + // Additional – used for deletion by name + readonly deleteDatabaseButton: Locator + + // MODULE + readonly moduleTooltip: Locator + + constructor(page: Page) { + super(page) + this.toast = new Toast(page) + + // BUTTONS & ACTION SELECTORS + this.deleteRowButton = page.locator('[data-testid^="delete-instance-"]') + this.confirmDeleteButton = page.locator( + '[data-testid^="delete-instance-"]', + { hasText: 'Remove' }, + ) + this.confirmDeleteAllDbButton = page.getByTestId('delete-selected-dbs') + + // TABLE / LIST SELECTORS + this.instanceRow = page.locator('[class*=euiTableRow-isSelectable]') + this.selectAllCheckbox = page.locator( + '[data-test-subj="checkboxSelectAll"]', + ) + this.deleteButtonInPopover = page.locator('#deletePopover button') + this.dbNameList = page.locator('[data-testid^="instance-name"]') + this.tableRowContent = page.locator( + '[data-test-subj="database-alias-column"]', + ) + this.editDatabaseButton = page.locator('[data-testid^="edit-instance"]') + + // NAVIGATION SELECTORS + this.databasePageLink = page.getByTestId('home-tab-databases') + this.rdiPageLink = page.getByTestId('home-tab-rdi-instances') + + // Additional – we alias deleteDatabaseButton to the same as deleteRowButton + this.deleteDatabaseButton = page.locator( + '[data-testid^="delete-instance-"]', + ) + + // MODULE + this.moduleTooltip = page.locator('.euiToolTipPopover') + } + + async reloadPage(): Promise { + await this.page.reload() + } + + async setActivePage(type: RedisOverviewPage): Promise { + if (type === RedisOverviewPage.Rdi) { + await this.rdiPageLink.click() + } else { + await this.databasePageLink.click() + } + } + + async deleteAllInstance(): Promise { + const count = await this.instanceRow.count() + if (count > 1) { + await this.selectAllCheckbox.click() + await this.deleteButtonInPopover.click() + await this.confirmDeleteAllDbButton.click() + } else if (count === 1) { + await this.deleteDatabaseButton.click() + await this.confirmDeleteButton.click() + } + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + } + + async deleteDatabaseByName(dbName: string): Promise { + const count = await this.tableRowContent.count() + for (let i = 0; i < count; i += 1) { + const text = (await this.tableRowContent.nth(i).textContent()) || '' + if (text.includes(dbName)) { + // Assumes that the delete button for the row is located at index i-1. + await this.deleteRowButton.nth(i - 1).click() + await this.confirmDeleteButton.click() + break + } + } + } + + async clickOnDBByName(dbName: string): Promise { + const db = this.dbNameList.filter({ hasText: dbName.trim() }) + await expect(db).toBeVisible({ timeout: 10000 }) + await db.first().click() + } + + async clickOnEditDBByName(databaseName: string): Promise { + const count = await this.dbNameList.count() + for (let i = 0; i < count; i += 1) { + const text = (await this.dbNameList.nth(i).textContent()) || '' + if (text.includes(databaseName)) { + await this.editDatabaseButton.nth(i).click() + break + } + } + } + + async checkModulesInTooltip(moduleNameList: string[]): Promise { + for (const item of moduleNameList) { + await expect( + this.moduleTooltip.locator('span', { hasText: `${item} v.` }), + ).toBeVisible() + } + } + + async checkModulesOnPage(moduleList: Locator[]): Promise { + for (const item of moduleList) { + await expect(item).toBeVisible() + } + } + + async getAllDatabases(): Promise { + const databases: string[] = [] + await expect(this.dbNameList).toBeVisible() + const n = await this.dbNameList.count() + for (let k = 0; k < n; k += 1) { + const name = await this.dbNameList.nth(k).textContent() + databases.push(name || '') + } + return databases + } + + async compareInstances( + actualList: string[], + sortedList: string[], + ): Promise { + for (let k = 0; k < actualList.length; k += 1) { + await expect(actualList[k].trim()).toEqual(sortedList[k].trim()) + } + } + + getDatabaseNamesFromListByResult( + listOfDb: DatabasesForImport, + result: string, + ): string[] { + return listOfDb + .filter((element) => element.result === result) + .map((item) => item.name!) + } +} diff --git a/tests/playwright/pageObjects/base-page.ts b/tests/playwright/pageObjects/base-page.ts new file mode 100644 index 0000000000..9eba42f98a --- /dev/null +++ b/tests/playwright/pageObjects/base-page.ts @@ -0,0 +1,63 @@ +import { Locator, Page, expect } from '@playwright/test' + +export class BasePage { + page: Page + + constructor(page: Page) { + this.page = page + } + + async reload(): Promise { + await this.page.reload() + } + + async navigateTo(url: string): Promise { + await this.page.goto(url) + } + + async navigateToHomeUrl(): Promise { + await this.page.goto('/') + } + + async click(locator: Locator): Promise { + await locator.click() + } + + async fill(selector: string, value: string): Promise { + await this.page.fill(selector, value) + } + + async getText(locator: Locator): Promise { + return locator.textContent() + } + + async isVisible(selctor: string): Promise { + return this.page.locator(selctor).isVisible() + } + + async getByTestId(testId: string): Promise { + return this.page.getByTestId(testId) + } + + async waitForLocatorVisible(locator: Locator, timeout = 6000) { + await expect(locator).toBeVisible({ timeout }) + } + + async waitForLocatorNotVisible(locator: Locator, timeout = 6000) { + await expect(locator).not.toBeVisible({ timeout }) + } + + async goBackHistor(): Promise { + await this.page.goBack() + } + + async elementExistsSelector(selector: string): Promise { + const count = await this.page.locator(selector).count() + return count > 0 + } + + async elementExistsLocator(locator: Locator): Promise { + const count = await locator.count() + return count > 0 + } +} diff --git a/tests/playwright/pageObjects/browser-page.ts b/tests/playwright/pageObjects/browser-page.ts new file mode 100755 index 0000000000..7e83a086bb --- /dev/null +++ b/tests/playwright/pageObjects/browser-page.ts @@ -0,0 +1,1285 @@ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-await-in-loop */ +/* eslint-disable @typescript-eslint/lines-between-class-members */ +import { expect, Locator, Page } from '@playwright/test' +import { Toast } from './components/common/toast' + +import { BasePage } from './base-page' +import { AddElementInList } from '../helpers/constants' + +export class BrowserPage extends BasePage { + private toast: Toast + // CSS Selectors + public readonly cssSelectorGrid: Locator + public readonly cssSelectorRows: Locator + public readonly cssSelectorKey: Locator + public readonly cssFilteringLabel: Locator + public readonly cssJsonValue: Locator + public readonly cssRowInVirtualizedTable: Locator + public readonly cssVirtualTableRow: Locator + public readonly cssKeyBadge: Locator + public readonly cssKeyTtl: Locator + public readonly cssKeySize: Locator + public readonly cssRemoveSuggestionItem: Locator + + // BUTTONS + public readonly applyButton: Locator + public readonly cancelButton: Locator + public readonly deleteKeyButton: Locator + public readonly submitDeleteKeyButton: Locator + public readonly confirmDeleteKeyButton: Locator + public readonly editKeyTTLButton: Locator + public readonly refreshKeysButton: Locator + public readonly refreshKeyButton: Locator + public readonly editKeyNameButton: Locator + public readonly editKeyValueButton: Locator + public readonly closeKeyButton: Locator + public readonly plusAddKeyButton: Locator + public readonly addKeyValueItemsButton: Locator + public readonly saveHashFieldButton: Locator + public readonly saveMemberButton: Locator + public readonly searchButtonInKeyDetails: Locator + public readonly addKeyButton: Locator + public readonly keyTypeDropDown: Locator + public readonly confirmRemoveHashFieldButton: Locator + public readonly removeSetMemberButton: Locator + public readonly removeHashFieldButton: Locator + public readonly removeZsetMemberButton: Locator + public readonly confirmRemoveSetMemberButton: Locator + public readonly confirmRemoveZSetMemberButton: Locator + public readonly saveElementButton: Locator + public readonly removeElementFromListIconButton: Locator + public readonly removeElementFromListButton: Locator + public readonly confirmRemoveListElementButton: Locator + public readonly removeElementFromListSelect: Locator + public readonly addJsonObjectButton: Locator + public readonly addJsonFieldButton: Locator + public readonly expandJsonObject: Locator + public readonly scoreButton: Locator + public readonly sortingButton: Locator + public readonly editJsonObjectButton: Locator + public readonly applyEditButton: Locator + public readonly cancelEditButton: Locator + public readonly scanMoreButton: Locator + public readonly resizeBtnKeyList: Locator + public readonly treeViewButton: Locator + public readonly browserViewButton: Locator + public readonly searchButton: Locator + public readonly clearFilterButton: Locator + public readonly fullScreenModeButton: Locator + public readonly closeRightPanel: Locator + public readonly addNewStreamEntry: Locator + public readonly removeEntryButton: Locator + public readonly confirmRemoveEntryButton: Locator + public readonly clearStreamEntryInputs: Locator + public readonly saveGroupsButton: Locator + public readonly acknowledgeButton: Locator + public readonly confirmAcknowledgeButton: Locator + public readonly claimPendingMessageButton: Locator + public readonly submitButton: Locator + public readonly consumerDestinationSelect: Locator + public readonly removeConsumerButton: Locator + public readonly removeConsumerGroupButton: Locator + public readonly optionalParametersSwitcher: Locator + public readonly forceClaimCheckbox: Locator + public readonly editStreamLastIdButton: Locator + public readonly saveButton: Locator + public readonly bulkActionsButton: Locator + public readonly editHashButton: Locator + public readonly editHashFieldTtlButton: Locator + public readonly editZsetButton: Locator + public readonly editListButton: Locator + public readonly cancelStreamGroupBtn: Locator + public readonly patternModeBtn: Locator + public readonly redisearchModeBtn: Locator + public readonly showFilterHistoryBtn: Locator + public readonly clearFilterHistoryBtn: Locator + public readonly loadSampleDataBtn: Locator + public readonly executeBulkKeyLoadBtn: Locator + public readonly backToBrowserBtn: Locator + public readonly loadAllBtn: Locator + public readonly downloadAllValueBtn: Locator + public readonly openTutorialsBtn: Locator + public readonly keyItem: Locator + public readonly columnsBtn: Locator + + // CONTAINERS + public readonly streamGroupsContainer: Locator + public readonly streamConsumersContainer: Locator + public readonly breadcrumbsContainer: Locator + public readonly virtualTableContainer: Locator + public readonly streamEntriesContainer: Locator + public readonly streamMessagesContainer: Locator + public readonly loader: Locator + public readonly newIndexPanel: Locator + + // LINKS + public readonly internalLinkToWorkbench: Locator + public readonly userSurveyLink: Locator + public readonly redisearchFreeLink: Locator + public readonly guideLinksBtn: Locator + + // OPTION ELEMENTS + public readonly stringOption: Locator + public readonly jsonOption: Locator + public readonly setOption: Locator + public readonly zsetOption: Locator + public readonly listOption: Locator + public readonly hashOption: Locator + public readonly streamOption: Locator + public readonly removeFromHeadSelection: Locator + public readonly filterOptionType: Locator + public readonly filterByKeyTypeDropDown: Locator + public readonly filterAllKeyType: Locator + public readonly consumerOption: Locator + public readonly claimTimeOptionSelect: Locator + public readonly relativeTimeOption: Locator + public readonly timestampOption: Locator + public readonly formatSwitcher: Locator + public readonly formatSwitcherIcon: Locator + public readonly refreshIndexButton: Locator + public readonly selectIndexDdn: Locator + public readonly createIndexBtn: Locator + public readonly cancelIndexCreationBtn: Locator + public readonly confirmIndexCreationBtn: Locator + public readonly resizeTrigger: Locator + public readonly filterHistoryOption: Locator + public readonly filterHistoryItemText: Locator + + // TABS + public readonly streamTabGroups: Locator + public readonly streamTabConsumers: Locator + public readonly streamTabs: Locator + + // TEXT INPUTS + public readonly addKeyNameInput: Locator + public readonly keyNameInput: Locator + public readonly keyTTLInput: Locator + public readonly editKeyTTLInput: Locator + public readonly ttlText: Locator + public readonly hashFieldValueInput: Locator + public readonly hashFieldNameInput: Locator + public readonly hashFieldValueEditor: Locator + public readonly hashTtlFieldInput: Locator + public readonly listKeyElementEditorInput: Locator + public readonly stringKeyValueInput: Locator + public readonly jsonKeyValueInput: Locator + public readonly jsonUploadInput: Locator + public readonly setMemberInput: Locator + public readonly zsetMemberScoreInput: Locator + public readonly filterByPatterSearchInput: Locator + public readonly hashFieldInput: Locator + public readonly hashValueInput: Locator + public readonly searchInput: Locator + public readonly jsonKeyInput: Locator + public readonly jsonValueInput: Locator + public readonly countInput: Locator + public readonly streamEntryId: Locator + public readonly streamField: Locator + public readonly streamValue: Locator + public readonly addAdditionalElement: Locator + public readonly streamFieldsValues: Locator + public readonly streamEntryIDDateValue: Locator + public readonly groupNameInput: Locator + public readonly consumerIdInput: Locator + public readonly streamMinIdleTimeInput: Locator + public readonly claimIdleTimeInput: Locator + public readonly claimRetryCountInput: Locator + public readonly lastIdInput: Locator + public readonly inlineItemEditor: Locator + public readonly indexNameInput: Locator + public readonly prefixFieldInput: Locator + public readonly indexIdentifierInput: Locator + + // TEXT ELEMENTS + public readonly keySizeDetails: Locator + public readonly keyLengthDetails: Locator + public readonly keyNameInTheList: Locator + public readonly hashFieldsList: Locator + public readonly hashValuesList: Locator + public readonly hashField: Locator + public readonly hashFieldValue: Locator + public readonly setMembersList: Locator + public readonly zsetMembersList: Locator + public readonly zsetScoresList: Locator + public readonly listElementsList: Locator + public readonly jsonKeyValue: Locator + public readonly jsonError: Locator + public readonly tooltip: Locator + public readonly dialog: Locator + public readonly noResultsFound: Locator + public readonly noResultsFoundOnly: Locator + public readonly searchAdvices: Locator + public readonly keysNumberOfResults: Locator + public readonly scannedValue: Locator + public readonly totalKeysNumber: Locator + public readonly keyDetailsBadge: Locator + public readonly modulesTypeDetails: Locator + public readonly filteringLabel: Locator + public readonly keysSummary: Locator + public readonly multiSearchArea: Locator + public readonly keyDetailsHeader: Locator + public readonly keyListTable: Locator + public readonly keyListMessage: Locator + public readonly keyDetailsTable: Locator + public readonly keyNameFormDetails: Locator + public readonly keyDetailsTTL: Locator + public readonly progressLine: Locator + public readonly progressKeyList: Locator + public readonly jsonScalarValue: Locator + public readonly noKeysToDisplayText: Locator + public readonly streamEntryDate: Locator + public readonly streamEntryIdValue: Locator + public readonly streamFields: Locator + public readonly streamVirtualContainer: Locator + public readonly streamEntryFields: Locator + public readonly confirmationMessagePopover: Locator + public readonly streamGroupId: Locator + public readonly streamGroupName: Locator + public readonly streamMessage: Locator + public readonly streamConsumerName: Locator + public readonly consumerGroup: Locator + public readonly entryIdInfoIcon: Locator + public readonly entryIdError: Locator + public readonly pendingCount: Locator + public readonly streamRangeBar: Locator + public readonly rangeLeftTimestamp: Locator + public readonly rangeRightTimestamp: Locator + public readonly jsonValue: Locator + public readonly stringValueAsJson: Locator + + // POPUPS + public readonly changeValueWarning: Locator + + // TABLE + public readonly keyListItem: Locator + + // DIALOG + public readonly noReadySearchDialogTitle: Locator + + // CHECKBOXES + public readonly showTtlCheckbox: Locator + public readonly showTtlColumnCheckbox: Locator + public readonly showSizeColumnCheckbox: Locator + + // UTILITY FUNCTIONS + public readonly getHashTtlFieldInput: (fieldName: string) => Locator + public readonly getListElementInput: (count: number) => Locator + public readonly getKeySize: (keyName: string) => Locator + public readonly getKeyTTl: (keyName: string) => Locator + + constructor(page: Page) { + super(page) + this.page = page + this.toast = new Toast(page) + + // CSS Selectors + this.cssSelectorGrid = page.locator('[aria-label="grid"]') + this.cssSelectorRows = page.locator('[aria-label="row"]') + this.cssSelectorKey = page.locator('[data-testid^="key-"]') + this.cssFilteringLabel = page.getByTestId('multi-search') + this.cssJsonValue = page.getByTestId('value-as-json') + this.cssRowInVirtualizedTable = page.locator('[role="gridcell"]') + this.cssVirtualTableRow = page.locator('[aria-label="row"]') + this.cssKeyBadge = page.locator('[data-testid^="badge-"]') + this.cssKeyTtl = page.locator('[data-testid^="ttl-"]') + this.cssKeySize = page.locator('[data-testid^="size-"]') + this.cssRemoveSuggestionItem = page.locator( + '[data-testid^="remove-suggestion-item-"]', + ) + + // BUTTONS + this.applyButton = page.getByTestId('apply-btn') + this.cancelButton = page.getByTestId('cancel-btn') + this.deleteKeyButton = page.getByTestId('delete-key-btn') + this.submitDeleteKeyButton = page.getByTestId('submit-delete-key') + this.confirmDeleteKeyButton = page.getByTestId('delete-key-confirm-btn') + this.editKeyTTLButton = page.getByTestId('edit-ttl-btn') + this.refreshKeysButton = page.getByTestId('keys-refresh-btn') + this.refreshKeyButton = page.getByTestId('key-refresh-btn') + this.editKeyNameButton = page.getByTestId('edit-key-btn') + this.editKeyValueButton = page.getByTestId('edit-key-value-btn') + this.closeKeyButton = page.getByTestId('close-key-btn') + this.plusAddKeyButton = page.getByTestId('btn-add-key') + this.addKeyValueItemsButton = page.getByTestId( + 'add-key-value-items-btn', + ) + this.saveHashFieldButton = page.getByTestId('save-fields-btn') + this.saveMemberButton = page.getByTestId('save-members-btn') + this.searchButtonInKeyDetails = page.getByTestId('search-button') + this.addKeyButton = page.locator('button', { + hasText: /^Add Key$/, + }) + this.keyTypeDropDown = page.locator( + 'fieldset button.euiSuperSelectControl', + ) + this.confirmRemoveHashFieldButton = page.locator( + '[data-testid^="remove-hash-button-"] span', + ) + this.removeSetMemberButton = page.getByTestId('set-remove-btn') + this.removeHashFieldButton = page.getByTestId('remove-hash-button') + this.removeZsetMemberButton = page.getByTestId('zset-remove-button') + this.confirmRemoveSetMemberButton = page.locator( + '[data-testid^="set-remove-btn-"] span', + ) + this.confirmRemoveZSetMemberButton = page.locator( + '[data-testid^="zset-remove-button-"] span', + ) + this.saveElementButton = page.getByTestId('save-elements-btn') + this.removeElementFromListIconButton = page.getByTestId( + 'remove-key-value-items-btn', + ) + this.removeElementFromListButton = page.getByTestId( + 'remove-elements-btn', + ) + this.confirmRemoveListElementButton = page.getByTestId('remove-submit') + this.removeElementFromListSelect = + page.getByTestId('destination-select') + this.addJsonObjectButton = page.getByTestId('add-object-btn') + this.addJsonFieldButton = page.getByTestId('add-field-btn') + this.expandJsonObject = page.getByTestId('expand-object') + this.scoreButton = page.getByTestId('score-button') + this.sortingButton = page.getByTestId('header-sorting-button') + this.editJsonObjectButton = page.getByTestId('edit-json-field') + this.applyEditButton = page.getByTestId('apply-edit-btn') + this.cancelEditButton = page.getByTestId('cancel-edit-btn') + this.scanMoreButton = page.getByTestId('scan-more') + this.resizeBtnKeyList = page.locator( + '[data-test-subj="resize-btn-keyList-keyDetails"]', + ) + this.treeViewButton = page.getByTestId('view-type-list-btn') + this.browserViewButton = page.getByTestId('view-type-browser-btn') + this.searchButton = page.getByTestId('search-btn') + this.clearFilterButton = page.getByTestId('reset-filter-btn') + this.fullScreenModeButton = page.getByTestId('toggle-full-screen') + this.closeRightPanel = page.getByTestId('close-right-panel-btn') + this.addNewStreamEntry = page.getByTestId('add-key-value-items-btn') + this.removeEntryButton = page.locator( + '[data-testid^="remove-entry-button-"]', + ) + this.confirmRemoveEntryButton = page + .locator('[data-testid^="remove-entry-button-"]') + .filter({ hasText: 'Remove' }) + this.clearStreamEntryInputs = page.getByTestId('remove-item') + this.saveGroupsButton = page.getByTestId('save-groups-btn') + this.acknowledgeButton = page.getByTestId('acknowledge-btn') + this.confirmAcknowledgeButton = page.getByTestId('acknowledge-submit') + this.claimPendingMessageButton = page.getByTestId( + 'claim-pending-message', + ) + this.submitButton = page.getByTestId('btn-submit') + this.consumerDestinationSelect = page.getByTestId('destination-select') + this.removeConsumerButton = page.locator( + '[data-testid^="remove-consumer-button"]', + ) + this.removeConsumerGroupButton = page.locator( + '[data-testid^="remove-groups-button"]', + ) + this.optionalParametersSwitcher = page.getByTestId( + 'optional-parameters-switcher', + ) + this.forceClaimCheckbox = page + .getByTestId('force-claim-checkbox') + .locator('..') + this.editStreamLastIdButton = page.getByTestId('stream-group_edit-btn') + this.saveButton = page.getByTestId('save-btn') + this.bulkActionsButton = page.getByTestId('btn-bulk-actions') + this.editHashButton = page.locator('[data-testid^="hash_edit-btn-"]') + this.editHashFieldTtlButton = page.locator( + '[data-testid^="hash-ttl_edit-btn-"]', + ) + this.editZsetButton = page.locator('[data-testid^="zset_edit-btn-"]') + this.editListButton = page.locator('[data-testid^="list_edit-btn-"]') + this.cancelStreamGroupBtn = page.getByTestId('cancel-stream-groups-btn') + this.patternModeBtn = page.getByTestId('search-mode-pattern-btn') + this.redisearchModeBtn = page.getByTestId('search-mode-redisearch-btn') + this.showFilterHistoryBtn = page.getByTestId('show-suggestions-btn') + this.clearFilterHistoryBtn = page.getByTestId('clear-history-btn') + this.loadSampleDataBtn = page.getByTestId('load-sample-data-btn') + this.executeBulkKeyLoadBtn = page.getByTestId( + 'load-sample-data-btn-confirm', + ) + this.backToBrowserBtn = page.getByTestId('back-right-panel-btn') + this.loadAllBtn = page.getByTestId('load-all-value-btn') + this.downloadAllValueBtn = page.getByTestId('download-all-value-btn') + this.openTutorialsBtn = page.getByTestId('explore-msg-btn') + this.keyItem = page.locator( + '[data-testid*="node-item"][data-testid*="keys:"]', + ) + this.columnsBtn = page.getByTestId('btn-columns-actions') + + // CONTAINERS + this.streamGroupsContainer = page.getByTestId('stream-groups-container') + this.streamConsumersContainer = page.getByTestId( + 'stream-consumers-container', + ) + this.breadcrumbsContainer = page.getByTestId('breadcrumbs-container') + this.virtualTableContainer = page.getByTestId('virtual-table-container') + this.streamEntriesContainer = page.getByTestId( + 'stream-entries-container', + ) + this.streamMessagesContainer = page.getByTestId( + 'stream-messages-container', + ) + this.loader = page.getByTestId('type-loading') + this.newIndexPanel = page.getByTestId('create-index-panel') + + // LINKS + this.internalLinkToWorkbench = page.getByTestId( + 'internal-workbench-link', + ) + this.userSurveyLink = page.getByTestId('user-survey-link') + this.redisearchFreeLink = page.getByTestId('get-started-link') + this.guideLinksBtn = page.locator('[data-testid^="guide-button-"]') + + // OPTION ELEMENTS + this.stringOption = page.locator('#string') + this.jsonOption = page.locator('#ReJSON-RL') + this.setOption = page.locator('#set') + this.zsetOption = page.locator('#zset') + this.listOption = page.locator('#list') + this.hashOption = page.locator('#hash') + this.streamOption = page.locator('#stream') + this.removeFromHeadSelection = page.locator('#HEAD') + this.filterOptionType = page.locator( + '[data-test-subj^="filter-option-type-"]', + ) + this.filterByKeyTypeDropDown = page.getByTestId( + 'select-filter-key-type', + ) + this.filterAllKeyType = page.locator('#all') + this.consumerOption = page.getByTestId('consumer-option') + this.claimTimeOptionSelect = page.getByTestId('time-option-select') + this.relativeTimeOption = page.locator('#idle') + this.timestampOption = page.locator('#time') + this.formatSwitcher = page.getByTestId('select-format-key-value') + this.formatSwitcherIcon = page.locator( + '[data-testid^="key-value-formatter-option-selected"]', + ) + this.refreshIndexButton = page.getByTestId('refresh-indexes-btn') + this.selectIndexDdn = page.locator( + '[data-testid="select-index-placeholder"],[data-testid="select-search-mode"]', + ) + this.createIndexBtn = page.getByTestId('create-index-btn') + this.cancelIndexCreationBtn = page.getByTestId( + 'create-index-cancel-btn', + ) + this.confirmIndexCreationBtn = page.getByTestId('create-index-btn') + this.resizeTrigger = page.locator('[data-testid^="resize-trigger-"]') + this.filterHistoryOption = page.getByTestId('suggestion-item-') + this.filterHistoryItemText = page.getByTestId('suggestion-item-text') + + // TABS + this.streamTabGroups = page.getByTestId('stream-tab-Groups') + this.streamTabConsumers = page.getByTestId('stream-tab-Consumers') + this.streamTabs = page.locator('[data-test-subj="stream-tabs"]') + + // TEXT INPUTS + this.addKeyNameInput = page.getByTestId('key') + this.keyNameInput = page.getByTestId('edit-key-input') + this.keyTTLInput = page.getByTestId('ttl') + this.editKeyTTLInput = page.getByTestId('edit-ttl-input') + this.ttlText = page.getByTestId('key-ttl-text').locator('span') + this.hashFieldValueInput = page.getByTestId('field-value') + this.hashFieldNameInput = page.getByTestId('field-name') + this.hashFieldValueEditor = page.getByTestId('hash_value-editor') + this.hashTtlFieldInput = page.getByTestId('hash-ttl') + this.listKeyElementEditorInput = page.getByTestId('list_value-editor-') + this.stringKeyValueInput = page.getByTestId('string-value') + this.jsonKeyValueInput = page.locator( + 'div[data-mode-id=json] textarea', + ) + this.jsonUploadInput = page.getByTestId('upload-input-file') + this.setMemberInput = page.getByTestId('member-name') + this.zsetMemberScoreInput = page.getByTestId('member-score') + this.filterByPatterSearchInput = page.getByTestId('search-key') + this.hashFieldInput = page.getByTestId('hash-field') + this.hashValueInput = page.getByTestId('hash-value') + this.searchInput = page.getByTestId('search') + this.jsonKeyInput = page.getByTestId('json-key') + this.jsonValueInput = page.getByTestId('json-value') + this.countInput = page.getByTestId('count-input') + this.streamEntryId = page.getByTestId('entryId') + this.streamField = page.getByTestId('field-name') + this.streamValue = page.getByTestId('field-value') + this.addAdditionalElement = page.getByTestId('add-item') + this.streamFieldsValues = page.getByTestId('stream-entry-field-') + this.streamEntryIDDateValue = page.locator( + '[data-testid^="stream-entry-"][data-testid$="date"]', + ) + this.groupNameInput = page.getByTestId('group-name-field') + this.consumerIdInput = page.getByTestId('id-field') + this.streamMinIdleTimeInput = page.getByTestId('min-idle-time') + this.claimIdleTimeInput = page.getByTestId('time-count') + this.claimRetryCountInput = page.getByTestId('retry-count') + this.lastIdInput = page.getByTestId('last-id-field') + this.inlineItemEditor = page.getByTestId('inline-item-editor') + this.indexNameInput = page.getByTestId('index-name') + this.prefixFieldInput = page.locator('[data-test-subj="comboBoxInput"]') + this.indexIdentifierInput = page.getByTestId('identifier-') + + // TEXT ELEMENTS + this.keySizeDetails = page.getByTestId('key-size-text') + this.keyLengthDetails = page.getByTestId('key-length-text') + this.keyNameInTheList = this.cssSelectorKey + this.hashFieldsList = page.getByTestId('hash-field-').locator('span') + this.hashValuesList = page + .getByTestId('hash_content-value-') + .locator('span') + this.hashField = page.getByTestId('hash-field-').first() + this.hashFieldValue = page.getByTestId('hash_content-value-') + this.setMembersList = page.getByTestId('set-member-value-') + this.zsetMembersList = page.getByTestId('zset-member-value-') + this.zsetScoresList = page.getByTestId('zset_content-value-') + this.listElementsList = page.getByTestId('list_content-value-') + this.jsonKeyValue = page.getByTestId('json-data') + this.jsonError = page.getByTestId('edit-json-error') + this.tooltip = page.locator('[role="tooltip"]') + this.dialog = page.locator('[role="dialog"]') + this.noResultsFound = page.locator('[data-test-subj="no-result-found"]') + this.noResultsFoundOnly = page.getByTestId('no-result-found-only') + this.searchAdvices = page.locator('[data-test-subj="search-advices"]') + this.keysNumberOfResults = page.getByTestId('keys-number-of-results') + this.scannedValue = page.getByTestId('keys-number-of-scanned') + this.totalKeysNumber = page.getByTestId('keys-total') + this.keyDetailsBadge = page.locator( + '.key-details-header .euiBadge__text', + ) + this.modulesTypeDetails = page.getByTestId('modules-type-details') + this.filteringLabel = page.getByTestId('badge-') + this.keysSummary = page.getByTestId('keys-summary') + this.multiSearchArea = page.getByTestId('multi-search') + this.keyDetailsHeader = page.getByTestId('key-details-header') + this.keyListTable = page.getByTestId('keyList-table') + this.keyListMessage = page.getByTestId('no-result-found-msg') + this.keyDetailsTable = page.getByTestId('key-details') + this.keyNameFormDetails = page.getByTestId('key-name-text') + this.keyDetailsTTL = page.getByTestId('key-ttl-text') + this.progressLine = page.locator('div.euiProgress') + this.progressKeyList = page.getByTestId('progress-key-list') + this.jsonScalarValue = page.getByTestId('json-scalar-value') + this.noKeysToDisplayText = page.getByTestId('no-result-found-msg') + this.streamEntryDate = page.locator( + '[data-testid*="-date"][data-testid*="stream-entry"]', + ) + this.streamEntryIdValue = page.locator( + '.streamItemId[data-testid*="stream-entry"]', + ) + this.streamFields = page.locator( + '[data-test-subj="stream-entries-container"] .truncateText', + ) + this.streamVirtualContainer = page + .locator('[data-testid="virtual-grid-container"] div div') + .first() + this.streamEntryFields = page.getByTestId('stream-entry-field') + this.confirmationMessagePopover = page.locator( + 'div.euiPopover__panel .euiText', + ) + this.streamGroupId = page + .locator('.streamItemId[data-testid^="stream-group-id"]') + .first() + this.streamGroupName = page.getByTestId('stream-group-name') + this.streamMessage = page.locator( + '[data-testid*="-date"][data-testid^="stream-message"]', + ) + this.streamConsumerName = page.getByTestId('stream-consumer-') + this.consumerGroup = page.getByTestId('stream-group-') + this.entryIdInfoIcon = page.getByTestId('entry-id-info-icon') + this.entryIdError = page.getByTestId('id-error') + this.pendingCount = page.getByTestId('pending-count') + this.streamRangeBar = page.getByTestId('mock-fill-range') + this.rangeLeftTimestamp = page.getByTestId('range-left-timestamp') + this.rangeRightTimestamp = page.getByTestId('range-right-timestamp') + this.jsonValue = page.getByTestId('value-as-json') + this.stringValueAsJson = page.getByTestId('value-as-json') + + // POPUPS + this.changeValueWarning = page.getByTestId('approve-popover') + + // TABLE + this.keyListItem = page.locator('[role="rowgroup"] [role="row"]') + + // DIALOG + this.noReadySearchDialogTitle = page.getByTestId('welcome-page-title') + + // CHECKBOXES + this.showTtlCheckbox = page.getByTestId('test-check-ttl').locator('..') + this.showTtlColumnCheckbox = page.getByTestId('show-ttl').locator('..') + this.showSizeColumnCheckbox = page + .getByTestId('show-key-size') + .locator('..') + + // UTILITY FUNCTIONS + this.getHashTtlFieldInput = (fieldName: string): Locator => + page.getByTestId(`hash-ttl_content-value-${fieldName}`) + this.getListElementInput = (count: number): Locator => + page.locator(`[data-testid*="element-${count}"]`) + this.getKeySize = (keyName: string): Locator => + page.getByTestId(`size-${keyName}`) + this.getKeyTTl = (keyName: string): Locator => + page.getByTestId(`ttl-${keyName}`) + } + + async commonAddNewKey(keyName: string, TTL?: string): Promise { + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + if (TTL !== undefined) { + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + } + await this.keyTypeDropDown.click() + } + + async addStringKey( + keyName: string, + value = ' ', + TTL?: string, + ): Promise { + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.stringOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + if (TTL !== undefined) { + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + } + await this.stringKeyValueInput.click() + await this.stringKeyValueInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.addKeyButton.click() + } + + async addJsonKey( + keyName: string, + value: string, + TTL?: string, + ): Promise { + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.jsonOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + }) + await this.jsonKeyValueInput.click() + await this.jsonKeyValueInput.fill(value, { + timeout: 0, + }) + if (TTL !== undefined) { + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + } + await this.addKeyButton.click() + } + + async addSetKey(keyName: string, TTL = ' ', members = ' '): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.setOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(members, { + timeout: 0, + noWaitAfter: false, + }) + await this.addKeyButton.click() + await this.toast.closeToast() + } + + async addZSetKey( + keyName: string, + scores = ' ', + TTL = ' ', + members = ' ', + ): Promise { + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.zsetOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + await this.setMemberInput.fill(members, { + timeout: 0, + noWaitAfter: false, + }) + await this.zsetMemberScoreInput.fill(scores, { + timeout: 0, + noWaitAfter: false, + }) + await this.addKeyButton.click() + } + + async addListKey( + keyName: string, + TTL = ' ', + element: string[] = [' '], + position: AddElementInList = AddElementInList.Tail, + ): Promise { + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.listOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + if (position === AddElementInList.Head) { + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await expect(this.removeFromHeadSelection).not.toBeVisible() + } + for (let i = 0; i < element.length; i += 1) { + await this.getListElementInput(i).click() + await this.getListElementInput(i).fill(element[i], { + timeout: 0, + noWaitAfter: false, + }) + if (element.length > 1 && i < element.length - 1) { + await this.addAdditionalElement.click() + } + } + await this.addKeyButton.click() + } + + async addHashKey( + keyName: string, + TTL = ' ', + field = ' ', + value = ' ', + fieldTtl = '', + ): Promise { + if (await this.toast.isCloseButtonVisible()) { + await this.toast.closeToast() + } + await this.waitForLocatorNotVisible(this.progressLine) + await this.waitForLocatorNotVisible(this.loader) + await this.plusAddKeyButton.click() + await this.keyTypeDropDown.click() + await this.hashOption.click() + await this.addKeyNameInput.click() + await this.addKeyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.keyTTLInput.click() + await this.keyTTLInput.fill(TTL, { timeout: 0, noWaitAfter: false }) + await this.hashFieldNameInput.fill(field, { + timeout: 0, + noWaitAfter: false, + }) + await this.hashFieldValueInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + if (fieldTtl !== '') { + await this.hashTtlFieldInput.fill(fieldTtl, { + timeout: 0, + noWaitAfter: false, + }) + } + await this.addKeyButton.click() + await this.toast.closeToast() + } + + async addStreamKey( + keyName: string, + field: string, + value: string, + TTL?: string, + ): Promise { + await this.commonAddNewKey(keyName, TTL) + await this.streamOption.click() + await expect(this.streamEntryId).toHaveValue('*', { timeout: 5000 }) + await this.streamField.fill(field, { timeout: 0, noWaitAfter: false }) + await this.streamValue.fill(value, { timeout: 0, noWaitAfter: false }) + await expect(this.addKeyButton).not.toBeDisabled() + await this.addKeyButton.click() + await this.toast.closeToast() + } + + async addEntryToStream( + field: string, + value: string, + entryId?: string, + ): Promise { + await this.addNewStreamEntry.click() + await this.streamField.fill(field, { timeout: 0, noWaitAfter: false }) + await this.streamValue.fill(value, { timeout: 0, noWaitAfter: false }) + if (entryId !== undefined) { + await this.streamEntryId.fill(entryId, { + timeout: 0, + noWaitAfter: false, + }) + } + await this.saveElementButton.click() + await expect(this.streamEntriesContainer).toContainText(field) + await expect(this.streamEntriesContainer).toContainText(value) + } + + async fulfillSeveralStreamFields( + fields: string[], + values: string[], + entryId?: string, + ): Promise { + for (let i = 0; i < fields.length; i += 1) { + await this.streamField + .nth(-1) + .fill(fields[i], { timeout: 0, noWaitAfter: false }) + await this.streamValue + .nth(-1) + .fill(values[i], { timeout: 0, noWaitAfter: false }) + if (i < fields.length - 1) { + await this.addAdditionalElement.click() + } + } + if (entryId !== undefined) { + await this.streamEntryId.fill(entryId, { + timeout: 0, + noWaitAfter: false, + }) + } + } + + async selectFilterGroupType(groupName: string): Promise { + await this.filterByKeyTypeDropDown.click() + await this.filterOptionType.locator(groupName).click() + } + + async setAllKeyType(): Promise { + await this.filterByKeyTypeDropDown.click() + await this.filterAllKeyType.click() + } + + async searchByKeyName(keyName: string): Promise { + await this.filterByPatterSearchInput.click() + await this.filterByPatterSearchInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.page.keyboard.press('Enter') + } + + getKeySelectorByName(keyName: string): Locator { + return this.page.locator(`[data-testid="key-${keyName}"]`) + } + + async isKeyIsDisplayedInTheList(keyName: string): Promise { + const keyNameInTheList = this.getKeySelectorByName(keyName) + await this.waitForLocatorNotVisible(this.loader) + return keyNameInTheList.isVisible() + } + + async deleteKey(): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.keyNameInTheList.click() + await this.deleteKeyButton.click() + await this.confirmDeleteKeyButton.click() + } + + async deleteKeyByName(keyName: string): Promise { + await this.searchByKeyName(keyName) + await this.keyNameInTheList.hover() + await this.keyNameInTheList.click() + await this.deleteKeyButton.click() + await this.confirmDeleteKeyButton.click() + } + + async deleteKeysByNames(keyNames: string[]): Promise { + for (const name of keyNames) { + await this.deleteKeyByName(name) + } + } + + async deleteKeyByNameFromList(keyName: string): Promise { + await this.searchByKeyName(keyName) + await this.keyNameInTheList.hover() + await this.page + .locator(`[data-testid="delete-key-btn-${keyName}"]`) + .click() + await this.submitDeleteKeyButton.click() + } + + async editKeyName(keyName: string): Promise { + await this.editKeyNameButton.click() + await this.keyNameInput.fill(keyName, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async editStringKeyValue(value: string): Promise { + await this.stringKeyValueInput.click() + await this.stringKeyValueInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async getStringKeyValue(): Promise { + return this.stringKeyValueInput.textContent() + } + + async getZsetKeyScore(): Promise { + return this.zsetScoresList.textContent() + } + + async addFieldToHash( + keyFieldValue: string, + keyValue: string, + fieldTtl = '', + ): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + await this.hashFieldInput.fill(keyFieldValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.hashValueInput.fill(keyValue, { + timeout: 0, + noWaitAfter: false, + }) + if (fieldTtl !== '') { + await this.hashTtlFieldInput.fill(fieldTtl, { + timeout: 0, + noWaitAfter: false, + }) + } + await this.saveHashFieldButton.click() + } + + async editHashKeyValue(value: string): Promise { + await this.hashFieldValue.hover() + await this.editHashButton.click() + await this.hashFieldValueEditor.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async editHashFieldTtlValue( + fieldName: string, + fieldTtl: string, + ): Promise { + await this.getHashTtlFieldInput(fieldName).hover() + await this.editHashFieldTtlButton.click() + await this.inlineItemEditor.fill(fieldTtl, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async getHashKeyValue(): Promise { + return this.hashFieldValue.textContent() + } + + async editListKeyValue(value: string): Promise { + await this.listElementsList.hover() + await this.editListButton.click() + await this.listKeyElementEditorInput.fill(value, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async getListKeyValue(): Promise { + return this.listElementsList.textContent() + } + + async getJsonKeyValue(): Promise { + return this.jsonKeyValue.textContent() + } + + async searchByTheValueInKeyDetails(value: string): Promise { + await this.searchButtonInKeyDetails.click() + await this.searchInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + async secondarySearchByTheValueInKeyDetails(value: string): Promise { + await this.searchInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + async searchByTheValueInSetKey(value: string): Promise { + await this.searchInput.click() + await this.searchInput.fill(value, { timeout: 0, noWaitAfter: false }) + await this.page.keyboard.press('Enter') + } + + async addMemberToSet(keyMember: string): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + await this.setMemberInput.fill(keyMember, { + timeout: 0, + noWaitAfter: false, + }) + await this.saveMemberButton.click() + } + + async addMemberToZSet(keyMember: string, score: string): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + await this.setMemberInput.fill(keyMember, { + timeout: 0, + noWaitAfter: false, + }) + await this.zsetMemberScoreInput.fill(score, { + timeout: 0, + noWaitAfter: false, + }) + await this.saveMemberButton.click() + } + + async openKeyDetails(keyName: string): Promise { + await this.searchByKeyName(keyName) + await this.keyNameInTheList.click() + } + + async openKeyDetailsByKeyName(keyName: string): Promise { + const keyNameInTheList = this.page.locator( + `[data-testid="key-${keyName}"]`, + ) + await keyNameInTheList.click() + } + + async addElementToList( + element: string[], + position: AddElementInList = AddElementInList.Tail, + ): Promise { + if (await this.toast.toastCloseButton.isVisible()) { + await this.toast.toastCloseButton.click() + } + await this.addKeyValueItemsButton.click() + if (position === AddElementInList.Head) { + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await expect(this.removeFromHeadSelection).not.toBeVisible() + } + for (let i = 0; i < element.length; i += 1) { + await this.getListElementInput(i).click() + await this.getListElementInput(i).fill(element[i], { + timeout: 0, + noWaitAfter: false, + }) + if (element.length > 1 && i < element.length - 1) { + await this.addAdditionalElement.click() + } + } + await this.addKeyButton.click() + } + + async removeListElementFromHeadOld(): Promise { + await this.removeElementFromListIconButton.click() + await expect( + await this.countInput.getAttribute('disabled'), + ).toBeTruthy() + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async removeListElementFromTail(count: string): Promise { + await this.removeElementFromListIconButton.click() + await this.countInput.fill(count, { timeout: 0, noWaitAfter: false }) + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async removeListElementFromHead(count: string): Promise { + await this.removeElementFromListIconButton.click() + await this.countInput.fill(count, { timeout: 0, noWaitAfter: false }) + await this.removeElementFromListSelect.click() + await this.removeFromHeadSelection.click() + await this.removeElementFromListButton.click() + await this.confirmRemoveListElementButton.click() + } + + async addJsonKeyOnTheSameLevel( + jsonKey: string, + jsonKeyValue: string, + ): Promise { + await this.addJsonObjectButton.click() + await this.jsonKeyInput.fill(jsonKey, { + timeout: 0, + noWaitAfter: false, + }) + await this.jsonValueInput.fill(jsonKeyValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async addJsonKeyInsideStructure( + jsonKey: string, + jsonKeyValue: string, + ): Promise { + await this.expandJsonObject.click() + await this.addJsonFieldButton.click() + await this.jsonKeyInput.fill(jsonKey, { + timeout: 0, + noWaitAfter: false, + }) + await this.jsonValueInput.fill(jsonKeyValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async addJsonValueInsideStructure(jsonKeyValue: string): Promise { + await this.expandJsonObject.click() + await this.addJsonFieldButton.click() + await this.jsonValueInput.fill(jsonKeyValue, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyButton.click() + } + + async addJsonStructure(jsonStructure: string): Promise { + if (await this.expandJsonObject.isVisible()) { + await this.expandJsonObject.click() + } + await this.editJsonObjectButton.click() + await this.jsonValueInput.fill(jsonStructure, { + timeout: 0, + noWaitAfter: false, + }) + await this.applyEditButton.click() + } + + async deleteStreamEntry(): Promise { + await this.removeEntryButton.click() + await this.confirmRemoveEntryButton.click() + } + + async getKeyLength(): Promise { + const rawValue = await this.keyLengthDetails.textContent() + const parts = (rawValue ?? '').split(' ') + return parts[parts.length - 1] + } + + async createConsumerGroup(groupName: string, id?: string): Promise { + await this.addKeyValueItemsButton.click() + await this.groupNameInput.fill(groupName, { + timeout: 0, + noWaitAfter: false, + }) + if (id !== undefined) { + await this.consumerIdInput.fill(id, { + timeout: 0, + noWaitAfter: false, + }) + } + await this.saveGroupsButton.click() + } + + async openStreamPendingsView(keyName: string): Promise { + await this.openKeyDetails(keyName) + await this.streamTabGroups.click() + await this.consumerGroup.click() + await this.streamConsumerName.click() + } + + async selectFormatter(formatter: string): Promise { + const option = this.page.locator( + `[data-test-subj="format-option-${formatter}"]`, + ) + await this.formatSwitcher.click() + await option.click() + } + + async verifyScannningMore(): Promise { + for (let i = 10; i < 100; i += 10) { + const rememberedScanResults = Number( + (await this.keysNumberOfResults.textContent())?.replace( + /\s/g, + '', + ), + ) + await expect(this.progressKeyList).not.toBeVisible({ + timeout: 30000, + }) + const scannedValueText = await this.scannedValue.textContent() + const regExp = new RegExp(`${i} ...`) + await expect(scannedValueText).toMatch(regExp) + await this.scanMoreButton.click() + const scannedResults = Number( + (await this.keysNumberOfResults.textContent())?.replace( + /\s/g, + '', + ), + ) + await expect(scannedResults).toBeGreaterThan(rememberedScanResults) + } + } + + async selectIndexByName(index: string): Promise { + const option = this.page.locator( + `[data-test-subj="mode-option-type-${index}"]`, + ) + await this.selectIndexDdn.click() + await option.click() + } + + async verifyNoKeysInDatabase(): Promise { + await expect(this.keyListMessage).toBeVisible() + await expect(this.keysSummary).not.toBeVisible() + } + + async clearFilter(): Promise { + await this.clearFilterButton.click() + } + + async clickGuideLinksByName(guide: string): Promise { + const linkGuide = this.page.locator(guide) + await linkGuide.click() + } +} diff --git a/tests/playwright/pageObjects/components/common/toast.ts b/tests/playwright/pageObjects/components/common/toast.ts new file mode 100644 index 0000000000..d90e5731a4 --- /dev/null +++ b/tests/playwright/pageObjects/components/common/toast.ts @@ -0,0 +1,38 @@ +import { Locator, Page } from '@playwright/test' +import { BasePage } from '../../base-page' +import { ToastSelectors } from '../../../selectors' + +export class Toast extends BasePage { + public readonly toastHeader: Locator + + public readonly toastBody: Locator + + public readonly toastSuccess: Locator + + public readonly toastError: Locator + + public readonly toastCloseButton: Locator + + public readonly toastSubmitBtn: Locator + + public readonly toastCancelBtn: Locator + + constructor(page: Page) { + super(page) + this.toastHeader = page.locator(ToastSelectors.toastHeader) + this.toastBody = page.locator(ToastSelectors.toastBody) + this.toastSuccess = page.locator(ToastSelectors.toastSuccess) + this.toastError = page.locator(ToastSelectors.toastError) + this.toastCloseButton = page.locator(ToastSelectors.toastCloseButton) + this.toastSubmitBtn = page.getByTestId(ToastSelectors.toastSubmitBtn) + this.toastCancelBtn = page.getByTestId(ToastSelectors.toastCancelBtn) + } + + async isCloseButtonVisible(): Promise { + return this.isVisible(ToastSelectors.toastCloseButton) + } + + async closeToast(): Promise { + await this.toastCloseButton.click() + } +} diff --git a/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts new file mode 100644 index 0000000000..8112215bdf --- /dev/null +++ b/tests/playwright/pageObjects/components/redis-cloud-sign-in-panel.ts @@ -0,0 +1,29 @@ +import { Locator, Page } from '@playwright/test' +import { BasePage } from '../base-page' + +export class RedisCloudSigninPanel extends BasePage { + readonly ssoOauthButton: Locator + + readonly ssoEmailInput: Locator + + readonly submitBtn: Locator + + readonly oauthAgreement: Locator + + readonly googleOauth: Locator + + readonly githubOauth: Locator + + readonly ssoOauth: Locator + + constructor(page: Page) { + super(page) + this.ssoOauthButton = page.getByTestId('sso-oauth') + this.ssoEmailInput = page.getByTestId('sso-email') + this.submitBtn = page.getByTestId('btn-submit') + this.oauthAgreement = page.locator('[for="ouath-agreement"]') + this.googleOauth = page.getByTestId('google-oauth') + this.githubOauth = page.getByTestId('github-oauth') + this.ssoOauth = page.getByTestId('sso-oauth') + } +} diff --git a/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts new file mode 100755 index 0000000000..542f3c704c --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/add-rdi-instance-dialog.ts @@ -0,0 +1,54 @@ +import { Page, Locator } from '@playwright/test' +import { BasePage } from '../base-page' + +export class AddRdiInstanceDialog extends BasePage { + // INPUTS + readonly rdiAliasInput: Locator + + readonly urlInput: Locator + + readonly usernameInput: Locator + + readonly passwordInput: Locator + + // BUTTONS + readonly addInstanceButton: Locator + + readonly cancelInstanceBtn: Locator + + readonly connectToRdiForm: Locator + + // ICONS + readonly urlInputInfoIcon: Locator + + readonly usernameInputInfoIcon: Locator + + readonly passwordInputInfoIcon: Locator + + constructor(page: Page) { + super(page) + this.page = page + this.rdiAliasInput = page.getByTestId('connection-form-name-input') + this.urlInput = page.getByTestId('connection-form-url-input') + this.usernameInput = page.getByTestId('connection-form-username-input') + this.passwordInput = page.getByTestId('connection-form-password-input') + + this.addInstanceButton = page.getByTestId('connection-form-add-button') + this.cancelInstanceBtn = page.getByTestId( + 'connection-form-cancel-button', + ) + this.connectToRdiForm = page.getByTestId('connection-form') + + // Assuming that the two-level parent traversal is needed. + // Using an XPath locator to navigate two ancestors then find an SVG element. + this.urlInputInfoIcon = page + .getByTestId('connection-form-url-input') + .locator('xpath=ancestor::div[2]//svg') + this.usernameInputInfoIcon = page + .getByTestId('connection-form-username-input') + .locator('xpath=ancestor::div[2]//svg') + this.passwordInputInfoIcon = page + .getByTestId('connection-form-password-input') + .locator('xpath=ancestor::div[2]//svg') + } +} diff --git a/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts new file mode 100644 index 0000000000..95256a2cdf --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/add-redis-database-dialog.ts @@ -0,0 +1,331 @@ +import { expect, Locator, Page } from '@playwright/test' +import { TlsCertificates } from '../../helpers/constants' +import { RedisCloudSigninPanel } from '../components/redis-cloud-sign-in-panel' +import { + SentinelParameters, + AddNewDatabaseParameters, + SSHParameters, +} from '../../types' +import { BasePage } from '../base-page' + +export class AddRedisDatabaseDialog extends BasePage { + readonly redisCloudSigninPanel: RedisCloudSigninPanel + + // BUTTONS + readonly addDatabaseButton: Locator + + readonly addRedisDatabaseButton: Locator + + readonly customSettingsButton: Locator + + readonly addAutoDiscoverDatabase: Locator + + readonly addCloudDatabaseButton: Locator + + readonly redisSoftwareButton: Locator + + readonly redisSentinelButton: Locator + + // TEXT INPUTS + readonly hostInput: Locator + + readonly portInput: Locator + + readonly databaseAliasInput: Locator + + readonly passwordInput: Locator + + readonly usernameInput: Locator + + readonly accessKeyInput: Locator + + readonly secretKeyInput: Locator + + readonly databaseIndexInput: Locator + + // TABS + readonly generalTab: Locator + + readonly securityTab: Locator + + readonly decompressionTab: Locator + + // DROPDOWNS + readonly caCertField: Locator + + readonly clientCertField: Locator + + readonly selectCompressor: Locator + + // CHECKBOXES + readonly databaseIndexCheckbox: Locator + + readonly useSSHCheckbox: Locator + + // RADIO BUTTONS + readonly sshPasswordRadioBtn: Locator + + readonly sshPrivateKeyRadioBtn: Locator + + // LABELS + readonly dataCompressorLabel: Locator + + // SSH TEXT INPUTS + readonly sshHostInput: Locator + + readonly sshPortInput: Locator + + readonly sshUsernameInput: Locator + + readonly sshPasswordInput: Locator + + readonly sshPrivateKeyInput: Locator + + readonly sshPassphraseInput: Locator + + // OTHER + readonly timeoutInput: Locator + + // For certificate removal + aiChatMessage: Locator + + aiCloseMessage: Locator + + trashIconMsk(certificate: TlsCertificates): string { + return `[data-testid^="delete-${certificate}-cert"]` + } + + getDeleteCertificate(certificate: TlsCertificates): Locator { + return this.page.locator(this.trashIconMsk(certificate)) + } + + constructor(page: Page) { + super(page) + this.page = page + this.redisCloudSigninPanel = new RedisCloudSigninPanel(page) + + // BUTTONS + this.addDatabaseButton = page.locator( + '[data-testid^="add-redis-database"]', + ) + this.addRedisDatabaseButton = page.getByTestId('btn-submit') + this.customSettingsButton = page.getByTestId('btn-connection-settings') + this.addAutoDiscoverDatabase = page.getByTestId( + 'add-database_tab_software', + ) + this.addCloudDatabaseButton = page.getByTestId('create-free-db-btn') + this.redisSoftwareButton = page.getByTestId('option-btn-software') + this.redisSentinelButton = page.getByTestId('option-btn-sentinel') + + // TEXT INPUTS + this.hostInput = page.getByTestId('host') + this.portInput = page.getByTestId('port') + this.databaseAliasInput = page.getByTestId('name') + this.passwordInput = page.getByTestId('password') + this.usernameInput = page.getByTestId('username') + this.accessKeyInput = page.getByTestId('access-key') + this.secretKeyInput = page.getByTestId('secret-key') + this.databaseIndexInput = page.getByTestId('db') + + // TABS + this.generalTab = page.getByTestId('manual-form-tab-general') + this.securityTab = page.getByTestId('manual-form-tab-security') + this.decompressionTab = page.getByTestId( + 'manual-form-tab-decompression', + ) + + // DROPDOWNS + this.caCertField = page.getByTestId('select-ca-cert') + this.clientCertField = page.getByTestId('select-cert') + this.selectCompressor = page.getByTestId('select-compressor') + + // CHECKBOXES + this.databaseIndexCheckbox = page.locator( + '[data-testid="showDb"] ~ div', + ) + this.useSSHCheckbox = page.locator('[data-testid="use-ssh"] ~ div') + + // RADIO BUTTONS + this.sshPasswordRadioBtn = page.locator('#password ~ div') + this.sshPrivateKeyRadioBtn = page.locator('#privateKey ~ div') + + // LABELS + this.dataCompressorLabel = page.getByTestId( + '[data-testid="showCompressor"] ~ label', + ) + this.aiChatMessage = page.getByTestId('ai-chat-message-btn') + this.aiCloseMessage = page.locator( + '[aria-label="Closes this modal window"]', + ) + + // SSH TEXT INPUTS + this.sshHostInput = page.getByTestId('sshHost') + this.sshPortInput = page.getByTestId('sshPort') + this.sshUsernameInput = page.getByTestId('sshUsername') + this.sshPasswordInput = page.getByTestId('sshPassword') + this.sshPrivateKeyInput = page.getByTestId('sshPrivateKey') + this.sshPassphraseInput = page.getByTestId('sshPassphrase') + + // OTHER + this.timeoutInput = page.getByTestId('timeout') + } + + async addRedisDataBase( + parameters: AddNewDatabaseParameters, + ): Promise { + await expect(this.addDatabaseButton).toBeVisible({ timeout: 10000 }) + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + await this.hostInput.fill(parameters.host) + await this.portInput.fill(parameters.port) + await this.databaseAliasInput.fill(parameters.databaseName || '') + if (parameters.databaseUsername) { + await this.usernameInput.fill(parameters.databaseUsername) + } + if (parameters.databasePassword) { + await this.passwordInput.fill(parameters.databasePassword) + } + } + + async addLogicalRedisDatabase( + parameters: AddNewDatabaseParameters, + index: string, + ): Promise { + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + await this.hostInput.fill(parameters.host) + await this.portInput.fill(parameters.port) + await this.databaseAliasInput.fill(parameters.databaseName || '') + if (parameters.databaseUsername) { + await this.usernameInput.fill(parameters.databaseUsername) + } + if (parameters.databasePassword) { + await this.passwordInput.fill(parameters.databasePassword) + } + await this.databaseIndexCheckbox.click() + await this.databaseIndexInput.fill(index) + await this.addRedisDatabaseButton.click() + } + + async addStandaloneSSHDatabase( + databaseParameters: AddNewDatabaseParameters, + sshParameters: SSHParameters, + ): Promise { + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + await this.hostInput.fill(databaseParameters.host) + await this.portInput.fill(databaseParameters.port) + await this.databaseAliasInput.fill( + databaseParameters.databaseName || '', + ) + if (databaseParameters.databaseUsername) { + await this.usernameInput.fill(databaseParameters.databaseUsername) + } + if (databaseParameters.databasePassword) { + await this.passwordInput.fill(databaseParameters.databasePassword) + } + // Navigate to security tab and select SSH Tunnel checkbox + await this.securityTab.click() + await this.useSSHCheckbox.click() + // Fill SSH fields + await this.sshHostInput.fill(sshParameters.sshHost) + await this.sshPortInput.fill(sshParameters.sshPort) + await this.sshUsernameInput.fill(sshParameters.sshUsername) + if (sshParameters.sshPassword) { + await this.sshPasswordInput.fill(sshParameters.sshPassword) + } + if (sshParameters.sshPrivateKey) { + await this.sshPrivateKeyRadioBtn.click() + await this.sshPrivateKeyInput.fill(sshParameters.sshPrivateKey) + } + if (sshParameters.sshPassphrase) { + await this.sshPrivateKeyRadioBtn.click() + await this.sshPassphraseInput.fill(sshParameters.sshPassphrase) + } + await this.addRedisDatabaseButton.click() + } + + async discoverSentinelDatabases( + parameters: SentinelParameters, + ): Promise { + await this.addDatabaseButton.click() + await this.redisSentinelButton.click() + if (parameters.sentinelHost) { + await this.hostInput.fill(parameters.sentinelHost) + } + if (parameters.sentinelPort) { + await this.portInput.fill(parameters.sentinelPort) + } + if (parameters.sentinelPassword) { + await this.passwordInput.fill(parameters.sentinelPassword) + } + } + + async addAutodiscoverREClusterDatabase( + parameters: AddNewDatabaseParameters, + ): Promise { + await this.addDatabaseButton.click() + await this.redisSoftwareButton.click() + await this.hostInput.fill(parameters.host) + await this.portInput.fill(parameters.port) + await this.usernameInput.fill(parameters.databaseUsername || '') + await this.passwordInput.fill(parameters.databasePassword || '') + } + + async addAutodiscoverRECloudDatabase( + cloudAPIAccessKey: string, + cloudAPISecretKey: string, + ): Promise { + await this.addDatabaseButton.click() + await this.addCloudDatabaseButton.click() + await this.accessKeyInput.fill(cloudAPIAccessKey) + await this.secretKeyInput.fill(cloudAPISecretKey) + } + + async addOssClusterDatabase( + parameters: AddNewDatabaseParameters, + ): Promise { + await this.addDatabaseButton.click() + await this.customSettingsButton.click() + if (parameters.ossClusterHost) { + await this.hostInput.fill(parameters.ossClusterHost) + } + if (parameters.ossClusterPort) { + await this.portInput.fill(parameters.ossClusterPort) + } + if (parameters.ossClusterDatabaseName) { + await this.databaseAliasInput.fill( + parameters.ossClusterDatabaseName, + ) + } + } + + async setCompressorValue(compressor: string): Promise { + if (!(await this.selectCompressor.isVisible())) { + await this.dataCompressorLabel.click() + } + await this.selectCompressor.click() + await this.page.locator(`[id="${compressor}"]`).click() + } + + async removeCertificateButton( + certificate: TlsCertificates, + name: string, + ): Promise { + await this.securityTab.click() + const row = this.page + .locator('button') + .locator('div') + .filter({ hasText: name }) + const removeButtonFooter = this.page.locator( + '[class^="_popoverFooter"]', + ) + if (certificate === TlsCertificates.CA) { + await this.caCertField.click() + } else { + await this.clientCertField.click() + } + await row.locator(this.trashIconMsk(certificate)).click() + await removeButtonFooter.locator(this.trashIconMsk(certificate)).click() + } +} diff --git a/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts new file mode 100644 index 0000000000..1a21adb0b8 --- /dev/null +++ b/tests/playwright/pageObjects/dialogs/user-agreement-dialog.ts @@ -0,0 +1,65 @@ +import { expect, Locator, Page } from '@playwright/test' + +import { BasePage } from '../base-page' +import { UserAgreementSelectors } from '../../selectors' + +export class UserAgreementDialog extends BasePage { + readonly userAgreementsPopup: Locator + + readonly submitButton: Locator + + readonly switchOptionEula: Locator + + readonly switchOptionEncryption: Locator + + readonly pluginSectionWithText: Locator + + readonly recommendedSwitcher: Locator + + constructor(page: Page) { + super(page) + this.userAgreementsPopup = page.getByTestId( + UserAgreementSelectors.userAgreementsPopup, + ) + this.submitButton = page.getByTestId( + UserAgreementSelectors.submitButton, + ) + this.switchOptionEula = page.getByTestId( + UserAgreementSelectors.switchOptionEula, + ) + this.switchOptionEncryption = page.getByTestId( + UserAgreementSelectors.switchOptionEncryption, + ) + this.pluginSectionWithText = page.getByTestId( + UserAgreementSelectors.pluginSectionWithText, + ) + this.recommendedSwitcher = page.getByTestId( + UserAgreementSelectors.recommendedSwitcher, + ) + } + + async acceptLicenseTerms(): Promise { + try { + await this.switchOptionEula.waitFor({ timeout: 3000 }) // because the state isn't clear + } catch (error) { + // Ignore error if the dialog is not visible + } + + if (await this.switchOptionEula.isVisible()) { + await this.recommendedSwitcher.click() + await this.switchOptionEula.click() + await this.submitButton.click() + await expect(this.userAgreementsPopup).not.toBeVisible({ + timeout: 2000, + }) + } + } + + async getRecommendedSwitcherValue(): Promise { + return this.recommendedSwitcher.getAttribute('aria-checked') + } + + async isUserAgreementDialogVisible(): Promise { + return this.userAgreementsPopup.isVisible() + } +} diff --git a/tests/playwright/pageObjects/index.ts b/tests/playwright/pageObjects/index.ts new file mode 100644 index 0000000000..85b64cac53 --- /dev/null +++ b/tests/playwright/pageObjects/index.ts @@ -0,0 +1,10 @@ +export * from './components/common/toast' +export * from './components/redis-cloud-sign-in-panel' +export * from './dialogs/add-rdi-instance-dialog' +export * from './dialogs/add-redis-database-dialog' +export * from './dialogs/user-agreement-dialog' +export * from './base-overview-page' +export * from './browser-page' +export * from './rdi-instances-list-page' +export * from './auto-discover-redis-enterprise-databases' +export * from './base-page' diff --git a/tests/playwright/pageObjects/rdi-instances-list-page.ts b/tests/playwright/pageObjects/rdi-instances-list-page.ts new file mode 100755 index 0000000000..efde53a000 --- /dev/null +++ b/tests/playwright/pageObjects/rdi-instances-list-page.ts @@ -0,0 +1,184 @@ +/* eslint-disable no-await-in-loop */ +import { Page, Locator, expect } from '@playwright/test' +import { BaseOverviewPage } from './base-overview-page' +import { AddRdiInstanceDialog } from './dialogs/add-rdi-instance-dialog' +import { RdiInstance } from '../types' + +export class RdiInstancesListPage extends BaseOverviewPage { + readonly AddRdiInstanceDialog: AddRdiInstanceDialog + + readonly addRdiInstanceButton: Locator + + readonly addRdiFromEmptyListBtn: Locator + + readonly quickstartBtn: Locator + + readonly rdiInstanceRow: Locator + + readonly emptyRdiList: Locator + + readonly rdiNameList: Locator + + readonly searchInput: Locator + + readonly sortBy: Locator + + readonly cssRdiAlias: string + + readonly cssUrl: string + + readonly cssRdiVersion: string + + readonly cssLastConnection: string + + // Assuming these selectors exist—update their locators as needed. + readonly deleteRowButton: Locator + + readonly confirmDeleteButton: Locator + + readonly editRowButton: Locator + + readonly Toast: { toastCloseButton: Locator } + + constructor(page: Page) { + super(page) + this.page = page + + this.AddRdiInstanceDialog = new AddRdiInstanceDialog(page) + + // Use getByTestId for selectors with data-testid + this.addRdiInstanceButton = page.getByTestId('rdi-instance') + this.addRdiFromEmptyListBtn = page.getByTestId('empty-rdi-instance-button') + this.quickstartBtn = page.getByTestId('empty-rdi-quickstart-button') + + this.rdiInstanceRow = page.locator('[class*=euiTableRow-isSelectable]') + this.emptyRdiList = page.getByTestId('empty-rdi-instance-list') + this.rdiNameList = page.locator('[class*=column_name] div') + + this.searchInput = page.getByTestId('search-rdi-instance-list') + + // Selector using data-test-subj remains as locator + this.sortBy = page.locator('[data-test-subj=tableHeaderSortButton] span') + + // CSS selectors (kept as string constants) + this.cssRdiAlias = '[data-test-subj=rdi-alias-column]' + this.cssUrl = '[data-testid=url]' + this.cssRdiVersion = '[data-test-subj=rdi-instance-version-column]' + this.cssLastConnection = '[data-test-subj=rdi-instance-last-connection-column]' + + // These selectors are assumed. Adjust the test IDs as per your application. + this.deleteRowButton = page.getByTestId('delete-row-button') + this.confirmDeleteButton = page.getByTestId('confirm-delete-button') + this.editRowButton = page.getByTestId('edit-row-button') + this.Toast = { + toastCloseButton: page.getByTestId('toast-close-button') + } + } + + /** + * Add Rdi instance. + * @param instanceValue Rdi instance data + */ + async addRdi(instanceValue: RdiInstance): Promise { + await this.addRdiInstanceButton.click() + await this.AddRdiInstanceDialog.rdiAliasInput.fill(instanceValue.alias) + await this.AddRdiInstanceDialog.urlInput.fill(instanceValue.url) + if (instanceValue.username) { + await this.AddRdiInstanceDialog.usernameInput.fill(instanceValue.username) + } + if (instanceValue.password) { + await this.AddRdiInstanceDialog.passwordInput.fill(instanceValue.password) + } + await this.AddRdiInstanceDialog.addInstanceButton.click() + // Wait for the dialog to close after adding the Rdi instance + await this.AddRdiInstanceDialog.connectToRdiForm.waitFor({ state: 'hidden' }) + } + + /** + * Get Rdi instance values by index. + * @param index Index of Rdi instance. + */ + async getRdiInstanceValuesByIndex(index: number): Promise { + const alias = await this.rdiInstanceRow.nth(index).locator(this.cssRdiAlias).innerText() + const currentLastConnection = await this.rdiInstanceRow.nth(0).locator(this.cssLastConnection).innerText() + const currentVersion = await this.rdiInstanceRow.nth(0).locator(this.cssRdiVersion).innerText() + const currentUrl = await this.rdiInstanceRow.nth(0).locator(this.cssUrl).innerText() + + const rdiInstance: RdiInstance = { + alias, + url: currentUrl, + version: currentVersion, + lastConnection: currentLastConnection, + } + + return rdiInstance + } + + /** + * Delete Rdi by name. + * @param dbName The name of the Rdi to be deleted. + */ + async deleteRdiByName(dbName: string): Promise { + const dbNames = this.rdiInstanceRow + const count = await dbNames.count() + for (let i = 0; i < count; i += 1) { + const text = await dbNames.nth(i).innerText() + if (text.includes(dbName)) { + await this.deleteRowButton.nth(i).click() + await this.confirmDeleteButton.click() + break + } + } + } + + /** + * Edit Rdi by name. + * @param dbName The name of the Rdi to be edited. + */ + async clickEditRdiByName(dbName: string): Promise { + const rdiNames = this.rdiInstanceRow + const count = await rdiNames.count() + for (let i = 0; i < count; i += 1) { + const text = await rdiNames.nth(i).innerText() + if (text.includes(dbName)) { + await this.editRowButton.nth(i).click() + break + } + } + } + + /** + * Click Rdi by name. + * @param rdiName The name of the Rdi. + */ + async clickRdiByName(rdiName: string): Promise { + if (await this.Toast.toastCloseButton.isVisible()) { + await this.Toast.toastCloseButton.click() + } + // Use getByText with exact match for the Rdi name + const rdi = this.rdiNameList.getByText(rdiName.trim(), { exact: true }) + await expect(rdi).toBeVisible({ timeout: 3000 }) + await rdi.click() + } + + /** + * Sort Rdi list by column. + * @param columnName The name of the column. + */ + async sortByColumn(columnName: string): Promise { + await this.sortBy.filter({ hasText: columnName }).click() + } + + /** + * Get all Rdi aliases. + */ + async getAllRdiNames(): Promise { + const rdis: string[] = [] + const count = await this.rdiInstanceRow.count() + for (let i = 0; i < count; i += 1) { + const name = await this.rdiInstanceRow.nth(i).locator(this.cssRdiAlias).innerText() + rdis.push(name) + } + return rdis + } +} diff --git a/tests/playwright/playwright.config.ts b/tests/playwright/playwright.config.ts new file mode 100644 index 0000000000..cbf77fa969 --- /dev/null +++ b/tests/playwright/playwright.config.ts @@ -0,0 +1,127 @@ +import { defineConfig, devices } from '@playwright/test' +import { Status } from 'allure-js-commons' +import dotenv from 'dotenv' +import * as os from 'os' + +dotenv.config({ + path: process.env.envPath ?? 'env/.local-web.env', + override: true, +}) + +export type TestOptions = { + apiUrl: string +} + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 600 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + // workers: process.env.CI ? 1 : undefined, + workers: 1, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [ + ['line'], + ['html'], + [ + 'allure-playwright', + { + resultsDir: 'allure-results', + detail: true, + suiteTitle: true, + links: { + issue: { + nameTemplate: 'Issue #%s', + urlTemplate: 'https://issues.example.com/%s', + }, + tms: { + nameTemplate: 'TMS #%s', + urlTemplate: 'https://tms.example.com/%s', + }, + jira: { + urlTemplate: (v: any) => + `https://jira.example.com/browse/${v}`, + }, + }, + categories: [ + { + name: 'foo', + messageRegex: 'bar', + traceRegex: 'baz', + matchedStatuses: [Status.FAILED, Status.BROKEN], + }, + ], + environmentInfo: { + os_platform: os.platform(), + os_release: os.release(), + os_version: os.version(), + node_version: process.version, + }, + }, + ], + ], + + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + testIdAttribute: 'data-testid', + headless: true, + deviceScaleFactor: undefined, + viewport: { width: 1920, height: 1080 }, + video: { + mode: 'on', + size: { width: 1920, height: 1080 }, + }, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'Chromium', + testMatch: ['**.spec.ts'], + use: { + ...devices['Desktop Chrome'], + baseURL: process.env.COMMON_URL, + // headless: false, + launchOptions: { + args: [ + '--no-sandbox', + '--start-maximized', + '--disable-dev-shm-usage', + '--ignore-certificate-errors', + '--disable-search-engine-choice-screen', + // '--disable-blink-features=AutomationControlled', + // '--disable-component-extensions-with-background-pages', + ], + }, + }, + }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}) diff --git a/tests/playwright/selectors/index.ts b/tests/playwright/selectors/index.ts new file mode 100644 index 0000000000..a9d404ae29 --- /dev/null +++ b/tests/playwright/selectors/index.ts @@ -0,0 +1,2 @@ +export * from './toast-selectors' +export * from './user-agreement-selectors' diff --git a/tests/playwright/selectors/toast-selectors.ts b/tests/playwright/selectors/toast-selectors.ts new file mode 100644 index 0000000000..8d60edd874 --- /dev/null +++ b/tests/playwright/selectors/toast-selectors.ts @@ -0,0 +1,9 @@ +export const ToastSelectors = { + toastHeader: '[data-test-subj=euiToastHeader]', + toastBody: '[class*=euiToastBody]', + toastSuccess: '[class*=euiToast--success]', + toastError: '[class*=euiToast--danger]', + toastCloseButton: '[data-test-subj=toastCloseButton]', + toastSubmitBtn: 'submit-tooltip-btn', + toastCancelBtn: 'toast-cancel-btn', +} diff --git a/tests/playwright/selectors/user-agreement-selectors.ts b/tests/playwright/selectors/user-agreement-selectors.ts new file mode 100644 index 0000000000..f0f0105c8c --- /dev/null +++ b/tests/playwright/selectors/user-agreement-selectors.ts @@ -0,0 +1,8 @@ +export const UserAgreementSelectors = { + userAgreementsPopup: 'consents-settings-popup', + submitButton: 'btn-submit', + switchOptionEula: 'switch-option-eula', + switchOptionEncryption: 'switch-option-encryption', + pluginSectionWithText: 'plugin-section', + recommendedSwitcher: 'switch-option-recommended', +} diff --git a/tests/playwright/tests/basic-navigation.spec.ts b/tests/playwright/tests/basic-navigation.spec.ts new file mode 100644 index 0000000000..d5414cbba2 --- /dev/null +++ b/tests/playwright/tests/basic-navigation.spec.ts @@ -0,0 +1,11 @@ +import { test, expect } from '../fixtures/test' + +test.describe('Basic Navigation and Element Visibility', () => { + test('should navigate to the homepage and verify title', async ({ + page, + }) => { + const title = await page.title() + + expect(title).toBe('Redis databases') + }) +}) diff --git a/tests/playwright/tests/keys.spec.ts b/tests/playwright/tests/keys.spec.ts new file mode 100644 index 0000000000..b695b5bb73 --- /dev/null +++ b/tests/playwright/tests/keys.spec.ts @@ -0,0 +1,101 @@ +/* eslint-disable no-empty-pattern */ +import { faker } from '@faker-js/faker' + +import { BrowserPage } from '../pageObjects/browser-page' +import { test, expect } from '../fixtures/test' +import { ossStandaloneConfig } from '../helpers/conf' +import { + addStandaloneInstanceAndNavigateToIt, + navigateToStandaloneInstance, +} from '../helpers/utils' + +test.describe('Adding Database Keys', () => { + let browserPage: BrowserPage + let keyName: string + let cleanupInstance: () => Promise + + test.beforeEach(async ({ page, api: { databaseService } }) => { + browserPage = new BrowserPage(page) + keyName = faker.string.alphanumeric(10) + cleanupInstance = await addStandaloneInstanceAndNavigateToIt( + page, + databaseService, + ) + + await navigateToStandaloneInstance(page) + }) + + test.afterEach(async ({ api: { keyService } }) => { + // Clean up: delete the key + await keyService.deleteKeyByNameApi( + keyName, + ossStandaloneConfig.databaseName, + ) + + await cleanupInstance() + }) + + test('Verify that user can add Hash Key', async ({}) => { + await browserPage.addHashKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add Set Key', async ({}) => { + await browserPage.addSetKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add List Key', async ({}) => { + await browserPage.addListKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add String Key', async ({}) => { + await browserPage.addStringKey(keyName) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add ZSet Key', async ({}) => { + const scores = '111' + await browserPage.addZSetKey(keyName, scores) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) + + test('Verify that user can add Stream key', async ({}) => { + const keyField = faker.string.alphanumeric(20) + const keyValue = faker.string.alphanumeric(20) + + await browserPage.addStreamKey(keyName, keyField, keyValue) + + // Check that new key is displayed in the list + await browserPage.searchByKeyName(keyName) + const isKeyIsDisplayedInTheList = + await browserPage.isKeyIsDisplayedInTheList(keyName) + await expect(isKeyIsDisplayedInTheList).toBe(true) + }) +}) diff --git a/tests/playwright/types/connections.ts b/tests/playwright/types/connections.ts new file mode 100644 index 0000000000..ef9cd87025 --- /dev/null +++ b/tests/playwright/types/connections.ts @@ -0,0 +1,21 @@ +export type SentinelParameters = { + sentinelHost: string + sentinelPort: string + masters?: { + alias?: string + db?: string + name?: string + password?: string + }[] + sentinelPassword?: string + name?: string[] +} + +export type SSHParameters = { + sshHost: string + sshPort: string + sshUsername: string + sshPassword?: string + sshPrivateKey?: string + sshPassphrase?: string +} diff --git a/tests/playwright/types/databases.ts b/tests/playwright/types/databases.ts new file mode 100644 index 0000000000..9facc5810c --- /dev/null +++ b/tests/playwright/types/databases.ts @@ -0,0 +1,84 @@ +export type DatabasesForImport = { + host?: string + port?: number | string + name?: string + result?: string + username?: string + auth?: string + cluster?: boolean | string + indName?: string + db?: number + ssh_port?: number + timeout_connect?: number + timeout_execute?: number + other_field?: string + ssl?: boolean + ssl_ca_cert_path?: string + ssl_local_cert_path?: string + ssl_private_key_path?: string +}[] + +export type AddNewDatabaseParameters = { + host: string + port: string + databaseName?: string + databaseUsername?: string + databasePassword?: string + // For OSS Cluster parameters, you might use these fields: + ossClusterHost?: string + ossClusterPort?: string + ossClusterDatabaseName?: string + caCert?: { + name?: string + certificate?: string + } + clientCert?: { + name?: string + certificate?: string + key?: string + } +} + +export type DatabaseInstance = { + host: string + port: number + provider?: string + id: string + connectionType?: string + lastConnection?: Date + password?: string + username?: string + name?: string + db?: number + tls?: boolean + ssh?: boolean + sshOptions?: { + host: string + port: number + username?: string + password?: string | true + privateKey?: string + passphrase?: string | true + } + tlsClientAuthRequired?: boolean + verifyServerCert?: boolean + caCert?: object + clientCert?: object + authUsername?: string + authPass?: string + isDeleting?: boolean + sentinelMaster?: object + modules: object[] + version: string + isRediStack?: boolean + visible?: boolean + loading?: boolean + isFreeDb?: boolean + tags?: { + id: string + key: string + value: string + createdAt: string + updatedAt: string + }[] +} diff --git a/tests/playwright/types/index.ts b/tests/playwright/types/index.ts new file mode 100644 index 0000000000..e7a45e7df0 --- /dev/null +++ b/tests/playwright/types/index.ts @@ -0,0 +1,10 @@ +export * from './databases' +export * from './connections' +export * from './keys' +export * from './rdi' + +declare global { + interface Window { + windowId?: string + } +} diff --git a/tests/playwright/types/keys.ts b/tests/playwright/types/keys.ts new file mode 100644 index 0000000000..687b8da67c --- /dev/null +++ b/tests/playwright/types/keys.ts @@ -0,0 +1,137 @@ +/** + * Add new keys parameters + * @param keyName The name of the key + * @param TTL The ttl of the key + * @param value The value of the key + * @param members The members of the key + * @param scores The scores of the key member + * @param field The field of the key + */ +export type AddNewKeyParameters = { + keyName: string, + value?: string, + TTL?: string, + members?: string, + scores?: string, + field?: string, + fields?: [{ + field?: string, + valuse?: string + }] +} + +/** + * Hash key parameters + * @param keyName The name of the key + * @param fields The Array with fields + * @param field The field of the field + * @param value The value of the field + + */ +export type HashKeyParameters = { + keyName: string, + fields: { + field: string, + value: string + }[] +} + +/** + * Stream key parameters + * @param keyName The name of the key + * @param entries The Array with entries + * @param id The id of entry + * @param fields The Array with fields + */ +export type StreamKeyParameters = { + keyName: string, + entries: { + id: string, + fields: { + name: string, + value: string + }[] + }[] +} + +/** + * Set key parameters + * @param keyName The name of the key + * @param members The Array with members + */ +export type SetKeyParameters = { + keyName: string, + members: string[] +} + +/** + * Sorted Set key parameters + * @param keyName The name of the key + * @param members The Array with members + * @param name The name of the member + * @param id The id of the member + */ +export type SortedSetKeyParameters = { + keyName: string, + members: { + name: string, + score: number + }[] +} + +/** + * List key parameters + * @param keyName The name of the key + * @param element The element in list + */ +export type ListKeyParameters = { + keyName: string, + element: string +} + +/** + * String key parameters + * @param keyName The name of the key + * @param value The value in the string + */ +export type StringKeyParameters = { + keyName: string, + value: string +} + +/** + * The key arguments for multiple keys/fields adding + * @param keysCount The number of keys to add + * @param fieldsCount The number of fields in key to add + * @param elementsCount The number of elements in key to add + * @param membersCount The number of members in key to add + * @param keyName The full key name + * @param keyNameStartWith The name of key should start with + * @param fieldStartWitht The name of field should start with + * @param fieldValueStartWith The name of field value should start with + * @param elementStartWith The name of element should start with + * @param memberStartWith The name of member should start with + */ + +export type AddKeyArguments = { + keysCount?: number, + fieldsCount?: number, + elementsCount?: number, + membersCount?: number, + keyName?: string, + keyNameStartWith?: string, + fieldStartWith?: string, + fieldValueStartWith?: string, + elementStartWith?: string, + memberStartWith?: string +} + +/** + * Keys Data parameters + * @param textType The type of the key + * @param keyName The name of the key + */ +export type KeyData = { + textType: string, + keyName: string +}[] diff --git a/tests/playwright/types/rdi.ts b/tests/playwright/types/rdi.ts new file mode 100644 index 0000000000..0c88a211f9 --- /dev/null +++ b/tests/playwright/types/rdi.ts @@ -0,0 +1,8 @@ +export type RdiInstance = { + alias: string + url: string + version?: string + lastConnection?: string + username?: string + password?: string +} diff --git a/tests/playwright/yarn.lock b/tests/playwright/yarn.lock new file mode 100644 index 0000000000..372c760a93 --- /dev/null +++ b/tests/playwright/yarn.lock @@ -0,0 +1,1978 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" + integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== + +"@babel/core@^7.23.9": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.0.tgz#55dad808d5bf3445a108eefc88ea3fdf034749a4" + integrity sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.27.3" + "@babel/helpers" "^7.27.6" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.0" + "@babel/types" "^7.28.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.0.tgz#9cc2f7bd6eb054d77dc66c2664148a0c5118acd2" + integrity sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg== + dependencies: + "@babel/parser" "^7.28.0" + "@babel/types" "^7.28.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.27.3": + version "7.27.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz#db0bbcfba5802f9ef7870705a7ef8788508ede02" + integrity sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.27.3" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" + integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.27.6": + version "7.27.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.6.tgz#6456fed15b2cb669d2d1fabe84b66b34991d812c" + integrity sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.27.6" + +"@babel/parser@^7.23.9", "@babel/parser@^7.27.2", "@babel/parser@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.0.tgz#979829fbab51a29e13901e5a80713dbcb840825e" + integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g== + dependencies: + "@babel/types" "^7.28.0" + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.27.3", "@babel/traverse@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.0.tgz#518aa113359b062042379e333db18380b537e34b" + integrity sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.0" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.0" + debug "^4.3.1" + +"@babel/types@^7.27.1", "@babel/types@^7.27.6", "@babel/types@^7.28.0": + version "7.28.1" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.1.tgz#2aaf3c10b31ba03a77ac84f52b3912a0edef4cf9" + integrity sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + +"@faker-js/faker@^9.6.0": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.6.0.tgz#64235d20330b142eef3d1d1638ba56c083b4bf1d" + integrity sha512-3vm4by+B5lvsFPSyep3ELWmZfE3kicDtmemVpuwl1yH7tqtnHdsA6hG8fbXedMVdkzgtvzWoRgjSB4Q+FHnZiw== + +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.12" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz#2234ce26c62889f03db3d7fea43c1932ab3e927b" + integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz#7358043433b2e5da569aa02cbc4c121da3af27d7" + integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz#a58d31eaadaf92c6695680b2e1d464a9b8fbf7fc" + integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@playwright/test@^1.52.0": + version "1.52.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.52.0.tgz#267ec595b43a8f4fa5e444ea503689629e91a5b8" + integrity sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g== + dependencies: + playwright "1.52.0" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@types/node@^22.15.29": + version "22.15.29" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.29.tgz#c75999124a8224a3f79dd8b6ccfb37d74098f678" + integrity sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ== + dependencies: + undici-types "~6.21.0" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +agent-base@6, agent-base@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +allure-commandline@^2.33.0: + version "2.33.0" + resolved "https://registry.yarnpkg.com/allure-commandline/-/allure-commandline-2.33.0.tgz#140560c615ea904ff34c061c4c4b6d43858b2b68" + integrity sha512-oGMW1Zaqd9SqYJHUqeET1AP363guQkswnCKD+6jSX9YCK8BbttSqZJy9PeSmJtU16uW3qGB6cvgrvJwKUWG5Ew== + +allure-js-commons@3.2.0, allure-js-commons@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/allure-js-commons/-/allure-js-commons-3.2.0.tgz#064503cec8735564599c90fff5a239c36d016d66" + integrity sha512-UXRo3D6/XEIMosro+OldWj8phJ65eSOYaAUlThOpl6nJJ0sGngMpJYog+Z9FmZDo1BZn4edwLs4aAUaTgkz4Cg== + dependencies: + md5 "^2.3.0" + +allure-playwright@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/allure-playwright/-/allure-playwright-3.2.0.tgz#4bbb276c6785ee7a90540e0b2c93d6ccf273caa7" + integrity sha512-E9YNqFBXrycMaOs4x5/Tsdl4xN8Ss0yw8XnwcVUzezR3cjlPb5gUdR81G/zQsi+I3mb+UQMS21yORHKTI9W2fw== + dependencies: + allure-js-commons "3.2.0" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== + dependencies: + default-require-extensions "^3.0.0" + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.9.0.tgz#25534e3b72b54540077d33046f77e3b8d7081901" + integrity sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browserslist@^4.24.0: + version "4.25.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.25.1.tgz#ba9e8e6f298a1d86f829c9b975e07948967bb111" + integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw== + dependencies: + caniuse-lite "^1.0.30001726" + electron-to-chromium "^1.5.173" + node-releases "^2.0.19" + update-browserslist-db "^1.1.3" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" + +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +caniuse-lite@^1.0.30001726: + version "1.0.30001727" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz#22e9706422ad37aa50556af8c10e40e2d93a8b85" + integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q== + +charenc@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA== + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypt@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + integrity sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow== + +debug@4, debug@^4.3.3: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" + integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== + dependencies: + ms "^2.1.3" + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +default-require-extensions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" + integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== + dependencies: + strip-bom "^4.0.0" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +dotenv-cli@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-8.0.0.tgz#cea1519f5a06c7372a1428fca4605fcf3d50e1cf" + integrity sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw== + dependencies: + cross-spawn "^7.0.6" + dotenv "^16.3.0" + dotenv-expand "^10.0.0" + minimist "^1.2.6" + +dotenv-expand@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37" + integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A== + +dotenv@^16.3.0, dotenv@^16.4.7: + version "16.4.7" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" + integrity sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ== + +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +electron-to-chromium@^1.5.173: + version "1.5.182" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.182.tgz#4ab73104f893938acb3ab9c28d7bec170c116b3e" + integrity sha512-Lv65Btwv9W4J9pyODI6EWpdnhfvrve/us5h1WspW8B2Fb0366REPtY3hX7ounk1CkV/TBjWCEvCBBbYbmV0qCA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +foreground-child@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + +form-data@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.3.tgz#608b1b3f3e28be0fccf5901fc85fb3641e5cf0ae" + integrity sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.12" + +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@^11.3.0: + version "11.3.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d" + integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + 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" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-cache-semantics@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +is-buffer@~1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-processinfo@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz#366d454cd0dcb7eb6e0e419378e60072c8626169" + integrity sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.3" + istanbul-lib-coverage "^3.2.0" + p-map "^3.0.0" + rimraf "^3.0.0" + uuid "^8.3.2" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +md5@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" + integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== + dependencies: + charenc "0.0.2" + crypt "0.0.2" + is-buffer "~1.1.6" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.4.1.tgz#d75e0091daac1b0ffd7e9d41629faff7d0c1f1b6" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@^2.0.0, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-2.0.0.tgz#13c22c0187fcfccce1461844136372a47ddc027e" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== + +negotiator@^0.6.2: + version "0.6.4" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" + integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== + +node-abi@^3.3.0: + version "3.74.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.74.0.tgz#5bfb4424264eaeb91432d2adb9da23c63a301ed0" + integrity sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w== + dependencies: + semver "^7.3.5" + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +node-color-log@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/node-color-log/-/node-color-log-12.0.1.tgz#47d982e3cb6aa90c2936ca38cd910ef82076c6f5" + integrity sha512-fhbWy00HXAVucPHoji9KNZRtXHcDKuMoVJ3QA+vaMEcAyK6psmJAf5TF9t2SmkybuHz0jre+jgUDyXcFmpgSNg== + +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-8.4.1.tgz#3d49308fc31f768180957d6b5746845fbd429937" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +nyc@^17.1.0: + version "17.1.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-17.1.0.tgz#b6349a401a62ffeb912bd38ea9a018839fdb6eb1" + integrity sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^3.3.0" + get-package-type "^0.1.0" + glob "^7.1.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^6.0.2" + istanbul-lib-processinfo "^2.0.2" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + spawn-wrap "^2.0.0" + test-exclude "^6.0.0" + yargs "^15.0.2" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +playwright-core@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.52.0.tgz#238f1f0c3edd4ebba0434ce3f4401900319a3dca" + integrity sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg== + +playwright@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.52.0.tgz#26cb9a63346651e1c54c8805acfd85683173d4bd" + integrity sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw== + dependencies: + playwright-core "1.52.0" + optionalDependencies: + fsevents "2.3.2" + +prebuild-install@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.3.tgz#d630abad2b147443f20a212917beae68b8092eec" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +process-on-spawn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" + integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== + dependencies: + fromentries "^1.2.0" + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.2.tgz#836f3edd6bc2ee599256c924ffe0d88573ddcbf8" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== + dependencies: + es6-error "^4.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5: + version "7.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +semver@^7.5.3, semver@^7.5.4: + version "7.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" + integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.2, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.4" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" + integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-wrap@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-2.0.0.tgz#103685b8b8f9b79771318827aa78650a610d457e" + integrity sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg== + dependencies: + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^3.0.0" + signal-exit "^3.0.2" + which "^2.0.1" + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.yarnpkg.com/sqlite3/-/sqlite3-5.1.7.tgz#59ca1053c1ab38647396586edad019b1551041b7" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tar-fs@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5" + integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +update-browserslist-db@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420" + integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" diff --git a/yarn.lock b/yarn.lock index fec175a88b..a37c932b01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1465,7 +1465,7 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" -"@istanbuljs/load-nyc-config@^1.0.0": +"@istanbuljs/load-nyc-config@^1.0.0", "@istanbuljs/load-nyc-config@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== @@ -3600,7 +3600,7 @@ acorn-import-attributes@^1.9.5: resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== -acorn-jsx@^5.3.1: +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -3615,10 +3615,10 @@ acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.0.5, acorn@^8.1.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.0.4, acorn@^8.0.5, acorn@^8.1.0, acorn@^8.15.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -6543,6 +6543,11 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + eslint@^7.5.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -6589,6 +6594,15 @@ eslint@^7.5.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +espree@^10.3.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -7155,10 +7169,10 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.3.10: - version "10.4.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" - integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== +glob@^10.3.10, glob@^10.4.1: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -8321,7 +8335,7 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" -istanbul-lib-instrument@^6.0.0: +istanbul-lib-instrument@^6.0.0, istanbul-lib-instrument@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== @@ -10686,7 +10700,7 @@ pickleparser@^0.2.1: resolved "https://registry.yarnpkg.com/pickleparser/-/pickleparser-0.2.1.tgz#7a03f1e9204e91ec9b8efbd3ba2f1eb5955b994d" integrity sha512-kMzY3uFYcR6OjOqr7nV2nkaXaBsUEOafu3zgPxeD6s/2ueMfVQH8lrymcDWBPGx0OkVxGMikxQit6jgByXjwBg== -picocolors@^1.0.0, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -13061,6 +13075,15 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +test-exclude@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2" + integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^10.4.1" + minimatch "^9.0.4" + text-diff@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/text-diff/-/text-diff-1.0.1.tgz#6c105905435e337857375c9d2f6ca63e453ff565" @@ -13893,6 +13916,18 @@ vite-plugin-electron@^0.28.6: resolved "https://registry.yarnpkg.com/vite-plugin-electron/-/vite-plugin-electron-0.28.6.tgz#98bcf291179dfbdfef407f881cbb1e1d58249c57" integrity sha512-DANntooA/XcUQuaOG7tQ0nnWh8iP5yKur2e9GDafjslOPAVZehRyrbi2UEI6rlIhN6hHwcqAjY+/Zz8+thAL5g== +vite-plugin-istanbul@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/vite-plugin-istanbul/-/vite-plugin-istanbul-7.1.0.tgz#f1abd43d38cb094a1dae9d383b28e66a11141f91" + integrity sha512-md0774bPYfSrMbAMMy3Xui2+xqmEVwulCGN2ImGm4E4s+0VfO7TjFyJ4ITFIFyEmBhWoMM0sOOX0Yg0I1SsncQ== + dependencies: + "@istanbuljs/load-nyc-config" "^1.1.0" + espree "^10.3.0" + istanbul-lib-instrument "^6.0.3" + picocolors "^1.1.1" + source-map "^0.7.4" + test-exclude "^7.0.1" + vite-plugin-react-click-to-component@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/vite-plugin-react-click-to-component/-/vite-plugin-react-click-to-component-3.0.0.tgz#bd22d0210ca245bf00b513ad4f6f28065cc98880"