Skip to content

Commit

Permalink
Merge pull request #2224 from SalesforceCommerceCloud/fixing-e2e-dnt
Browse files Browse the repository at this point in the history
@W-17682173 Fixing E2E tests failures
  • Loading branch information
jeremy-jung1 authored Jan 31, 2025
2 parents 0778438 + 1ed1d2a commit ecf7901
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 50 deletions.
51 changes: 39 additions & 12 deletions e2e/scripts/pageHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@ const { expect } = require("@playwright/test");
const config = require("../config");
const { getCreditCardExpiry } = require("../scripts/utils.js")

/**
* 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) {
var text = 'Accept'
if (dnt)
text = 'Decline'
const answerButton = 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
* with the black variant selected
Expand All @@ -11,6 +31,7 @@ const { getCreditCardExpiry } = require("../scripts/utils.js")
export const navigateToPDPMobile = async ({page}) => {
// Home page
await page.goto(config.RETAIL_APP_HOME);
await answerConsentTrackingForm(page)

await page.getByLabel("Menu", { exact: true }).click();

Expand Down Expand Up @@ -64,6 +85,7 @@ export const navigateToPDPMobile = async ({page}) => {
*/
export const navigateToPDPDesktop = async ({page}) => {
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 });
Expand Down Expand Up @@ -144,6 +166,7 @@ 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 answerConsentTrackingForm(page)

await page.waitForLoadState();

Expand All @@ -160,10 +183,13 @@ export const registerShopper = async ({page, userCredentials, isMobile = false})
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 page.waitForLoadState();
await tokenResponsePromise;
expect((await tokenResponsePromise).status()).toBe(200);

await expect(
page.getByRole("heading", { name: /Account Details/i })
Expand All @@ -186,6 +212,8 @@ export const registerShopper = async ({page, userCredentials, isMobile = false})
*/
export const validateOrderHistory = async ({page}) => {
await page.goto(config.RETAIL_APP_HOME + "/account/orders");
await answerConsentTrackingForm(page)

await expect(
page.getByRole("heading", { name: /Order History/i })
).toBeVisible();
Expand All @@ -209,6 +237,7 @@ export const validateOrderHistory = async ({page}) => {
*/
export const validateWishlist = async ({page}) => {
await page.goto(config.RETAIL_APP_HOME + "/account/wishlist");
await answerConsentTrackingForm(page)

await expect(
page.getByRole("heading", { name: /Wishlist/i })
Expand Down Expand Up @@ -236,19 +265,17 @@ export const validateWishlist = async ({page}) => {
export const loginShopper = async ({page, userCredentials}) => {
try {
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 tokenResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/token')
await page.getByRole("button", { name: /Sign In/i }).click();

await page.waitForLoadState();

// redirected to Account Details page after logging in
await expect(
page.getByRole("heading", { name: /Account Details/i })
).toBeVisible({ timeout: 2000 });
return true;
await tokenResponsePromise;
return await tokenResponsePromise.status() === 200;
} catch {
return false;
}
Expand All @@ -263,7 +290,7 @@ export const loginShopper = async ({page, userCredentials}) => {
*/
export const searchProduct = async ({page, query, isMobile = false}) => {
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..."]');
Expand Down
47 changes: 11 additions & 36 deletions e2e/tests/dnt.spec.js → e2e/tests/desktop/dnt.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,16 @@
*/

const { test, expect } = require("@playwright/test");
const config = require("../config");
const config = require("../../config.js");
const {
generateUserCredentials
} = require("../scripts/utils.js");
} = require("../../scripts/utils.js");
const {
registerShopper
} = require("../../scripts/pageHelpers.js")

const REGISTERED_USER_CREDENTIALS = generateUserCredentials();

const registerUser = async (page) => {
await page.goto(config.RETAIL_APP_HOME + "/registration");

const registrationFormHeading = page.getByText(/Let's get started!/i);
await registrationFormHeading.waitFor();

await page
.locator("input#firstName")
.fill(REGISTERED_USER_CREDENTIALS.firstName);
await page
.locator("input#lastName")
.fill(REGISTERED_USER_CREDENTIALS.lastName);
await page.locator("input#email").fill(REGISTERED_USER_CREDENTIALS.email);
await page
.locator("input#password")
.fill(REGISTERED_USER_CREDENTIALS.password);

await page.getByRole("button", { name: /Create Account/i }).click();

await expect(
page.getByRole("heading", { name: /Account Details/i })
).toBeVisible();

await expect(
page.getByRole("heading", { name: /My Account/i })
).toBeVisible();
}

const checkDntCookie = async (page, expectedValue) => {
var cookies = await page.context().cookies();
var cookieName = 'dw_dnt';
Expand All @@ -49,9 +24,9 @@ const checkDntCookie = async (page, expectedValue) => {
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"]'
Expand All @@ -62,16 +37,16 @@ test("Shopper can use the consent tracking form", async ({ page }) => {
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();
});

// The value of 1 comes from defaultDnt prop in _app-config/index.jsx
checkDntCookie(page, '1')

await checkDntCookie(page, '1')

// Trigger einstein events
await page.click('text=Womens');
Expand All @@ -80,8 +55,8 @@ test("Shopper can use the consent tracking form", async ({ page }) => {
await expect(page.getByText(/Tracking Consent/i)).toBeHidden();

// Registering after setting DNT persists the preference
await registerUser(page)
checkDntCookie(page, '1')
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();
Expand Down
2 changes: 1 addition & 1 deletion e2e/tests/desktop/registered-shopper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ test("Registered shopper can add item to wishlist", async ({ page }) => {
})

if(!isLoggedIn) {
await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS})
await registerShopper({page, userCredentials: generateUserCredentials() })
}

// Navigate to PDP
Expand Down
2 changes: 2 additions & 0 deletions e2e/tests/homepage.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@

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("has title", async ({ page }) => {
Expand Down
91 changes: 91 additions & 0 deletions e2e/tests/mobile/dnt.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2023, Salesforce, Inc.
* All rights reserved.
* 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 { 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 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);
}

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 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');
}
await page.reload();
await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000});
expect(apiCallsMade).toBe(false);
});
2 changes: 1 addition & 1 deletion e2e/tests/mobile/registered-shopper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ test("Registered shopper can add item to wishlist", async ({ page }) => {
if(!isLoggedIn) {
await registerShopper({
page,
userCredentials: REGISTERED_USER_CREDENTIALS,
userCredentials: generateUserCredentials(),
isMobile: true
})
}
Expand Down

0 comments on commit ecf7901

Please sign in to comment.