Skip to content

Commit ecf7901

Browse files
authored
Merge pull request #2224 from SalesforceCommerceCloud/fixing-e2e-dnt
@W-17682173 Fixing E2E tests failures
2 parents 0778438 + 1ed1d2a commit ecf7901

File tree

6 files changed

+145
-50
lines changed

6 files changed

+145
-50
lines changed

e2e/scripts/pageHelpers.js

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@ const { expect } = require("@playwright/test");
22
const config = require("../config");
33
const { getCreditCardExpiry } = require("../scripts/utils.js")
44

5+
/**
6+
* Give an answer to the consent tracking form.
7+
*
8+
* Note: the consent tracking form hovers over some elements in the app. This can cause a test to fail.
9+
* Run this function after a page.goto to release the form from view.
10+
*
11+
* @param {Object} page - Object that represents a tab/window in the browser provided by playwright
12+
* @param {Boolean} dnt - Do Not Track value to answer the form. False to enable tracking, True to disable tracking.
13+
*/
14+
export const answerConsentTrackingForm = async (page, dnt = false) => {
15+
if (await page.locator('text=Tracking Consent').count() > 0) {
16+
var text = 'Accept'
17+
if (dnt)
18+
text = 'Decline'
19+
const answerButton = page.locator('button:visible', { hasText: text });
20+
await expect(answerButton).toBeVisible();
21+
await answerButton.click();
22+
}
23+
}
24+
525
/**
626
* Navigates to the `Cotton Turtleneck Sweater` PDP (Product Detail Page) on mobile
727
* with the black variant selected
@@ -11,6 +31,7 @@ const { getCreditCardExpiry } = require("../scripts/utils.js")
1131
export const navigateToPDPMobile = async ({page}) => {
1232
// Home page
1333
await page.goto(config.RETAIL_APP_HOME);
34+
await answerConsentTrackingForm(page)
1435

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

@@ -64,6 +85,7 @@ export const navigateToPDPMobile = async ({page}) => {
6485
*/
6586
export const navigateToPDPDesktop = async ({page}) => {
6687
await page.goto(config.RETAIL_APP_HOME);
88+
await answerConsentTrackingForm(page)
6789

6890
await page.getByRole("link", { name: "Womens" }).hover();
6991
const topsNav = await page.getByRole("link", { name: "Tops", exact: true });
@@ -144,6 +166,7 @@ export const addProductToCart = async ({page, isMobile = false}) => {
144166
export const registerShopper = async ({page, userCredentials, isMobile = false}) => {
145167
// Create Account and Sign In
146168
await page.goto(config.RETAIL_APP_HOME + "/registration");
169+
await answerConsentTrackingForm(page)
147170

148171
await page.waitForLoadState();
149172

@@ -160,10 +183,13 @@ export const registerShopper = async ({page, userCredentials, isMobile = false})
160183
await page
161184
.locator("input#password")
162185
.fill(userCredentials.password);
163-
186+
187+
// Best Practice: await the network call and assert on the network response rather than waiting for pageLoadState()
188+
// to avoid race conditions from lock in pageLoadState being released before network call resolves
189+
const tokenResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/token')
164190
await page.getByRole("button", { name: /Create Account/i }).click();
165-
166-
await page.waitForLoadState();
191+
await tokenResponsePromise;
192+
expect((await tokenResponsePromise).status()).toBe(200);
167193

168194
await expect(
169195
page.getByRole("heading", { name: /Account Details/i })
@@ -186,6 +212,8 @@ export const registerShopper = async ({page, userCredentials, isMobile = false})
186212
*/
187213
export const validateOrderHistory = async ({page}) => {
188214
await page.goto(config.RETAIL_APP_HOME + "/account/orders");
215+
await answerConsentTrackingForm(page)
216+
189217
await expect(
190218
page.getByRole("heading", { name: /Order History/i })
191219
).toBeVisible();
@@ -209,6 +237,7 @@ export const validateOrderHistory = async ({page}) => {
209237
*/
210238
export const validateWishlist = async ({page}) => {
211239
await page.goto(config.RETAIL_APP_HOME + "/account/wishlist");
240+
await answerConsentTrackingForm(page)
212241

213242
await expect(
214243
page.getByRole("heading", { name: /Wishlist/i })
@@ -236,19 +265,17 @@ export const validateWishlist = async ({page}) => {
236265
export const loginShopper = async ({page, userCredentials}) => {
237266
try {
238267
await page.goto(config.RETAIL_APP_HOME + "/login");
268+
await answerConsentTrackingForm(page)
269+
239270
await page.locator("input#email").fill(userCredentials.email);
240271
await page
241272
.locator("input#password")
242273
.fill(userCredentials.password);
274+
275+
const tokenResponsePromise=page.waitForResponse('**/shopper/auth/v1/organizations/**/oauth2/token')
243276
await page.getByRole("button", { name: /Sign In/i }).click();
244-
245-
await page.waitForLoadState();
246-
247-
// redirected to Account Details page after logging in
248-
await expect(
249-
page.getByRole("heading", { name: /Account Details/i })
250-
).toBeVisible({ timeout: 2000 });
251-
return true;
277+
await tokenResponsePromise;
278+
return await tokenResponsePromise.status() === 200;
252279
} catch {
253280
return false;
254281
}
@@ -263,7 +290,7 @@ export const loginShopper = async ({page, userCredentials}) => {
263290
*/
264291
export const searchProduct = async ({page, query, isMobile = false}) => {
265292
await page.goto(config.RETAIL_APP_HOME);
266-
293+
await answerConsentTrackingForm(page)
267294
// For accessibility reasons, we have two search bars
268295
// one for desktop and one for mobile depending on your device type
269296
const searchInputs = page.locator('input[aria-label="Search for products..."]');

e2e/tests/dnt.spec.js renamed to e2e/tests/desktop/dnt.spec.js

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,16 @@
66
*/
77

88
const { test, expect } = require("@playwright/test");
9-
const config = require("../config");
9+
const config = require("../../config.js");
1010
const {
1111
generateUserCredentials
12-
} = require("../scripts/utils.js");
12+
} = require("../../scripts/utils.js");
13+
const {
14+
registerShopper
15+
} = require("../../scripts/pageHelpers.js")
1316

1417
const REGISTERED_USER_CREDENTIALS = generateUserCredentials();
1518

16-
const registerUser = async (page) => {
17-
await page.goto(config.RETAIL_APP_HOME + "/registration");
18-
19-
const registrationFormHeading = page.getByText(/Let's get started!/i);
20-
await registrationFormHeading.waitFor();
21-
22-
await page
23-
.locator("input#firstName")
24-
.fill(REGISTERED_USER_CREDENTIALS.firstName);
25-
await page
26-
.locator("input#lastName")
27-
.fill(REGISTERED_USER_CREDENTIALS.lastName);
28-
await page.locator("input#email").fill(REGISTERED_USER_CREDENTIALS.email);
29-
await page
30-
.locator("input#password")
31-
.fill(REGISTERED_USER_CREDENTIALS.password);
32-
33-
await page.getByRole("button", { name: /Create Account/i }).click();
34-
35-
await expect(
36-
page.getByRole("heading", { name: /Account Details/i })
37-
).toBeVisible();
38-
39-
await expect(
40-
page.getByRole("heading", { name: /My Account/i })
41-
).toBeVisible();
42-
}
43-
4419
const checkDntCookie = async (page, expectedValue) => {
4520
var cookies = await page.context().cookies();
4621
var cookieName = 'dw_dnt';
@@ -49,9 +24,9 @@ const checkDntCookie = async (page, expectedValue) => {
4924
expect(cookie.value).toBe(expectedValue);
5025
}
5126

52-
5327
test("Shopper can use the consent tracking form", async ({ page }) => {
5428
await page.context().clearCookies();
29+
5530
await page.goto(config.RETAIL_APP_HOME);
5631

5732
const modalSelector = '[aria-label="Close consent tracking form"]'
@@ -62,16 +37,16 @@ test("Shopper can use the consent tracking form", async ({ page }) => {
6237
const declineButton = page.locator('button:visible', { hasText: 'Decline' });
6338
await expect(declineButton).toBeVisible();
6439
await declineButton.click();
40+
await page.waitForTimeout(5000);
6541

6642
// Intercept einstein request
6743
let apiCallsMade = false;
6844
await page.route('https://api.cquotient.com/v3/activities/aaij-MobileFirst/viewCategory', (route) => {
6945
apiCallsMade = true;
7046
route.continue();
7147
});
72-
73-
// The value of 1 comes from defaultDnt prop in _app-config/index.jsx
74-
checkDntCookie(page, '1')
48+
49+
await checkDntCookie(page, '1')
7550

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

8257
// Registering after setting DNT persists the preference
83-
await registerUser(page)
84-
checkDntCookie(page, '1')
58+
await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS});
59+
await checkDntCookie(page, '1')
8560

8661
// Logging out clears the preference
8762
const buttons = await page.getByText(/Log Out/i).elementHandles();

e2e/tests/desktop/registered-shopper.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ test("Registered shopper can add item to wishlist", async ({ page }) => {
148148
})
149149

150150
if(!isLoggedIn) {
151-
await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS})
151+
await registerShopper({page, userCredentials: generateUserCredentials() })
152152
}
153153

154154
// Navigate to PDP

e2e/tests/homepage.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
const { test, expect } = require("@playwright/test");
99
const config = require("../config");
10+
const {answerConsentTrackingForm} = require("../scripts/pageHelpers.js")
1011

1112
test.describe("Retail app home page loads", () => {
1213
test.beforeEach(async ({ page }) => {
1314
await page.goto(config.RETAIL_APP_HOME);
15+
await answerConsentTrackingForm(page);
1416
});
1517

1618
test("has title", async ({ page }) => {

e2e/tests/mobile/dnt.spec.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright (c) 2023, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
const { test, expect } = require("@playwright/test");
9+
const config = require("../../config.js");
10+
const {
11+
generateUserCredentials
12+
} = require("../../scripts/utils.js");
13+
const {
14+
registerShopper
15+
} = require("../../scripts/pageHelpers.js")
16+
17+
const REGISTERED_USER_CREDENTIALS = generateUserCredentials();
18+
19+
const checkDntCookie = async (page, expectedValue) => {
20+
var cookies = await page.context().cookies();
21+
var cookieName = 'dw_dnt';
22+
var cookie = cookies.find(cookie => cookie.name === cookieName);
23+
expect(cookie).toBeTruthy();
24+
expect(cookie.value).toBe(expectedValue);
25+
}
26+
27+
test("Shopper can use the consent tracking form", async ({ page }) => {
28+
await page.context().clearCookies();
29+
30+
await page.goto(config.RETAIL_APP_HOME);
31+
32+
const modalSelector = '[aria-label="Close consent tracking form"]'
33+
page.locator(modalSelector).waitFor()
34+
await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000});
35+
36+
// Decline Tracking
37+
const declineButton = page.locator('button:visible', { hasText: 'Decline' });
38+
await expect(declineButton).toBeVisible();
39+
await declineButton.click();
40+
41+
// Intercept einstein request
42+
let apiCallsMade = false;
43+
await page.route('https://api.cquotient.com/v3/activities/aaij-MobileFirst/viewCategory', (route) => {
44+
apiCallsMade = true;
45+
route.continue();
46+
});
47+
48+
await checkDntCookie(page, '1')
49+
50+
// Trigger einstein events
51+
await page.getByLabel("Menu", { exact: true }).click();
52+
53+
// SSR nav loads top level categories as direct links so we wait till all sub-categories load in the accordion
54+
const categoryAccordion = page.locator(
55+
"#category-nav .chakra-accordion__button svg+:text('Womens')"
56+
);
57+
await categoryAccordion.waitFor();
58+
59+
await page.getByRole("button", { name: "Womens" }).click();
60+
61+
const clothingNav = page.getByRole("button", { name: "Clothing" });
62+
63+
await clothingNav.waitFor();
64+
65+
await clothingNav.click();
66+
// Reloading the page after setting DNT makes the form not appear again
67+
await page.reload()
68+
await expect(page.getByText(/Tracking Consent/i)).toBeHidden();
69+
70+
// Registering after setting DNT persists the preference
71+
await registerShopper({page, userCredentials: REGISTERED_USER_CREDENTIALS});
72+
await checkDntCookie(page, '1')
73+
74+
// Logging out clears the preference
75+
await page.getByRole("heading", { name: /My Account/i }).click()
76+
const buttons = await page.getByText(/Log Out/i).elementHandles();
77+
for (const button of buttons) {
78+
if (await button.isVisible()) {
79+
await button.click();
80+
break;
81+
}
82+
}
83+
84+
var cookies = await page.context().cookies();
85+
if (cookies.some(item => item.name === "dw_dnt")) {
86+
throw new Error('dw_dnt still exists in the cookies');
87+
}
88+
await page.reload();
89+
await expect(page.getByText(/Tracking Consent/i)).toBeVisible({timeout: 10000});
90+
expect(apiCallsMade).toBe(false);
91+
});

e2e/tests/mobile/registered-shopper.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ test("Registered shopper can add item to wishlist", async ({ page }) => {
153153
if(!isLoggedIn) {
154154
await registerShopper({
155155
page,
156-
userCredentials: REGISTERED_USER_CREDENTIALS,
156+
userCredentials: generateUserCredentials(),
157157
isMobile: true
158158
})
159159
}

0 commit comments

Comments
 (0)