Skip to content

Commit 0880af3

Browse files
Merge branch 'develop' into t/commerce/W-18979555/updatingReadMeFile
2 parents 37dbd79 + e1306dc commit 0880af3

File tree

13 files changed

+857
-26
lines changed

13 files changed

+857
-26
lines changed

.github/workflows/e2e.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,25 @@ jobs:
411411
}
412412
env:
413413
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
414+
415+
test-extra-features:
416+
runs-on: ubuntu-latest
417+
steps:
418+
- name: Checkout
419+
uses: actions/checkout@v4
420+
- name: Setup Node.js
421+
uses: actions/setup-node@v4
422+
with:
423+
node-version: 22
424+
cache: npm
425+
- name: Install Monorepo Dependencies
426+
run: node ./scripts/gtime.js monorepo_install npm ci
427+
- name: Install Playwright Browsers
428+
run: npx playwright install --with-deps
429+
- name: Set PWA Kit E2E Test User
430+
run: export PWA_E2E_USER_EMAIL=e2e.pwa.kit@gmail.com PWA_E2E_USER_PASSWORD=hpv_pek-JZK_xkz0wzf
431+
- name: Run extra features tests
432+
env:
433+
PWA_E2E_USER_EMAIL: e2e.pwa.kit@gmail.com
434+
PWA_E2E_USER_PASSWORD: hpv_pek-JZK_xkz0wzf
435+
run: npm run test:e2e:extra_features
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
name: Sync Extra Features E2E Branch with Develop
2+
3+
on:
4+
# Trigger when develop branch is updated
5+
push:
6+
branches:
7+
- develop
8+
# Run daily at 11 PM PST (7 AM UTC) to catch any missed syncs
9+
schedule:
10+
- cron: '0 7 * * *'
11+
workflow_dispatch:
12+
13+
permissions:
14+
contents: write
15+
issues: write
16+
pull-requests: read
17+
18+
jobs:
19+
sync-branch:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0
26+
token: ${{ secrets.GITHUB_TOKEN }}
27+
28+
- name: Configure git
29+
run: |
30+
git config --global user.name ${{ secrets.GIT_CONFIG_USERNAME }}
31+
git config --global user.email ${{ secrets.GIT_CONFIG_EMAIL }}
32+
33+
- name: Sync extra-features-e2e-branch with develop
34+
id: sync
35+
continue-on-error: true
36+
run: |
37+
set -e
38+
39+
# Fetch all branches
40+
git fetch origin
41+
42+
# Check if the target branch exists
43+
if ! git show-ref --verify --quiet refs/remotes/origin/extra-features-e2e-branch; then
44+
echo "Branch extra-features-e2e-branch does not exist. Creating it from develop..."
45+
git checkout -b extra-features-e2e-branch origin/develop
46+
git push origin extra-features-e2e-branch
47+
echo "✅ Created extra-features-e2e-branch branch from develop"
48+
echo "status=created" >> $GITHUB_OUTPUT
49+
exit 0
50+
fi
51+
52+
# Switch to the target branch
53+
git checkout extra-features-e2e-branch
54+
git reset --hard origin/extra-features-e2e-branch
55+
56+
# Check if we're already up to date
57+
if git merge-base --is-ancestor origin/develop HEAD; then
58+
echo "✅ extra-features-e2e-branch is already up to date with develop"
59+
echo "status=up-to-date" >> $GITHUB_OUTPUT
60+
exit 0
61+
fi
62+
63+
if git merge origin/develop --no-edit; then
64+
echo "✅ Successfully merged develop into extra-features-e2e-branch"
65+
git push origin extra-features-e2e-branch
66+
echo "status=merged" >> $GITHUB_OUTPUT
67+
else
68+
echo "❌ Merge conflicts detected!"
69+
echo "📋 Files with conflicts:"
70+
git diff --name-only --diff-filter=U || true
71+
git merge --abort
72+
echo "status=conflict" >> $GITHUB_OUTPUT
73+
exit 1
74+
fi
75+
76+
- name: Create conflict resolution issue
77+
if: steps.sync.outputs.status == 'conflict'
78+
uses: actions/github-script@v7
79+
with:
80+
script: |
81+
const conflictFiles = `${{ steps.sync.outputs.conflict_files || 'Unknown files' }}`;
82+
const issueBody = `
83+
## 🚨 Automatic Sync Failed - Merge Conflicts Detected
84+
85+
Some features in PWA kit are defaulted to be off in \`develop\` branch (e.g. if they only work with private client).
86+
Hence there is another site that has these features enabled that E2E tests of these extra features are run against.
87+
The \`extra-features-e2e-branch\` is used for this site.
88+
89+
A job is run nightly to sync \`develop\` to \`extra-features-e2e-branch\` branch, but today this automatic sync from \`develop\` to \`extra-features-e2e-branch\` failed due to merge conflicts.
90+
91+
### Conflicting Files:
92+
\`\`\`
93+
${conflictFiles}
94+
\`\`\`
95+
96+
97+
### Manual Resolution:
98+
\`\`\`bash
99+
git checkout extra-features-e2e-branch
100+
git pull origin extra-features-e2e-branch
101+
git merge develop
102+
# Resolve conflicts manually
103+
git add .
104+
git commit -m "Resolve merge conflicts from develop"
105+
git push origin extra-features-e2e-branch
106+
\`\`\`
107+
108+
### After resolving the conflicts, close this issue
109+
`;
110+
111+
// Check if issue already exists
112+
const existingIssues = await github.rest.issues.listForRepo({
113+
owner: context.repo.owner,
114+
repo: context.repo.repo,
115+
state: 'open',
116+
labels: 'sync-conflict'
117+
});
118+
119+
if (existingIssues.data.length === 0) {
120+
await github.rest.issues.create({
121+
owner: context.repo.owner,
122+
repo: context.repo.repo,
123+
title: '🚨 Extra Features E2E Branch Sync Conflict - Manual Resolution Required',
124+
body: issueBody,
125+
labels: ['sync-conflict', 'automation']
126+
});
127+
}
128+
129+
deploy:
130+
needs: sync-branch
131+
if: needs.sync-branch.outputs.status != 'conflict' && needs.sync-branch.outputs.status != 'up-to-date'
132+
runs-on: ubuntu-latest
133+
environment: extra-features-e2e
134+
135+
steps:
136+
- name: Checkout extra-features-e2e-branch branch
137+
uses: actions/checkout@v4
138+
with:
139+
ref: extra-features-e2e-branch
140+
141+
- name: Setup Node.js
142+
uses: actions/setup-node@v4
143+
with:
144+
node-version: 22
145+
cache: 'npm'
146+
147+
- name: Install Monorepo Dependencies
148+
run: |
149+
# Install node dependencies
150+
node ./scripts/gtime.js monorepo_install npm ci
151+
152+
- name: Build project
153+
run: |
154+
cd packages/template-retail-react-app
155+
npm run build
156+
157+
- name: Create MRT credentials file
158+
uses: "./.github/actions/create_mrt"
159+
with:
160+
mobify_user: ${{ secrets.MOBIFY_CLIENT_USER }}
161+
mobify_api_key: ${{ secrets.MOBIFY_CLIENT_API_KEY }}
162+
163+
- name: Deploy to MRT
164+
uses: "./.github/actions/push_to_mrt"
165+
with:
166+
CWD: "./packages/template-retail-react-app"
167+
TARGET: extra-features-e2e
168+
PROJECT: scaffold-pwa
169+
MESSAGE: "Auto-sync from develop - build ${{ github.run_id }} (${{ github.sha }})"
170+
FLAGS: --wait

