From 70319a36a823af3554e7eed77e33dd70833071c5 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 09:41:31 -0800 Subject: [PATCH 01/18] Test time out --- e2e/tests/mobile/dnt.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests/mobile/dnt.spec.js b/e2e/tests/mobile/dnt.spec.js index 66f72e7748..119e5b9be5 100644 --- a/e2e/tests/mobile/dnt.spec.js +++ b/e2e/tests/mobile/dnt.spec.js @@ -69,6 +69,7 @@ test("Shopper can use the consent tracking form", async ({ page }) => { // Registering after setting DNT persists the preference await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}); + await page.waitForTimeout(5000); await checkDntCookie(page, '1') // Logging out clears the preference From 351ce1d200990fc1fd1fd513ecec0a2ab2d2fa25 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 10:01:22 -0800 Subject: [PATCH 02/18] bumping playwright --- package-lock.json | 30 +++++++++++++++++------------- package.json | 2 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index a3a7b097f0..269fb7f454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "node-fetch": "^2.6.9" }, "devDependencies": { - "@playwright/test": "^1.36.0", + "@playwright/test": "^1.49.0", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", @@ -1810,12 +1810,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.45.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz", - "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==", + "version": "1.50.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright": "1.45.1" + "playwright": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -4057,10 +4058,11 @@ }, "node_modules/fsevents": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -7953,12 +7955,13 @@ } }, "node_modules/playwright": { - "version": "1.45.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz", - "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==", + "version": "1.50.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.45.1" + "playwright-core": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -7971,10 +7974,11 @@ } }, "node_modules/playwright-core": { - "version": "1.45.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz", - "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==", + "version": "1.50.1", + "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", "dev": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, diff --git a/package.json b/package.json index 5b7213f8a6..9564e5cc4f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "check-dep-version": "syncpack list-mismatches" }, "devDependencies": { - "@playwright/test": "^1.36.0", + "@playwright/test": "^1.49.0", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", From d6b5b503aec2d5fc27c54ba35afde256338895a1 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 10:03:22 -0800 Subject: [PATCH 03/18] Bump playwright to latest --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 269fb7f454..926a958ef9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "node-fetch": "^2.6.9" }, "devDependencies": { - "@playwright/test": "^1.49.0", + "@playwright/test": "^1.50.1", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", diff --git a/package.json b/package.json index 9564e5cc4f..310082e369 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "check-dep-version": "syncpack list-mismatches" }, "devDependencies": { - "@playwright/test": "^1.49.0", + "@playwright/test": "^1.50.1", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", From 32000fd5a704b1ccdb97bca94259a4c21072465e Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Mon, 3 Feb 2025 10:11:13 -0800 Subject: [PATCH 04/18] Update npm matrix in CI --- .github/workflows/e2e.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4d33ace8de..ded8354e03 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -11,13 +11,9 @@ jobs: fail-fast: false matrix: # Run all matrix env at once because we will not deploy demo app to MRT. - node: [16, 18, 20, 22] + node: [18, 20, 22] npm: [8, 9, 10, 11] - exclude: # node 16 with npm 10/11 and node 18 with npm 11 are not compatible - - node: 16 - npm: 10 - - node: 16 - npm: 11 + exclude: # node 18 with npm 11 is not compatible - node: 18 npm: 11 runs-on: ubuntu-latest @@ -26,7 +22,7 @@ jobs: # For more: https://nodejs.org/en/download/releases/ # (We also use this env var for making sure a step runs once for the current node version) # Note: For node 18, the default was npm 9 until v18.19.0, when it became npm 10 - IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} + IS_DEFAULT_NPM: ${{ (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} steps: - name: Checkout uses: actions/checkout@v4 From 08cd2a00fbe7aff94e6fc659b0b5cf01441afa14 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 10:13:56 -0800 Subject: [PATCH 05/18] try version 1.49.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 926a958ef9..269fb7f454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "node-fetch": "^2.6.9" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.49.0", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", diff --git a/package.json b/package.json index 310082e369..9564e5cc4f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "check-dep-version": "syncpack list-mismatches" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.49.0", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", From ce7a3e40e1e132480ec1306177448698caf577a5 Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Mon, 3 Feb 2025 10:17:57 -0800 Subject: [PATCH 06/18] Bump all GithubAction workflows to use Node 22 --- .github/workflows/nightly_release.yml | 6 ++-- .github/workflows/setup_pwa_manual.yml | 4 +-- ...setup_template_retail_react_app_manual.yml | 4 +-- .github/workflows/test.yml | 34 +++++++------------ 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.github/workflows/nightly_release.yml b/.github/workflows/nightly_release.yml index a0e1a89d6e..8189407e5f 100644 --- a/.github/workflows/nightly_release.yml +++ b/.github/workflows/nightly_release.yml @@ -12,7 +12,7 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -42,9 +42,9 @@ jobs: echo "commerce_sdk_react_version_base=$version" >> "$GITHUB_ENV" - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 cache: npm - name: Install Monorepo Dependencies diff --git a/.github/workflows/setup_pwa_manual.yml b/.github/workflows/setup_pwa_manual.yml index 596444de21..a523a9d728 100644 --- a/.github/workflows/setup_pwa_manual.yml +++ b/.github/workflows/setup_pwa_manual.yml @@ -48,13 +48,13 @@ jobs: - name: Checkout id: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup node id: setup_node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 cache: "npm" - name: Setup PWA dependencies diff --git a/.github/workflows/setup_template_retail_react_app_manual.yml b/.github/workflows/setup_template_retail_react_app_manual.yml index 6f9e47531f..44d647a058 100644 --- a/.github/workflows/setup_template_retail_react_app_manual.yml +++ b/.github/workflows/setup_template_retail_react_app_manual.yml @@ -19,13 +19,13 @@ jobs: - name: Checkout id: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup node id: setup_node uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 cache: "npm" - name: Setup PWA dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 568e6ad3f3..ab4d5dba1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -44,13 +44,9 @@ jobs: strategy: fail-fast: false matrix: - node: [16, 18, 20, 22] + node: [18, 20, 22] npm: [8, 9, 10, 11] - exclude: # node 16 with npm 10/11 and node 18 with npm 11 are not compatible - - node: 16 - npm: 10 - - node: 16 - npm: 11 + exclude: # node 18 with npm 11 are not compatible - node: 18 npm: 11 runs-on: ubuntu-latest @@ -58,16 +54,16 @@ jobs: # The "default" npm is the one that ships with a given version of node. # For more: https://nodejs.org/en/download/releases/ # (We also use this env var for making sure a step runs once for the current node version) - IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} + IS_DEFAULT_NPM: ${{ (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} # The current recommended version for Managed Runtime: # https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/upgrade-node-version.html IS_MRT_NODE: ${{ matrix.node == 22 && matrix.npm == 10 }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: npm @@ -144,13 +140,9 @@ jobs: strategy: fail-fast: false matrix: - node: [16, 18, 20, 22] + node: [18, 20, 22] npm: [8, 9, 10, 11] - exclude: # node 16 with npm 10/11 and node 18 with npm 11 are not compatible - - node: 16 - npm: 10 - - node: 16 - npm: 11 + exclude: # node 18 with npm 11 is not compatible - node: 18 npm: 11 runs-on: windows-latest @@ -165,10 +157,10 @@ jobs: IS_MRT_NODE: ${{ matrix.node == 22 && matrix.npm == 10 }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: npm @@ -297,10 +289,10 @@ jobs: PROJECT_DIR: generated-${{ matrix.template }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 22 @@ -350,10 +342,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} cache: npm From af0f76d5a6be3b00dec1c42b6982e77e4ed1ff6c Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Mon, 3 Feb 2025 10:25:13 -0800 Subject: [PATCH 07/18] Bump all GithubAction workflows to use Node 22 --- .github/actions/changelog-check/action.yml | 2 +- .github/workflows/e2e.yml | 40 ++++++++-------------- .github/workflows/test.yml | 8 ++--- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/.github/actions/changelog-check/action.yml b/.github/actions/changelog-check/action.yml index d7a6804a9d..410ca2559b 100644 --- a/.github/actions/changelog-check/action.yml +++ b/.github/actions/changelog-check/action.yml @@ -9,7 +9,7 @@ runs: using: 'composite' steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch full history to access all commits diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ded8354e03..883776e408 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -58,13 +58,10 @@ jobs: # Run one matrix env at a time because we need to deploy each app to MRT and run e2e tests there max-parallel: 1 matrix: - node: [16, 18, 20, 22] + # Run all matrix env at once because we will not deploy demo app to MRT. + node: [18, 20, 22] npm: [8, 9, 10, 11] - exclude: # node 16 with npm 10/11 and node 18 with npm 11 are not compatible - - node: 16 - npm: 10 - - node: 16 - npm: 11 + exclude: # node 18 with npm 11 is not compatible - node: 18 npm: 11 runs-on: ubuntu-latest @@ -73,7 +70,7 @@ jobs: # For more: https://nodejs.org/en/download/releases/ # (We also use this env var for making sure a step runs once for the current node version) # Note: For node 18, the default was npm 9 until v18.19.0, when it became npm 10 - IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} + IS_DEFAULT_NPM: ${{ (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} steps: - name: Checkout uses: actions/checkout@v4 @@ -159,13 +156,10 @@ jobs: # Run one matrix env at a time because we need to deploy each app to MRT and run e2e tests there max-parallel: 1 matrix: - node: [16, 18, 20, 22] + # Run all matrix env at once because we will not deploy demo app to MRT. + node: [18, 20, 22] npm: [8, 9, 10, 11] - exclude: # node 16 with npm 10/11 and node 18 with npm 11 are not compatible - - node: 16 - npm: 10 - - node: 16 - npm: 11 + exclude: # node 18 with npm 11 is not compatible - node: 18 npm: 11 runs-on: ubuntu-latest @@ -174,7 +168,7 @@ jobs: # For more: https://nodejs.org/en/download/releases/ # (We also use this env var for making sure a step runs once for the current node version) # Note: For node 18, the default was npm 9 until v18.19.0, when it became npm 10 - IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} + IS_DEFAULT_NPM: ${{ (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} steps: - name: Checkout uses: actions/checkout@v4 @@ -258,23 +252,19 @@ jobs: # Run one matrix env at a time because we need to deploy each app to MRT and run e2e tests there max-parallel: 1 matrix: - # Run all matrix env at once because we will not deploy demo app to MRT. - node: [16, 18, 20, 22] - npm: [8, 9, 10, 11] - exclude: # node 16 with npm 10/11 and node 18 with npm 11 are not compatible - - node: 16 - npm: 10 - - node: 16 - npm: 11 - - node: 18 - npm: 11 + # Run all matrix env at once because we will not deploy demo app to MRT. + node: [18, 20, 22] + npm: [8, 9, 10, 11] + exclude: # node 18 with npm 11 is not compatible + - node: 18 + npm: 11 runs-on: ubuntu-latest env: # The "default" npm is the one that ships with a given version of node. # For more: https://nodejs.org/en/download/releases/ # (We also use this env var for making sure a step runs once for the current node version) # Note: For node 18, the default was npm 9 until v18.19.0, when it became npm 10 - IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} + IS_DEFAULT_NPM: ${{ (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab4d5dba1d..16fc10920b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Changelog Check uses: ./.github/actions/changelog-check @@ -151,7 +151,7 @@ jobs: # For more: https://nodejs.org/en/download/releases/ # (We also use this env var for making sure a step runs once for the current node version) # Note: For node 18, the default was npm 9 until v18.19.0, when it became npm 10 - IS_DEFAULT_NPM: ${{ (matrix.node == 16 && matrix.npm == 8) || (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} + IS_DEFAULT_NPM: ${{ (matrix.node == 18 && matrix.npm == 10) || (matrix.node == 20 && matrix.npm == 10) || (matrix.node == 22 && matrix.npm == 10) }} # The current recommended version for Managed Runtime: # https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/upgrade-node-version.html IS_MRT_NODE: ${{ matrix.node == 22 && matrix.npm == 10 }} @@ -190,10 +190,10 @@ jobs: PROJECT_DIR: generated-${{ matrix.template }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 22 From a3d019f48efe62b0e9fec01a8c9952655febccba Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 10:56:16 -0800 Subject: [PATCH 08/18] Update package-lock.json --- package-lock.json | 128 ---------------------------------------------- 1 file changed, 128 deletions(-) diff --git a/package-lock.json b/package-lock.json index 269fb7f454..9268223b75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1449,134 +1449,6 @@ "node": ">= 10" } }, - "node_modules/@nrwl/nx-darwin-x64": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-darwin-x64/-/nx-darwin-x64-15.9.7.tgz", - "integrity": "sha512-L+elVa34jhGf1cmn38Z0sotQatmLovxoASCIw5r1CBZZeJ5Tg7Y9nOwjRiDixZxNN56hPKXm6xl9EKlVHVeKlg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-linux-arm-gnueabihf": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-15.9.7.tgz", - "integrity": "sha512-pqmfqqEUGFu6PmmHKyXyUw1Al0Ki8PSaR0+ndgCAb1qrekVDGDfznJfaqxN0JSLeolPD6+PFtLyXNr9ZyPFlFg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-linux-arm64-gnu": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-15.9.7.tgz", - "integrity": "sha512-NYOa/eRrqmM+In5g3M0rrPVIS9Z+q6fvwXJYf/KrjOHqqan/KL+2TOfroA30UhcBrwghZvib7O++7gZ2hzwOnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-linux-arm64-musl": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-arm64-musl/-/nx-linux-arm64-musl-15.9.7.tgz", - "integrity": "sha512-zyStqjEcmbvLbejdTOrLUSEdhnxNtdQXlmOuymznCzYUEGRv+4f7OAepD3yRoR0a/57SSORZmmGQB7XHZoYZJA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-linux-x64-gnu": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-gnu/-/nx-linux-x64-gnu-15.9.7.tgz", - "integrity": "sha512-saNK5i2A8pKO3Il+Ejk/KStTApUpWgCxjeUz9G+T8A+QHeDloZYH2c7pU/P3jA9QoNeKwjVO9wYQllPL9loeVg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-linux-x64-musl": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-linux-x64-musl/-/nx-linux-x64-musl-15.9.7.tgz", - "integrity": "sha512-extIUThYN94m4Vj4iZggt6hhMZWQSukBCo8pp91JHnDcryBg7SnYmnikwtY1ZAFyyRiNFBLCKNIDFGkKkSrZ9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-win32-arm64-msvc": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-15.9.7.tgz", - "integrity": "sha512-GSQ54hJ5AAnKZb4KP4cmBnJ1oC4ILxnrG1mekxeM65c1RtWg9NpBwZ8E0gU3xNrTv8ZNsBeKi/9UhXBxhsIh8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nrwl/nx-win32-x64-msvc": { - "version": "15.9.7", - "resolved": "https://registry.npmjs.org/@nrwl/nx-win32-x64-msvc/-/nx-win32-x64-msvc-15.9.7.tgz", - "integrity": "sha512-x6URof79RPd8AlapVbPefUD3ynJZpmah3tYaYZ9xZRMXojVtEHV8Qh5vysKXQ1rNYJiiB8Ah6evSKWLbAH60tw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nrwl/tao": { "version": "15.9.7", "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-15.9.7.tgz", From ea0bcb85fa11e507dabf77717039e4b3782c1097 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 11:02:24 -0800 Subject: [PATCH 09/18] Bump to 1.50.1 since package-lock.json uses 1.50.1 even in lower versions --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9268223b75..90e1cad623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "node-fetch": "^2.6.9" }, "devDependencies": { - "@playwright/test": "^1.49.0", + "@playwright/test": "^1.50.1", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", diff --git a/package.json b/package.json index 9564e5cc4f..310082e369 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "check-dep-version": "syncpack list-mismatches" }, "devDependencies": { - "@playwright/test": "^1.49.0", + "@playwright/test": "^1.50.1", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", From 37f9583e28a22dc91ab73ec96ea3de94e559e339 Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Mon, 3 Feb 2025 11:04:37 -0800 Subject: [PATCH 10/18] Downgrade Playwright version to 1.49 --- package-lock.json | 30 +++++++++++++----------------- package.json | 2 +- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 90e1cad623..6d0c083b6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "node-fetch": "^2.6.9" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.49.0", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", @@ -1682,13 +1682,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.50.1", - "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/@playwright/test/-/test-1.50.1.tgz", - "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", + "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "playwright": "1.50.1" + "playwright": "1.49.0" }, "bin": { "playwright": "cli.js" @@ -3930,11 +3929,10 @@ }, "node_modules/fsevents": { "version": "2.3.2", - "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/fsevents/-/fsevents-2.3.2.tgz", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" @@ -7827,13 +7825,12 @@ } }, "node_modules/playwright": { - "version": "1.50.1", - "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/playwright/-/playwright-1.50.1.tgz", - "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.50.1" + "playwright-core": "1.49.0" }, "bin": { "playwright": "cli.js" @@ -7846,11 +7843,10 @@ } }, "node_modules/playwright-core": { - "version": "1.50.1", - "resolved": "https://nexus-proxy.repo.local.sfdc.net/nexus/content/groups/npm-all/playwright-core/-/playwright-core-1.50.1.tgz", - "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", "dev": true, - "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, diff --git a/package.json b/package.json index 310082e369..9564e5cc4f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "check-dep-version": "syncpack list-mismatches" }, "devDependencies": { - "@playwright/test": "^1.50.1", + "@playwright/test": "^1.49.0", "commander": "^9.5.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-no-relative-import-paths": "^1.5.3", From df8a77ab2c4f5d67378c0bd6467c1336ca25e118 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 11:57:52 -0800 Subject: [PATCH 11/18] put same timeout for mobile as for desktop --- e2e/tests/mobile/dnt.spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/tests/mobile/dnt.spec.js b/e2e/tests/mobile/dnt.spec.js index 119e5b9be5..03f756685a 100644 --- a/e2e/tests/mobile/dnt.spec.js +++ b/e2e/tests/mobile/dnt.spec.js @@ -44,7 +44,7 @@ test("Shopper can use the consent tracking form", async ({ page }) => { apiCallsMade = true; route.continue(); }); - + await page.waitForTimeout(5000); await checkDntCookie(page, '1') // Trigger einstein events @@ -69,7 +69,6 @@ test("Shopper can use the consent tracking form", async ({ page }) => { // Registering after setting DNT persists the preference await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}); - await page.waitForTimeout(5000); await checkDntCookie(page, '1') // Logging out clears the preference From a07edba5f09775c6cce0d965a201fd6a366a3d39 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 12:27:25 -0800 Subject: [PATCH 12/18] A space just to see change in line number --- e2e/tests/mobile/dnt.spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/tests/mobile/dnt.spec.js b/e2e/tests/mobile/dnt.spec.js index 03f756685a..603ba5cfc9 100644 --- a/e2e/tests/mobile/dnt.spec.js +++ b/e2e/tests/mobile/dnt.spec.js @@ -44,6 +44,7 @@ test("Shopper can use the consent tracking form", async ({ page }) => { apiCallsMade = true; route.continue(); }); + await page.waitForTimeout(5000); await checkDntCookie(page, '1') From 669b089b5192068e9c59c539afc6b700a5e66d4c Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 14:54:09 -0800 Subject: [PATCH 13/18] try to print playwright version --- .github/workflows/e2e.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 883776e408..5389dbc98d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -43,6 +43,14 @@ jobs: # Install node dependencies node ./scripts/gtime.js monorepo_install npm ci + - name: Check Playwright Versions + run: |- + # Check the installed version of playwright + echo "Playwright version: $(npm list playwright --depth=0)" + + # Check the installed version of @playwright/test + echo "@playwright/test version: $(npm list @playwright/test --depth=0)" + - name: Generate Retail App Demo uses: ./.github/actions/e2e_generate_app with: @@ -91,6 +99,14 @@ jobs: # Install node dependencies node ./scripts/gtime.js monorepo_install npm ci + - name: Check Playwright Versions + run: |- + # Check the installed version of playwright + echo "Playwright version: $(npm list playwright --depth=0)" + + # Check the installed version of @playwright/test + echo "@playwright/test version: $(npm list @playwright/test --depth=0)" + - name: Generate Retail App Without Extensibility uses: ./.github/actions/e2e_generate_app with: @@ -189,6 +205,14 @@ jobs: # Install node dependencies node ./scripts/gtime.js monorepo_install npm ci + - name: Check Playwright Versions + run: |- + # Check the installed version of playwright + echo "Playwright version: $(npm list playwright --depth=0)" + + # Check the installed version of @playwright/test + echo "@playwright/test version: $(npm list @playwright/test --depth=0)" + - name: Generate Retail App With Extensibility uses: ./.github/actions/e2e_generate_app with: @@ -284,6 +308,14 @@ jobs: # Install node dependencies node ./scripts/gtime.js monorepo_install npm ci + - name: Check Playwright Versions + run: |- + # Check the installed version of playwright + echo "Playwright version: $(npm list playwright --depth=0)" + + # Check the installed version of @playwright/test + echo "@playwright/test version: $(npm list @playwright/test --depth=0)" + - name: Generate Retail App Private Client uses: ./.github/actions/e2e_generate_app with: From 2eea5f686452c2668d3eda949089b27e32f3fc89 Mon Sep 17 00:00:00 2001 From: Jang ho Jung Date: Mon, 3 Feb 2025 21:54:07 -0800 Subject: [PATCH 14/18] Try adding an await --- e2e/scripts/pageHelpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index ba6e01c92b..63e8d93b80 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -16,7 +16,7 @@ export const answerConsentTrackingForm = async (page, dnt = false) => { var text = 'Accept' if (dnt) text = 'Decline' - const answerButton = page.locator('button:visible', { hasText: text }); + const answerButton = await page.locator('button:visible', { hasText: text }); await expect(answerButton).toBeVisible(); await answerButton.click(); } From 04ef9006ed2c56a9e3bee41f975e8f7d7a5f7000 Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Thu, 6 Feb 2025 12:46:26 -0800 Subject: [PATCH 15/18] Fix user login flows and order --- e2e/scripts/pageHelpers.js | 6 ++- e2e/tests/desktop/registered-shopper.spec.js | 53 ++++++++++++++----- e2e/tests/mobile/registered-shopper.spec.js | 55 ++++++++++++++------ 3 files changed, 83 insertions(+), 31 deletions(-) diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index 63e8d93b80..901c6bcd4a 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -272,10 +272,14 @@ export const loginShopper = async ({page, userCredentials}) => { .locator("input#password") .fill(userCredentials.password); + const loginResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/login') const tokenResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/token') await page.getByRole("button", { name: /Sign In/i }).click(); + await loginResponsePromise; + expect((await loginResponsePromise).status()).toBe(303); // Login returns a 303 redirect to /callback with authCode and usid await tokenResponsePromise; - return await tokenResponsePromise.status() === 200; + expect((await tokenResponsePromise).status()).toBe(200); + return true; } catch { return false; } diff --git a/e2e/tests/desktop/registered-shopper.spec.js b/e2e/tests/desktop/registered-shopper.spec.js index bc6aa7fada..dd5156a43a 100644 --- a/e2e/tests/desktop/registered-shopper.spec.js +++ b/e2e/tests/desktop/registered-shopper.spec.js @@ -7,7 +7,7 @@ const { test, expect } = require("@playwright/test"); const config = require("../../config"); -const { +const { addProductToCart, registerShopper, validateOrderHistory, @@ -20,7 +20,12 @@ const { getCreditCardExpiry, } = require("../../scripts/utils.js"); -const REGISTERED_USER_CREDENTIALS = generateUserCredentials(); +let REGISTERED_USER_CREDENTIALS = {}; + +test.beforeAll(async () => { + // Generate credentials once and use throughout tests to avoid creating a new account + REGISTERED_USER_CREDENTIALS = generateUserCredentials(); +}); /** * Test that registered shoppers can add a product to cart and go through the entire checkout process, @@ -28,11 +33,26 @@ const REGISTERED_USER_CREDENTIALS = generateUserCredentials(); * and that order shows up in order history */ test("Registered shopper can checkout items", async ({ page }) => { - // register and login user - await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}); + // Since we're re-using the same account, we need to check if the user is already registered. + // This ensures the tests are independent and not dependent on the order they are run in. + const isLoggedIn = await loginShopper({ + page, + userCredentials: REGISTERED_USER_CREDENTIALS, + }); + + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: REGISTERED_USER_CREDENTIALS, + }); + } + + await expect( + page.getByRole("heading", { name: /Account Details/i }) + ).toBeVisible(); // Shop for items as registered user - await addProductToCart({page}); + await addProductToCart({ page }); // cart await page.getByLabel(/My cart/i).click(); @@ -135,7 +155,7 @@ test("Registered shopper can checkout items", async ({ page }) => { ).toBeVisible(); // order history - await validateOrderHistory({page}); + await validateOrderHistory({ page }); }); /** @@ -144,15 +164,22 @@ test("Registered shopper can checkout items", async ({ page }) => { test("Registered shopper can add item to wishlist", async ({ page }) => { const isLoggedIn = await loginShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS - }) + userCredentials: REGISTERED_USER_CREDENTIALS, + }); - if(!isLoggedIn) { - await registerShopper({page, userCredentials: generateUserCredentials() }) + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: REGISTERED_USER_CREDENTIALS, + }); } + await expect( + page.getByRole("heading", { name: /Account Details/i }) + ).toBeVisible(); + // Navigate to PDP - await navigateToPDPDesktop({page}); + await navigateToPDPDesktop({ page }); // add product to wishlist await expect( @@ -160,8 +187,8 @@ test("Registered shopper can add item to wishlist", async ({ page }) => { ).toBeVisible(); await page.getByRole("radio", { name: "L", exact: true }).click(); - await page.getByRole("button", { name: /Add to Wishlist/i }).click() + await page.getByRole("button", { name: /Add to Wishlist/i }).click(); // wishlist - await validateWishlist({page}) + await validateWishlist({ page }); }); diff --git a/e2e/tests/mobile/registered-shopper.spec.js b/e2e/tests/mobile/registered-shopper.spec.js index 19c134a60f..aed89cd34d 100644 --- a/e2e/tests/mobile/registered-shopper.spec.js +++ b/e2e/tests/mobile/registered-shopper.spec.js @@ -13,14 +13,19 @@ const { validateOrderHistory, validateWishlist, loginShopper, - navigateToPDPMobile + navigateToPDPMobile, } = require("../../scripts/pageHelpers"); const { generateUserCredentials, getCreditCardExpiry, } = require("../../scripts/utils.js"); -const REGISTERED_USER_CREDENTIALS = generateUserCredentials(); +let REGISTERED_USER_CREDENTIALS = {}; + +test.beforeAll(async () => { + // Generate credentials once and use throughout tests to avoid creating a new account + REGISTERED_USER_CREDENTIALS = generateUserCredentials(); +}); /** * Test that registered shoppers can add a product to cart and go through the entire checkout process, @@ -28,15 +33,27 @@ const REGISTERED_USER_CREDENTIALS = generateUserCredentials(); * and that order shows up in order history */ test("Registered shopper can checkout items", async ({ page }) => { - // Create Account and Sign In - await registerShopper({ + // Since we're re-using the same account, we need to check if the user is already registered. + // This ensures the tests are independent and not dependent on the order they are run in. + const isLoggedIn = await loginShopper({ page, userCredentials: REGISTERED_USER_CREDENTIALS, - isMobile: true - }) + }); + + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: REGISTERED_USER_CREDENTIALS, + isMobile: true, + }); + } + + await expect( + page.getByRole("heading", { name: /Account Details/i }) + ).toBeVisible(); // Shop for items as registered user - await addProductToCart({page, isMobile: true}) + await addProductToCart({ page, isMobile: true }); // cart await page.getByLabel(/My cart/i).click(); @@ -138,7 +155,7 @@ test("Registered shopper can checkout items", async ({ page }) => { ).toBeVisible(); // order history - await validateOrderHistory({page}); + await validateOrderHistory({ page }); }); /** @@ -147,27 +164,31 @@ test("Registered shopper can checkout items", async ({ page }) => { test("Registered shopper can add item to wishlist", async ({ page }) => { const isLoggedIn = await loginShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS - }) + userCredentials: REGISTERED_USER_CREDENTIALS, + }); - if(!isLoggedIn) { + if (!isLoggedIn) { await registerShopper({ page, - userCredentials: generateUserCredentials(), - isMobile: true - }) + userCredentials: REGISTERED_USER_CREDENTIALS, + isMobile: true, + }); } + await expect( + page.getByRole("heading", { name: /Account Details/i }) + ).toBeVisible(); + // PDP - await navigateToPDPMobile({page}); + await navigateToPDPMobile({ page }); // add product to wishlist await expect( page.getByRole("heading", { name: /Cotton Turtleneck Sweater/i }) ).toBeVisible(); await page.getByRole("radio", { name: "L", exact: true }).click(); - await page.getByRole("button", { name: /Add to Wishlist/i }).click() + await page.getByRole("button", { name: /Add to Wishlist/i }).click(); // wishlist - await validateWishlist({page}) + await validateWishlist({ page }); }); From 1a001e337fe6de376baf7e33539b62445e1d5f22 Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Thu, 6 Feb 2025 12:57:55 -0800 Subject: [PATCH 16/18] Add note for best practice --- e2e/scripts/pageHelpers.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index 901c6bcd4a..010fcfab4f 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -2,6 +2,13 @@ const { expect } = require("@playwright/test"); const config = require("../config"); const { getCreditCardExpiry } = require("../scripts/utils.js") +/** + * Note: As a best practice, we should await the network call and assert on the network response rather than waiting for pageLoadState() + * to avoid race conditions from lock in pageLoadState being released before network call resolves. + * + * This is a best practice for tests that are dependent on the network call. Eg.: Shopper login, registration, etc. + */ + /** * Give an answer to the consent tracking form. * From f94418c6c05d005e5f829d4acf0c40be5f674180 Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Thu, 6 Feb 2025 14:23:01 -0800 Subject: [PATCH 17/18] Fix linting --- e2e/tests/desktop/registered-shopper.spec.js | 26 +++++++++--------- e2e/tests/mobile/registered-shopper.spec.js | 28 ++++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/e2e/tests/desktop/registered-shopper.spec.js b/e2e/tests/desktop/registered-shopper.spec.js index dd5156a43a..6b9f731a0e 100644 --- a/e2e/tests/desktop/registered-shopper.spec.js +++ b/e2e/tests/desktop/registered-shopper.spec.js @@ -20,11 +20,11 @@ const { getCreditCardExpiry, } = require("../../scripts/utils.js"); -let REGISTERED_USER_CREDENTIALS = {}; +let registeredUserCredentials = {}; test.beforeAll(async () => { // Generate credentials once and use throughout tests to avoid creating a new account - REGISTERED_USER_CREDENTIALS = generateUserCredentials(); + registeredUserCredentials = generateUserCredentials(); }); /** @@ -37,13 +37,13 @@ test("Registered shopper can checkout items", async ({ page }) => { // This ensures the tests are independent and not dependent on the order they are run in. const isLoggedIn = await loginShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, }); if (!isLoggedIn) { await registerShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, }); } @@ -76,23 +76,23 @@ test("Registered shopper can checkout items", async ({ page }) => { await page .locator("input#firstName") - .fill(REGISTERED_USER_CREDENTIALS.firstName); + .fill(registeredUserCredentials.firstName); await page .locator("input#lastName") - .fill(REGISTERED_USER_CREDENTIALS.lastName); - await page.locator("input#phone").fill(REGISTERED_USER_CREDENTIALS.phone); + .fill(registeredUserCredentials.lastName); + await page.locator("input#phone").fill(registeredUserCredentials.phone); await page .locator("input#address1") - .fill(REGISTERED_USER_CREDENTIALS.address.street); + .fill(registeredUserCredentials.address.street); await page .locator("input#city") - .fill(REGISTERED_USER_CREDENTIALS.address.city); + .fill(registeredUserCredentials.address.city); await page .locator("select#stateCode") - .selectOption(REGISTERED_USER_CREDENTIALS.address.state); + .selectOption(registeredUserCredentials.address.state); await page .locator("input#postalCode") - .fill(REGISTERED_USER_CREDENTIALS.address.zipcode); + .fill(registeredUserCredentials.address.zipcode); await page .getByRole("button", { name: /Continue to Shipping Method/i }) @@ -164,13 +164,13 @@ test("Registered shopper can checkout items", async ({ page }) => { test("Registered shopper can add item to wishlist", async ({ page }) => { const isLoggedIn = await loginShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, }); if (!isLoggedIn) { await registerShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, }); } diff --git a/e2e/tests/mobile/registered-shopper.spec.js b/e2e/tests/mobile/registered-shopper.spec.js index aed89cd34d..7b95cea739 100644 --- a/e2e/tests/mobile/registered-shopper.spec.js +++ b/e2e/tests/mobile/registered-shopper.spec.js @@ -20,11 +20,11 @@ const { getCreditCardExpiry, } = require("../../scripts/utils.js"); -let REGISTERED_USER_CREDENTIALS = {}; +let registeredUserCredentials = {}; test.beforeAll(async () => { // Generate credentials once and use throughout tests to avoid creating a new account - REGISTERED_USER_CREDENTIALS = generateUserCredentials(); + registeredUserCredentials = generateUserCredentials(); }); /** @@ -32,18 +32,18 @@ test.beforeAll(async () => { * validating that shopper is able to get to the order summary section, * and that order shows up in order history */ -test("Registered shopper can checkout items", async ({ page }) => { +test("Registered shopper can checkout items", async ({page}) => { // Since we're re-using the same account, we need to check if the user is already registered. // This ensures the tests are independent and not dependent on the order they are run in. const isLoggedIn = await loginShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, }); if (!isLoggedIn) { await registerShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, isMobile: true, }); } @@ -77,23 +77,23 @@ test("Registered shopper can checkout items", async ({ page }) => { await page .locator("input#firstName") - .fill(REGISTERED_USER_CREDENTIALS.firstName); + .fill(registeredUserCredentials.firstName); await page .locator("input#lastName") - .fill(REGISTERED_USER_CREDENTIALS.lastName); - await page.locator("input#phone").fill(REGISTERED_USER_CREDENTIALS.phone); + .fill(registeredUserCredentials.lastName); + await page.locator("input#phone").fill(registeredUserCredentials.phone); await page .locator("input#address1") - .fill(REGISTERED_USER_CREDENTIALS.address.street); + .fill(registeredUserCredentials.address.street); await page .locator("input#city") - .fill(REGISTERED_USER_CREDENTIALS.address.city); + .fill(registeredUserCredentials.address.city); await page .locator("select#stateCode") - .selectOption(REGISTERED_USER_CREDENTIALS.address.state); + .selectOption(registeredUserCredentials.address.state); await page .locator("input#postalCode") - .fill(REGISTERED_USER_CREDENTIALS.address.zipcode); + .fill(registeredUserCredentials.address.zipcode); await page .getByRole("button", { name: /Continue to Shipping Method/i }) @@ -164,13 +164,13 @@ test("Registered shopper can checkout items", async ({ page }) => { test("Registered shopper can add item to wishlist", async ({ page }) => { const isLoggedIn = await loginShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, }); if (!isLoggedIn) { await registerShopper({ page, - userCredentials: REGISTERED_USER_CREDENTIALS, + userCredentials: registeredUserCredentials, isMobile: true, }); } From 8c494ac4c5058479653f94d44562f727ba53737d Mon Sep 17 00:00:00 2001 From: Jainam Tushar Sheth Date: Thu, 6 Feb 2025 19:24:08 -0800 Subject: [PATCH 18/18] Match root linting rules with those in packages --- .prettierignore | 5 + .prettierrc.yaml | 7 + e2e/scripts/execute-shell-commands.js | 92 ++--- e2e/scripts/generate-project.js | 140 ++++--- e2e/scripts/pageHelpers.js | 404 +++++++++---------- e2e/scripts/utils.js | 130 +++--- e2e/scripts/validate-generated-project.js | 131 +++--- e2e/tests/desktop/dnt.spec.js | 117 +++--- e2e/tests/desktop/guest-shopper.spec.js | 178 ++++---- e2e/tests/desktop/registered-shopper.spec.js | 310 +++++++------- e2e/tests/homepage.spec.js | 36 +- e2e/tests/mobile/dnt.spec.js | 157 ++++--- e2e/tests/mobile/guest-shopper.spec.js | 272 ++++++------- e2e/tests/mobile/registered-shopper.spec.js | 310 +++++++------- 14 files changed, 1060 insertions(+), 1229 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.yaml diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..e4e5e605db --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +build +docs +coverage +scripts/generator/assets +app/static diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100644 index 0000000000..33069bf2b2 --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,7 @@ +printWidth: 100 +singleQuote: true +semi: false +bracketSpacing: false +tabWidth: 4 +arrowParens: 'always' +trailingComma: 'none' diff --git a/e2e/scripts/execute-shell-commands.js b/e2e/scripts/execute-shell-commands.js index d9e7308ea1..b46afadb8d 100644 --- a/e2e/scripts/execute-shell-commands.js +++ b/e2e/scripts/execute-shell-commands.js @@ -5,57 +5,57 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { exec } = require("child_process"); -const { isPrompt } = require("./utils.js"); +const {exec} = require('child_process') +const {isPrompt} = require('./utils.js') const runGeneratorWithResponses = (cmd, cliResponses = []) => { - const child = exec(cmd); - return new Promise((resolve, reject) => { - let expectedPrompt, response; - if (cliResponses && cliResponses.length) { - ({ expectedPrompt, response } = cliResponses.shift()); - } - - child.stdout.on("data", (data) => { - console.log(data); - if (isPrompt(data, expectedPrompt)) { - child.stdin.write(response); - if (cliResponses.length > 0) { - ({ expectedPrompt, response } = cliResponses.shift()); + const child = exec(cmd) + return new Promise((resolve, reject) => { + let expectedPrompt, response + if (cliResponses && cliResponses.length) { + ;({expectedPrompt, response} = cliResponses.shift()) } - } - }); - child.stderr.on("data", (err) => { - console.error(err); - }); - - child.on("error", (code) => { - reject(`Child process exited with code ${code}.`); - }); - - child.on("close", (code) => { - resolve(`Child process exited with code ${code}.`); - }); - }); -}; + child.stdout.on('data', (data) => { + console.log(data) + if (isPrompt(data, expectedPrompt)) { + child.stdin.write(response) + if (cliResponses.length > 0) { + ;({expectedPrompt, response} = cliResponses.shift()) + } + } + }) + + child.stderr.on('data', (err) => { + console.error(err) + }) + + child.on('error', (code) => { + reject(`Child process exited with code ${code}.`) + }) + + child.on('close', (code) => { + resolve(`Child process exited with code ${code}.`) + }) + }) +} const executeCommand = (cmd) => { - return new Promise((resolve, reject) => { - exec(cmd, (error, stdout, stderr) => { - if (error) { - reject(error.message); - } - if (stderr) { - reject(stderr); - } - - resolve(stdout); - }); - }); -}; + return new Promise((resolve, reject) => { + exec(cmd, (error, stdout, stderr) => { + if (error) { + reject(error.message) + } + if (stderr) { + reject(stderr) + } + + resolve(stdout) + }) + }) +} module.exports = { - runGeneratorWithResponses, - executeCommand, -}; + runGeneratorWithResponses, + executeCommand +} diff --git a/e2e/scripts/generate-project.js b/e2e/scripts/generate-project.js index 37923e48f3..997ae9e832 100644 --- a/e2e/scripts/generate-project.js +++ b/e2e/scripts/generate-project.js @@ -4,85 +4,81 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { runGeneratorWithResponses } = require("./execute-shell-commands.js"); -const config = require("../config.js"); -const { program } = require("commander"); -const { mkdirIfNotExists } = require("./utils.js"); +const {runGeneratorWithResponses} = require('./execute-shell-commands.js') +const config = require('../config.js') +const {program} = require('commander') +const {mkdirIfNotExists} = require('./utils.js') const main = async (opts) => { - const { projectKey, projectConfig } = opts; + const {projectKey, projectConfig} = opts - if (!projectKey && !projectConfig) { - console.error("You must provide either or ."); - console.log(program.helpInformation()); - process.exit(1); - } - - try { - let cliResponses = []; - let projectDir = projectKey; - let preset; - if (projectKey) { - cliResponses = config.CLI_RESPONSES[projectKey]; - preset = config.PRESET[projectKey]; - } else { - projectDir = projectConfig["projectDir"]; - let cliResponsesJsonArr = projectConfig["responses"]; - cliResponsesJsonArr.forEach((item) => { - cliResponses.push({ - expectedPrompt: new RegExp(item.expectedPrompt, "i"), - response: item.response, - }); - }); + if (!projectKey && !projectConfig) { + console.error('You must provide either or .') + console.log(program.helpInformation()) + process.exit(1) } - // Explicitly create outputDir because generator runs into permissions issue when generating no-ext projects. - await mkdirIfNotExists(config.GENERATED_PROJECTS_DIR); - const outputDir = `${config.GENERATED_PROJECTS_DIR}/${projectDir}`; - let generateAppCommand = `${config.GENERATOR_CMD} ${outputDir}`; - // TODO: Update script to setup local verdaccio npm repo to allow running 'npx @salesforce/pwa-kit-create-app' to generate apps - if (preset) { - generateAppCommand = `${config.GENERATOR_CMD} ${outputDir} --preset ${preset}`; + try { + let cliResponses = [] + let projectDir = projectKey + let preset + if (projectKey) { + cliResponses = config.CLI_RESPONSES[projectKey] + preset = config.PRESET[projectKey] + } else { + projectDir = projectConfig['projectDir'] + let cliResponsesJsonArr = projectConfig['responses'] + cliResponsesJsonArr.forEach((item) => { + cliResponses.push({ + expectedPrompt: new RegExp(item.expectedPrompt, 'i'), + response: item.response + }) + }) + } + + // Explicitly create outputDir because generator runs into permissions issue when generating no-ext projects. + await mkdirIfNotExists(config.GENERATED_PROJECTS_DIR) + const outputDir = `${config.GENERATED_PROJECTS_DIR}/${projectDir}` + let generateAppCommand = `${config.GENERATOR_CMD} ${outputDir}` + // TODO: Update script to setup local verdaccio npm repo to allow running 'npx @salesforce/pwa-kit-create-app' to generate apps + if (preset) { + generateAppCommand = `${config.GENERATOR_CMD} ${outputDir} --preset ${preset}` + } + return await runGeneratorWithResponses(generateAppCommand, cliResponses) + } catch (err) { + // Generator failed to create project + console.error('Generator failed to create project', err) + process.exit(1) } - return await runGeneratorWithResponses(generateAppCommand, cliResponses); - } catch (err) { - // Generator failed to create project - console.error("Generator failed to create project", err); - process.exit(1); - } -}; +} // Define the program with description and arguments program - .description( - "Generate a retail-react-app project using the key or the JSON " - ) - .option("--project-key ", "Project key", (value) => { - const validKeys = [ - "retail-app-demo", - "retail-app-ext", - "retail-app-no-ext", - "retail-app-private-client", - ]; - if (!validKeys.includes(value)) { - throw new Error("Invalid project key."); - } - return value; - }) - .option( - "--project-config ", - "Project config as JSON string", - (value) => { - try { - return JSON.parse(value); - } catch (e) { - throw new Error("Invalid JSON string."); - } - } - ) - .action((options) => { - // Call the main function with parsed options - main(options); - }); + .description( + 'Generate a retail-react-app project using the key or the JSON ' + ) + .option('--project-key ', 'Project key', (value) => { + const validKeys = [ + 'retail-app-demo', + 'retail-app-ext', + 'retail-app-no-ext', + 'retail-app-private-client' + ] + if (!validKeys.includes(value)) { + throw new Error('Invalid project key.') + } + return value + }) + .option('--project-config ', 'Project config as JSON string', (value) => { + try { + return JSON.parse(value) + } catch (e) { + throw new Error('Invalid JSON string.') + } + }) + .action((options) => { + // Call the main function with parsed options + main(options) + }) -program.parse(process.argv); +program.parse(process.argv) diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index 010fcfab4f..6f7e578166 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -1,167 +1,164 @@ -const { expect } = require("@playwright/test"); -const config = require("../config"); -const { getCreditCardExpiry } = require("../scripts/utils.js") +const {expect} = require('@playwright/test') +const config = require('../config') +const {getCreditCardExpiry} = require('../scripts/utils.js') /** - * Note: As a best practice, we should await the network call and assert on the network response rather than waiting for pageLoadState() + * Note: As a best practice, we should await the network call and assert on the network response rather than waiting for pageLoadState() * to avoid race conditions from lock in pageLoadState being released before network call resolves. - * + * * This is a best practice for tests that are dependent on the network call. Eg.: Shopper login, registration, etc. */ /** * Give an answer to the consent tracking form. - * + * * Note: the consent tracking form hovers over some elements in the app. This can cause a test to fail. * Run this function after a page.goto to release the form from view. - * + * * @param {Object} page - Object that represents a tab/window in the browser provided by playwright * @param {Boolean} dnt - Do Not Track value to answer the form. False to enable tracking, True to disable tracking. */ -export const answerConsentTrackingForm = async (page, dnt = false) => { - if (await page.locator('text=Tracking Consent').count() > 0) { +export const answerConsentTrackingForm = async (page, dnt = false) => { + if ((await page.locator('text=Tracking Consent').count()) > 0) { var text = 'Accept' - if (dnt) - text = 'Decline' - const answerButton = await page.locator('button:visible', { hasText: text }); - await expect(answerButton).toBeVisible(); - await answerButton.click(); + if (dnt) text = 'Decline' + const answerButton = await page.locator('button:visible', {hasText: text}) + await expect(answerButton).toBeVisible() + await answerButton.click() } } /** - * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on mobile + * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on mobile * with the black variant selected - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ export const navigateToPDPMobile = async ({page}) => { // Home page - await page.goto(config.RETAIL_APP_HOME); + await page.goto(config.RETAIL_APP_HOME) await answerConsentTrackingForm(page) - await page.getByLabel("Menu", { exact: true }).click(); + await page.getByLabel('Menu', {exact: true}).click() // SSR nav loads top level categories as direct links so we wait till all sub-categories load in the accordion const categoryAccordion = page.locator( "#category-nav .chakra-accordion__button svg+:text('Womens')" - ); - await categoryAccordion.waitFor(); + ) + await categoryAccordion.waitFor() - await page.getByRole("button", { name: "Womens" }).click(); + await page.getByRole('button', {name: 'Womens'}).click() - const clothingNav = page.getByRole("button", { name: "Clothing" }); + const clothingNav = page.getByRole('button', {name: 'Clothing'}) - await clothingNav.waitFor(); + await clothingNav.waitFor() - await clothingNav.click(); + await clothingNav.click() - const topsLink = page.getByLabel('Womens').getByRole("link", { name: "Tops" }); - await topsLink.click(); + const topsLink = page.getByLabel('Womens').getByRole('link', {name: 'Tops'}) + await topsLink.click() // Wait for the nav menu to close first await topsLink.waitFor({state: 'hidden'}) - await expect(page.getByRole("heading", { name: "Tops" })).toBeVisible(); + await expect(page.getByRole('heading', {name: 'Tops'})).toBeVisible() // PLP - const productTile = page.getByRole("link", { - name: /Cotton Turtleneck Sweater/i, - }); + const productTile = page.getByRole('link', { + name: /Cotton Turtleneck Sweater/i + }) await productTile.scrollIntoViewIfNeeded() // selecting swatch - const productTileImg = productTile.locator("img"); + const productTileImg = productTile.locator('img') await productTileImg.waitFor({state: 'visible'}) - const initialSrc = await productTileImg.getAttribute("src"); - await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible(); + const initialSrc = await productTileImg.getAttribute('src') + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() - await productTile.getByLabel(/Black/, { exact: true }).click(); + await productTile.getByLabel(/Black/, {exact: true}).click() // Make sure the image src has changed await expect(async () => { - const newSrc = await productTileImg.getAttribute("src") + const newSrc = await productTileImg.getAttribute('src') expect(newSrc).not.toBe(initialSrc) }).toPass() - await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible(); - await productTile.click(); + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() + await productTile.click() } /** - * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on Desktop + * Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on Desktop * with the black variant selected. - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ export const navigateToPDPDesktop = async ({page}) => { - await page.goto(config.RETAIL_APP_HOME); + await page.goto(config.RETAIL_APP_HOME) await answerConsentTrackingForm(page) - await page.getByRole("link", { name: "Womens" }).hover(); - const topsNav = await page.getByRole("link", { name: "Tops", exact: true }); - await expect(topsNav).toBeVisible(); - - await topsNav.click(); + await page.getByRole('link', {name: 'Womens'}).hover() + const topsNav = await page.getByRole('link', {name: 'Tops', exact: true}) + await expect(topsNav).toBeVisible() + + await topsNav.click() // PLP - const productTile = page.getByRole("link", { - name: /Cotton Turtleneck Sweater/i, - }); + const productTile = page.getByRole('link', { + name: /Cotton Turtleneck Sweater/i + }) // selecting swatch - const productTileImg = productTile.locator("img"); + const productTileImg = productTile.locator('img') await productTileImg.waitFor({state: 'visible'}) - const initialSrc = await productTileImg.getAttribute("src"); - await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible(); - - await productTile.getByLabel(/Black/, { exact: true }).hover(); + const initialSrc = await productTileImg.getAttribute('src') + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() + + await productTile.getByLabel(/Black/, {exact: true}).hover() // Make sure the image src has changed await expect(async () => { - const newSrc = await productTileImg.getAttribute("src") - expect(newSrc).not.toBe(initialSrc) + const newSrc = await productTileImg.getAttribute('src') + expect(newSrc).not.toBe(initialSrc) }).toPass() - await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible(); - await productTile.click(); + await expect(productTile.getByText(/From \$39\.99/i)).toBeVisible() + await productTile.click() } /** * Adds the `Cotton Turtleneck Sweater` product to the cart with the variant: * Color: Black * Size: L - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright * @param {Boolean} options.isMobile - Flag to indicate if device type is mobile or not, defaulted to false */ export const addProductToCart = async ({page, isMobile = false}) => { // Navigate to Cotton Turtleneck Sweater with Black color variant selected - if(isMobile) { + if (isMobile) { await navigateToPDPMobile({page}) } else { await navigateToPDPDesktop({page}) } - + // PDP - await expect( - page.getByRole("heading", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - await page.getByRole("radio", { name: "L", exact: true }).click(); - - await page.locator("button[data-testid='quantity-increment']").click(); - + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await page.getByRole('radio', {name: 'L', exact: true}).click() + + await page.locator("button[data-testid='quantity-increment']").click() + // Selected Size and Color texts are broken into multiple elements on the page. // So we need to look at the page URL to verify selected variants - const updatedPageURL = await page.url(); - const params = updatedPageURL.split("?")[1]; - expect(params).toMatch(/size=9LG/i); - expect(params).toMatch(/color=JJ169XX/i); - await page.getByRole("button", { name: /Add to Cart/i }).click(); - - const addedToCartModal = page.getByText(/2 items added to cart/i); - - await addedToCartModal.waitFor(); - - await page.getByLabel("Close").click(); + const updatedPageURL = await page.url() + const params = updatedPageURL.split('?')[1] + expect(params).toMatch(/size=9LG/i) + expect(params).toMatch(/color=JJ169XX/i) + await page.getByRole('button', {name: /Add to Cart/i}).click() + + const addedToCartModal = page.getByText(/2 items added to cart/i) + + await addedToCartModal.waitFor() + + await page.getByLabel('Close').click() } /** * Registers a shopper with provided user credentials - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright * @param {Object} options.userCredentials - Object containing user credentials with the following properties: * - firstName @@ -172,150 +169,134 @@ export const addProductToCart = async ({page, isMobile = false}) => { */ export const registerShopper = async ({page, userCredentials, isMobile = false}) => { // Create Account and Sign In - await page.goto(config.RETAIL_APP_HOME + "/registration"); + await page.goto(config.RETAIL_APP_HOME + '/registration') await answerConsentTrackingForm(page) - await page.waitForLoadState(); - - const registrationFormHeading = page.getByText(/Let's get started!/i); - await registrationFormHeading.waitFor(); - - await page - .locator("input#firstName") - .fill(userCredentials.firstName); - await page - .locator("input#lastName") - .fill(userCredentials.lastName); - await page.locator("input#email").fill(userCredentials.email); - await page - .locator("input#password") - .fill(userCredentials.password); - + await page.waitForLoadState() + + const registrationFormHeading = page.getByText(/Let's get started!/i) + await registrationFormHeading.waitFor() + + await page.locator('input#firstName').fill(userCredentials.firstName) + await page.locator('input#lastName').fill(userCredentials.lastName) + await page.locator('input#email').fill(userCredentials.email) + await page.locator('input#password').fill(userCredentials.password) + // Best Practice: await the network call and assert on the network response rather than waiting for pageLoadState() // to avoid race conditions from lock in pageLoadState being released before network call resolves - const tokenResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/token') - await page.getByRole("button", { name: /Create Account/i }).click(); - await tokenResponsePromise; - expect((await tokenResponsePromise).status()).toBe(200); - - await expect( - page.getByRole("heading", { name: /Account Details/i }) - ).toBeVisible(); - - if(!isMobile) { - await expect( - page.getByRole("heading", { name: /My Account/i }) - ).toBeVisible(); + const tokenResponsePromise = page.waitForResponse( + '**/shopper/auth/v1/organizations/**/oauth2/token' + ) + await page.getByRole('button', {name: /Create Account/i}).click() + await tokenResponsePromise + expect((await tokenResponsePromise).status()).toBe(200) + + await expect(page.getByRole('heading', {name: /Account Details/i})).toBeVisible() + + if (!isMobile) { + await expect(page.getByRole('heading', {name: /My Account/i})).toBeVisible() } - await expect(page.getByText(/Email/i)).toBeVisible(); - await expect(page.getByText(userCredentials.email)).toBeVisible(); + await expect(page.getByText(/Email/i)).toBeVisible() + await expect(page.getByText(userCredentials.email)).toBeVisible() } /** * Validates that the `Cotton Turtleneck Sweater` product appears in the Order History page - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ export const validateOrderHistory = async ({page}) => { - await page.goto(config.RETAIL_APP_HOME + "/account/orders"); + await page.goto(config.RETAIL_APP_HOME + '/account/orders') await answerConsentTrackingForm(page) - await expect( - page.getByRole("heading", { name: /Order History/i }) - ).toBeVisible(); - - await page.getByRole('link', { name: 'View details' }).click(); - - await expect( - page.getByRole("heading", { name: /Order Details/i }) - ).toBeVisible(); - await expect( - page.getByRole("heading", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - await expect(page.getByText(/Color: Black/i)).toBeVisible(); - await expect(page.getByText(/Size: L/i)).toBeVisible(); + await expect(page.getByRole('heading', {name: /Order History/i})).toBeVisible() + + await page.getByRole('link', {name: 'View details'}).click() + + await expect(page.getByRole('heading', {name: /Order Details/i})).toBeVisible() + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() } /** * Validates that the `Cotton Turtleneck Sweater` product appears in the Wishlist page - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright */ export const validateWishlist = async ({page}) => { - await page.goto(config.RETAIL_APP_HOME + "/account/wishlist"); + await page.goto(config.RETAIL_APP_HOME + '/account/wishlist') await answerConsentTrackingForm(page) - await expect( - page.getByRole("heading", { name: /Wishlist/i }) - ).toBeVisible(); - - await expect( - page.getByRole("heading", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); + await expect(page.getByRole('heading', {name: /Wishlist/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() await expect(page.getByText(/Color: Black/i)).toBeVisible() await expect(page.getByText(/Size: L/i)).toBeVisible() } /** * Attempts to log in a shopper with provided user credentials. - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright * @param {Object} options.userCredentials - Object containing user credentials with the following properties: * - firstName * - lastName * - email * - password - * + * * @return {Boolean} - denotes whether or not login was successful */ export const loginShopper = async ({page, userCredentials}) => { try { - await page.goto(config.RETAIL_APP_HOME + "/login"); + await page.goto(config.RETAIL_APP_HOME + '/login') await answerConsentTrackingForm(page) - await page.locator("input#email").fill(userCredentials.email); - await page - .locator("input#password") - .fill(userCredentials.password); - - const loginResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/login') - const tokenResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/token') - await page.getByRole("button", { name: /Sign In/i }).click(); - await loginResponsePromise; - expect((await loginResponsePromise).status()).toBe(303); // Login returns a 303 redirect to /callback with authCode and usid - await tokenResponsePromise; - expect((await tokenResponsePromise).status()).toBe(200); - return true; + await page.locator('input#email').fill(userCredentials.email) + await page.locator('input#password').fill(userCredentials.password) + + const loginResponsePromise = page.waitForResponse( + '**/shopper/auth/v1/organizations/**/oauth2/login' + ) + const tokenResponsePromise = page.waitForResponse( + '**/shopper/auth/v1/organizations/**/oauth2/token' + ) + await page.getByRole('button', {name: /Sign In/i}).click() + await loginResponsePromise + expect((await loginResponsePromise).status()).toBe(303) // Login returns a 303 redirect to /callback with authCode and usid + await tokenResponsePromise + expect((await tokenResponsePromise).status()).toBe(200) + return true } catch { - return false; + return false } } /** * Search for products by query string that takes you to the PLP - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright * @param {String} options.query - Product name other product related descriptors to search for * @param {Object} options.isMobile - Flag to indicate if device type is mobile or not, defaulted to false */ export const searchProduct = async ({page, query, isMobile = false}) => { - await page.goto(config.RETAIL_APP_HOME); + await page.goto(config.RETAIL_APP_HOME) await answerConsentTrackingForm(page) // For accessibility reasons, we have two search bars // one for desktop and one for mobile depending on your device type - const searchInputs = page.locator('input[aria-label="Search for products..."]'); + const searchInputs = page.locator('input[aria-label="Search for products..."]') - let searchInput = isMobile ? searchInputs.nth(1) : searchInputs.nth(0); - await searchInput.fill(query); - await searchInput.press('Enter'); + let searchInput = isMobile ? searchInputs.nth(1) : searchInputs.nth(0) + await searchInput.fill(query) + await searchInput.press('Enter') - await page.waitForLoadState(); + await page.waitForLoadState() } /** * Checkout products that are in the cart - * + * * @param {Object} options.page - Object that represents a tab/window in the browser provided by playwright * @param {Object} options.userCredentials - Object containing user credentials with the following properties: * - firstName @@ -323,83 +304,66 @@ export const searchProduct = async ({page, query, isMobile = false}) => { * - email * - password */ -export const checkoutProduct = async ({ page, userCredentials }) => { - await page.getByRole("link", { name: "Proceed to Checkout" }).click(); - - await expect( - page.getByRole("heading", { name: /Contact Info/i }) - ).toBeVisible(); +export const checkoutProduct = async ({page, userCredentials}) => { + await page.getByRole('link', {name: 'Proceed to Checkout'}).click() + + await expect(page.getByRole('heading', {name: /Contact Info/i})).toBeVisible() - await page.locator("input#email").fill("test@gmail.com"); + await page.locator('input#email').fill('test@gmail.com') - await page.getByRole("button", { name: /Checkout as guest/i }).click(); + await page.getByRole('button', {name: /Checkout as guest/i}).click() // Confirm the email input toggles to show edit button on clicking "Checkout as guest" - const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']"); - - await expect(step0Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - - await expect( - page.getByRole("heading", { name: /Shipping Address/i }) - ).toBeVisible(); - - await page.locator("input#firstName").fill(userCredentials.firstName); - await page.locator("input#lastName").fill(userCredentials.lastName); - await page.locator("input#phone").fill(userCredentials.phone); - await page - .locator("input#address1") - .fill(userCredentials.address.street); - await page.locator("input#city").fill(userCredentials.address.city); - await page - .locator("select#stateCode") - .selectOption(userCredentials.address.state); - await page - .locator("input#postalCode") - .fill(userCredentials.address.zipcode); - - await page - .getByRole("button", { name: /Continue to Shipping Method/i }) - .click(); + const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']") + + await expect(step0Card.getByRole('button', {name: /Edit/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Shipping Address/i})).toBeVisible() + + await page.locator('input#firstName').fill(userCredentials.firstName) + await page.locator('input#lastName').fill(userCredentials.lastName) + await page.locator('input#phone').fill(userCredentials.phone) + await page.locator('input#address1').fill(userCredentials.address.street) + await page.locator('input#city').fill(userCredentials.address.city) + await page.locator('select#stateCode').selectOption(userCredentials.address.state) + await page.locator('input#postalCode').fill(userCredentials.address.zipcode) + + await page.getByRole('button', {name: /Continue to Shipping Method/i}).click() // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" - const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']"); + const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']") - await expect(step1Card.getByRole("button", { name: /Edit/i })).toBeVisible(); + await expect(step1Card.getByRole('button', {name: /Edit/i})).toBeVisible() - await expect( - page.getByRole("heading", { name: /Shipping & Gift Options/i }) - ).toBeVisible(); + await expect(page.getByRole('heading', {name: /Shipping & Gift Options/i})).toBeVisible() try { // sometimes the shipping & gifts section gets skipped // so there is no 'Continue to payment' button available - const continueToPayment = page.getByRole("button", { + const continueToPayment = page.getByRole('button', { name: /Continue to Payment/i - }); - await expect(continueToPayment).toBeVisible({ timeout: 2000 }); - await continueToPayment.click(); - } catch { - - } + }) + await expect(continueToPayment).toBeVisible({timeout: 2000}) + await continueToPayment.click() + } catch {} - await expect(page.getByRole("heading", { name: /Payment/i })).toBeVisible(); - const creditCardExpiry = getCreditCardExpiry(); + await expect(page.getByRole('heading', {name: /Payment/i})).toBeVisible() + const creditCardExpiry = getCreditCardExpiry() - await page.locator("input#number").fill("4111111111111111"); - await page.locator("input#holder").fill("John Doe"); - await page.locator("input#expiry").fill(creditCardExpiry); - await page.locator("input#securityCode").fill("213"); + await page.locator('input#number').fill('4111111111111111') + await page.locator('input#holder').fill('John Doe') + await page.locator('input#expiry').fill(creditCardExpiry) + await page.locator('input#securityCode').fill('213') - await page.getByRole("button", { name: /Review Order/i }).click(); + await page.getByRole('button', {name: /Review Order/i}).click() - page - .getByRole("button", { name: /Place Order/i }) + page.getByRole('button', {name: /Place Order/i}) .first() - .click(); + .click() // order confirmation - const orderConfirmationHeading = page.getByRole("heading", { - name: /Thank you for your order!/i, - }); - await orderConfirmationHeading.waitFor(); + const orderConfirmationHeading = page.getByRole('heading', { + name: /Thank you for your order!/i + }) + await orderConfirmationHeading.waitFor() } diff --git a/e2e/scripts/utils.js b/e2e/scripts/utils.js index 42dfe801e9..b81fd171e0 100644 --- a/e2e/scripts/utils.js +++ b/e2e/scripts/utils.js @@ -4,34 +4,33 @@ * SPDX-License-Identifier: BSD-3-Clause * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { types } = require("util"); -const fs = require("fs"); -const promisify = require("util").promisify; -const statAsync = promisify(fs.stat); -const mkdirAsync = promisify(fs.mkdir); +const {types} = require('util') +const fs = require('fs') +const promisify = require('util').promisify +const statAsync = promisify(fs.stat) +const mkdirAsync = promisify(fs.mkdir) const isPrompt = (streamData, expectedText) => { - if (!streamData || !expectedText) return false; + if (!streamData || !expectedText) return false - if (types.isRegExp(expectedText)) { - return streamData.match(expectedText); - } else return streamData.includes(expectedText); -}; + if (types.isRegExp(expectedText)) { + return streamData.match(expectedText) + } else return streamData.includes(expectedText) +} -const mkdirIfNotExists = (dirname) => - statAsync(dirname).catch(() => mkdirAsync(dirname)); +const mkdirIfNotExists = (dirname) => statAsync(dirname).catch(() => mkdirAsync(dirname)) const diffArrays = (expectedArr, actualArr) => { - const actualSet = new Set(actualArr); - return [...expectedArr].filter((x) => !actualSet.has(x)); -}; + const actualSet = new Set(actualArr) + return [...expectedArr].filter((x) => !actualSet.has(x)) +} const getCreditCardExpiry = (yearsFromNow = 5) => { - const padMonth = "00"; - return `${(padMonth + (new Date().getMonth() + 1)).slice(-padMonth.length)}/${ - (new Date().getFullYear() % 100) + parseInt(yearsFromNow) - }`; -}; + const padMonth = '00' + return `${(padMonth + (new Date().getMonth() + 1)).slice(-padMonth.length)}/${ + (new Date().getFullYear() % 100) + parseInt(yearsFromNow) + }` +} /** * Generates a random string of given length containing uppercase letters, lowercase letters and numbers. @@ -39,19 +38,16 @@ const getCreditCardExpiry = (yearsFromNow = 5) => { * @returns Randomly generated alphanumeric string. */ const generateRandomString = function (length) { - let randomString = ""; - const characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const charactersLength = characters.length; - let counter = 0; - while (counter < length) { - randomString += characters.charAt( - Math.floor(Math.random() * charactersLength) - ); - counter += 1; - } - return randomString; -}; + let randomString = '' + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + const charactersLength = characters.length + let counter = 0 + while (counter < length) { + randomString += characters.charAt(Math.floor(Math.random() * charactersLength)) + counter += 1 + } + return randomString +} /** * Generates a random valid phone number string @@ -59,48 +55,46 @@ const generateRandomString = function (length) { * @returns Randomly generated numeric string. */ const generateRandomNumericString = function (length) { - // US Phone numbers must have the format NXX NXX-XXXX - // where N cannot be 0 or 1. - // The area code cannot have 9 in the 2nd digit - // The middle 3 digits cannot be N11 + // US Phone numbers must have the format NXX NXX-XXXX + // where N cannot be 0 or 1. + // The area code cannot have 9 in the 2nd digit + // The middle 3 digits cannot be N11 - let randomPhone = ""; - const validNumbers = "23456789"; // exclude 0 or 1 to keep things simple - const validNumbersLength = validNumbers.length; - let counter = 0; - while (counter < length) { - randomPhone += validNumbers.charAt( - Math.floor(Math.random() * validNumbersLength) - ); - counter += 1; - } - return randomPhone; -}; + let randomPhone = '' + const validNumbers = '23456789' // exclude 0 or 1 to keep things simple + const validNumbersLength = validNumbers.length + let counter = 0 + while (counter < length) { + randomPhone += validNumbers.charAt(Math.floor(Math.random() * validNumbersLength)) + counter += 1 + } + return randomPhone +} /** * Generates a random user object containing firstName, lastName, phone, email and password based on locale (Supports en_US and en_GB only). * @returns Object containing randomly generated user data. */ const generateUserCredentials = function () { - const user = {}; - user.firstName = generateRandomString(8); - user.lastName = generateRandomString(8); - user.phone = "857" + generateRandomNumericString(7); - user.email = (generateRandomString(12) + "@domain.com").toLowerCase(); - user.password = generateRandomString(15) + "Ab1!%&*$#@^+:;=?"; - user.address = {} - user.address.street = generateRandomString(10); - user.address.city = "Burlington"; - user.address.state = "MA"; - user.address.zipcode = "02" + generateRandomNumericString(3); + const user = {} + user.firstName = generateRandomString(8) + user.lastName = generateRandomString(8) + user.phone = '857' + generateRandomNumericString(7) + user.email = (generateRandomString(12) + '@domain.com').toLowerCase() + user.password = generateRandomString(15) + 'Ab1!%&*$#@^+:;=?' + user.address = {} + user.address.street = generateRandomString(10) + user.address.city = 'Burlington' + user.address.state = 'MA' + user.address.zipcode = '02' + generateRandomNumericString(3) - return user; -}; + return user +} module.exports = { - isPrompt, - mkdirIfNotExists, - diffArrays, - getCreditCardExpiry, - generateUserCredentials, -}; + isPrompt, + mkdirIfNotExists, + diffArrays, + getCreditCardExpiry, + generateUserCredentials +} diff --git a/e2e/scripts/validate-generated-project.js b/e2e/scripts/validate-generated-project.js index 5ddeccebe4..ce00f8bf98 100644 --- a/e2e/scripts/validate-generated-project.js +++ b/e2e/scripts/validate-generated-project.js @@ -5,89 +5,76 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { program, Argument } = require("commander"); -const { diffArrays } = require("./utils.js"); -const fs = require("fs"); -const config = require("../config.js"); -const path = require("path"); +const {program, Argument} = require('commander') +const {diffArrays} = require('./utils.js') +const fs = require('fs') +const config = require('../config.js') +const path = require('path') const validateGeneratedArtifacts = async (project) => { - const generatedProjectDirPath = path.join( - process.cwd(), - config.GENERATED_PROJECTS_DIR, - project - ); - const generatedArtifacts = await fs.readdirSync(generatedProjectDirPath); + const generatedProjectDirPath = path.join(process.cwd(), config.GENERATED_PROJECTS_DIR, project) + const generatedArtifacts = await fs.readdirSync(generatedProjectDirPath) - return new Promise((resolve, reject) => { - const missingArtifacts = diffArrays( - config.EXPECTED_GENERATED_ARTIFACTS[project], - generatedArtifacts - ); - if (missingArtifacts && missingArtifacts.length > 0) { - reject( - `Generated project (${project}) is missing one or more artifacts: ${missingArtifacts}` - ); - } else { - resolve(`Successfully validated generated artifacts for: ${project} `); - } - }); -}; + return new Promise((resolve, reject) => { + const missingArtifacts = diffArrays( + config.EXPECTED_GENERATED_ARTIFACTS[project], + generatedArtifacts + ) + if (missingArtifacts && missingArtifacts.length > 0) { + reject( + `Generated project (${project}) is missing one or more artifacts: ${missingArtifacts}` + ) + } else { + resolve(`Successfully validated generated artifacts for: ${project} `) + } + }) +} const validateExtensibilityConfig = async (project) => { - const pkgPath = path.join( - process.cwd(), - config.GENERATED_PROJECTS_DIR, - project, - "package.json" - ); - const pkg = require(pkgPath); - return new Promise((resolve, reject) => { - if ( - pkg.hasOwnProperty("ccExtensibility") && - pkg["ccExtensibility"].hasOwnProperty("extends") && - pkg["ccExtensibility"].hasOwnProperty("overridesDir") && - pkg["ccExtensibility"].extends === "@salesforce/retail-react-app" && - pkg["ccExtensibility"].overridesDir === "overrides" - ) { - resolve(`Successfully validated extensibility config for ${project}`); - } - reject( - `Generated project ${project} is missing extensibility config in package.json` - ); - }); -}; + const pkgPath = path.join(process.cwd(), config.GENERATED_PROJECTS_DIR, project, 'package.json') + const pkg = require(pkgPath) + return new Promise((resolve, reject) => { + if ( + pkg.hasOwnProperty('ccExtensibility') && + pkg['ccExtensibility'].hasOwnProperty('extends') && + pkg['ccExtensibility'].hasOwnProperty('overridesDir') && + pkg['ccExtensibility'].extends === '@salesforce/retail-react-app' && + pkg['ccExtensibility'].overridesDir === 'overrides' + ) { + resolve(`Successfully validated extensibility config for ${project}`) + } + reject(`Generated project ${project} is missing extensibility config in package.json`) + }) +} const main = async (opts) => { - const { args } = opts; - const [project] = args; - if (opts.args.length !== 1) { - console.log(program.helpInformation()); - process.exit(1); - } + const {args} = opts + const [project] = args + if (opts.args.length !== 1) { + console.log(program.helpInformation()) + process.exit(1) + } - try { - console.log(await validateGeneratedArtifacts(project)); - if (project === "retail-app-ext" || project === "retail-app-ext") { - console.log(await validateExtensibilityConfig(project)); + try { + console.log(await validateGeneratedArtifacts(project)) + if (project === 'retail-app-ext' || project === 'retail-app-ext') { + console.log(await validateExtensibilityConfig(project)) + } + } catch (err) { + console.error(err) } - } catch (err) { - console.error(err); - } -}; +} -program.description( - `Validate project generated by generator using the key ` -); +program.description(`Validate project generated by generator using the key `) program.addArgument( - new Argument("", "project key").choices([ - "retail-app-demo", - "retail-app-ext", - "retail-app-no-ext", - ]) -); + new Argument('', 'project key').choices([ + 'retail-app-demo', + 'retail-app-ext', + 'retail-app-no-ext' + ]) +) -program.parse(process.argv); +program.parse(process.argv) -main(program); +main(program) diff --git a/e2e/tests/desktop/dnt.spec.js b/e2e/tests/desktop/dnt.spec.js index 538b0115c6..4fc8b67557 100644 --- a/e2e/tests/desktop/dnt.spec.js +++ b/e2e/tests/desktop/dnt.spec.js @@ -5,72 +5,71 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const config = require("../../config.js"); -const { - generateUserCredentials -} = require("../../scripts/utils.js"); -const { - registerShopper -} = require("../../scripts/pageHelpers.js") +const {test, expect} = require('@playwright/test') +const config = require('../../config.js') +const {generateUserCredentials} = require('../../scripts/utils.js') +const {registerShopper} = require('../../scripts/pageHelpers.js') -const REGISTERED_USER_CREDENTIALS = generateUserCredentials(); +const REGISTERED_USER_CREDENTIALS = generateUserCredentials() const checkDntCookie = async (page, expectedValue) => { - var cookies = await page.context().cookies(); - var cookieName = 'dw_dnt'; - var cookie = cookies.find(cookie => cookie.name === cookieName); - expect(cookie).toBeTruthy(); - expect(cookie.value).toBe(expectedValue); + var cookies = await page.context().cookies() + var cookieName = 'dw_dnt' + var cookie = cookies.find((cookie) => cookie.name === cookieName) + expect(cookie).toBeTruthy() + expect(cookie.value).toBe(expectedValue) } -test("Shopper can use the consent tracking form", async ({ page }) => { - await page.context().clearCookies(); +test('Shopper can use the consent tracking form', async ({page}) => { + await page.context().clearCookies() - await page.goto(config.RETAIL_APP_HOME); + await page.goto(config.RETAIL_APP_HOME) - const modalSelector = '[aria-label="Close consent tracking form"]' - page.locator(modalSelector).waitFor() - await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}); - - // Decline Tracking - const declineButton = page.locator('button:visible', { hasText: 'Decline' }); - await expect(declineButton).toBeVisible(); - await declineButton.click(); - await page.waitForTimeout(5000); - - // Intercept einstein request - let apiCallsMade = false; - await page.route('https://api.cquotient.com/v3/activities/aaij-MobileFirst/viewCategory', (route) => { - apiCallsMade = true; - route.continue(); - }); + const modalSelector = '[aria-label="Close consent tracking form"]' + page.locator(modalSelector).waitFor() + await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}) - await checkDntCookie(page, '1') + // Decline Tracking + const declineButton = page.locator('button:visible', {hasText: 'Decline'}) + await expect(declineButton).toBeVisible() + await declineButton.click() + await page.waitForTimeout(5000) - // Trigger einstein events - await page.click('text=Womens'); - // Reloading the page after setting DNT makes the form not appear again - await page.reload() - await expect(page.getByText(/Tracking Consent/i)).toBeHidden(); - - // Registering after setting DNT persists the preference - await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}); - await checkDntCookie(page, '1') - - // Logging out clears the preference - const buttons = await page.getByText(/Log Out/i).elementHandles(); - for (const button of buttons) { - if (await button.isVisible()) { - await button.click(); - break; + // Intercept einstein request + let apiCallsMade = false + await page.route( + 'https://api.cquotient.com/v3/activities/aaij-MobileFirst/viewCategory', + (route) => { + apiCallsMade = true + route.continue() + } + ) + + await checkDntCookie(page, '1') + + // Trigger einstein events + await page.click('text=Womens') + // Reloading the page after setting DNT makes the form not appear again + await page.reload() + await expect(page.getByText(/Tracking Consent/i)).toBeHidden() + + // Registering after setting DNT persists the preference + await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}) + await checkDntCookie(page, '1') + + // Logging out clears the preference + const buttons = await page.getByText(/Log Out/i).elementHandles() + for (const button of buttons) { + if (await button.isVisible()) { + await button.click() + break + } + } + var cookies = await page.context().cookies() + if (cookies.some((item) => item.name === 'dw_dnt')) { + throw new Error('dw_dnt still exists in the cookies') } - } - var cookies = await page.context().cookies(); - if (cookies.some(item => item.name === "dw_dnt")) { - throw new Error('dw_dnt still exists in the cookies'); - } - await page.reload(); - await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}); - expect(apiCallsMade).toBe(false); -}); + await page.reload() + await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}) + expect(apiCallsMade).toBe(false) +}) diff --git a/e2e/tests/desktop/guest-shopper.spec.js b/e2e/tests/desktop/guest-shopper.spec.js index 29dcf7c325..a41f098e25 100644 --- a/e2e/tests/desktop/guest-shopper.spec.js +++ b/e2e/tests/desktop/guest-shopper.spec.js @@ -5,126 +5,110 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const { - generateUserCredentials, -} = require("../../scripts/utils.js"); -const { addProductToCart, searchProduct, checkoutProduct } = require("../../scripts/pageHelpers.js") +const {test, expect} = require('@playwright/test') +const {generateUserCredentials} = require('../../scripts/utils.js') +const {addProductToCart, searchProduct, checkoutProduct} = require('../../scripts/pageHelpers.js') -const GUEST_USER_CREDENTIALS = generateUserCredentials(); +const GUEST_USER_CREDENTIALS = generateUserCredentials() /** * Test that guest shoppers can add a product to cart and go through the entire checkout process, * validating that shopper is able to get to the order summary section */ -test("Guest shopper can checkout items as guest", async ({ page }) => { - await addProductToCart({page}) +test('Guest shopper can checkout items as guest', async ({page}) => { + await addProductToCart({page}) - // cart - await page.getByLabel(/My cart/i).click(); + // cart + await page.getByLabel(/My cart/i).click() - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS }); + await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS}) - await expect( - page.getByRole("heading", { name: /Order Summary/i }) - ).toBeVisible(); - await expect(page.getByText(/2 Items/i)).toBeVisible(); - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); -}); + await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() + await expect(page.getByText(/2 Items/i)).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() +}) /** * Test that guest shoppers can use the product edit modal on cart page */ -test("Guest shopper can edit product item in cart", async ({ page }) => { - await addProductToCart({page}); +test('Guest shopper can edit product item in cart', async ({page}) => { + await addProductToCart({page}) - // cart - await page.getByLabel(/My cart/i).click(); - await page.waitForLoadState(); + // cart + await page.getByLabel(/My cart/i).click() + await page.waitForLoadState() - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await expect(page.getByText(/Color: Black/i)).toBeVisible(); - await expect(page.getByText(/Size: L/i)).toBeVisible(); + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() - // open product edit modal - const editBtn = page.getByRole("button", { name: /Edit/i }); - await editBtn.waitFor(); + // open product edit modal + const editBtn = page.getByRole('button', {name: /Edit/i}) + await editBtn.waitFor() - expect(editBtn).toBeAttached(); + expect(editBtn).toBeAttached() - await editBtn.click(); - await page.waitForLoadState(); + await editBtn.click() + await page.waitForLoadState() - // Product edit modal should be open - await expect(page.getByTestId('product-view')).toBeVisible(); - - await page.getByRole("radio", { name: "S", exact: true }).click(); - await page.getByRole("radio", { name: "Meadow Violet", exact: true }).click(); - await page.getByRole("button", { name: /Update/i }).click(); + // Product edit modal should be open + await expect(page.getByTestId('product-view')).toBeVisible() - await page.waitForLoadState(); - await expect(page.getByText(/Color: Meadow Violet/i)).toBeVisible(); - await expect(page.getByText(/Size: S/i)).toBeVisible(); -}); + await page.getByRole('radio', {name: 'S', exact: true}).click() + await page.getByRole('radio', {name: 'Meadow Violet', exact: true}).click() + await page.getByRole('button', {name: /Update/i}).click() + + await page.waitForLoadState() + await expect(page.getByText(/Color: Meadow Violet/i)).toBeVisible() + await expect(page.getByText(/Size: S/i)).toBeVisible() +}) /** * Test that guest shoppers can add product bundle to cart and successfully checkout */ -test("Guest shopper can checkout product bundle", async ({ page }) => { - await searchProduct({page, query: 'bundle'}); - - await page.getByRole("link", { - name: /Turquoise Jewelry Bundle/i, - }).click(); - - await page.waitForLoadState(); - - await expect( - page.getByRole("heading", { name: /Turquoise Jewelry Bundle/i }) - ).toBeVisible(); - - await page.getByRole("button", { name: /Add Bundle to Cart/i }).click(); - - const addedToCartModal = page.getByText(/1 item added to cart/i); - await addedToCartModal.waitFor(); - await page.getByLabel("Close").click(); - - await page.getByLabel(/My cart/i).click(); - await page.waitForLoadState(); - - await expect( - page.getByRole("heading", { name: /Turquoise Jewelry Bundle/i }) - ).toBeVisible(); - - // bundle child selections with all color gold - await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible(); - - const qtyText = page.locator('text="Qty: 1"'); - const colorGoldText = page.locator('text="Color: Gold"'); - await expect(colorGoldText).toHaveCount(3); - await expect(qtyText).toHaveCount(3); - - await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS }); - - await expect( - page.getByRole("heading", { name: /Order Summary/i }) - ).toBeVisible(); - await expect(page.getByText(/1 Item/i)).toBeVisible(); - await expect( - page.getByRole("link", { name: /Turquoise Jewelry Bundle/i }) - ).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible(); -}); +test('Guest shopper can checkout product bundle', async ({page}) => { + await searchProduct({page, query: 'bundle'}) + + await page + .getByRole('link', { + name: /Turquoise Jewelry Bundle/i + }) + .click() + + await page.waitForLoadState() + + await expect(page.getByRole('heading', {name: /Turquoise Jewelry Bundle/i})).toBeVisible() + + await page.getByRole('button', {name: /Add Bundle to Cart/i}).click() + + const addedToCartModal = page.getByText(/1 item added to cart/i) + await addedToCartModal.waitFor() + await page.getByLabel('Close').click() + + await page.getByLabel(/My cart/i).click() + await page.waitForLoadState() + + await expect(page.getByRole('heading', {name: /Turquoise Jewelry Bundle/i})).toBeVisible() + + // bundle child selections with all color gold + await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible() + + const qtyText = page.locator('text="Qty: 1"') + const colorGoldText = page.locator('text="Color: Gold"') + await expect(colorGoldText).toHaveCount(3) + await expect(qtyText).toHaveCount(3) + + await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS}) + + await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() + await expect(page.getByText(/1 Item/i)).toBeVisible() + await expect(page.getByRole('link', {name: /Turquoise Jewelry Bundle/i})).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible() +}) diff --git a/e2e/tests/desktop/registered-shopper.spec.js b/e2e/tests/desktop/registered-shopper.spec.js index 6b9f731a0e..0a37318352 100644 --- a/e2e/tests/desktop/registered-shopper.spec.js +++ b/e2e/tests/desktop/registered-shopper.spec.js @@ -5,190 +5,154 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const config = require("../../config"); +const {test, expect} = require('@playwright/test') +const config = require('../../config') const { - addProductToCart, - registerShopper, - validateOrderHistory, - validateWishlist, - loginShopper, - navigateToPDPDesktop, -} = require("../../scripts/pageHelpers"); -const { - generateUserCredentials, - getCreditCardExpiry, -} = require("../../scripts/utils.js"); + addProductToCart, + registerShopper, + validateOrderHistory, + validateWishlist, + loginShopper, + navigateToPDPDesktop +} = require('../../scripts/pageHelpers') +const {generateUserCredentials, getCreditCardExpiry} = require('../../scripts/utils.js') -let registeredUserCredentials = {}; +let registeredUserCredentials = {} test.beforeAll(async () => { - // Generate credentials once and use throughout tests to avoid creating a new account - registeredUserCredentials = generateUserCredentials(); -}); + // Generate credentials once and use throughout tests to avoid creating a new account + registeredUserCredentials = generateUserCredentials() +}) /** * Test that registered shoppers can add a product to cart and go through the entire checkout process, * validating that shopper is able to get to the order summary section, * and that order shows up in order history */ -test("Registered shopper can checkout items", async ({ page }) => { - // Since we're re-using the same account, we need to check if the user is already registered. - // This ensures the tests are independent and not dependent on the order they are run in. - const isLoggedIn = await loginShopper({ - page, - userCredentials: registeredUserCredentials, - }); - - if (!isLoggedIn) { - await registerShopper({ - page, - userCredentials: registeredUserCredentials, - }); - } - - await expect( - page.getByRole("heading", { name: /Account Details/i }) - ).toBeVisible(); - - // Shop for items as registered user - await addProductToCart({ page }); - - // cart - await page.getByLabel(/My cart/i).click(); - - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - - await page.getByRole("link", { name: "Proceed to Checkout" }).click(); - - // Confirm the email input toggles to show sign out button on clicking "Checkout as guest" - const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']"); - - await expect( - step0Card.getByRole("button", { name: /Sign Out/i }) - ).toBeVisible(); - - await expect( - page.getByRole("heading", { name: /Shipping Address/i }) - ).toBeVisible(); - - await page - .locator("input#firstName") - .fill(registeredUserCredentials.firstName); - await page - .locator("input#lastName") - .fill(registeredUserCredentials.lastName); - await page.locator("input#phone").fill(registeredUserCredentials.phone); - await page - .locator("input#address1") - .fill(registeredUserCredentials.address.street); - await page - .locator("input#city") - .fill(registeredUserCredentials.address.city); - await page - .locator("select#stateCode") - .selectOption(registeredUserCredentials.address.state); - await page - .locator("input#postalCode") - .fill(registeredUserCredentials.address.zipcode); - - await page - .getByRole("button", { name: /Continue to Shipping Method/i }) - .click(); - - // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" - const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']"); - - await expect(step1Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - - await expect( - page.getByRole("heading", { name: /Shipping & Gift Options/i }) - ).toBeVisible(); - await page.waitForLoadState(); - - const continueToPayment = page.getByRole("button", { - name: /Continue to Payment/i, - }); - - if (continueToPayment.isEnabled()) { - await continueToPayment.click(); - } - - // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" - const step2Card = page.locator("div[data-testid='sf-toggle-card-step-2']"); - - await expect(step2Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - - await expect(page.getByRole("heading", { name: /Payment/i })).toBeVisible(); - - const creditCardExpiry = getCreditCardExpiry(); - - await page.locator("input#number").fill("4111111111111111"); - await page.locator("input#holder").fill("John Doe"); - await page.locator("input#expiry").fill(creditCardExpiry); - await page.locator("input#securityCode").fill("213"); - - await page.getByRole("button", { name: /Review Order/i }).click(); - - // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" - const step3Card = page.locator("div[data-testid='sf-toggle-card-step-3']"); - - await expect(step3Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - page - .getByRole("button", { name: /Place Order/i }) - .first() - .click(); - - const orderConfirmationHeading = page.getByRole("heading", { - name: /Thank you for your order!/i, - }); - await orderConfirmationHeading.waitFor(); - - await expect( - page.getByRole("heading", { name: /Order Summary/i }) - ).toBeVisible(); - await expect(page.getByText(/2 Items/i)).toBeVisible(); - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - - // order history - await validateOrderHistory({ page }); -}); +test('Registered shopper can checkout items', async ({page}) => { + // Since we're re-using the same account, we need to check if the user is already registered. + // This ensures the tests are independent and not dependent on the order they are run in. + const isLoggedIn = await loginShopper({ + page, + userCredentials: registeredUserCredentials + }) + + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: registeredUserCredentials + }) + } + + await expect(page.getByRole('heading', {name: /Account Details/i})).toBeVisible() + + // Shop for items as registered user + await addProductToCart({page}) + + // cart + await page.getByLabel(/My cart/i).click() + + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + + await page.getByRole('link', {name: 'Proceed to Checkout'}).click() + + // Confirm the email input toggles to show sign out button on clicking "Checkout as guest" + const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']") + + await expect(step0Card.getByRole('button', {name: /Sign Out/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Shipping Address/i})).toBeVisible() + + await page.locator('input#firstName').fill(registeredUserCredentials.firstName) + await page.locator('input#lastName').fill(registeredUserCredentials.lastName) + await page.locator('input#phone').fill(registeredUserCredentials.phone) + await page.locator('input#address1').fill(registeredUserCredentials.address.street) + await page.locator('input#city').fill(registeredUserCredentials.address.city) + await page.locator('select#stateCode').selectOption(registeredUserCredentials.address.state) + await page.locator('input#postalCode').fill(registeredUserCredentials.address.zipcode) + + await page.getByRole('button', {name: /Continue to Shipping Method/i}).click() + + // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" + const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']") + + await expect(step1Card.getByRole('button', {name: /Edit/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Shipping & Gift Options/i})).toBeVisible() + await page.waitForLoadState() + + const continueToPayment = page.getByRole('button', { + name: /Continue to Payment/i + }) + + if (continueToPayment.isEnabled()) { + await continueToPayment.click() + } + + // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" + const step2Card = page.locator("div[data-testid='sf-toggle-card-step-2']") + + await expect(step2Card.getByRole('button', {name: /Edit/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Payment/i})).toBeVisible() + + const creditCardExpiry = getCreditCardExpiry() + + await page.locator('input#number').fill('4111111111111111') + await page.locator('input#holder').fill('John Doe') + await page.locator('input#expiry').fill(creditCardExpiry) + await page.locator('input#securityCode').fill('213') + + await page.getByRole('button', {name: /Review Order/i}).click() + + // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" + const step3Card = page.locator("div[data-testid='sf-toggle-card-step-3']") + + await expect(step3Card.getByRole('button', {name: /Edit/i})).toBeVisible() + page.getByRole('button', {name: /Place Order/i}) + .first() + .click() + + const orderConfirmationHeading = page.getByRole('heading', { + name: /Thank you for your order!/i + }) + await orderConfirmationHeading.waitFor() + + await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() + await expect(page.getByText(/2 Items/i)).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + + // order history + await validateOrderHistory({page}) +}) /** * Test that registered shoppers can navigate to PDP and add a product to wishlist */ -test("Registered shopper can add item to wishlist", async ({ page }) => { - const isLoggedIn = await loginShopper({ - page, - userCredentials: registeredUserCredentials, - }); - - if (!isLoggedIn) { - await registerShopper({ - page, - userCredentials: registeredUserCredentials, - }); - } - - await expect( - page.getByRole("heading", { name: /Account Details/i }) - ).toBeVisible(); - - // Navigate to PDP - await navigateToPDPDesktop({ page }); - - // add product to wishlist - await expect( - page.getByRole("heading", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - - await page.getByRole("radio", { name: "L", exact: true }).click(); - await page.getByRole("button", { name: /Add to Wishlist/i }).click(); - - // wishlist - await validateWishlist({ page }); -}); +test('Registered shopper can add item to wishlist', async ({page}) => { + const isLoggedIn = await loginShopper({ + page, + userCredentials: registeredUserCredentials + }) + + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: registeredUserCredentials + }) + } + + await expect(page.getByRole('heading', {name: /Account Details/i})).toBeVisible() + + // Navigate to PDP + await navigateToPDPDesktop({page}) + + // add product to wishlist + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + + await page.getByRole('radio', {name: 'L', exact: true}).click() + await page.getByRole('button', {name: /Add to Wishlist/i}).click() + + // wishlist + await validateWishlist({page}) +}) diff --git a/e2e/tests/homepage.spec.js b/e2e/tests/homepage.spec.js index fe0687f9cd..b80e2265d1 100644 --- a/e2e/tests/homepage.spec.js +++ b/e2e/tests/homepage.spec.js @@ -5,26 +5,26 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const config = require("../config"); -const {answerConsentTrackingForm} = require("../scripts/pageHelpers.js") +const {test, expect} = require('@playwright/test') +const config = require('../config') +const {answerConsentTrackingForm} = require('../scripts/pageHelpers.js') -test.describe("Retail app home page loads", () => { - test.beforeEach(async ({ page }) => { - await page.goto(config.RETAIL_APP_HOME); - await answerConsentTrackingForm(page); - }); +test.describe('Retail app home page loads', () => { + test.beforeEach(async ({page}) => { + await page.goto(config.RETAIL_APP_HOME) + await answerConsentTrackingForm(page) + }) - test("has title", async ({ page }) => { - await expect(page).toHaveTitle(/Home Page/); - }); + test('has title', async ({page}) => { + await expect(page).toHaveTitle(/Home Page/) + }) - test("get started link", async ({ page }) => { - await page.getByRole("link", { name: "Get started" }).click(); + test('get started link', async ({page}) => { + await page.getByRole('link', {name: 'Get started'}).click() - const getStartedPage = await page.waitForEvent("popup"); - await getStartedPage.waitForLoadState(); + const getStartedPage = await page.waitForEvent('popup') + await getStartedPage.waitForLoadState() - await expect(getStartedPage).toHaveURL(/.*getting-started/); - }); -}); + await expect(getStartedPage).toHaveURL(/.*getting-started/) + }) +}) diff --git a/e2e/tests/mobile/dnt.spec.js b/e2e/tests/mobile/dnt.spec.js index 603ba5cfc9..05d7268d6c 100644 --- a/e2e/tests/mobile/dnt.spec.js +++ b/e2e/tests/mobile/dnt.spec.js @@ -5,88 +5,87 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const config = require("../../config.js"); -const { - generateUserCredentials -} = require("../../scripts/utils.js"); -const { - registerShopper -} = require("../../scripts/pageHelpers.js") +const {test, expect} = require('@playwright/test') +const config = require('../../config.js') +const {generateUserCredentials} = require('../../scripts/utils.js') +const {registerShopper} = require('../../scripts/pageHelpers.js') -const REGISTERED_USER_CREDENTIALS = generateUserCredentials(); +const REGISTERED_USER_CREDENTIALS = generateUserCredentials() const checkDntCookie = async (page, expectedValue) => { - var cookies = await page.context().cookies(); - var cookieName = 'dw_dnt'; - var cookie = cookies.find(cookie => cookie.name === cookieName); - expect(cookie).toBeTruthy(); - expect(cookie.value).toBe(expectedValue); + var cookies = await page.context().cookies() + var cookieName = 'dw_dnt' + var cookie = cookies.find((cookie) => cookie.name === cookieName) + expect(cookie).toBeTruthy() + expect(cookie.value).toBe(expectedValue) } -test("Shopper can use the consent tracking form", async ({ page }) => { - await page.context().clearCookies(); - - await page.goto(config.RETAIL_APP_HOME); - - const modalSelector = '[aria-label="Close consent tracking form"]' - page.locator(modalSelector).waitFor() - await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}); - - // Decline Tracking - const declineButton = page.locator('button:visible', { hasText: 'Decline' }); - await expect(declineButton).toBeVisible(); - await declineButton.click(); - - // Intercept einstein request - let apiCallsMade = false; - await page.route('https://api.cquotient.com/v3/activities/aaij-MobileFirst/viewCategory', (route) => { - apiCallsMade = true; - route.continue(); - }); - - await page.waitForTimeout(5000); - await checkDntCookie(page, '1') - - // Trigger einstein events - await page.getByLabel("Menu", { exact: true }).click(); - - // SSR nav loads top level categories as direct links so we wait till all sub-categories load in the accordion - const categoryAccordion = page.locator( - "#category-nav .chakra-accordion__button svg+:text('Womens')" - ); - await categoryAccordion.waitFor(); - - await page.getByRole("button", { name: "Womens" }).click(); - - const clothingNav = page.getByRole("button", { name: "Clothing" }); - - await clothingNav.waitFor(); - - await clothingNav.click(); - // Reloading the page after setting DNT makes the form not appear again - await page.reload() - await expect(page.getByText(/Tracking Consent/i)).toBeHidden(); - - // Registering after setting DNT persists the preference - await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}); - await checkDntCookie(page, '1') - - // Logging out clears the preference - await page.getByRole("heading", { name: /My Account/i }).click() - const buttons = await page.getByText(/Log Out/i).elementHandles(); - for (const button of buttons) { - if (await button.isVisible()) { - await button.click(); - break; +test('Shopper can use the consent tracking form', async ({page}) => { + await page.context().clearCookies() + + await page.goto(config.RETAIL_APP_HOME) + + const modalSelector = '[aria-label="Close consent tracking form"]' + page.locator(modalSelector).waitFor() + await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}) + + // Decline Tracking + const declineButton = page.locator('button:visible', {hasText: 'Decline'}) + await expect(declineButton).toBeVisible() + await declineButton.click() + + // Intercept einstein request + let apiCallsMade = false + await page.route( + 'https://api.cquotient.com/v3/activities/aaij-MobileFirst/viewCategory', + (route) => { + apiCallsMade = true + route.continue() + } + ) + + await page.waitForTimeout(5000) + await checkDntCookie(page, '1') + + // Trigger einstein events + await page.getByLabel('Menu', {exact: true}).click() + + // SSR nav loads top level categories as direct links so we wait till all sub-categories load in the accordion + const categoryAccordion = page.locator( + "#category-nav .chakra-accordion__button svg+:text('Womens')" + ) + await categoryAccordion.waitFor() + + await page.getByRole('button', {name: 'Womens'}).click() + + const clothingNav = page.getByRole('button', {name: 'Clothing'}) + + await clothingNav.waitFor() + + await clothingNav.click() + // Reloading the page after setting DNT makes the form not appear again + await page.reload() + await expect(page.getByText(/Tracking Consent/i)).toBeHidden() + + // Registering after setting DNT persists the preference + await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS}) + await checkDntCookie(page, '1') + + // Logging out clears the preference + await page.getByRole('heading', {name: /My Account/i}).click() + const buttons = await page.getByText(/Log Out/i).elementHandles() + for (const button of buttons) { + if (await button.isVisible()) { + await button.click() + break + } + } + + var cookies = await page.context().cookies() + if (cookies.some((item) => item.name === 'dw_dnt')) { + throw new Error('dw_dnt still exists in the cookies') } - } - - var cookies = await page.context().cookies(); - if (cookies.some(item => item.name === "dw_dnt")) { - throw new Error('dw_dnt still exists in the cookies'); - } - await page.reload(); - await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}); - expect(apiCallsMade).toBe(false); -}); + await page.reload() + await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000}) + expect(apiCallsMade).toBe(false) +}) diff --git a/e2e/tests/mobile/guest-shopper.spec.js b/e2e/tests/mobile/guest-shopper.spec.js index 38dbc849ed..cebc7c25fc 100644 --- a/e2e/tests/mobile/guest-shopper.spec.js +++ b/e2e/tests/mobile/guest-shopper.spec.js @@ -5,198 +5,166 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const config = require("../../config"); -const { addProductToCart, searchProduct, checkoutProduct } = require("../../scripts/pageHelpers"); -const { - generateUserCredentials, - getCreditCardExpiry, -} = require("../../scripts/utils.js"); +const {test, expect} = require('@playwright/test') +const config = require('../../config') +const {addProductToCart, searchProduct, checkoutProduct} = require('../../scripts/pageHelpers') +const {generateUserCredentials, getCreditCardExpiry} = require('../../scripts/utils.js') -const GUEST_USER_CREDENTIALS = generateUserCredentials(); +const GUEST_USER_CREDENTIALS = generateUserCredentials() /** * Test that guest shoppers can add a product to cart and go through the entire checkout process, * validating that shopper is able to get to the order summary section */ -test("Guest shopper can checkout items as guest", async ({ page }) => { - await addProductToCart({page, isMobile: true}) +test('Guest shopper can checkout items as guest', async ({page}) => { + await addProductToCart({page, isMobile: true}) - // Cart - await page.getByLabel(/My cart/i).click(); + // Cart + await page.getByLabel(/My cart/i).click() - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await page.getByRole("link", { name: "Proceed to Checkout" }).click(); + await page.getByRole('link', {name: 'Proceed to Checkout'}).click() - // Check out - await expect( - page.getByRole("heading", { name: /Contact Info/i }) - ).toBeVisible(); + // Check out + await expect(page.getByRole('heading', {name: /Contact Info/i})).toBeVisible() - await page.locator("input#email").fill("test@gmail.com"); + await page.locator('input#email').fill('test@gmail.com') - await page.getByRole("button", { name: /Checkout as guest/i }).click(); + await page.getByRole('button', {name: /Checkout as guest/i}).click() - // Confirm the email input toggles to show edit button on clicking "Checkout as guest" - const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']"); + // Confirm the email input toggles to show edit button on clicking "Checkout as guest" + const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']") - await expect(step0Card.getByRole("button", { name: /Edit/i })).toBeVisible(); + await expect(step0Card.getByRole('button', {name: /Edit/i})).toBeVisible() - await expect( - page.getByRole("heading", { name: /Shipping Address/i }) - ).toBeVisible(); + await expect(page.getByRole('heading', {name: /Shipping Address/i})).toBeVisible() - await page.locator("input#firstName").fill(GUEST_USER_CREDENTIALS.firstName); - await page.locator("input#lastName").fill(GUEST_USER_CREDENTIALS.lastName); - await page.locator("input#phone").fill(GUEST_USER_CREDENTIALS.phone); - await page - .locator("input#address1") - .fill(GUEST_USER_CREDENTIALS.address.street); - await page.locator("input#city").fill(GUEST_USER_CREDENTIALS.address.city); - await page - .locator("select#stateCode") - .selectOption(GUEST_USER_CREDENTIALS.address.state); - await page - .locator("input#postalCode") - .fill(GUEST_USER_CREDENTIALS.address.zipcode); + await page.locator('input#firstName').fill(GUEST_USER_CREDENTIALS.firstName) + await page.locator('input#lastName').fill(GUEST_USER_CREDENTIALS.lastName) + await page.locator('input#phone').fill(GUEST_USER_CREDENTIALS.phone) + await page.locator('input#address1').fill(GUEST_USER_CREDENTIALS.address.street) + await page.locator('input#city').fill(GUEST_USER_CREDENTIALS.address.city) + await page.locator('select#stateCode').selectOption(GUEST_USER_CREDENTIALS.address.state) + await page.locator('input#postalCode').fill(GUEST_USER_CREDENTIALS.address.zipcode) - await page - .getByRole("button", { name: /Continue to Shipping Method/i }) - .click(); + await page.getByRole('button', {name: /Continue to Shipping Method/i}).click() - // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" - const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']"); + // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" + const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']") - await expect(step1Card.getByRole("button", { name: /Edit/i })).toBeVisible(); + await expect(step1Card.getByRole('button', {name: /Edit/i})).toBeVisible() - await expect( - page.getByRole("heading", { name: /Shipping & Gift Options/i }) - ).toBeVisible(); - await page.waitForLoadState(); + await expect(page.getByRole('heading', {name: /Shipping & Gift Options/i})).toBeVisible() + await page.waitForLoadState() - const continueToPayment = page.getByRole("button", { - name: /Continue to Payment/i, - }); + const continueToPayment = page.getByRole('button', { + name: /Continue to Payment/i + }) - if (continueToPayment.isEnabled()) { - await continueToPayment.click(); - } + if (continueToPayment.isEnabled()) { + await continueToPayment.click() + } - await expect(page.getByRole("heading", { name: /Payment/i })).toBeVisible(); + await expect(page.getByRole('heading', {name: /Payment/i})).toBeVisible() - const creditCardExpiry = getCreditCardExpiry(); + const creditCardExpiry = getCreditCardExpiry() - await page.locator("input#number").fill("4111111111111111"); - await page.locator("input#holder").fill("John Doe"); - await page.locator("input#expiry").fill(creditCardExpiry); - await page.locator("input#securityCode").fill("213"); + await page.locator('input#number').fill('4111111111111111') + await page.locator('input#holder').fill('John Doe') + await page.locator('input#expiry').fill(creditCardExpiry) + await page.locator('input#securityCode').fill('213') - await page.getByRole("button", { name: /Review Order/i }).click(); + await page.getByRole('button', {name: /Review Order/i}).click() - // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" - const step3Card = page.locator("div[data-testid='sf-toggle-card-step-3']"); + // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" + const step3Card = page.locator("div[data-testid='sf-toggle-card-step-3']") - await expect(step3Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - page - .getByRole("button", { name: /Place Order/i }) - .first() - .click(); + await expect(step3Card.getByRole('button', {name: /Edit/i})).toBeVisible() + page.getByRole('button', {name: /Place Order/i}) + .first() + .click() - // Order confirmation - const orderConfirmationHeading = page.getByRole("heading", { - name: /Thank you for your order!/i, - }); - await orderConfirmationHeading.waitFor(); + // Order confirmation + const orderConfirmationHeading = page.getByRole('heading', { + name: /Thank you for your order!/i + }) + await orderConfirmationHeading.waitFor() - await expect( - page.getByRole("heading", { name: /Order Summary/i }) - ).toBeVisible(); - await expect(page.getByText(/2 Items/i)).toBeVisible(); - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); -}); + await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() + await expect(page.getByText(/2 Items/i)).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() +}) /** * Test that guest shoppers can use the product edit modal on cart page */ -test("Guest shopper can edit product item in cart", async ({ page }) => { - await addProductToCart({page, isMobile: true}); +test('Guest shopper can edit product item in cart', async ({page}) => { + await addProductToCart({page, isMobile: true}) - // Cart - await page.getByLabel(/My cart/i).click(); + // Cart + await page.getByLabel(/My cart/i).click() - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() - await expect(page.getByText(/Color: Black/i)).toBeVisible() - await expect(page.getByText(/Size: L/i)).toBeVisible() + await expect(page.getByText(/Color: Black/i)).toBeVisible() + await expect(page.getByText(/Size: L/i)).toBeVisible() - await page.getByRole("button", { name: "Edit" }).click(); - await expect(page.getByTestId('product-view')).toBeVisible() - - // update variant in product edit modal - await page.getByRole("radio", { name: "S", exact: true }).click(); - await page.getByRole("radio", { name: "Meadow Violet", exact: true }).click(); - await page.getByRole("button", { name: /Update/i }).click() + await page.getByRole('button', {name: 'Edit'}).click() + await expect(page.getByTestId('product-view')).toBeVisible() - await expect(page.getByText(/Color: Meadow Violet/i)).toBeVisible() - await expect(page.getByText(/Size: S/i)).toBeVisible() -}); + // update variant in product edit modal + await page.getByRole('radio', {name: 'S', exact: true}).click() + await page.getByRole('radio', {name: 'Meadow Violet', exact: true}).click() + await page.getByRole('button', {name: /Update/i}).click() + + await expect(page.getByText(/Color: Meadow Violet/i)).toBeVisible() + await expect(page.getByText(/Size: S/i)).toBeVisible() +}) /** * Test that guest shoppers can add product bundle to cart and successfully checkout */ -test("Guest shopper can checkout product bundle", async ({ page }) => { - await searchProduct({page, query: 'bundle', isMobile: true}); - - await page.getByRole("link", { - name: /Turquoise Jewelry Bundle/i, - }).click(); - - await page.waitForLoadState(); - - await expect( - page.getByRole("heading", { name: /Turquoise Jewelry Bundle/i }) - ).toBeVisible(); - - await page.getByRole("button", { name: /Add Bundle to Cart/i }).click(); - - const addedToCartModal = page.getByText(/1 item added to cart/i); - await addedToCartModal.waitFor(); - await page.getByLabel("Close").click(); - - await page.getByLabel(/My cart/i).click(); - await page.waitForLoadState(); - - await expect( - page.getByRole("heading", { name: /Turquoise Jewelry Bundle/i }) - ).toBeVisible(); - - // bundle child selections with all color gold - await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible(); - - const qtyText = page.locator('text="Qty: 1"'); - const colorGoldText = page.locator('text="Color: Gold"'); - await expect(colorGoldText).toHaveCount(3); - await expect(qtyText).toHaveCount(3); - - await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS }); - - await expect( - page.getByRole("heading", { name: /Order Summary/i }) - ).toBeVisible(); - await expect(page.getByText(/1 Item/i)).toBeVisible(); - await expect( - page.getByRole("link", { name: /Turquoise Jewelry Bundle/i }) - ).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible(); - await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible(); -}); +test('Guest shopper can checkout product bundle', async ({page}) => { + await searchProduct({page, query: 'bundle', isMobile: true}) + + await page + .getByRole('link', { + name: /Turquoise Jewelry Bundle/i + }) + .click() + + await page.waitForLoadState() + + await expect(page.getByRole('heading', {name: /Turquoise Jewelry Bundle/i})).toBeVisible() + + await page.getByRole('button', {name: /Add Bundle to Cart/i}).click() + + const addedToCartModal = page.getByText(/1 item added to cart/i) + await addedToCartModal.waitFor() + await page.getByLabel('Close').click() + + await page.getByLabel(/My cart/i).click() + await page.waitForLoadState() + + await expect(page.getByRole('heading', {name: /Turquoise Jewelry Bundle/i})).toBeVisible() + + // bundle child selections with all color gold + await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible() + + const qtyText = page.locator('text="Qty: 1"') + const colorGoldText = page.locator('text="Color: Gold"') + await expect(colorGoldText).toHaveCount(3) + await expect(qtyText).toHaveCount(3) + + await checkoutProduct({page, userCredentials: GUEST_USER_CREDENTIALS}) + + await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() + await expect(page.getByText(/1 Item/i)).toBeVisible() + await expect(page.getByRole('link', {name: /Turquoise Jewelry Bundle/i})).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Bracelet/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Necklace/i)).toBeVisible() + await expect(page.getByText(/Turquoise and Gold Hoop Earring/i)).toBeVisible() +}) diff --git a/e2e/tests/mobile/registered-shopper.spec.js b/e2e/tests/mobile/registered-shopper.spec.js index 7b95cea739..ac56f77cba 100644 --- a/e2e/tests/mobile/registered-shopper.spec.js +++ b/e2e/tests/mobile/registered-shopper.spec.js @@ -5,190 +5,154 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -const { test, expect } = require("@playwright/test"); -const config = require("../../config"); +const {test, expect} = require('@playwright/test') +const config = require('../../config') const { - registerShopper, - addProductToCart, - validateOrderHistory, - validateWishlist, - loginShopper, - navigateToPDPMobile, -} = require("../../scripts/pageHelpers"); -const { - generateUserCredentials, - getCreditCardExpiry, -} = require("../../scripts/utils.js"); + registerShopper, + addProductToCart, + validateOrderHistory, + validateWishlist, + loginShopper, + navigateToPDPMobile +} = require('../../scripts/pageHelpers') +const {generateUserCredentials, getCreditCardExpiry} = require('../../scripts/utils.js') -let registeredUserCredentials = {}; +let registeredUserCredentials = {} test.beforeAll(async () => { - // Generate credentials once and use throughout tests to avoid creating a new account - registeredUserCredentials = generateUserCredentials(); -}); + // Generate credentials once and use throughout tests to avoid creating a new account + registeredUserCredentials = generateUserCredentials() +}) /** * Test that registered shoppers can add a product to cart and go through the entire checkout process, * validating that shopper is able to get to the order summary section, * and that order shows up in order history */ -test("Registered shopper can checkout items", async ({page}) => { - // Since we're re-using the same account, we need to check if the user is already registered. - // This ensures the tests are independent and not dependent on the order they are run in. - const isLoggedIn = await loginShopper({ - page, - userCredentials: registeredUserCredentials, - }); - - if (!isLoggedIn) { - await registerShopper({ - page, - userCredentials: registeredUserCredentials, - isMobile: true, - }); - } - - await expect( - page.getByRole("heading", { name: /Account Details/i }) - ).toBeVisible(); - - // Shop for items as registered user - await addProductToCart({ page, isMobile: true }); - - // cart - await page.getByLabel(/My cart/i).click(); - - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - - await page.getByRole("link", { name: "Proceed to Checkout" }).click(); - - // Confirm the email input toggles to show sign out button on clicking "Checkout as guest" - const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']"); - - await expect( - step0Card.getByRole("button", { name: /Sign Out/i }) - ).toBeVisible(); - - await expect( - page.getByRole("heading", { name: /Shipping Address/i }) - ).toBeVisible(); - - await page - .locator("input#firstName") - .fill(registeredUserCredentials.firstName); - await page - .locator("input#lastName") - .fill(registeredUserCredentials.lastName); - await page.locator("input#phone").fill(registeredUserCredentials.phone); - await page - .locator("input#address1") - .fill(registeredUserCredentials.address.street); - await page - .locator("input#city") - .fill(registeredUserCredentials.address.city); - await page - .locator("select#stateCode") - .selectOption(registeredUserCredentials.address.state); - await page - .locator("input#postalCode") - .fill(registeredUserCredentials.address.zipcode); - - await page - .getByRole("button", { name: /Continue to Shipping Method/i }) - .click(); - - // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" - const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']"); - - await expect(step1Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - - await expect( - page.getByRole("heading", { name: /Shipping & Gift Options/i }) - ).toBeVisible(); - - await page.waitForLoadState(); - const continueToPayment = page.getByRole("button", { - name: /Continue to Payment/i, - }); - if (continueToPayment.isEnabled()) { - await continueToPayment.click(); - } - - // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" - const step2Card = page.locator("div[data-testid='sf-toggle-card-step-2']"); - - await expect(step2Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - - await expect(page.getByRole("heading", { name: /Payment/i })).toBeVisible(); - - const creditCardExpiry = getCreditCardExpiry(); - - await page.locator("input#number").fill("4111111111111111"); - await page.locator("input#holder").fill("John Doe"); - await page.locator("input#expiry").fill(creditCardExpiry); - await page.locator("input#securityCode").fill("213"); - - await page.getByRole("button", { name: /Review Order/i }).click(); - - // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" - const step3Card = page.locator("div[data-testid='sf-toggle-card-step-3']"); - - await expect(step3Card.getByRole("button", { name: /Edit/i })).toBeVisible(); - page - .getByRole("button", { name: /Place Order/i }) - .first() - .click(); - - const orderConfirmationHeading = page.getByRole("heading", { - name: /Thank you for your order!/i, - }); - await orderConfirmationHeading.waitFor(); - - await expect( - page.getByRole("heading", { name: /Order Summary/i }) - ).toBeVisible(); - await expect(page.getByText(/2 Items/i)).toBeVisible(); - await expect( - page.getByRole("link", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - - // order history - await validateOrderHistory({ page }); -}); +test('Registered shopper can checkout items', async ({page}) => { + // Since we're re-using the same account, we need to check if the user is already registered. + // This ensures the tests are independent and not dependent on the order they are run in. + const isLoggedIn = await loginShopper({ + page, + userCredentials: registeredUserCredentials + }) + + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: registeredUserCredentials, + isMobile: true + }) + } + + await expect(page.getByRole('heading', {name: /Account Details/i})).toBeVisible() + + // Shop for items as registered user + await addProductToCart({page, isMobile: true}) + + // cart + await page.getByLabel(/My cart/i).click() + + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + + await page.getByRole('link', {name: 'Proceed to Checkout'}).click() + + // Confirm the email input toggles to show sign out button on clicking "Checkout as guest" + const step0Card = page.locator("div[data-testid='sf-toggle-card-step-0']") + + await expect(step0Card.getByRole('button', {name: /Sign Out/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Shipping Address/i})).toBeVisible() + + await page.locator('input#firstName').fill(registeredUserCredentials.firstName) + await page.locator('input#lastName').fill(registeredUserCredentials.lastName) + await page.locator('input#phone').fill(registeredUserCredentials.phone) + await page.locator('input#address1').fill(registeredUserCredentials.address.street) + await page.locator('input#city').fill(registeredUserCredentials.address.city) + await page.locator('select#stateCode').selectOption(registeredUserCredentials.address.state) + await page.locator('input#postalCode').fill(registeredUserCredentials.address.zipcode) + + await page.getByRole('button', {name: /Continue to Shipping Method/i}).click() + + // Confirm the shipping details form toggles to show edit button on clicking "Checkout as guest" + const step1Card = page.locator("div[data-testid='sf-toggle-card-step-1']") + + await expect(step1Card.getByRole('button', {name: /Edit/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Shipping & Gift Options/i})).toBeVisible() + + await page.waitForLoadState() + const continueToPayment = page.getByRole('button', { + name: /Continue to Payment/i + }) + if (continueToPayment.isEnabled()) { + await continueToPayment.click() + } + + // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" + const step2Card = page.locator("div[data-testid='sf-toggle-card-step-2']") + + await expect(step2Card.getByRole('button', {name: /Edit/i})).toBeVisible() + + await expect(page.getByRole('heading', {name: /Payment/i})).toBeVisible() + + const creditCardExpiry = getCreditCardExpiry() + + await page.locator('input#number').fill('4111111111111111') + await page.locator('input#holder').fill('John Doe') + await page.locator('input#expiry').fill(creditCardExpiry) + await page.locator('input#securityCode').fill('213') + + await page.getByRole('button', {name: /Review Order/i}).click() + + // Confirm the shipping options form toggles to show edit button on clicking "Checkout as guest" + const step3Card = page.locator("div[data-testid='sf-toggle-card-step-3']") + + await expect(step3Card.getByRole('button', {name: /Edit/i})).toBeVisible() + page.getByRole('button', {name: /Place Order/i}) + .first() + .click() + + const orderConfirmationHeading = page.getByRole('heading', { + name: /Thank you for your order!/i + }) + await orderConfirmationHeading.waitFor() + + await expect(page.getByRole('heading', {name: /Order Summary/i})).toBeVisible() + await expect(page.getByText(/2 Items/i)).toBeVisible() + await expect(page.getByRole('link', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + + // order history + await validateOrderHistory({page}) +}) /** * Test that registered shoppers can navigate to PDP and add a product to wishlist */ -test("Registered shopper can add item to wishlist", async ({ page }) => { - const isLoggedIn = await loginShopper({ - page, - userCredentials: registeredUserCredentials, - }); - - if (!isLoggedIn) { - await registerShopper({ - page, - userCredentials: registeredUserCredentials, - isMobile: true, - }); - } - - await expect( - page.getByRole("heading", { name: /Account Details/i }) - ).toBeVisible(); - - // PDP - await navigateToPDPMobile({ page }); - - // add product to wishlist - await expect( - page.getByRole("heading", { name: /Cotton Turtleneck Sweater/i }) - ).toBeVisible(); - await page.getByRole("radio", { name: "L", exact: true }).click(); - await page.getByRole("button", { name: /Add to Wishlist/i }).click(); - - // wishlist - await validateWishlist({ page }); -}); +test('Registered shopper can add item to wishlist', async ({page}) => { + const isLoggedIn = await loginShopper({ + page, + userCredentials: registeredUserCredentials + }) + + if (!isLoggedIn) { + await registerShopper({ + page, + userCredentials: registeredUserCredentials, + isMobile: true + }) + } + + await expect(page.getByRole('heading', {name: /Account Details/i})).toBeVisible() + + // PDP + await navigateToPDPMobile({page}) + + // add product to wishlist + await expect(page.getByRole('heading', {name: /Cotton Turtleneck Sweater/i})).toBeVisible() + await page.getByRole('radio', {name: 'L', exact: true}).click() + await page.getByRole('button', {name: /Add to Wishlist/i}).click() + + // wishlist + await validateWishlist({page}) +})