From 8217a6140a87eecdff32e9a9d69f77757f6d05f6 Mon Sep 17 00:00:00 2001 From: shashwata Halder Date: Wed, 8 Jan 2025 15:05:27 +0600 Subject: [PATCH 1/7] Add: playwright sharding for parallel test execution (#2504) * Add: product edit nonce to for direct navigation * Add: playwright sharding for parallel job * Update packages * Fix: flaky tests --- .github/workflows/e2e_api_tests.yml | 310 ++++++-- tests/pw/e2e.config.ts | 6 +- tests/pw/package-lock.json | 829 ++++++++++++++------- tests/pw/package.json | 16 +- tests/pw/pages/menuManagerPage.ts | 6 +- tests/pw/pages/productsPage.ts | 122 +-- tests/pw/pages/vendorPage.ts | 3 +- tests/pw/tests/api/calculation.spec.ts | 10 +- tests/pw/tests/e2e/_auth.setup.ts | 5 + tests/pw/tests/e2e/payments.spec.ts | 2 +- tests/pw/tests/e2e/productsDetails.spec.ts | 227 +++--- tests/pw/tests/e2e/shipstation.spec.ts | 1 - tests/pw/tests/e2e/storeAppearance.spec.ts | 1 + tests/pw/tests/e2e/storelisting.spec.ts | 2 +- tests/pw/types/environment.d.ts | 2 + tests/pw/utils/mergeCoverageSummary.ts | 107 +++ tests/pw/utils/mergeSummaryReport.ts | 131 ++++ tests/pw/utils/summaryReporter.ts | 8 +- tests/pw/utils/testData.ts | 1 + 19 files changed, 1281 insertions(+), 508 deletions(-) create mode 100644 tests/pw/utils/mergeCoverageSummary.ts create mode 100644 tests/pw/utils/mergeSummaryReport.ts diff --git a/.github/workflows/e2e_api_tests.yml b/.github/workflows/e2e_api_tests.yml index e3ff8b30b6..6e429875a1 100644 --- a/.github/workflows/e2e_api_tests.yml +++ b/.github/workflows/e2e_api_tests.yml @@ -56,7 +56,6 @@ env: DB_PORT: 9998 DATABASE: tests-wordpress DB_PREFIX: wp - PR_NUMBER: ${{ github.event.number }} SHA: ${{ github.event.pull_request.head.sha }} SYSTEM_INFO: ./tests/pw/playwright/systemInfo.json @@ -66,25 +65,35 @@ env: E2E_COVERAGE: ./tests/pw/playwright-report/e2e/coverage-report/coverage.json jobs: - tests: - name: e2e_api tests + e2e_tests: + name: e2e tests runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3] + shardTotal: [3] steps: + # Checkout testing repo - name: Checkout testing repo id: clone-dokan-lite uses: actions/checkout@v4 + # Use desired version of NodeJS - name: Use desired version of NodeJS uses: actions/setup-node@v4 with: node-version: 20 cache: npm + # Composer install and build (Dokan-lite) - name: Composer install and build (Dokan-lite) run: | composer i --no-dev -o || composer update --no-dev -o + # Npm install and build (Dokan-lite) - name: Npm install and build (Dokan-lite) run: | npm ci || npm i @@ -108,31 +117,19 @@ jobs: id: wp-env uses: nick-fields/retry@v3 with: - timeout_minutes: 4 + timeout_minutes: 5 max_attempts: 2 retry_on: error command: | cd tests/pw npm run start:env - # Set permalink structure - - name: Set Permalink structure - working-directory: tests/pw - run: | - npm run wp-env run tests-cli wp rewrite structure /%postname%/ - - # Activate theme - - name: Activate theme:Storefront - working-directory: tests/pw - run: | - npm run wp-env run tests-cli wp theme activate storefront - # Get Playwright version - name: Get installed Playwright version id: playwright-version working-directory: tests/pw run: | - echo "PLAYWRIGHT_VERSION=$(npm ls @playwright/test --json | jq --raw-output '.dependencies["@playwright/test"].version')" >> $GITHUB_ENV + echo "PLAYWRIGHT_VERSION=$(npm ls @playwright/test --json | jq --raw-output '.dependencies["@playwright/test"].version')" >> "$GITHUB_ENV" # Cache browser binaries, cache key is based on Playwright version and OS - name: Cache playwright binaries @@ -151,23 +148,23 @@ jobs: if: steps.playwright-cache.outputs.cache-hit != 'true' working-directory: tests/pw run: | - npm run pw:browser-with-deps + npm run pw:chrome-with-deps - # # Install only the OS dependencies if cache hit not needed + # # Install only the OS dependencies if cache hit # - name: Install Playwright OS dependencies # if: steps.playwright-cache.outputs.cache-hit == 'true' # working-directory: tests/pw # run: | - # npm run pw:deps-only + # npm run pw:chrome-deps-only - # Run e2e tests - - name: 🧪 Run e2e tests + # Run E2E tests + - name: 🧪 Run e2e tests-${{ matrix.shardIndex }} id: e2e-test if: success() && (github.event_name != 'workflow_dispatch' || ( github.event_name == 'workflow_dispatch' && (github.event.inputs.testsuite == 'E2E' || github.event.inputs.testsuite == 'All'))) - timeout-minutes: 40 + timeout-minutes: 15 working-directory: tests/pw run: | - npm run test:e2e + npm run test:e2e -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} # Run e2e coverage - name: 🧪 Run e2e coverage @@ -176,20 +173,254 @@ jobs: run: | npm run test:e2e:coverage - # Run api tests - - name: 🧪 Run api tests + # # Prepare test summary + # - name: Prepare test summary + # id: prepare-test-summary + # uses: actions/github-script@v7 + # if: always() && steps.clone-dokan-lite.outcome == 'success' + # with: + # result-encoding: string + # script: | + # const script = require("./tests/pw/utils/gitTestSummary.ts") + # return await script({github, context, core}) + + # Backup Database + - name: Backup Database + if: always() && steps.wp-env.outcome == 'success' + working-directory: tests/pw + run: | + npm run wp-env run tests-cli wp db export wp-data/db.sql + + # Upload artifacts + - name: Archive test artifacts (screenshots, HTML snapshots, Reports) + if: always() && steps.debug-log.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: test-artifact-e2e-${{ matrix.shardIndex }} + path: | + tests/pw/wp-data + tests/pw/playwright + tests/pw/playwright-report + if-no-files-found: ignore + retention-days: 1 + + api_tests: + name: api tests + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + shardIndex: [1] + shardTotal: [1] + + steps: + # Checkout testing repo + - name: Checkout testing repo + id: clone-dokan-lite + uses: actions/checkout@v4 + + # Use desired version of NodeJS + - name: Use desired version of NodeJS + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + # Composer install and build (Dokan-lite) + - name: Composer install and build (Dokan-lite) + run: | + composer i --no-dev -o || composer update --no-dev -o + + # Npm install and build (Dokan-lite) + - name: Npm install and build (Dokan-lite) + run: | + npm ci || npm i + npm run build + + # Install test dependencies + - name: Install test dependencies + working-directory: tests/pw + run: | + npm ci || npm i + + # Create wp debuglog file + - name: Create wp debuglog file + id: debug-log + working-directory: tests/pw + run: | + mkdir -p wp-data && touch wp-data/debug.log + + # Start wordpress environment + - name: Start WordPress Env + id: wp-env + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 2 + retry_on: error + command: | + cd tests/pw + npm run start:env + + # Get Playwright version + - name: Get installed Playwright version + id: playwright-version + working-directory: tests/pw + run: | + echo "PLAYWRIGHT_VERSION=$(npm ls @playwright/test --json | jq --raw-output '.dependencies["@playwright/test"].version')" >> "$GITHUB_ENV" + + # Cache browser binaries, cache key is based on Playwright version and OS + - name: Cache playwright binaries + id: playwright-cache + uses: actions/cache@v4 + with: + path: | + ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} + restore-keys: | + ${{ runner.os }}-playwright- + + # Install browser binaries & OS dependencies if cache missed + - name: Install Playwright browser binaries & OS dependencies + id: pw-install + if: steps.playwright-cache.outputs.cache-hit != 'true' + working-directory: tests/pw + run: | + npm run pw:chrome-with-deps + + # # Install only the OS dependencies if cache hit + # - name: Install Playwright OS dependencies + # if: steps.playwright-cache.outputs.cache-hit == 'true' + # working-directory: tests/pw + # run: | + # npm run pw:chrome-deps-only + + # Run API tests + - name: 🧪 Run api tests-${{ matrix.shardIndex }} id: api-test if: always() && steps.wp-env.outcome == 'success' && ( github.event_name != 'workflow_dispatch' || ( github.event_name == 'workflow_dispatch' && (github.event.inputs.testsuite == 'API' || github.event.inputs.testsuite == 'All'))) timeout-minutes: 5 working-directory: tests/pw run: | - npm run test:api + npm run test:api -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} + + # # Prepare test summary + # - name: Prepare test summary + # id: prepare-test-summary + # if: always() && steps.clone-dokan-lite.outcome == 'success' + # uses: actions/github-script@v7 + # with: + # result-encoding: string + # script: | + # const script = require("./tests/pw/utils/gitTestSummary.ts") + # return await script({github, context, core}) + + # Backup Database + - name: Backup Database + if: always() && steps.wp-env.outcome == 'success' + working-directory: tests/pw + run: | + npm run wp-env run tests-cli wp db export wp-data/db.sql + + # Upload artifacts + - name: Archive test artifacts (screenshots, HTML snapshots, Reports) + if: always() && steps.debug-log.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + # name: test-artifact-api-${{ matrix.shardIndex }} + name: test-artifact-api + path: | + tests/pw/wp-data + tests/pw/playwright + tests/pw/playwright-report + if-no-files-found: ignore + retention-days: 1 + + merge-reports: + # Merge reports after playwright-tests, even if some shards have failed + if: ${{ !cancelled() }} + needs: [e2e_tests, api_tests] + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + # Override reports path specifically for this job + env: + API_TEST_RESULT: ./tests/pw/all-reports/test-artifact-api/playwright-report/api/summary-report/results.json + API_COVERAGE: ./tests/pw/all-reports/test-artifact-api/playwright-report/api/coverage-report/coverage.json + E2E_TEST_RESULT: ./tests/pw/all-reports/merged-summary.json + E2E_COVERAGE: ./tests/pw/all-reports/merged-coverage.json + + steps: + # Checkout testing repo + - name: Checkout testing repo + id: clone-dokan-lite + uses: actions/checkout@v4 + + # Use desired version of NodeJS + - name: Use desired version of NodeJS + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + # Install test dependencies + - name: Install test dependencies + working-directory: tests/pw + run: | + npm ci || npm i + + # Download all test artifacts from GitHub Actions + - name: Download all test artifacts from GitHub Actions + id: download-test-artifacts + uses: actions/download-artifact@v4 + with: + path: tests/pw/all-reports + pattern: test-artifact-* + + # Move all blob report folder contents into a single folder + - name: Move all blob report folder contents into a single folder + working-directory: tests/pw/all-reports + run: | + mkdir -p all-blob-reports + find . -type f -path "*/blob-report/*" -exec mv {} all-blob-reports/ \; + # ls all-blob-reports + + # Generate HTML Report + - name: Generate HTML Report + id: generate-html-report + working-directory: tests/pw + run: | + npx playwright merge-reports --reporter html ./all-reports/all-blob-reports + mv playwright-report ./all-reports/html-report + + # Clean all extracted blob reports (except ZIPs) + - name: Clean all extracted blob reports (except ZIPs) + working-directory: tests/pw + run: | + find all-reports/all-blob-reports -mindepth 1 ! -name "*.zip" -exec rm -rf {} + + + # Generate merged summary report + - name: Generate merged summary report + id: merge-summary-report + if: steps.download-test-artifacts.outcome == 'success' + working-directory: tests/pw + run: | + REPORT_TYPE=e2e npx ts-node ./utils/mergeSummaryReport.ts + + # Generate merged coverage report + - name: Generate merged coverage report + id: merge-coverage-report + if: steps.download-test-artifacts.outcome == 'success' + working-directory: tests/pw + run: | + REPORT_TYPE=e2e npx ts-node ./utils/mergeCoverageSummary.ts # Prepare test summary - name: Prepare test summary id: prepare-test-summary + if: always() && steps.merge-summary-report.outcome == 'success' uses: actions/github-script@v7 - if: always() && steps.clone-dokan-lite.outcome == 'success' with: result-encoding: string script: | @@ -217,22 +448,13 @@ jobs: # reactions: hooray # edit-mode: replace - # Backup Database - - name: Backup Database - if: always() && steps.wp-env.outcome == 'success' - working-directory: tests/pw - run: | - npm run wp-env run tests-cli wp db export wp-data/db.sql - - # Upload artifacts - - name: Archive test artifacts (screenshots, HTML snapshots, Reports) - if: always() && steps.debug-log.outcome == 'success' + # Upload final test artifact + - name: Upload final test artifact + if: always() && steps.download-test-artifacts.outcome == 'success' uses: actions/upload-artifact@v4 with: - name: test-artifact - path: | - tests/pw/wp-data - tests/pw/playwright - tests/pw/playwright-report + name: final-test-artifact + path: tests/pw/all-reports if-no-files-found: ignore - retention-days: 30 + retention-days: 7 + diff --git a/tests/pw/e2e.config.ts b/tests/pw/e2e.config.ts index afc051c1dd..b3987e4f6b 100644 --- a/tests/pw/e2e.config.ts +++ b/tests/pw/e2e.config.ts @@ -50,14 +50,14 @@ export default defineConfig({ reporter: CI ? [ // ['github'], - ['html', { open: 'never', outputFolder: 'playwright-report/e2e/html-report' }], - // ['junit', { outputFile: 'playwright-report/e2e/junit-report/e2e-results.xml' }], + // ['html', { open: 'never', outputFolder: 'playwright-report/e2e/html-report' }], + ['blob', { open: 'outputDir', outputDir: 'playwright-report/e2e/blob-report' }], ['list', { printSteps: true }], ['./utils/summaryReporter.ts', { outputFile: 'playwright-report/e2e/summary-report/results.json' }], ] : [ + // ['blob', { open: 'outputDir', outputDir: 'playwright-report/e2e/blob-report' }], ['html', { open: 'never', outputFolder: 'playwright-report/e2e/html-report' }], - // ['junit', { outputFile: 'playwright-report/e2e/junit-report/e2e-results.xml' }], ['list', { printSteps: true }], ['./utils/summaryReporter.ts', { outputFile: 'playwright-report/e2e/summary-report/results.json' }], ], diff --git a/tests/pw/package-lock.json b/tests/pw/package-lock.json index 1a4d6ed0eb..566256c8c1 100644 --- a/tests/pw/package-lock.json +++ b/tests/pw/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@faker-js/faker": "^9.3.0", "@playwright/test": "1.49", - "@wordpress/env": "^10.14.0", + "@wordpress/env": "^10.15.0", "dotenv": "^16.4.7", "mysql2": "^3.12.0", "php-serialize": "^5.0.1", @@ -19,9 +19,9 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@types/node": "^22.10.2", - "@typescript-eslint/eslint-plugin": "^8.19.0", - "@typescript-eslint/parser": "^8.19.0", + "@types/node": "^22.10.5", + "@typescript-eslint/eslint-plugin": "^8.19.1", + "@typescript-eslint/parser": "^8.19.1", "eslint": "9.17", "eslint-config-prettier": "^9.1.0", "eslint-plugin-playwright": "^2.1.0", @@ -245,6 +245,250 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inquirer/checkbox": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.4.tgz", + "integrity": "sha512-fYAKCAcGNMdfjL6hZTRUwkIByQ8EIZCXKrIQZH7XjADnN/xvRUhj8UdBbpC4zoUzvChhkSC/zRKaP/tDs3dZpg==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.1.tgz", + "integrity": "sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.2.tgz", + "integrity": "sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ==", + "dependencies": { + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.1.tgz", + "integrity": "sha512-xn9aDaiP6nFa432i68JCaL302FyL6y/6EG97nAtfIPnWZ+mWPgCMLGc4XZ2QQMsZtu9q3Jd5AzBPjXh10aX9kA==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.4.tgz", + "integrity": "sha512-GYocr+BPyxKPxQ4UZyNMqZFSGKScSUc0Vk17II3J+0bDcgGsQm0KYQNooN1Q5iBfXsy3x/VWmHGh20QnzsaHwg==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.1.tgz", + "integrity": "sha512-nAXAHQndZcXB+7CyjIW3XuQZZHbQQ0q8LX6miY6bqAWwDzNa9JUioDBYrFmOUNIsuF08o1WT/m2gbBXvBhYVxg==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.4.tgz", + "integrity": "sha512-DX7a6IXRPU0j8kr2ovf+QaaDiIf+zEKaZVzCWdLOTk7XigqSXvoh4cul7x68xp54WTQrgSnW7P1WBJDbyY3GhA==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.4.tgz", + "integrity": "sha512-wiliQOWdjM8FnBmdIHtQV2Ca3S1+tMBUerhyjkRCv1g+4jSvEweGu9GCcvVEgKDhTBT15nrxvk5/bVrGUqSs1w==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.2.1.tgz", + "integrity": "sha512-v2JSGri6/HXSfoGIwuKEn8sNCQK6nsB2BNpy2lSX6QH9bsECrMv93QHnj5+f+1ZWpF/VNioIV2B/PDox8EvGuQ==", + "dependencies": { + "@inquirer/checkbox": "^4.0.4", + "@inquirer/confirm": "^5.1.1", + "@inquirer/editor": "^4.2.1", + "@inquirer/expand": "^4.0.4", + "@inquirer/input": "^4.1.1", + "@inquirer/number": "^3.0.4", + "@inquirer/password": "^4.0.4", + "@inquirer/rawlist": "^4.0.4", + "@inquirer/search": "^3.0.4", + "@inquirer/select": "^4.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.4.tgz", + "integrity": "sha512-IsVN2EZdNHsmFdKWx9HaXb8T/s3FlR/U1QPt9dwbSyPtjFbMTlW9CRFvnn0bm/QIsrMRD2oMZqrQpSWPQVbXXg==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.4.tgz", + "integrity": "sha512-tSkJk2SDmC2MEdTIjknXWmCnmPr5owTs9/xjfa14ol1Oh95n6xW7SYn5fiPk4/vrJPys0ggSWiISdPze4LTa7A==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.4.tgz", + "integrity": "sha512-ZzYLuLoUzTIW9EJm++jBpRiTshGqS3Q1o5qOEQqgzaBlmdsjQr6pA4TUNkwu6OBYgM2mIRbCz6mUhFDfl/GF+w==", + "dependencies": { + "@inquirer/core": "^10.1.2", + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.2.tgz", + "integrity": "sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -492,9 +736,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "dependencies": { "undici-types": "~6.20.0" } @@ -508,20 +752,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", - "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", + "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/type-utils": "8.19.0", - "@typescript-eslint/utils": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/type-utils": "8.19.1", + "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -537,15 +781,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", - "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", + "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4" }, "engines": { @@ -561,13 +805,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", - "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", + "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0" + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -578,15 +822,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", - "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", + "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/utils": "8.19.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -601,9 +845,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", - "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", + "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -614,19 +858,19 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", - "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", + "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -664,15 +908,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", - "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", + "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0" + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -687,12 +931,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", - "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", + "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/types": "8.19.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -716,16 +960,16 @@ } }, "node_modules/@wordpress/env": { - "version": "10.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.14.0.tgz", - "integrity": "sha512-tDJyW6KaaEs9jz2XMTjY0RpGWdsjEfOCx5jeCMWtzkgrDY5N9iZr1BFjNzmFzY1BcXQshnFsrecsnYdyIfvsTA==", + "version": "10.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.15.0.tgz", + "integrity": "sha512-UMd7taPznebwMnW7tHlr+PzRMrCFzPL/rtJZdufxGCCr4MNaAYecb4OjJsuy2w1Bm9erDjTa7BjUEw2V+jWB/Q==", "dependencies": { + "@inquirer/prompts": "^7.2.0", "chalk": "^4.0.0", "copy-dir": "^1.3.0", "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", - "inquirer": "^7.1.0", "js-yaml": "^3.13.1", "ora": "^4.0.2", "rimraf": "^5.0.10", @@ -994,11 +1238,11 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { @@ -1516,16 +1760,16 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -1572,28 +1816,6 @@ "pend": "~1.2.0" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -1884,29 +2106,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "node_modules/inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2047,11 +2246,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2732,14 +2926,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2763,22 +2949,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -2963,11 +3133,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -2992,15 +3157,15 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-node": { @@ -3307,6 +3472,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", @@ -3454,6 +3630,169 @@ "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true }, + "@inquirer/checkbox": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.0.4.tgz", + "integrity": "sha512-fYAKCAcGNMdfjL6hZTRUwkIByQ8EIZCXKrIQZH7XjADnN/xvRUhj8UdBbpC4zoUzvChhkSC/zRKaP/tDs3dZpg==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/confirm": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.1.tgz", + "integrity": "sha512-vVLSbGci+IKQvDOtzpPTCOiEJCNidHcAq9JYVoWTW0svb5FiwSLotkM+JXNXejfjnzVYV9n0DTBythl9+XgTxg==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + } + }, + "@inquirer/core": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.2.tgz", + "integrity": "sha512-bHd96F3ezHg1mf/J0Rb4CV8ndCN0v28kUlrHqP7+ECm1C/A+paB7Xh2lbMk6x+kweQC+rZOxM/YeKikzxco8bQ==", + "requires": { + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "dependencies": { + "mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==" + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "@inquirer/editor": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.1.tgz", + "integrity": "sha512-xn9aDaiP6nFa432i68JCaL302FyL6y/6EG97nAtfIPnWZ+mWPgCMLGc4XZ2QQMsZtu9q3Jd5AzBPjXh10aX9kA==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "external-editor": "^3.1.0" + } + }, + "@inquirer/expand": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.4.tgz", + "integrity": "sha512-GYocr+BPyxKPxQ4UZyNMqZFSGKScSUc0Vk17II3J+0bDcgGsQm0KYQNooN1Q5iBfXsy3x/VWmHGh20QnzsaHwg==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/figures": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.9.tgz", + "integrity": "sha512-BXvGj0ehzrngHTPTDqUoDT3NXL8U0RxUk2zJm2A66RhCEIWdtU1v6GuUqNAgArW4PQ9CinqIWyHdQgdwOj06zQ==" + }, + "@inquirer/input": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.1.tgz", + "integrity": "sha512-nAXAHQndZcXB+7CyjIW3XuQZZHbQQ0q8LX6miY6bqAWwDzNa9JUioDBYrFmOUNIsuF08o1WT/m2gbBXvBhYVxg==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + } + }, + "@inquirer/number": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.4.tgz", + "integrity": "sha512-DX7a6IXRPU0j8kr2ovf+QaaDiIf+zEKaZVzCWdLOTk7XigqSXvoh4cul7x68xp54WTQrgSnW7P1WBJDbyY3GhA==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2" + } + }, + "@inquirer/password": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.4.tgz", + "integrity": "sha512-wiliQOWdjM8FnBmdIHtQV2Ca3S1+tMBUerhyjkRCv1g+4jSvEweGu9GCcvVEgKDhTBT15nrxvk5/bVrGUqSs1w==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2" + } + }, + "@inquirer/prompts": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.2.1.tgz", + "integrity": "sha512-v2JSGri6/HXSfoGIwuKEn8sNCQK6nsB2BNpy2lSX6QH9bsECrMv93QHnj5+f+1ZWpF/VNioIV2B/PDox8EvGuQ==", + "requires": { + "@inquirer/checkbox": "^4.0.4", + "@inquirer/confirm": "^5.1.1", + "@inquirer/editor": "^4.2.1", + "@inquirer/expand": "^4.0.4", + "@inquirer/input": "^4.1.1", + "@inquirer/number": "^3.0.4", + "@inquirer/password": "^4.0.4", + "@inquirer/rawlist": "^4.0.4", + "@inquirer/search": "^3.0.4", + "@inquirer/select": "^4.0.4" + } + }, + "@inquirer/rawlist": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.4.tgz", + "integrity": "sha512-IsVN2EZdNHsmFdKWx9HaXb8T/s3FlR/U1QPt9dwbSyPtjFbMTlW9CRFvnn0bm/QIsrMRD2oMZqrQpSWPQVbXXg==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/type": "^3.0.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/search": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.4.tgz", + "integrity": "sha512-tSkJk2SDmC2MEdTIjknXWmCnmPr5owTs9/xjfa14ol1Oh95n6xW7SYn5fiPk4/vrJPys0ggSWiISdPze4LTa7A==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/select": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.4.tgz", + "integrity": "sha512-ZzYLuLoUzTIW9EJm++jBpRiTshGqS3Q1o5qOEQqgzaBlmdsjQr6pA4TUNkwu6OBYgM2mIRbCz6mUhFDfl/GF+w==", + "requires": { + "@inquirer/core": "^10.1.2", + "@inquirer/figures": "^1.0.9", + "@inquirer/type": "^3.0.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/type": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.2.tgz", + "integrity": "sha512-ZhQ4TvhwHZF+lGhQ2O/rsjo80XoZR5/5qhOY3t6FJuX5XBg5Be8YzYTvaUGJnc12AUGI2nr4QSUE4PhKSigx7g==", + "requires": {} + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3652,9 +3991,9 @@ } }, "@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "requires": { "undici-types": "~6.20.0" } @@ -3668,77 +4007,77 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", - "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", + "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/type-utils": "8.19.0", - "@typescript-eslint/utils": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/type-utils": "8.19.1", + "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", - "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", + "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", - "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", + "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", "dev": true, "requires": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0" + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1" } }, "@typescript-eslint/type-utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", - "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", + "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/utils": "8.19.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" } }, "@typescript-eslint/types": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", - "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", + "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", - "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", + "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", "dev": true, "requires": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "dependencies": { "brace-expansion": { @@ -3762,24 +4101,24 @@ } }, "@typescript-eslint/utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", - "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", + "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0" + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1" } }, "@typescript-eslint/visitor-keys": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", - "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", + "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", "dev": true, "requires": { - "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/types": "8.19.1", "eslint-visitor-keys": "^4.2.0" }, "dependencies": { @@ -3792,16 +4131,16 @@ } }, "@wordpress/env": { - "version": "10.14.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.14.0.tgz", - "integrity": "sha512-tDJyW6KaaEs9jz2XMTjY0RpGWdsjEfOCx5jeCMWtzkgrDY5N9iZr1BFjNzmFzY1BcXQshnFsrecsnYdyIfvsTA==", + "version": "10.15.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.15.0.tgz", + "integrity": "sha512-UMd7taPznebwMnW7tHlr+PzRMrCFzPL/rtJZdufxGCCr4MNaAYecb4OjJsuy2w1Bm9erDjTa7BjUEw2V+jWB/Q==", "requires": { + "@inquirer/prompts": "^7.2.0", "chalk": "^4.0.0", "copy-dir": "^1.3.0", "docker-compose": "^0.24.3", "extract-zip": "^1.6.7", "got": "^11.8.5", - "inquirer": "^7.1.0", "js-yaml": "^3.13.1", "ora": "^4.0.2", "rimraf": "^5.0.10", @@ -3995,9 +4334,9 @@ "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==" }, "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==" }, "cliui": { "version": "8.0.1", @@ -4364,16 +4703,16 @@ "dev": true }, "fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "dependencies": { "glob-parent": { @@ -4416,21 +4755,6 @@ "pend": "~1.2.0" } }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "requires": { - "escape-string-regexp": "^1.0.5" - }, - "dependencies": { - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - } - } - }, "file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4638,26 +4962,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4766,11 +5070,6 @@ "p-locate": "^5.0.0" } }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5247,11 +5546,6 @@ "glob": "^10.3.7" } }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5261,21 +5555,6 @@ "queue-microtask": "^1.2.2" } }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "requires": { - "tslib": "^1.9.0" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - } - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -5406,11 +5685,6 @@ "supports-hyperlinks": "^2.0.0" } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -5429,9 +5703,9 @@ } }, "ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "requires": {} }, @@ -5639,6 +5913,11 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==" + }, "zod": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", diff --git a/tests/pw/package.json b/tests/pw/package.json index 58047c872b..b12e608e1f 100644 --- a/tests/pw/package.json +++ b/tests/pw/package.json @@ -7,8 +7,11 @@ "nid": "npm install --include=dev", "nip": "npm install --omit=dev", "pw:chrome-only": "playwright install chromium", - "pw:browser-with-deps": "playwright install --with-deps chromium", - "pw:deps-only": "playwright install-deps chromium", + "pw:chrome-with-deps": "playwright install --with-deps chromium", + "pw:chrome-deps-only": "playwright install-deps chromium", + "pw:browsers": "playwright install", + "pw:browsers-with-deps": "playwright install --with-deps", + "pw:browsers-deps-only": "playwright install-deps", "test": "npx playwright test", "site:reset": "NO_SETUP=false npm run site:setup && npm run env:setup", "site:setup": "SKIP_DOKAN=true npx playwright test --project=local_site_setup --config=e2e.config.ts", @@ -25,6 +28,7 @@ "test:codegen": "playwright codegen", "test:report": "npx playwright show-report", "test:trace": "npx playwright show-trace trace.zip", + "merge:report": "npx playwright merge-reports --reporter html", "allure:generate": "allure generate playwright-report/allure-report -o allure-report --clean", "allure:open": "allure open allure-report", "start:env": "wp-env start", @@ -50,9 +54,9 @@ "license": "ISC", "devDependencies": { "@types/js-yaml": "^4.0.9", - "@types/node": "^22.10.2", - "@typescript-eslint/eslint-plugin": "^8.19.0", - "@typescript-eslint/parser": "^8.19.0", + "@types/node": "^22.10.5", + "@typescript-eslint/eslint-plugin": "^8.19.1", + "@typescript-eslint/parser": "^8.19.1", "eslint": "9.17", "eslint-config-prettier": "^9.1.0", "eslint-plugin-playwright": "^2.1.0", @@ -66,7 +70,7 @@ "dependencies": { "@faker-js/faker": "^9.3.0", "@playwright/test": "1.49", - "@wordpress/env": "^10.14.0", + "@wordpress/env": "^10.15.0", "dotenv": "^16.4.7", "mysql2": "^3.12.0", "php-serialize": "^5.0.1", diff --git a/tests/pw/pages/menuManagerPage.ts b/tests/pw/pages/menuManagerPage.ts index e167d77563..56ae30e222 100644 --- a/tests/pw/pages/menuManagerPage.ts +++ b/tests/pw/pages/menuManagerPage.ts @@ -25,13 +25,13 @@ export class MenuManagerPage extends BasePage { // update menu status async updateMenuStatus(menu: string, action: string, menuLink: string) { - await this.goto(data.subUrls.backend.dokan.settings); + await this.gotoUntilNetworkidle(data.subUrls.backend.dokan.settings, { waitUntil: 'networkidle' }, true); await this.click(settingsAdmin.menus.menuManager); switch (action) { case 'activate': await this.enableSwitcher(settingsAdmin.menuManager.menuSwitcher(menu)); - await this.clickAndWaitForResponseAndLoadState(data.subUrls.ajax, settingsAdmin.saveChanges); + await this.clickAndWaitForResponseAndLoadStateUntilNetworkIdle(data.subUrls.ajax, settingsAdmin.saveChanges); await this.toHaveBackgroundColor(settingsAdmin.menuManager.menuSwitcher(menu) + '//span', 'rgb(0, 144, 255)'); //assertion await this.goto(data.subUrls.frontend.vDashboard.dashboard); @@ -42,7 +42,7 @@ export class MenuManagerPage extends BasePage { case 'deactivate': await this.disableSwitcher(settingsAdmin.menuManager.menuSwitcher(menu)); - await this.clickAndWaitForResponseAndLoadState(data.subUrls.ajax, settingsAdmin.saveChanges); + await this.clickAndWaitForResponseAndLoadStateUntilNetworkIdle(data.subUrls.ajax, settingsAdmin.saveChanges); await this.toHaveBackgroundColor(settingsAdmin.menuManager.menuSwitcher(menu) + '//span', 'rgb(215, 218, 221)'); //assertion await this.goto(data.subUrls.frontend.vDashboard.dashboard); diff --git a/tests/pw/pages/productsPage.ts b/tests/pw/pages/productsPage.ts index 714f37ac59..0e7519dd14 100644 --- a/tests/pw/pages/productsPage.ts +++ b/tests/pw/pages/productsPage.ts @@ -5,7 +5,7 @@ import { data } from '@utils/testData'; import { helpers } from '@utils/helpers'; import { product, vendor } from '@utils/interfaces'; -const { DOKAN_PRO } = process.env; +const { DOKAN_PRO, PRODUCT_EDIT_NONCE } = process.env; // selectors const productsAdmin = selector.admin.products; @@ -612,6 +612,24 @@ export class ProductsPage extends AdminPage { await this.toBeVisible(productsVendor.productLink(productName)); } + // get product edit nonce + async getProductEditNonce(): Promise { + await this.gotoUntilNetworkidle(data.subUrls.frontend.vDashboard.dashboard); + const url = await this.getAttributeValue(selector.vendor.vDashboard.products.addNewProduct, 'href'); + const nonce = url?.match(/_dokan_edit_product_nonce=([\w\d]+)/)?.[1]; + if (!nonce) throw new Error('Nonce not found'); + return nonce; + } + + // go to product edit by id + async goToProductEditById(productId: string, nonce: string = PRODUCT_EDIT_NONCE): Promise { + if (productId && !Number.isNaN(Number(productId))) { + await this.gotoUntilNetworkidle(data.subUrls.frontend.vDashboard.productEdit(productId, nonce)); + } else { + await this.goToProductEdit(productId); + } + } + // go to product edit async goToProductEdit(productName: string): Promise { await this.searchProduct(productName); @@ -795,7 +813,7 @@ export class ProductsPage extends AdminPage { // add product title async addProductTitle(productName: string, title: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.title, title); await this.saveProduct(); await this.toHaveValue(productsVendor.title, title); @@ -803,7 +821,7 @@ export class ProductsPage extends AdminPage { // add product permalink async addProductPermalink(productName: string, permalink: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.click(productsVendor.permalink.permalinkEdit); await this.clearAndType(productsVendor.permalink.permalinkInput, permalink); await this.clickAndWaitForResponse(data.subUrls.ajax, productsVendor.permalink.confirmPermalinkEdit); @@ -814,7 +832,7 @@ export class ProductsPage extends AdminPage { // add product price async addPrice(productName: string, price: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.price, price); await this.saveProduct(); await this.toHaveValue(productsVendor.price, price); @@ -822,7 +840,7 @@ export class ProductsPage extends AdminPage { // remove product price async removePrice(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.price, ''); await this.saveProduct(); await this.toHaveValue(productsVendor.price, ''); @@ -832,7 +850,7 @@ export class ProductsPage extends AdminPage { async cantAddGreaterDiscount(productName: string, discount: product['discount']): Promise { const regularPrice = discount.regularPrice.replace('.', ','); const discountPrice = String(Number(discount.regularPrice) + Number(discount.discountPrice)).replace('.', ','); - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.price, regularPrice); await this.type(productsVendor.discount.discountedPrice, discountPrice); await this.toBeVisible(productsVendor.discount.greaterDiscountError); @@ -842,7 +860,7 @@ export class ProductsPage extends AdminPage { async addDiscount(productName: string, discount: product['discount'], schedule = false, hasSchedule = false): Promise { const regularPrice = discount.regularPrice.replace('.', ','); const discountPrice = String(Number(discount.regularPrice) - Number(discount.discountPrice)).replace('.', ','); - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.price, regularPrice); await this.clearAndType(productsVendor.discount.discountedPrice, discountPrice); if (schedule) { @@ -861,7 +879,7 @@ export class ProductsPage extends AdminPage { // add remove discount async removeDiscount(productName: string, schedule: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.discount.discountedPrice, ''); if (schedule) { await this.click(productsVendor.discount.scheduleCancel); @@ -904,7 +922,7 @@ export class ProductsPage extends AdminPage { // add product category async addProductCategory(productName: string, categories: string[], multiple: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); for (const category of categories) { await this.vendorAddProductCategory(category, multiple); } @@ -916,7 +934,7 @@ export class ProductsPage extends AdminPage { // remove product category async removeProductCategory(productName: string, categories: string[]): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); for (const category of categories) { await this.click(productsVendor.category.removeSelectedCategory(category)); await this.notToBeVisible(productsVendor.category.selectedCategory(category)); @@ -929,13 +947,13 @@ export class ProductsPage extends AdminPage { // can't add product category async cantAddCategory(productName: string, category: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.vendorAddProductCategory(category, false, true); } // add product tags async addProductTags(productName: string, tags: string[]): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); for (const tag of tags) { await this.typeAndWaitForResponse(data.subUrls.ajax, productsVendor.tags.tagInput, tag); await this.click(productsVendor.tags.searchedTag(tag)); @@ -949,7 +967,7 @@ export class ProductsPage extends AdminPage { // remove product tags async removeProductTags(productName: string, tags: string[]): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); for (const tag of tags) { await this.click(productsVendor.tags.removeSelectedTags(tag)); await this.press('Escape'); // shift focus from element @@ -963,7 +981,7 @@ export class ProductsPage extends AdminPage { // add product cover image async addProductCoverImage(productName: string, coverImage: string, removePrevious: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); // remove previous cover image if (removePrevious) { await this.hover(productsVendor.image.coverImageDiv); @@ -979,7 +997,7 @@ export class ProductsPage extends AdminPage { // remove product cover image async removeProductCoverImage(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.hover(productsVendor.image.coverImageDiv); await this.click(productsVendor.image.removeFeatureImage); await this.saveProduct(); @@ -989,7 +1007,7 @@ export class ProductsPage extends AdminPage { // add product gallery images async addProductGalleryImages(productName: string, galleryImages: string[], removePrevious: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); // remove previous gallery images if (removePrevious) { await this.removeGalleryImages(); @@ -1015,7 +1033,7 @@ export class ProductsPage extends AdminPage { // remove product gallery images async removeProductGalleryImages(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.removeGalleryImages(); await this.saveProduct(); await this.toHaveCount(productsVendor.image.uploadedGalleryImage, 0); @@ -1023,7 +1041,7 @@ export class ProductsPage extends AdminPage { // add product short description async addProductShortDescription(productName: string, shortDescription: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.typeFrameSelector(productsVendor.shortDescription.shortDescriptionIframe, productsVendor.shortDescription.shortDescriptionHtmlBody, shortDescription); await this.saveProduct(); await this.toContainTextFrameLocator(productsVendor.shortDescription.shortDescriptionIframe, productsVendor.shortDescription.shortDescriptionHtmlBody, shortDescription); @@ -1031,7 +1049,7 @@ export class ProductsPage extends AdminPage { // add product description async addProductDescription(productName: string, description: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.typeFrameSelector(productsVendor.description.descriptionIframe, productsVendor.description.descriptionHtmlBody, description); await this.saveProduct(); await this.toContainTextFrameLocator(productsVendor.description.descriptionIframe, productsVendor.description.descriptionHtmlBody, description); @@ -1039,7 +1057,7 @@ export class ProductsPage extends AdminPage { // add product downloadable options async addProductDownloadableOptions(productName: string, downloadableOption: product['productInfo']['downloadableOptions']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.downloadable); await this.click(productsVendor.downloadableOptions.addFile); await this.clearAndType(productsVendor.downloadableOptions.fileName, downloadableOption.fileName); @@ -1056,7 +1074,7 @@ export class ProductsPage extends AdminPage { // remove product downloadable files async removeDownloadableFile(productName: string, downloadableOption: product['productInfo']['downloadableOptions']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); const fileCount = await this.getElementCount(productsVendor.downloadableOptions.deleteFile); for (let i = 0; i < fileCount; i++) { await this.clickFirstLocator(productsVendor.downloadableOptions.deleteFile); @@ -1071,7 +1089,7 @@ export class ProductsPage extends AdminPage { // add product virtual option async addProductVirtualOption(productName: string, enable: boolean): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); if (enable) { await this.check(productsVendor.virtual); } else { @@ -1091,7 +1109,7 @@ export class ProductsPage extends AdminPage { // add product inventory async addProductInventory(productName: string, inventory: product['productInfo']['inventory'], choice: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); switch (choice) { case 'sku': @@ -1148,7 +1166,7 @@ export class ProductsPage extends AdminPage { } // remove product inventory [stock management] async removeProductInventory(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.uncheck(productsVendor.inventory.enableStockManagement); await this.saveProduct(); await this.notToBeChecked(productsVendor.inventory.enableStockManagement); @@ -1156,7 +1174,7 @@ export class ProductsPage extends AdminPage { // add product other options (product status, visibility, purchase note, reviews) async addProductOtherOptions(productName: string, otherOption: product['productInfo']['otherOptions'], choice: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); switch (choice) { case 'status': @@ -1205,7 +1223,7 @@ export class ProductsPage extends AdminPage { // add product catalog mode async addProductCatalogMode(productName: string, hidePrice: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.catalogMode.removeAddToCart); if (hidePrice) await this.check(productsVendor.catalogMode.hideProductPrice); await this.saveProduct(); @@ -1215,7 +1233,7 @@ export class ProductsPage extends AdminPage { // remove product catalog mode async removeProductCatalogMode(productName: string, onlyPrice: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); if (onlyPrice) { await this.uncheck(productsVendor.catalogMode.hideProductPrice); @@ -1236,7 +1254,7 @@ export class ProductsPage extends AdminPage { // add product shipping async addProductShipping(productName: string, shipping: product['productInfo']['shipping']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.shipping.requiresShipping); await this.clearAndType(productsVendor.shipping.weight, shipping.weight); await this.clearAndType(productsVendor.shipping.length, shipping.length); @@ -1254,7 +1272,7 @@ export class ProductsPage extends AdminPage { // remove product shipping async removeProductShipping(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.uncheck(productsVendor.shipping.requiresShipping); await this.saveProduct(); await this.notToBeChecked(productsVendor.shipping.requiresShipping); @@ -1262,7 +1280,7 @@ export class ProductsPage extends AdminPage { // add product tax async addProductTax(productName: string, tax: product['productInfo']['tax'], hasClass: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.selectByValue(productsVendor.tax.status, tax.status); if (hasClass) await this.selectByValue(productsVendor.tax.class, tax.class); await this.saveProduct(); @@ -1272,7 +1290,7 @@ export class ProductsPage extends AdminPage { // add product linked products async addProductLinkedProducts(productName: string, linkedProducts: product['productInfo']['linkedProducts'], choice: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); switch (choice) { case 'up-sells': for (const linkedProduct of linkedProducts.upSells) { @@ -1312,7 +1330,7 @@ export class ProductsPage extends AdminPage { // add product linked products async removeProductLinkedProducts(productName: string, linkedProducts: product['productInfo']['linkedProducts'], choice: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); switch (choice) { case 'up-sells': for (const linkedProduct of linkedProducts.upSells) { @@ -1350,7 +1368,7 @@ export class ProductsPage extends AdminPage { // add product attribute async addProductAttribute(productName: string, attribute: product['productInfo']['attribute'], addTerm: boolean = false): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.selectByLabel(productsVendor.attribute.customAttribute, attribute.attributeName); await this.clickAndWaitForResponse(data.subUrls.ajax, productsVendor.attribute.addAttribute); await this.check(productsVendor.attribute.visibleOnTheProductPage); @@ -1369,14 +1387,14 @@ export class ProductsPage extends AdminPage { // cant add added attribute async cantAddAlreadyAddedAttribute(productName: string, attributeName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.toBeVisible(productsVendor.attribute.savedAttribute(attributeName)); await this.toHaveAttribute(productsVendor.attribute.disabledAttribute(attributeName), 'disabled', 'disabled'); } // remove product attribute async removeProductAttribute(productName: string, attribute: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.click(productsVendor.attribute.removeAttribute(attribute)); await this.click(productsVendor.attribute.confirmRemoveAttribute); await this.notToBeVisible(productsVendor.attribute.savedAttribute(attribute)); @@ -1386,7 +1404,7 @@ export class ProductsPage extends AdminPage { // remove product attribute term async removeProductAttributeTerm(productName: string, attribute: string, attributeTerm: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.click(productsVendor.attribute.savedAttribute(attribute)); await this.click(productsVendor.attribute.removeSelectedAttributeTerm(attributeTerm)); await this.press('Escape'); // shift focus from element @@ -1399,7 +1417,7 @@ export class ProductsPage extends AdminPage { // add product discount options async addProductBulkDiscountOptions(productName: string, quantityDiscount: product['productInfo']['quantityDiscount']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.bulkDiscount.enableBulkDiscount); await this.clearAndType(productsVendor.bulkDiscount.lotMinimumQuantity, quantityDiscount.minimumQuantity); await this.clearAndType(productsVendor.bulkDiscount.lotDiscountInPercentage, quantityDiscount.discountPercentage); @@ -1411,7 +1429,7 @@ export class ProductsPage extends AdminPage { // add product discount options async removeProductBulkDiscountOptions(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.uncheck(productsVendor.bulkDiscount.enableBulkDiscount); await this.saveProduct(); await this.notToBeChecked(productsVendor.bulkDiscount.enableBulkDiscount); @@ -1421,7 +1439,7 @@ export class ProductsPage extends AdminPage { // add product geolocation async addProductGeolocation(productName: string, location: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.uncheck(productsVendor.geolocation.sameAsStore); await this.typeAndWaitForResponse(data.subUrls.gmap, productsVendor.geolocation.productLocation, location); await this.press(data.key.arrowDown); @@ -1433,7 +1451,7 @@ export class ProductsPage extends AdminPage { // remove product geolocation async removeProductGeolocation(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.geolocation.sameAsStore); await this.saveProduct(); await this.toBeChecked(productsVendor.geolocation.sameAsStore); @@ -1441,7 +1459,7 @@ export class ProductsPage extends AdminPage { // add product EU compliance async addProductEuCompliance(productName: string, euCompliance: product['productInfo']['euCompliance']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.selectByValue(productsVendor.euComplianceFields.saleLabel, euCompliance.saleLabel); await this.selectByValue(productsVendor.euComplianceFields.saleRegularLabel, euCompliance.saleRegularLabel); await this.selectByValue(productsVendor.euComplianceFields.unit, euCompliance.unit); @@ -1477,7 +1495,7 @@ export class ProductsPage extends AdminPage { // add product addons async addProductAddon(productName: string, addon: product['productInfo']['addon']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.toPass(async () => { await this.clickAndWaitForResponse(data.subUrls.ajax, productsVendor.addon.addField); await this.toBeVisible(productsVendor.addon.addonForm); @@ -1517,7 +1535,7 @@ export class ProductsPage extends AdminPage { // import addon async importAddon(productName: string, addon: string, addonTitle: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.click(productsVendor.addon.import); await this.clearAndType(productsVendor.addon.importInput, addon); await this.saveProduct(); @@ -1526,14 +1544,14 @@ export class ProductsPage extends AdminPage { // export addon async exportAddon(productName: string, addon: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.click(productsVendor.addon.export); await this.toContainText(productsVendor.addon.exportInput, addon); } // delete addon async removeAddon(productName: string, addonName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.click(productsVendor.addon.removeAddon(addonName)); await this.click(productsVendor.addon.confirmRemove); await this.notToBeVisible(productsVendor.addon.addonRow(addonName)); @@ -1543,7 +1561,7 @@ export class ProductsPage extends AdminPage { // add product rma options async addProductRmaOptions(productName: string, rma: vendor['rma']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.rma.overrideDefaultRmaSettings); await this.clearAndType(productsVendor.rma.label, rma.label); await this.selectByValue(productsVendor.rma.type, rma.type); @@ -1590,7 +1608,7 @@ export class ProductsPage extends AdminPage { // remove product rma options async removeProductRmaOptions(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.uncheck(productsVendor.rma.overrideDefaultRmaSettings); await this.saveProduct(); await this.notToBeChecked(productsVendor.rma.overrideDefaultRmaSettings); @@ -1598,7 +1616,7 @@ export class ProductsPage extends AdminPage { // add product wholesale options async addProductWholesaleOptions(productName: string, wholesaleOption: product['productInfo']['wholesaleOption']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.check(productsVendor.wholesale.enableWholesale); await this.clearAndType(productsVendor.wholesale.wholesalePrice, wholesaleOption.wholesalePrice); await this.clearAndType(productsVendor.wholesale.minimumQuantity, wholesaleOption.minimumQuantity); @@ -1610,7 +1628,7 @@ export class ProductsPage extends AdminPage { // remove product wholesale options async removeProductWholesaleOptions(productName: string): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.uncheck(productsVendor.wholesale.enableWholesale); await this.saveProduct(); await this.notToBeChecked(productsVendor.wholesale.enableWholesale); @@ -1618,7 +1636,7 @@ export class ProductsPage extends AdminPage { // add product min-max options async addProductMinMaxOptions(productName: string, minMaxOption: product['productInfo']['minMax']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.minMax.minimumQuantity, minMaxOption.minimumProductQuantity); await this.clearAndType(productsVendor.minMax.maximumQuantity, minMaxOption.maximumProductQuantity); await this.saveProduct(); @@ -1628,7 +1646,7 @@ export class ProductsPage extends AdminPage { // can't add product min greater than max async cantAddGreaterMin(productName: string, minMaxOption: product['productInfo']['minMax']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.minMax.minimumQuantity, minMaxOption.minimumProductQuantity); await this.clearAndType(productsVendor.minMax.maximumQuantity, minMaxOption.maximumProductQuantity); await this.press('Tab'); // to trigger validation @@ -1640,7 +1658,7 @@ export class ProductsPage extends AdminPage { // remove product min-max options async removeProductMinMaxOptions(productName: string, minMaxOption: product['productInfo']['minMax']): Promise { - await this.goToProductEdit(productName); + await this.goToProductEditById(productName); await this.clearAndType(productsVendor.minMax.minimumQuantity, minMaxOption.minimumProductQuantity); await this.clearAndType(productsVendor.minMax.maximumQuantity, minMaxOption.maximumProductQuantity); await this.saveProduct(); diff --git a/tests/pw/pages/vendorPage.ts b/tests/pw/pages/vendorPage.ts index 1586d410f1..5640fde18e 100644 --- a/tests/pw/pages/vendorPage.ts +++ b/tests/pw/pages/vendorPage.ts @@ -49,8 +49,9 @@ export class VendorPage extends BasePage { // go to product edit async goToProductEdit(productName: string): Promise { await this.searchProduct(productName); + await this.removeAttribute(productsVendor.rowActions(productName), 'class'); // forcing the row actions to be visible, to avoid flakiness await this.hover(productsVendor.productCell(productName)); - await this.clickAndWaitForResponseAndLoadState(data.subUrls.frontend.vDashboard.products, productsVendor.editProduct(productName)); + await this.clickAndWaitForResponseAndLoadStateUntilNetworkIdle(data.subUrls.frontend.vDashboard.products, productsVendor.editProduct(productName)); await this.toHaveValue(productsVendor.title, productName); } diff --git a/tests/pw/tests/api/calculation.spec.ts b/tests/pw/tests/api/calculation.spec.ts index bce39aa7a5..85ff4e3a1e 100644 --- a/tests/pw/tests/api/calculation.spec.ts +++ b/tests/pw/tests/api/calculation.spec.ts @@ -26,11 +26,11 @@ test.describe.serial('commission calculation test', () => { await apiUtils.dispose(); }); - test.skip('calculation (debug)', { tag: ['@lite'] }, async () => { - const orderId = '4100'; - const [orderResponse, orderResponseBody] = await apiUtils.getSingleOrder(orderId); - await assertOrderCalculation([orderResponse, orderResponseBody, orderId]); - }); + // test.skip('calculation (debug)', { tag: ['@lite'] }, async () => { + // const orderId = '4100'; + // const [orderResponse, orderResponseBody] = await apiUtils.getSingleOrder(orderId); + // await assertOrderCalculation([orderResponse, orderResponseBody, orderId]); + // }); test('global commission fixed (only percentage)', { tag: ['@lite'] }, async () => { // set order condition diff --git a/tests/pw/tests/e2e/_auth.setup.ts b/tests/pw/tests/e2e/_auth.setup.ts index 434e13b065..4ade63d556 100644 --- a/tests/pw/tests/e2e/_auth.setup.ts +++ b/tests/pw/tests/e2e/_auth.setup.ts @@ -1,5 +1,6 @@ import { test as setup, expect, request } from '@playwright/test'; import { LoginPage } from '@pages/loginPage'; +import { ProductsPage } from '@pages/productsPage'; import { ApiUtils } from '@utils/apiUtils'; import { payloads } from '@utils/payloads'; import { data } from '@utils/testData'; @@ -38,6 +39,7 @@ setup.describe('add & authenticate users', () => { const [, sellerId] = await apiUtils.createStore(payloads.createStore1, payloads.adminAuth, true); // add open-close time await apiUtils.updateStore(sellerId, { ...payloads.storeResetFields, ...payloads.storeOpenClose }, payloads.adminAuth); + // add review if (DOKAN_PRO) { await apiUtils.createStoreReview(sellerId, { ...payloads.createStoreReview, rating: 5 }, payloads.adminAuth); @@ -75,6 +77,9 @@ setup.describe('add & authenticate users', () => { setup('authenticate vendor', { tag: ['@lite'] }, async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.login(data.vendor, data.auth.vendorAuthFile); + const productsPage = new ProductsPage(page); + const nonce = await productsPage.getProductEditNonce(); + helpers.createEnvVar('PRODUCT_EDIT_NONCE', nonce); }); setup('authenticate customer2', { tag: ['@lite'] }, async ({ page }) => { diff --git a/tests/pw/tests/e2e/payments.spec.ts b/tests/pw/tests/e2e/payments.spec.ts index c02e5a23f7..3bb6e610dd 100644 --- a/tests/pw/tests/e2e/payments.spec.ts +++ b/tests/pw/tests/e2e/payments.spec.ts @@ -92,7 +92,7 @@ test.describe('Payments test', () => { await admin.setupPaypalMarketPlace(data.payment); }); - test('admin can add Mangopay payment method', { tag: ['@pro', '@admin'] }, async () => { + test.skip('admin can add Mangopay payment method', { tag: ['@pro', '@admin'] }, async () => { test.slow(); await apiUtils.updateBatchWcSettingsOptions('general', payloads.currency, payloads.adminAuth); await admin.setupMangoPay(data.payment); diff --git a/tests/pw/tests/e2e/productsDetails.spec.ts b/tests/pw/tests/e2e/productsDetails.spec.ts index b0134db2d4..654cc74928 100644 --- a/tests/pw/tests/e2e/productsDetails.spec.ts +++ b/tests/pw/tests/e2e/productsDetails.spec.ts @@ -15,9 +15,8 @@ test.describe('Product details functionality test', () => { let vPage: Page; let apiUtils: ApiUtils; let productResponseBody: responseBody; - let productId: string; - let productNameFull: string; // has all fields - let productNameBasic: string; // has only required fields + let productIdFull: string; // has all fields + let productIdBasic: string; // has only required fields test.beforeAll(async ({ browser }) => { const vendorContext = await browser.newContext(data.auth.vendorAuth); @@ -25,14 +24,16 @@ test.describe('Product details functionality test', () => { vendor = new ProductsPage(vPage); apiUtils = new ApiUtils(await request.newContext()); + // product with only required fields - [, , productNameBasic] = await apiUtils.createProduct(payloads.createProductRequiredFields(), payloads.vendorAuth); + [, productIdBasic] = await apiUtils.createProduct(payloads.createProductRequiredFields(), payloads.vendorAuth); + // product with all fields // const [, , mediaUrl] = await apiUtils.uploadMedia(data.image.avatar, payloads.mimeTypes.png, payloads.vendorAuth); - // [productResponseBody, productId, productNameFull] = await apiUtils.createProductWc({ ...payloads.createProductAllFields(), images: [{ src: mediaUrl }, { src: mediaUrl }] }, payloads.vendorAuth); // todo: mediaUrl is not working on git action - [productResponseBody, productId, productNameFull] = await apiUtils.createProductWc(payloads.createProductAllFields(), payloads.vendorAuth); + // [productResponseBody, productIdFull] = await apiUtils.createProductWc({ ...payloads.createProductAllFields(), images: [{ src: mediaUrl }, { src: mediaUrl }] }, payloads.vendorAuth); // todo: mediaUrl is not working on git action + [productResponseBody, productIdFull] = await apiUtils.createProductWc(payloads.createProductAllFields(), payloads.vendorAuth); await apiUtils.updateProduct( - productId, + productIdFull, { meta_data: [ { key: '_product_addons', value: [payloads.createProductAddon()] }, @@ -54,260 +55,260 @@ test.describe('Product details functionality test', () => { // product title test('vendor can update product title', { tag: ['@lite', '@vendor'] }, async () => { - const [, , productName] = await apiUtils.createProduct(payloads.createProduct(), payloads.vendorAuth); - await vendor.addProductTitle(productName, data.product.productInfo.title); + const [, productId] = await apiUtils.createProduct(payloads.createProduct(), payloads.vendorAuth); + await vendor.addProductTitle(productId, data.product.productInfo.title); }); // product permalink test('vendor can update product permalink', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductPermalink(productNameFull, data.product.productInfo.permalink); + await vendor.addProductPermalink(productIdFull, data.product.productInfo.permalink); }); // product price test('vendor can add product price', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addPrice(productNameBasic, data.product.productInfo.price()); + await vendor.addPrice(productIdBasic, data.product.productInfo.price()); }); test('vendor can update product price', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addPrice(productNameFull, data.product.productInfo.price()); + await vendor.addPrice(productIdFull, data.product.productInfo.price()); }); test('vendor can remove product price', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.removePrice(productNameFull); + await vendor.removePrice(productIdFull); }); // product discount price test('vendor can add product discount price', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addDiscount(productNameBasic, data.product.productInfo.discount); + await vendor.addDiscount(productIdBasic, data.product.productInfo.discount); }); test('vendor can add product discount price (with schedule)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addDiscount(productNameBasic, data.product.productInfo.discount, true); + await vendor.addDiscount(productIdBasic, data.product.productInfo.discount, true); }); test('vendor can update product discount price', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addDiscount(productNameFull, { ...data.product.productInfo.discount, regularPrice: productResponseBody.price }); + await vendor.addDiscount(productIdFull, { ...data.product.productInfo.discount, regularPrice: productResponseBody.price }); }); test('vendor can update product discount price (with schedule)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addDiscount(productNameFull, { ...data.product.productInfo.discount, regularPrice: productResponseBody.price }, true, true); + await vendor.addDiscount(productIdFull, { ...data.product.productInfo.discount, regularPrice: productResponseBody.price }, true, true); }); test("vendor can't add product discount price higher than price", { tag: ['@lite', '@vendor'] }, async () => { - await vendor.cantAddGreaterDiscount(productNameFull, { ...data.product.productInfo.discount, regularPrice: productResponseBody.price }); + await vendor.cantAddGreaterDiscount(productIdFull, { ...data.product.productInfo.discount, regularPrice: productResponseBody.price }); }); test('vendor can remove product discount price', { tag: ['@lite', '@vendor'] }, async () => { - const [, , productName] = await apiUtils.createProduct({ ...payloads.createDiscountProduct(), date_on_sale_from: '', date_on_sale_to: '' }, payloads.vendorAuth); - await vendor.removeDiscount(productName); + const [, productId] = await apiUtils.createProduct({ ...payloads.createDiscountProduct(), date_on_sale_from: '', date_on_sale_to: '' }, payloads.vendorAuth); + await vendor.removeDiscount(productId); }); test('vendor can remove product discount schedule', { tag: ['@lite', '@vendor'] }, async () => { - const [, , productName] = await apiUtils.createProduct(payloads.createDiscountProduct(), payloads.vendorAuth); - await vendor.removeDiscount(productName, true); + const [, productId] = await apiUtils.createProduct(payloads.createDiscountProduct(), payloads.vendorAuth); + await vendor.removeDiscount(productId, true); }); // product category test('vendor can update product category (single)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductCategory(productNameFull, [data.product.category.clothings]); + await vendor.addProductCategory(productIdFull, [data.product.category.clothings]); }); test('vendor can add product category (multiple)', { tag: ['@pro', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { product_category_style: 'multiple' }); - await vendor.addProductCategory(productNameBasic, data.product.category.categories, true); + await vendor.addProductCategory(productIdBasic, data.product.category.categories, true); }); test('vendor can remove product category (multiple)', { tag: ['@pro', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { product_category_style: 'multiple' }); const uncategorizedId = await apiUtils.getCategoryId('Uncategorized', payloads.adminAuth); - const [, , productName] = await apiUtils.createProduct({ ...payloads.createProduct(), categories: [{ id: uncategorizedId }, { id: CATEGORY_ID }] }, payloads.vendorAuth); // need multiple categories - await vendor.removeProductCategory(productName, [data.product.category.clothings]); + const [, productId] = await apiUtils.createProduct({ ...payloads.createProduct(), categories: [{ id: uncategorizedId }, { id: CATEGORY_ID }] }, payloads.vendorAuth); // need multiple categories + await vendor.removeProductCategory(productId, [data.product.category.clothings]); }); test('vendor can add multi-step product category (last category)', { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { product_category_style: 'single' }); - await vendor.addProductCategory(productNameBasic, [data.product.category.multistepCategories.at(-1)!]); + await vendor.addProductCategory(productIdBasic, [data.product.category.multistepCategories.at(-1)!]); }); test('vendor can add multi-step product category (any category)', { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { dokan_any_category_selection: 'on' }); - await vendor.addProductCategory(productNameFull, [data.product.category.multistepCategories.at(-2)!]); + await vendor.addProductCategory(productIdFull, [data.product.category.multistepCategories.at(-2)!]); }); test("vendor can't add multi-step product category (any category)", { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { dokan_any_category_selection: 'off' }); - await vendor.cantAddCategory(productNameFull, data.product.category.multistepCategories.at(-2)!); + await vendor.cantAddCategory(productIdFull, data.product.category.multistepCategories.at(-2)!); }); // product tags test('vendor can add product tags', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductTags(productNameBasic, data.product.productInfo.tags.tags); + await vendor.addProductTags(productIdBasic, data.product.productInfo.tags.tags); }); test('vendor can remove product tags', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.removeProductTags(productNameFull, data.product.productInfo.tags.tags); + await vendor.removeProductTags(productIdFull, data.product.productInfo.tags.tags); }); test('vendor can create product tags', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductTags(productNameFull, data.product.productInfo.tags.randomTags); + await vendor.addProductTags(productIdFull, data.product.productInfo.tags.randomTags); }); // product cover image test('vendor can add product cover image', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductCoverImage(productNameBasic, data.product.productInfo.images.cover); + await vendor.addProductCoverImage(productIdBasic, data.product.productInfo.images.cover); }); test('vendor can update product cover image', { tag: ['@lite', '@vendor'] }, async () => { // todo: need a product with cover image - await vendor.addProductCoverImage(productNameFull, data.product.productInfo.images.cover); - await vendor.addProductCoverImage(productNameFull, data.product.productInfo.images.cover, true); + await vendor.addProductCoverImage(productIdFull, data.product.productInfo.images.cover); + await vendor.addProductCoverImage(productIdFull, data.product.productInfo.images.cover, true); }); test('vendor can remove product cover image', { tag: ['@lite', '@vendor'] }, async () => { // todo: need a product with cover image - await vendor.addProductCoverImage(productNameFull, data.product.productInfo.images.cover, true); - await vendor.removeProductCoverImage(productNameFull); + await vendor.addProductCoverImage(productIdFull, data.product.productInfo.images.cover, true); + await vendor.removeProductCoverImage(productIdFull); }); // product gallery image test('vendor can add product gallery image', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductGalleryImages(productNameBasic, data.product.productInfo.images.gallery); + await vendor.addProductGalleryImages(productIdBasic, data.product.productInfo.images.gallery); }); test('vendor can update product gallery image', { tag: ['@lite', '@vendor'] }, async () => { // todo: need a product with gallery images - await vendor.addProductGalleryImages(productNameFull, data.product.productInfo.images.gallery); - await vendor.addProductGalleryImages(productNameFull, data.product.productInfo.images.gallery, true); + await vendor.addProductGalleryImages(productIdFull, data.product.productInfo.images.gallery); + await vendor.addProductGalleryImages(productIdFull, data.product.productInfo.images.gallery, true); }); test('vendor can remove product gallery image', { tag: ['@lite', '@vendor'] }, async () => { // todo: need a product with gallery images - await vendor.addProductGalleryImages(productNameFull, data.product.productInfo.images.gallery, true); - await vendor.removeProductGalleryImages(productNameFull); + await vendor.addProductGalleryImages(productIdFull, data.product.productInfo.images.gallery, true); + await vendor.removeProductGalleryImages(productIdFull); }); // product short description test('vendor can add product short description', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductShortDescription(productNameBasic, data.product.productInfo.description.shortDescription); + await vendor.addProductShortDescription(productIdBasic, data.product.productInfo.description.shortDescription); }); test('vendor can update product short description', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductShortDescription(productNameFull, data.product.productInfo.description.shortDescription); + await vendor.addProductShortDescription(productIdFull, data.product.productInfo.description.shortDescription); }); test('vendor can remove product short description', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductShortDescription(productNameFull, ''); + await vendor.addProductShortDescription(productIdFull, ''); }); // product description test('vendor can update product description', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductDescription(productNameFull, data.product.productInfo.description.description); + await vendor.addProductDescription(productIdFull, data.product.productInfo.description.description); }); // product downloadable options test('vendor can add product downloadable options', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductDownloadableOptions(productNameBasic, data.product.productInfo.downloadableOptions); + await vendor.addProductDownloadableOptions(productIdBasic, data.product.productInfo.downloadableOptions); }); test('vendor can update product downloadable options', { tag: ['@lite', '@vendor'] }, async () => { // todo: need a product with downloadable file - await vendor.addProductDownloadableOptions(productNameFull, data.product.productInfo.downloadableOptions); + await vendor.addProductDownloadableOptions(productIdFull, data.product.productInfo.downloadableOptions); }); test('vendor can remove product downloadable file', { tag: ['@lite', '@vendor'] }, async () => { // todo: need a product with downloadable file - await vendor.addProductDownloadableOptions(productNameFull, data.product.productInfo.downloadableOptions); - await vendor.removeDownloadableFile(productNameFull, { ...data.product.productInfo.downloadableOptions, downloadLimit: '', downloadExpiry: '' }); + await vendor.addProductDownloadableOptions(productIdFull, data.product.productInfo.downloadableOptions); + await vendor.removeDownloadableFile(productIdFull, { ...data.product.productInfo.downloadableOptions, downloadLimit: '', downloadExpiry: '' }); }); // product virtual options test('vendor can add product virtual option', { tag: ['@lite', '@vendor'] }, async () => { - const [, , productName] = await apiUtils.createProduct(payloads.createProductRequiredFields(), payloads.vendorAuth); - await vendor.addProductVirtualOption(productName, true); + const [, productId] = await apiUtils.createProduct(payloads.createProductRequiredFields(), payloads.vendorAuth); + await vendor.addProductVirtualOption(productId, true); }); test('vendor can remove product virtual option', { tag: ['@lite', '@vendor'] }, async () => { - const [, , productName] = await apiUtils.createProduct({ ...payloads.createProductRequiredFields(), virtual: true }, payloads.vendorAuth); - await vendor.addProductVirtualOption(productName, false); + const [, productId] = await apiUtils.createProduct({ ...payloads.createProductRequiredFields(), virtual: true }, payloads.vendorAuth); + await vendor.addProductVirtualOption(productId, false); }); // product inventory options test('vendor can add product inventory options (SKU)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameBasic, data.product.productInfo.inventory(), 'sku'); + await vendor.addProductInventory(productIdBasic, data.product.productInfo.inventory(), 'sku'); }); test('vendor can update product inventory options (SKU)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameFull, data.product.productInfo.inventory(), 'sku'); + await vendor.addProductInventory(productIdFull, data.product.productInfo.inventory(), 'sku'); }); test('vendor can remove product inventory options (SKU)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameFull, { ...data.product.productInfo.inventory(), sku: '' }, 'sku'); + await vendor.addProductInventory(productIdFull, { ...data.product.productInfo.inventory(), sku: '' }, 'sku'); }); test('vendor can add product inventory options (stock status)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameBasic, data.product.productInfo.inventory(), 'stock-status'); + await vendor.addProductInventory(productIdBasic, data.product.productInfo.inventory(), 'stock-status'); }); test('vendor can add product inventory options (stock management)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameBasic, data.product.productInfo.inventory(), 'stock-management'); + await vendor.addProductInventory(productIdBasic, data.product.productInfo.inventory(), 'stock-management'); }); test('vendor can update product inventory options (stock management)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameBasic, data.product.productInfo.inventory(), 'stock-management'); + await vendor.addProductInventory(productIdBasic, data.product.productInfo.inventory(), 'stock-management'); }); test('vendor can remove product inventory options (stock management)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.removeProductInventory(productNameFull); + await vendor.removeProductInventory(productIdFull); }); test('vendor can add product inventory options (allow single quantity)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameBasic, data.product.productInfo.inventory(), 'one-quantity'); + await vendor.addProductInventory(productIdBasic, data.product.productInfo.inventory(), 'one-quantity'); }); test('vendor can remove product inventory options (allow single quantity)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductInventory(productNameFull, { ...data.product.productInfo.inventory(), oneQuantity: false }, 'one-quantity'); + await vendor.addProductInventory(productIdFull, { ...data.product.productInfo.inventory(), oneQuantity: false }, 'one-quantity'); }); // product other options test('vendor can add product other options (product status)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameBasic, data.product.productInfo.otherOptions, 'status'); + await vendor.addProductOtherOptions(productIdBasic, data.product.productInfo.otherOptions, 'status'); }); test('vendor can add product other options (visibility)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameBasic, data.product.productInfo.otherOptions, 'visibility'); + await vendor.addProductOtherOptions(productIdBasic, data.product.productInfo.otherOptions, 'visibility'); }); test('vendor can add product other options (purchase note)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameBasic, data.product.productInfo.otherOptions, 'purchaseNote'); + await vendor.addProductOtherOptions(productIdBasic, data.product.productInfo.otherOptions, 'purchaseNote'); }); test('vendor can update product other options (purchase note)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameFull, data.product.productInfo.otherOptions, 'purchaseNote'); + await vendor.addProductOtherOptions(productIdFull, data.product.productInfo.otherOptions, 'purchaseNote'); }); test('vendor can remove product other options (purchase note)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameFull, { ...data.product.productInfo.otherOptions, purchaseNote: '' }, 'purchaseNote'); + await vendor.addProductOtherOptions(productIdFull, { ...data.product.productInfo.otherOptions, purchaseNote: '' }, 'purchaseNote'); }); test('vendor can add product other options (product review)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameBasic, data.product.productInfo.otherOptions, 'reviews'); + await vendor.addProductOtherOptions(productIdBasic, data.product.productInfo.otherOptions, 'reviews'); }); test('vendor can remove product other options (product review)', { tag: ['@lite', '@vendor'] }, async () => { - await vendor.addProductOtherOptions(productNameFull, { ...data.product.productInfo.otherOptions, enableReview: false }, 'reviews'); + await vendor.addProductOtherOptions(productIdFull, { ...data.product.productInfo.otherOptions, enableReview: false }, 'reviews'); }); // catalog mode @@ -316,86 +317,86 @@ test.describe('Product details functionality test', () => { test('vendor can add product catalog mode', { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { catalog_mode_hide_add_to_cart_button: 'on' }); - await vendor.addProductCatalogMode(productNameBasic); + await vendor.addProductCatalogMode(productIdBasic); }); test('vendor can add product catalog mode (with price hidden)', { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { catalog_mode_hide_add_to_cart_button: 'on', catalog_mode_hide_product_price: 'on' }); - await vendor.addProductCatalogMode(productNameBasic, true); + await vendor.addProductCatalogMode(productIdBasic, true); }); test('vendor can remove product catalog mode', { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { catalog_mode_hide_add_to_cart_button: 'on', catalog_mode_hide_product_price: 'on' }); - await vendor.removeProductCatalogMode(productNameFull); + await vendor.removeProductCatalogMode(productIdFull); }); test('vendor can remove product catalog mode (price hidden option)', { tag: ['@lite', '@vendor'] }, async () => { await dbUtils.updateOptionValue(dbData.dokan.optionName.selling, { catalog_mode_hide_add_to_cart_button: 'on', catalog_mode_hide_product_price: 'on' }); - await vendor.removeProductCatalogMode(productNameFull, true); + await vendor.removeProductCatalogMode(productIdFull, true); }); // shipping and tax test('vendor can add product shipping', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductShipping(productNameBasic, data.product.productInfo.shipping); + await vendor.addProductShipping(productIdBasic, data.product.productInfo.shipping); }); test('vendor can update product shipping', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductShipping(productNameFull, data.product.productInfo.shipping); + await vendor.addProductShipping(productIdFull, data.product.productInfo.shipping); }); test('vendor can remove product shipping', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductShipping(productNameFull); + await vendor.removeProductShipping(productIdFull); }); test('vendor can add product tax', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductTax(productNameBasic, data.product.productInfo.tax); + await vendor.addProductTax(productIdBasic, data.product.productInfo.tax); }); test('vendor can add product tax (with tax class)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductTax(productNameBasic, data.product.productInfo.tax, true); + await vendor.addProductTax(productIdBasic, data.product.productInfo.tax, true); }); // linked products test('vendor can add product linked products (up-sells)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductLinkedProducts(productNameBasic, data.product.productInfo.linkedProducts, 'up-sells'); + await vendor.addProductLinkedProducts(productIdBasic, data.product.productInfo.linkedProducts, 'up-sells'); }); test('vendor can add product linked products (cross-sells)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductLinkedProducts(productNameBasic, data.product.productInfo.linkedProducts, 'cross-sells'); + await vendor.addProductLinkedProducts(productIdBasic, data.product.productInfo.linkedProducts, 'cross-sells'); }); test('vendor can remove product linked products (up-sells)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductLinkedProducts(productNameFull, data.product.productInfo.linkedProducts, 'up-sells'); + await vendor.removeProductLinkedProducts(productIdFull, data.product.productInfo.linkedProducts, 'up-sells'); }); test('vendor can remove product linked products (cross-sells)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductLinkedProducts(productNameFull, data.product.productInfo.linkedProducts, 'cross-sells'); + await vendor.removeProductLinkedProducts(productIdFull, data.product.productInfo.linkedProducts, 'cross-sells'); }); // attribute test('vendor can add product attribute', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductAttribute(productNameBasic, data.product.productInfo.attribute); + await vendor.addProductAttribute(productIdBasic, data.product.productInfo.attribute); }); test("vendor can't add already added product attribute", { tag: ['@pro', '@vendor'] }, async () => { - await vendor.cantAddAlreadyAddedAttribute(productNameFull, data.product.productInfo.attribute.attributeName); + await vendor.cantAddAlreadyAddedAttribute(productIdFull, data.product.productInfo.attribute.attributeName); }); // todo: refactor below tests test('vendor can create product attribute term', { tag: ['@pro', '@vendor'] }, async () => { const [, , , attributeName] = await apiUtils.createAttributeTerm(payloads.createAttribute(), payloads.createAttributeTerm(), payloads.adminAuth); - const [, , productId] = await apiUtils.createProduct(payloads.createProduct(), payloads.vendorAuth); + const [, productId] = await apiUtils.createProduct(payloads.createProduct(), payloads.vendorAuth); await vendor.addProductAttribute(productId, { ...data.product.productInfo.attribute, attributeName: attributeName }, true); }); test('vendor can remove product attribute', { tag: ['@pro', '@vendor'] }, async () => { const [, attributeId, , attributeName, attributeTerm] = await apiUtils.createAttributeTerm(payloads.createAttribute(), payloads.createAttributeTerm(), payloads.adminAuth); const attributes = { id: attributeId, name: attributeName, options: [attributeTerm] }; - const [, , productId] = await apiUtils.createProduct({ ...payloads.createProduct(), attributes: [attributes] }, payloads.vendorAuth); + const [, productId] = await apiUtils.createProduct({ ...payloads.createProduct(), attributes: [attributes] }, payloads.vendorAuth); await vendor.removeProductAttribute(productId, attributeName); }); @@ -403,51 +404,51 @@ test.describe('Product details functionality test', () => { const [, attributeId, , attributeName, attributeTerm] = await apiUtils.createAttributeTerm(payloads.createAttribute(), payloads.createAttributeTerm(), payloads.adminAuth); const [, , , , attributeTerm2] = await apiUtils.createAttributeTerm(payloads.createAttribute(), payloads.createAttributeTerm(), payloads.adminAuth); const attributes = { id: attributeId, name: attributeName, options: [attributeTerm, attributeTerm2] }; - const [, , productId] = await apiUtils.createProduct({ ...payloads.createProduct(), attributes: [attributes] }, payloads.vendorAuth); + const [, productId] = await apiUtils.createProduct({ ...payloads.createProduct(), attributes: [attributes] }, payloads.vendorAuth); await vendor.removeProductAttributeTerm(productId, attributeName, attributeTerm2); }); // discount options test('vendor can add product bulk discount options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductBulkDiscountOptions(productNameBasic, data.product.productInfo.quantityDiscount); + await vendor.addProductBulkDiscountOptions(productIdBasic, data.product.productInfo.quantityDiscount); }); test('vendor can update product bulk discount options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductBulkDiscountOptions(productNameFull, data.product.productInfo.quantityDiscount); + await vendor.addProductBulkDiscountOptions(productIdFull, data.product.productInfo.quantityDiscount); }); test('vendor can remove product bulk discount options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductBulkDiscountOptions(productNameFull); + await vendor.removeProductBulkDiscountOptions(productIdFull); }); // geolocation test('vendor can add product geolocation (individual)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductGeolocation(productNameBasic, data.product.productInfo.geolocation); + await vendor.addProductGeolocation(productIdBasic, data.product.productInfo.geolocation); }); test('vendor can update product geolocation (individual)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductGeolocation(productNameFull, data.product.productInfo.geolocation); + await vendor.addProductGeolocation(productIdFull, data.product.productInfo.geolocation); }); test('vendor can remove product geolocation (individual)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductGeolocation(productNameFull); + await vendor.removeProductGeolocation(productIdFull); }); // EU compliance options // todo: duplicate test from euCompliance test.skip('vendor can add product EU compliance data', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductEuCompliance(productNameBasic, data.product.productInfo.euCompliance); + await vendor.addProductEuCompliance(productIdBasic, data.product.productInfo.euCompliance); }); test.skip('vendor can update product EU compliance data', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductEuCompliance(productNameFull, data.product.productInfo.euCompliance); + await vendor.addProductEuCompliance(productIdFull, data.product.productInfo.euCompliance); }); test.skip('vendor can remove product EU compliance data', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductEuCompliance(productNameFull, { ...data.product.productInfo.euCompliance, productUnits: '', basePriceUnits: '', freeShipping: false, regularUnitPrice: '', saleUnitPrice: '', optionalMiniDescription: '' }); + await vendor.addProductEuCompliance(productIdFull, { ...data.product.productInfo.euCompliance, productUnits: '', basePriceUnits: '', freeShipping: false, regularUnitPrice: '', saleUnitPrice: '', optionalMiniDescription: '' }); }); // addon @@ -455,17 +456,17 @@ test.describe('Product details functionality test', () => { test('vendor can add product addon', { tag: ['@pro', '@vendor'] }, async () => { test.slow(); - await vendor.addProductAddon(productNameBasic, data.product.productInfo.addon); + await vendor.addProductAddon(productIdBasic, data.product.productInfo.addon); }); test('vendor can import product addon', { tag: ['@pro', '@vendor'] }, async () => { const addon = payloads.createProductAddon(); - await vendor.importAddon(productNameBasic, serialize([addon]), addon.name); + await vendor.importAddon(productIdBasic, serialize([addon]), addon.name); }); test('vendor can export product addon', { tag: ['@pro', '@vendor'] }, async () => { - const [responseBody, , productName] = await apiUtils.createProductWithAddon(payloads.createProduct(), [payloads.createProductAddon()], payloads.vendorAuth); - await vendor.exportAddon(productName, serialize(apiUtils.getMetaDataValue(responseBody.meta_data, '_product_addons'))); + const [responseBody, productId] = await apiUtils.createProductWithAddon(payloads.createProduct(), [payloads.createProductAddon()], payloads.vendorAuth); + await vendor.exportAddon(productId, serialize(apiUtils.getMetaDataValue(responseBody.meta_data, '_product_addons'))); }); test('vendor can remove product addon', { tag: ['@pro', '@vendor'] }, async () => { @@ -476,57 +477,57 @@ test.describe('Product details functionality test', () => { // rma options test('vendor can add product rma options (no warranty)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductRmaOptions(productNameBasic, { ...data.vendor.rma, type: 'no_warranty' }); + await vendor.addProductRmaOptions(productIdBasic, { ...data.vendor.rma, type: 'no_warranty' }); }); test('vendor can add product rma options (warranty included limited)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductRmaOptions(productNameBasic, data.vendor.rma); + await vendor.addProductRmaOptions(productIdBasic, data.vendor.rma); }); test('vendor can add product rma options (warranty included lifetime)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductRmaOptions(productNameBasic, { ...data.vendor.rma, length: 'lifetime' }); + await vendor.addProductRmaOptions(productIdBasic, { ...data.vendor.rma, length: 'lifetime' }); }); test('vendor can add product rma options (warranty as addon)', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductRmaOptions(productNameBasic, { ...data.vendor.rma, type: 'addon_warranty' }); + await vendor.addProductRmaOptions(productIdBasic, { ...data.vendor.rma, type: 'addon_warranty' }); }); //todo: add update rma options tests test('vendor can remove product rma options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductRmaOptions(productNameFull); + await vendor.removeProductRmaOptions(productIdFull); }); // wholesale options test('vendor can add product wholesale options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductWholesaleOptions(productNameBasic, data.product.productInfo.wholesaleOption); + await vendor.addProductWholesaleOptions(productIdBasic, data.product.productInfo.wholesaleOption); }); test('vendor can update product wholesale options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductWholesaleOptions(productNameFull, data.product.productInfo.wholesaleOption); + await vendor.addProductWholesaleOptions(productIdFull, data.product.productInfo.wholesaleOption); }); test('vendor can remove product wholesale options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductWholesaleOptions(productNameFull); + await vendor.removeProductWholesaleOptions(productIdFull); }); // mix-max options test('vendor can add product min-max options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductMinMaxOptions(productNameBasic, data.product.productInfo.minMax); + await vendor.addProductMinMaxOptions(productIdBasic, data.product.productInfo.minMax); }); test('vendor can update product min-max options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.addProductMinMaxOptions(productNameFull, data.product.productInfo.minMax); + await vendor.addProductMinMaxOptions(productIdFull, data.product.productInfo.minMax); }); test("vendor can't add product min limit grater than max limit", { tag: ['@pro', '@vendor'] }, async () => { - await vendor.cantAddGreaterMin(productNameFull, { minimumProductQuantity: '100', maximumProductQuantity: '50' }); + await vendor.cantAddGreaterMin(productIdFull, { minimumProductQuantity: '100', maximumProductQuantity: '50' }); }); test('vendor can remove product min-max options', { tag: ['@pro', '@vendor'] }, async () => { - await vendor.removeProductMinMaxOptions(productNameFull, { minimumProductQuantity: '', maximumProductQuantity: '' }); + await vendor.removeProductMinMaxOptions(productIdFull, { minimumProductQuantity: '', maximumProductQuantity: '' }); }); // todo: advertising diff --git a/tests/pw/tests/e2e/shipstation.spec.ts b/tests/pw/tests/e2e/shipstation.spec.ts index ed2139038b..3e4738b73d 100644 --- a/tests/pw/tests/e2e/shipstation.spec.ts +++ b/tests/pw/tests/e2e/shipstation.spec.ts @@ -7,7 +7,6 @@ import { payloads } from '@utils/payloads'; const { VENDOR_ID } = process.env; test.describe('ShipStation test', () => { - test.skip(true, 'remove after pr is merged'); let admin: ShipStationPage; let vendor: ShipStationPage; let aPage: Page, vPage: Page; diff --git a/tests/pw/tests/e2e/storeAppearance.spec.ts b/tests/pw/tests/e2e/storeAppearance.spec.ts index 4b7eeaff3e..c4f52b7f2e 100644 --- a/tests/pw/tests/e2e/storeAppearance.spec.ts +++ b/tests/pw/tests/e2e/storeAppearance.spec.ts @@ -19,6 +19,7 @@ test.describe('Store Appearance test', () => { admin = new StoreAppearancePage(aPage); apiUtils = new ApiUtils(await request.newContext()); + await dbUtils.setOptionValue('sidebars_widgets', dbData.emptySideBarsWidgets); // await dbUtils.updateOptionValue(dbData.dokanWidgets.names.storeLocation, dbData.dokanWidgets.values.storeLocationWidget); // await dbUtils.updateOptionValue(dbData.dokanWidgets.names.storeOpenClose, dbData.dokanWidgets.values.storeOpenCloseWidget); // await dbUtils.updateOptionValue(dbData.dokanWidgets.names.storeContactForm, dbData.dokanWidgets.values.storeContactFormWidget); diff --git a/tests/pw/tests/e2e/storelisting.spec.ts b/tests/pw/tests/e2e/storelisting.spec.ts index eb2ab4bf76..d4b392db88 100644 --- a/tests/pw/tests/e2e/storelisting.spec.ts +++ b/tests/pw/tests/e2e/storelisting.spec.ts @@ -50,7 +50,7 @@ test.describe('Store list functionality test', () => { await customer.filterStores('featured'); }); - test('customer can filter open now stores', { tag: ['@pro', '@customer'] }, async () => { + test.skip('customer can filter open now stores', { tag: ['@pro', '@customer'] }, async () => { await customer.filterStores('open-now'); }); diff --git a/tests/pw/types/environment.d.ts b/tests/pw/types/environment.d.ts index bb4253493a..9a8afa9397 100644 --- a/tests/pw/types/environment.d.ts +++ b/tests/pw/types/environment.d.ts @@ -18,6 +18,7 @@ declare global { CUSTOMER2_ID: string; PRODUCT_ID: string; PRODUCT_ID_V2: string; + PRODUCT_EDIT_NONCE: string; CATEGORY_ID: string; ATTRIBUTE_ID: string; TAG_ID: string; @@ -76,6 +77,7 @@ declare global { API_COVERAGE: string; E2E_COVERAGE: string; SKIP_DOKAN: boolean; + REPORT_TYPE: string; } } } diff --git a/tests/pw/utils/mergeCoverageSummary.ts b/tests/pw/utils/mergeCoverageSummary.ts new file mode 100644 index 0000000000..377432a560 --- /dev/null +++ b/tests/pw/utils/mergeCoverageSummary.ts @@ -0,0 +1,107 @@ +// @ts-nocheck +// eslint-disable-next-line @typescript-eslint/no-require-imports +const fs = require('fs'); +const path = require('path'); + +const { REPORT_TYPE } = process.env; + +interface CoverageReport { + total_features: number; + total_covered_features: number; + coverage: string; + page_coverage: Record; + covered_features: string[]; + uncovered_features: string[]; +} + +// Helper function to merge page coverage +const mergePageCoverage = (existing: Record, newCoverage: Record): Record => { + const merged: Record = { ...existing }; + for (const page in newCoverage) { + merged[page] = Math.round(((merged[page] ?? 0) + newCoverage[page]) * 100) / 100; + } + return merged; +}; + +// Main function to merge coverage files +const mergeCoverageReports = (reportPaths: string[]): CoverageReport => { + const mergedReport: CoverageReport = { + total_features: 0, + total_covered_features: 0, + coverage: '0', + page_coverage: {}, + covered_features: [], + uncovered_features: [], + }; + + reportPaths.forEach(reportPath => { + const report: CoverageReport = JSON.parse(fs.readFileSync(reportPath, 'utf8')); + + // Add total features and covered features + // mergedReport.total_features += report.total_features; + // mergedReport.total_covered_features += report.total_covered_features; + + // Add coverage percentages (convert string to number, remove "%") + // mergedReport.coverage = (parseFloat(mergedReport.coverage.replace('%', '')) + parseFloat(report.coverage.replace('%', ''))).toFixed(2) + '%'; + + // Merge page coverage + mergedReport.page_coverage = mergePageCoverage(mergedReport.page_coverage, report.page_coverage); + + // Append features (deduplication and sorting will be handled separately) + mergedReport.covered_features.push(...report.covered_features); + mergedReport.uncovered_features.push(...report.uncovered_features); + }); + + // Deduplicate and sort features after all reports are merged + mergedReport.covered_features = [...new Set(mergedReport.covered_features)].sort(); + mergedReport.uncovered_features = [...new Set(mergedReport.uncovered_features)].filter(feature => !mergedReport.covered_features.includes(feature)).sort(); + + mergedReport.total_features = mergedReport.covered_features.length + mergedReport.uncovered_features.length; + mergedReport.total_covered_features = mergedReport.covered_features.length; + + // Recalculate overall coverage percentage + mergedReport.coverage = ((mergedReport.total_covered_features / mergedReport.total_features) * 100).toFixed(2) + '%'; + + return mergedReport; +}; + +// Main script execution +const reportsFolder = './all-reports'; // Update to your artifacts folder location +const reportPaths: string[] = []; + +// Collect all coverage.json files +const findReports = (dir: string): void => { + // console.log(`Scanning directory: ${dir}`); + const files = fs.readdirSync(dir); + + files.forEach(file => { + const fullPath = path.join(dir, file); + const isDirectory = fs.statSync(fullPath).isDirectory(); + // console.log(`Checking: ${fullPath} (${isDirectory ? 'Directory' : 'File'})`); + if (isDirectory) { + findReports(fullPath); // Recurse into all directories + } else if (file === 'coverage.json') { + // Push the file path if it matches REPORT_TYPE + if (fullPath.includes(`${path.sep}${REPORT_TYPE}${path.sep}`)) { + console.log(`Matched file: ${fullPath}`); + reportPaths.push(fullPath); + } + } + }); +}; + +findReports(reportsFolder); + +if (reportPaths.length === 0) { + console.error('No coverage.json files found in artifacts.'); + process.exit(1); +} + +// Merge reports +const mergedCoverage = mergeCoverageReports(reportPaths); + +// Save the merged coverage report +const outputPath = './all-reports/merged-coverage.json'; +fs.writeFileSync(outputPath, JSON.stringify(mergedCoverage, null, 2), 'utf8'); + +console.log(`Merged coverage report saved to ${outputPath}`); diff --git a/tests/pw/utils/mergeSummaryReport.ts b/tests/pw/utils/mergeSummaryReport.ts new file mode 100644 index 0000000000..9c5781ae88 --- /dev/null +++ b/tests/pw/utils/mergeSummaryReport.ts @@ -0,0 +1,131 @@ +// @ts-nocheck +// eslint-disable-next-line @typescript-eslint/no-require-imports +const fs = require('fs'); +const path = require('path'); + +const { REPORT_TYPE } = process.env; +console.log(REPORT_TYPE); + +interface TestReport { + suite_name: string; + total_tests: number; + passed: number; + failed: number; + flaky: number; + skipped: number; + suite_duration: number; + suite_duration_formatted?: string; + all_suite_durations: number[]; + tests: string[]; + passed_tests: string[]; + failed_tests: string[]; + flaky_tests: string[]; + skipped_tests: string[]; +} + +// Helper function to format duration +const getFormattedDuration = (milliseconds: number): string => { + const hours = Math.floor(milliseconds / (1000 * 60 * 60)); + const minutes = Math.floor((milliseconds / (1000 * 60)) % 60); + const seconds = Math.floor((milliseconds / 1000) % 60); + return `${hours < 1 ? '' : hours + 'h '}${minutes < 1 ? '' : minutes + 'm '}${seconds < 1 ? '' : seconds + 's'}`; +}; + +// Main function to merge reports +const mergeReports = (reportPaths: string[]): TestReport => { + const mergedReport: TestReport = { + suite_name: '', + total_tests: 0, + passed: 0, + failed: 0, + flaky: 0, + skipped: 0, + suite_duration: 0, + suite_duration_formatted: '', + all_suite_durations: [], + tests: [], + passed_tests: [], + failed_tests: [], + flaky_tests: [], + skipped_tests: [], + }; + + reportPaths.forEach(reportPath => { + const report: TestReport = JSON.parse(fs.readFileSync(reportPath, 'utf8')); + + // mergedReport.total_tests += report.total_tests; + // mergedReport.passed += report.passed; + // mergedReport.failed += report.failed; + // mergedReport.flaky += report.flaky; + // mergedReport.skipped += report.skipped; + // mergedReport.suite_duration += report.suite_duration; + mergedReport.all_suite_durations.push(report.suite_duration); + + // Append and de-duplicate test arrays + mergedReport.tests.push(...report.tests); + mergedReport.passed_tests.push(...report.passed_tests); + mergedReport.failed_tests.push(...report.failed_tests); + mergedReport.flaky_tests.push(...report.flaky_tests); + mergedReport.skipped_tests.push(...report.skipped_tests); + }); + + // Remove duplicates and sort arrays + mergedReport.tests = [...new Set(mergedReport.tests)].sort(); + mergedReport.passed_tests = [...new Set(mergedReport.passed_tests)].sort(); + mergedReport.failed_tests = [...new Set(mergedReport.failed_tests)].sort(); + mergedReport.flaky_tests = [...new Set(mergedReport.flaky_tests)].sort(); + mergedReport.skipped_tests = [...new Set(mergedReport.skipped_tests)].sort(); + + mergedReport.total_tests = mergedReport.tests.length; + mergedReport.passed = mergedReport.passed_tests.length; + mergedReport.failed = mergedReport.failed_tests.length; + mergedReport.flaky = mergedReport.flaky_tests.length; + mergedReport.skipped = mergedReport.skipped_tests.length; + mergedReport.suite_duration = Math.max(...mergedReport.all_suite_durations); + + // Format the suite duration + mergedReport.suite_duration_formatted = getFormattedDuration(mergedReport.suite_duration); + + return mergedReport; +}; + +// Main script execution +const reportsFolder = './all-reports'; // Change to your artifacts location +const reportPaths: string[] = []; + +// Collect all reports.json files +const findReports = (dir: string): void => { + // console.log(`Scanning directory: ${dir}`); + const files = fs.readdirSync(dir); + + files.forEach(file => { + const fullPath = path.join(dir, file); + const isDirectory = fs.statSync(fullPath).isDirectory(); + // console.log(`Checking: ${fullPath} (${isDirectory ? 'Directory' : 'File'})`); + if (isDirectory) { + findReports(fullPath); // Recurse into all directories + } else if (file === 'results.json') { + // Push the file path if it matches REPORT_TYPE + if (fullPath.includes(`${path.sep}${REPORT_TYPE}${path.sep}`)) { + console.log(`Matched file: ${fullPath}`); + reportPaths.push(fullPath); + } + } + }); +}; + +findReports(reportsFolder); + +if (reportPaths.length === 0) { + console.error('No results.json files found in artifacts.'); + process.exit(1); +} + +// Merge reports +const mergedReport = mergeReports(reportPaths); + +// Save the merged report +const outputPath = './all-reports/merged-summary.json'; +fs.writeFileSync(outputPath, JSON.stringify(mergedReport, null, 2), 'utf8'); + +console.log(`Merged summary report saved to ${outputPath}`); diff --git a/tests/pw/utils/summaryReporter.ts b/tests/pw/utils/summaryReporter.ts index 3df2aa1870..62a9507a60 100644 --- a/tests/pw/utils/summaryReporter.ts +++ b/tests/pw/utils/summaryReporter.ts @@ -79,6 +79,8 @@ export default class summaryReport implements Reporter { onTestEnd(test: TestCase, result: TestResult): void { this.testResults[test.id] = test.outcome(); + // N.B. playwright skip tests from all tests, but we included them in the total tests to track all tests count comment below line and enable other commented line to make similar to playwright + summary.tests.push(test.title); switch (test.outcome()) { case 'skipped': @@ -86,16 +88,16 @@ export default class summaryReport implements Reporter { break; case 'expected': summary.passed_tests.push(test.title); - summary.tests.push(test.title); + // summary.tests.push(test.title); break; case 'unexpected': if (!summary.failed_tests.includes(test.title)) { summary.failed_tests.push(test.title); - summary.tests.push(test.title); + // summary.tests.push(test.title); } break; case 'flaky': - summary.flaky_tests.push(test.title); + // summary.flaky_tests.push(test.title); const index = summary.failed_tests.indexOf(test.title); if (index !== -1) { summary.failed_tests.splice(index, 1); diff --git a/tests/pw/utils/testData.ts b/tests/pw/utils/testData.ts index c1120801f5..93c6f85f70 100644 --- a/tests/pw/utils/testData.ts +++ b/tests/pw/utils/testData.ts @@ -1173,6 +1173,7 @@ export const data = { setupWizard: '?page=dokan-seller-setup', dashboard: 'dashboard', products: 'dashboard/products', + productEdit: (productId: string, nonce: string) => `dashboard/products/?product_id=${productId}&action=edit&_dokan_edit_product_nonce=${nonce}`, spmv: 'dashboard/products-search', orders: 'dashboard/orders', userSubscriptions: 'dashboard/user-subscription', From 9ae1575587a3757f1d8cd46d8c37798c25a03d6e Mon Sep 17 00:00:00 2001 From: Asad Nur <30354772+devAsadNur@users.noreply.github.com> Date: Thu, 9 Jan 2025 15:13:44 +0600 Subject: [PATCH 2/7] fix: Translation on some pages were not working due to wrong text-domain (#2503) --- .../commission/AdminCommission.vue | 6 +- includes/Admin/WithdrawLogExporter.php | 2 +- src/admin/components/AddReverseWithdraw.vue | 4 +- src/admin/pages/DummyData.vue | 4 +- src/admin/pages/VendorSingle.vue | 108 +++++++++--------- src/admin/pages/Withdraw.vue | 2 +- 6 files changed, 63 insertions(+), 63 deletions(-) diff --git a/assets/src/js/setup-wizard/commission/AdminCommission.vue b/assets/src/js/setup-wizard/commission/AdminCommission.vue index 9295f94c71..1f0ee0c389 100644 --- a/assets/src/js/setup-wizard/commission/AdminCommission.vue +++ b/assets/src/js/setup-wizard/commission/AdminCommission.vue @@ -1,7 +1,7 @@ @@ -101,22 +101,22 @@