e2e/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = {
99
RETAIL_APP_HOME:
1010
process.env.RETAIL_APP_HOME ||
1111
"https://scaffold-pwa-e2e-tests-pwa-kit.mobify-storefront.com",
12+
RETAIL_APP_HOME_SITE: "RefArchGlobal",
1213
GENERATED_PROJECTS_DIR: "../generated-projects",
1314
GENERATE_PROJECTS: ["retail-app-demo", "retail-app-ext", "retail-app-no-ext"],
1415
GENERATOR_CMD:
@@ -162,5 +163,6 @@ module.exports = {
162163
},
163164
PWA_E2E_USER_EMAIL: process.env.PWA_E2E_USER_EMAIL,
164165
PWA_E2E_USER_PASSWORD: process.env.PWA_E2E_USER_PASSWORD,
165-
SOCIAL_LOGIN_RETAIL_APP_HOME: "https://wasatch-mrt-feature-public.mrt-storefront-staging.com"
166+
EXTRA_FEATURES_E2E_RETAIL_APP_HOME: "https://scaffold-pwa-extra-features-e2e.mobify-storefront.com",
167+
EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE: "RefArchGlobal"
166168
};

e2e/scripts/pageHelpers.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ export const navigateToPDPDesktopSocial = async ({
165165
productColor,
166166
productPrice
167167
}) => {
168-
await page.goto(config.SOCIAL_LOGIN_RETAIL_APP_HOME)
168+
await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME)
169169
await answerConsentTrackingForm(page)
170170

171171
await page.getByRole('link', {name: 'Womens'}).hover()
@@ -373,7 +373,7 @@ export const loginShopper = async ({page, userCredentials}) => {
373373
*/
374374
export const socialLoginShopper = async ({page}) => {
375375
try {
376-
await page.goto(config.SOCIAL_LOGIN_RETAIL_APP_HOME + '/login')
376+
await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login')
377377

378378
await page.getByText(/Google/i).click()
379379
await expect(page.getByText(/Sign in with Google/i)).toBeVisible({timeout: 10000})
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright (c) 2025, 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 {generateUserCredentials} = require('../../scripts/utils.js')
11+
const {answerConsentTrackingForm} = require('../../scripts/pageHelpers.js')
12+
13+
const GUEST_USER_CREDENTIALS = generateUserCredentials()
14+
/**
15+
* Test that a user can login with passwordless login on mobile. There is no programmatic way to check the email,
16+
* so we will check that the necessary API call is being made and expected UI is shown
17+
*/
18+
test('Verify passwordless login request', async ({page}) => {
19+
let interceptedRequest = null
20+
21+
await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', (route) => {
22+
interceptedRequest = route.request()
23+
route.continue()
24+
})
25+
26+
await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login')
27+
await answerConsentTrackingForm(page)
28+
29+
await page.locator('#email').scrollIntoViewIfNeeded()
30+
await page.fill('#email', config.PWA_E2E_USER_EMAIL)
31+
32+
await page.getByRole('button', {name: 'Continue Securely'}).click()
33+
34+
await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login')
35+
36+
expect(interceptedRequest).toBeTruthy()
37+
expect(interceptedRequest.method()).toBe('POST')
38+
39+
const postData = interceptedRequest.postData()
40+
expect(postData).toBeTruthy()
41+
42+
const params = new URLSearchParams(postData)
43+
44+
expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL)
45+
expect(params.get('mode')).toBe('callback')
46+
expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE)
47+
expect(params.get('callback_uri')).toMatch(/.*\/passwordless-login-callback$/)
48+
})
49+
50+
test('Verify password reset callback request', async ({page}) => {
51+
let interceptedRequest = null
52+
53+
await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', (route) => {
54+
interceptedRequest = route.request()
55+
route.continue()
56+
})
57+
58+
await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login')
59+
await answerConsentTrackingForm(page)
60+
61+
await page.locator('#email').scrollIntoViewIfNeeded()
62+
await page.fill('#email', config.PWA_E2E_USER_EMAIL)
63+
64+
await page.getByRole('button', {name: 'Password'}).click()
65+
await page.getByRole('button', {name: 'Forgot password?'}).click()
66+
67+
await page.fill('#email', config.PWA_E2E_USER_EMAIL)
68+
await page.getByRole('button', {name: 'Reset Password'}).click()
69+
70+
await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset')
71+
72+
expect(interceptedRequest).toBeTruthy()
73+
expect(interceptedRequest.method()).toBe('POST')
74+
75+
const postData = interceptedRequest.postData()
76+
expect(postData).toBeTruthy()
77+
78+
const params = new URLSearchParams(postData)
79+
80+
expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL)
81+
expect(params.get('mode')).toBe('callback')
82+
expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE)
83+
expect(params.get('callback_uri')).toMatch(/.*\/reset-password-callback$/)
84+
expect(params.get('hint')).toBe('cross_device')
85+
})
86+
87+
// Verify on the login UI that looks different when extra login features are not enabled
88+
test('Verify password reset callback request when extra login features are not enabled', async ({page}) => {
89+
let interceptedRequest = null
90+
91+
await page.route('**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', (route) => {
92+
interceptedRequest = route.request()
93+
route.continue()
94+
})
95+
96+
await page.goto(config.RETAIL_APP_HOME + '/login')
97+
await answerConsentTrackingForm(page)
98+
99+
await page.locator('#email').scrollIntoViewIfNeeded()
100+
await page.fill('#email', config.PWA_E2E_USER_EMAIL)
101+
102+
await page.getByRole('button', {name: 'Forgot password?'}).click()
103+
104+
await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password')
105+
await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL)
106+
await page.getByRole('button', {name: /reset password/i}).click()
107+
await page.waitForResponse('**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset')
108+
109+
expect(interceptedRequest).toBeTruthy()
110+
expect(interceptedRequest.method()).toBe('POST')
111+
112+
const postData = interceptedRequest.postData()
113+
expect(postData).toBeTruthy()
114+
115+
const params = new URLSearchParams(postData)
116+
117+
expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL)
118+
expect(params.get('mode')).toBe('callback')
119+
expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE)
120+
expect(params.get('callback_uri')).toMatch(/.*\/reset-password-callback$/)
121+
expect(params.get('hint')).toBe('cross_device')
122+
})
123+
124+
125+
test('Verify password reset request', async ({page}) => {
126+
let interceptedRequest = null
127+
await page.route('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', (route) => {
128+
interceptedRequest = route.request()
129+
route.continue()
130+
})
131+
132+
await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}`)
133+
await answerConsentTrackingForm(page)
134+
135+
await page.fill('#password', GUEST_USER_CREDENTIALS.password)
136+
await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password)
137+
138+
expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password)
139+
expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password)
140+
await page.getByRole('button', {name: 'Reset Password'}).click()
141+
142+
await page.waitForResponse('**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action')
143+
144+
expect(interceptedRequest).toBeTruthy()
145+
})

0 commit comments

Comments
 (0)