diff --git a/e2e/scripts/pageHelpers.js b/e2e/scripts/pageHelpers.js index 1269e427e0..3bcec94e73 100644 --- a/e2e/scripts/pageHelpers.js +++ b/e2e/scripts/pageHelpers.js @@ -7,6 +7,7 @@ const {expect} = require('@playwright/test') const config = require('../config') const {getCreditCardExpiry, runAccessibilityTest} = require('../scripts/utils.js') +const crypto = require('crypto') /** * 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. @@ -791,3 +792,85 @@ export const selectStoreFromPLP = async ({page}) => { await page.getByRole('button', {name: 'Close'}).click() } } + +/** + * Validates that a passkey login request is made to the /webAuthn/authenticate/finish endpoint. + * We can't register an actual passkey in the E2E environment because registration requires a token verification. + * Instead,we add a mock credential to the virtual authenticator to bypass the registration flow and verify the + * request to the /webAuthn/authenticate/finish endpoint. + * + * @param {Object} options.page - Playwright page object representing a browser tab/window + */ +export const validatePasskeyLogin = async ({page}) => { + // Start a CDP session to interact with WebAuthn + const client = await page.context().newCDPSession(page) + await client.send('WebAuthn.enable') + // Create a virtual authenticator to simulate a hardware authenticator for testing + const {authenticatorId} = await client.send('WebAuthn.addVirtualAuthenticator', { + options: { + protocol: 'ctap2', + transport: 'internal', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + // Enabling automaticPresenceSimulation automatically completes the device's passkey prompt without user interaction + automaticPresenceSimulation: true + } + }) + + // Preload mock credential into the virtual authenticator + const rpId = new URL(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME).hostname + // Generate a valid EC key pair for WebAuthn (ES256/P-256) + const {privateKey} = crypto.generateKeyPairSync('ec', {namedCurve: 'P-256'}) + const privateKeyBase64 = privateKey.export({format: 'der', type: 'pkcs8'}).toString('base64') + + console.log('privateKeyBase64', privateKeyBase64) + const credentialIdBuffer = Buffer.from('mock-credential-id-' + Date.now()) + const credentialIdBase64 = credentialIdBuffer.toString('base64') // For mock credential + const credentialId = credentialIdBuffer.toString('base64url') // For verifying the request + await client.send('WebAuthn.addCredential', { + authenticatorId, + credential: { + credentialId: credentialIdBase64, + isResidentCredential: true, + rpId, + privateKey: privateKeyBase64, + userHandle: Buffer.from('test-user-handle').toString('base64'), + signCount: 0, + transports: ['internal'] + } + }) + + let interceptedRequest = null + + // Intercept the WebAuthn authenticate/finish endpoint to verify the request + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/webauthn/authenticate/finish', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') + + // Wait for the WebAuthn authenticate/finish request + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/webauthn/authenticate/finish' + ) + + // Verify the /webAuthn/authenticate/finish request + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + const postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + const requestBody = JSON.parse(postData) + expect(requestBody).toBeTruthy() + + // Verify the request body structure matches expected format + expect(requestBody.client_id).toBeTruthy() + expect(requestBody.channel_id).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) + expect(requestBody.credential.id).toBe(credentialId) + expect(requestBody.credential.clientExtensionResults).toBeTruthy() + expect(requestBody.credential.response).toBeTruthy() +} diff --git a/e2e/tests/desktop/extra-features.spec.js b/e2e/tests/desktop/extra-features.spec.js index b6f7b1e993..c8928f7066 100644 --- a/e2e/tests/desktop/extra-features.spec.js +++ b/e2e/tests/desktop/extra-features.spec.js @@ -5,192 +5,209 @@ * 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 {test, expect, describe} = require('@playwright/test') const config = require('../../config.js') const {generateUserCredentials} = require('../../scripts/utils.js') -const {answerConsentTrackingForm} = require('../../scripts/pageHelpers.js') +const {answerConsentTrackingForm, validatePasskeyLogin} = require('../../scripts/pageHelpers.js') const GUEST_USER_CREDENTIALS = generateUserCredentials() -/** - * Test that a user can login with passwordless login on mobile. There is no programmatic way to check the email, - * so we will check that the necessary API call is being made and expected UI is shown - */ -test('Verify passwordless login request', async ({page}) => { - let interceptedRequest = null - - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Continue'}).click() - - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login' - ) - - // Verify the passwordless login request - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - let postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - let params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('email') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) - - // Wait for OTP input fields to appear and fill the 8-digit code - const otpCode = '12345678' // Replace with actual OTP code - const otpInputs = page.locator('input[inputmode="numeric"][maxlength="1"]') - await otpInputs.first().waitFor() - - // Fill each input field with one digit - for (let i = 0; i < 8; i++) { - await otpInputs.nth(i).fill(otpCode[i]) - } - - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token' - ) - - // Verify the passwordless login token request - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - params = new URLSearchParams(postData) - expect(params.get('pwdless_login_token')).toBe(otpCode) - expect(params.get('hint')).toBe('pwdless_login') +describe('Passkey Login', () => { + test('Verify passkey login', async ({page}) => { + // Override the global test timeout to 70s to ensure the test can complete + test.setTimeout(70000) + // webauthn/authenticate/start has a 60s cooldown; wait 1 min to ensure it can be called again + await page.waitForTimeout(60000) + await validatePasskeyLogin({page}) + }) }) -test('Verify password reset request', async ({page}) => { - let interceptedRequest = null - - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', - (route) => { - interceptedRequest = route.request() - route.continue() +describe('Passwordless login', () => { + /** + * Test that a user can login with passwordless login. There is no programmatic way to check the email, + * so we will check that the necessary API call is being made and expected UI is shown + */ + test('Verify passwordless login request', async ({page}) => { + let interceptedRequest = null + + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') + await answerConsentTrackingForm(page) + + await page.locator('#email').scrollIntoViewIfNeeded() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) + + await page.getByRole('button', {name: 'Continue'}).click() + + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login' + ) + + // Verify the passwordless login request + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + + let postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + + let params = new URLSearchParams(postData) + + expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) + expect(params.get('mode')).toBe('email') + expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) + + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + // Wait for OTP input fields to appear and fill the 8-digit code + const otpCode = '12345678' // Replace with actual OTP code + const otpInputs = page.locator('input[inputmode="numeric"][maxlength="1"]') + await otpInputs.first().waitFor() + + // Fill each input field with one digit + for (let i = 0; i < 8; i++) { + await otpInputs.nth(i).fill(otpCode[i]) } - ) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Password'}).click() - await page.getByRole('button', {name: 'Forgot password?'}).click() - - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: 'Reset Password'}).click() - - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset' - ) - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('email') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - expect(params.get('hint')).toBe('cross_device') -}) - -// Verify on the login UI that looks different when extra login features are not enabled -test('Verify password reset request when extra login features are not enabled', async ({page}) => { - let interceptedRequest = null - - await page.route( - '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) - - await page.goto(config.RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Forgot password?'}).click() - - await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password') - await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: /reset password/i}).click() - await page.waitForResponse( - '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset' - ) - - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - const params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('email') - expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE) - expect(params.get('hint')).toBe('cross_device') + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token' + ) + + // Verify the passwordless login token request + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + params = new URLSearchParams(postData) + expect(params.get('pwdless_login_token')).toBe(otpCode) + expect(params.get('hint')).toBe('pwdless_login') + }) }) -test('Verify password reset action request', async ({page}) => { - let interceptedRequest = null - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) - - await page.goto( - config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + - `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}` - ) - await answerConsentTrackingForm(page) - - await page.fill('#password', GUEST_USER_CREDENTIALS.password) - await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password) - - expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password) - expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password) - await page.getByRole('button', {name: 'Reset Password'}).click() - - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action' - ) +describe('Password reset', () => { + test('Verify password reset request', async ({page}) => { + let interceptedRequest = null + + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') + await answerConsentTrackingForm(page) + + await page.locator('#email').scrollIntoViewIfNeeded() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) + + await page.getByRole('button', {name: 'Password'}).click() + await page.getByRole('button', {name: 'Forgot password?'}).click() + + await page.fill('#email', config.PWA_E2E_USER_EMAIL) + await page.getByRole('button', {name: 'Reset Password'}).click() + + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset' + ) + + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + + const postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + + const params = new URLSearchParams(postData) + + expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) + expect(params.get('mode')).toBe('email') + expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) + expect(params.get('hint')).toBe('cross_device') + }) + + // Verify on the login UI that looks different when extra login features are not enabled + test('Verify password reset request when extra login features are not enabled', async ({ + page + }) => { + let interceptedRequest = null + + await page.route( + '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + await page.goto(config.RETAIL_APP_HOME + '/login') + await answerConsentTrackingForm(page) + + await page.locator('#email').scrollIntoViewIfNeeded() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) + + await page.getByRole('button', {name: 'Forgot password?'}).click() + + await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password') + await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL) + await page.getByRole('button', {name: /reset password/i}).click() + await page.waitForResponse( + '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset' + ) + + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + + const postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + + const params = new URLSearchParams(postData) + + expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) + expect(params.get('mode')).toBe('email') + expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE) + expect(params.get('hint')).toBe('cross_device') + }) + + test('Verify password reset action request', async ({page}) => { + let interceptedRequest = null + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + await page.goto( + config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + + `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}` + ) + await answerConsentTrackingForm(page) + + await page.fill('#password', GUEST_USER_CREDENTIALS.password) + await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password) + + expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password) + expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password) + await page.getByRole('button', {name: 'Reset Password'}).click() + + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action' + ) - expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest).toBeTruthy() + }) }) diff --git a/e2e/tests/mobile/extra-features.spec.js b/e2e/tests/mobile/extra-features.spec.js index e521f9bd4d..29be2e41db 100644 --- a/e2e/tests/mobile/extra-features.spec.js +++ b/e2e/tests/mobile/extra-features.spec.js @@ -5,201 +5,215 @@ * 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 {test, expect, describe} = require('@playwright/test') const config = require('../../config.js') const {generateUserCredentials} = require('../../scripts/utils.js') -const {answerConsentTrackingForm} = require('../../scripts/pageHelpers.js') +const {answerConsentTrackingForm, validatePasskeyLogin} = require('../../scripts/pageHelpers.js') const GUEST_USER_CREDENTIALS = generateUserCredentials() -/** - * Test that a user can login with passwordless login on mobile. There is no programmatic way to check the email, - * so we will check that the necessary API call is being made and expected UI is shown - */ -test('Verify passwordless login request on mobile', async ({page}) => { - let interceptedRequest = null - - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) - - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) - - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - - await page.getByRole('button', {name: 'Continue'}).scrollIntoViewIfNeeded() - await page.getByRole('button', {name: 'Continue'}).click() - - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login' - ) - - // Verify the passwordless login request - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - - let postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - - let params = new URLSearchParams(postData) - - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('email') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) +describe('Passkey Login', () => { + test('Verify passkey login', async ({page}) => { + // Override the global test timeout to 70s to ensure the test can complete + test.setTimeout(70000) + // webauthn/authenticate/start has a 60s cooldown; wait 1 min to ensure it can be called again + await page.waitForTimeout(60000) + await validatePasskeyLogin({page}) + }) +}) - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token', - (route) => { - interceptedRequest = route.request() - route.continue() +describe('Passwordless login', () => { + /** + * Test that a user can login with passwordless login on mobile. There is no programmatic way to check the email, + * so we will check that the necessary API call is being made and expected UI is shown + */ + test('Verify passwordless login request on mobile', async ({page}) => { + let interceptedRequest = null + + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') + await answerConsentTrackingForm(page) + + await page.locator('#email').scrollIntoViewIfNeeded() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) + + await page.getByRole('button', {name: 'Continue'}).scrollIntoViewIfNeeded() + await page.getByRole('button', {name: 'Continue'}).click() + + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/login' + ) + + // Verify the passwordless login request + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + + let postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + + let params = new URLSearchParams(postData) + + expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) + expect(params.get('mode')).toBe('email') + expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) + + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) + + // Wait for OTP input fields to appear and fill the 8-digit code + const otpCode = '12345678' // Replace with actual OTP code + const otpInputs = page.locator('input[inputmode="numeric"][maxlength="1"]') + await otpInputs.first().waitFor() + + // Fill each input field with one digit + for (let i = 0; i < 8; i++) { + await otpInputs.nth(i).fill(otpCode[i]) } - ) - - // Wait for OTP input fields to appear and fill the 8-digit code - const otpCode = '12345678' // Replace with actual OTP code - const otpInputs = page.locator('input[inputmode="numeric"][maxlength="1"]') - await otpInputs.first().waitFor() - - // Fill each input field with one digit - for (let i = 0; i < 8; i++) { - await otpInputs.nth(i).fill(otpCode[i]) - } - - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token' - ) - - // Verify the passwordless login token request - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') - postData = interceptedRequest.postData() - expect(postData).toBeTruthy() - params = new URLSearchParams(postData) - expect(params.get('pwdless_login_token')).toBe(otpCode) - expect(params.get('hint')).toBe('pwdless_login') + + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/passwordless/token' + ) + + // Verify the passwordless login token request + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') + postData = interceptedRequest.postData() + expect(postData).toBeTruthy() + params = new URLSearchParams(postData) + expect(params.get('pwdless_login_token')).toBe(otpCode) + expect(params.get('hint')).toBe('pwdless_login') + }) }) -test('Verify password reset request on mobile (extra features enabled)', async ({page}) => { - let interceptedRequest = null +describe('Password reset', () => { + test('Verify password reset request on mobile (extra features enabled)', async ({page}) => { + let interceptedRequest = null - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) - await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) + await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login') + await answerConsentTrackingForm(page) - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) + await page.locator('#email').scrollIntoViewIfNeeded() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: 'Password'}).click() - await page.getByRole('button', {name: 'Forgot password?'}).click() + await page.getByRole('button', {name: 'Password'}).click() + await page.getByRole('button', {name: 'Forgot password?'}).click() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: /reset password/i}).click() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) + await page.getByRole('button', {name: /reset password/i}).click() - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset' - ) + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/reset' + ) - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() + const postData = interceptedRequest.postData() + expect(postData).toBeTruthy() - const params = new URLSearchParams(postData) + const params = new URLSearchParams(postData) - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('email') - expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) - expect(params.get('hint')).toBe('cross_device') -}) + expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) + expect(params.get('mode')).toBe('email') + expect(params.get('channel_id')).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE) + expect(params.get('hint')).toBe('cross_device') + }) -test('Verify password reset request on mobile when extra login features are not enabled', async ({ - page -}) => { - let interceptedRequest = null + test('Verify password reset request on mobile when extra login features are not enabled', async ({ + page + }) => { + let interceptedRequest = null - await page.route( - '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) + await page.route( + '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) - await page.goto(config.RETAIL_APP_HOME + '/login') - await answerConsentTrackingForm(page) + await page.goto(config.RETAIL_APP_HOME + '/login') + await answerConsentTrackingForm(page) - await page.locator('#email').scrollIntoViewIfNeeded() - await page.fill('#email', config.PWA_E2E_USER_EMAIL) + await page.locator('#email').scrollIntoViewIfNeeded() + await page.fill('#email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: 'Forgot password?'}).click() + await page.getByRole('button', {name: 'Forgot password?'}).click() - await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password') - await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL) - await page.getByRole('button', {name: /reset password/i}).click() + await page.waitForSelector('form[data-testid="sf-auth-modal-form"] >> text=Reset Password') + await page.fill('form[data-testid="sf-auth-modal-form"] #email', config.PWA_E2E_USER_EMAIL) + await page.getByRole('button', {name: /reset password/i}).click() - await page.waitForResponse( - '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset' - ) + await page.waitForResponse( + '**/mobify/proxy/api/shopper/auth/v1/organizations/*/oauth2/password/reset' + ) - expect(interceptedRequest).toBeTruthy() - expect(interceptedRequest.method()).toBe('POST') + expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest.method()).toBe('POST') - const postData = interceptedRequest.postData() - expect(postData).toBeTruthy() + const postData = interceptedRequest.postData() + expect(postData).toBeTruthy() - const params = new URLSearchParams(postData) + const params = new URLSearchParams(postData) - expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) - expect(params.get('mode')).toBe('email') - expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE) - expect(params.get('hint')).toBe('cross_device') -}) + expect(params.get('user_id')).toBe(config.PWA_E2E_USER_EMAIL) + expect(params.get('mode')).toBe('email') + expect(params.get('channel_id')).toBe(config.RETAIL_APP_HOME_SITE) + expect(params.get('hint')).toBe('cross_device') + }) -test('Verify password reset action request on mobile', async ({page}) => { - let interceptedRequest = null - await page.route( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', - (route) => { - interceptedRequest = route.request() - route.continue() - } - ) + test('Verify password reset action request on mobile', async ({page}) => { + let interceptedRequest = null + await page.route( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action', + (route) => { + interceptedRequest = route.request() + route.continue() + } + ) - await page.goto( - config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + - `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}` - ) - await answerConsentTrackingForm(page) + await page.goto( + config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + + `/reset-password-landing?token=1234567&email=${GUEST_USER_CREDENTIALS.email}` + ) + await answerConsentTrackingForm(page) - await page.locator('#password').scrollIntoViewIfNeeded() - await page.fill('#password', GUEST_USER_CREDENTIALS.password) + await page.locator('#password').scrollIntoViewIfNeeded() + await page.fill('#password', GUEST_USER_CREDENTIALS.password) - await page.locator('#confirmPassword').scrollIntoViewIfNeeded() - await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password) + await page.locator('#confirmPassword').scrollIntoViewIfNeeded() + await page.fill('#confirmPassword', GUEST_USER_CREDENTIALS.password) - expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password) - expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password) + expect(await page.inputValue('#password')).toBe(GUEST_USER_CREDENTIALS.password) + expect(await page.inputValue('#confirmPassword')).toBe(GUEST_USER_CREDENTIALS.password) - await page.getByRole('button', {name: 'Reset Password'}).scrollIntoViewIfNeeded() - await page.getByRole('button', {name: 'Reset Password'}).click() + await page.getByRole('button', {name: 'Reset Password'}).scrollIntoViewIfNeeded() + await page.getByRole('button', {name: 'Reset Password'}).click() - await page.waitForResponse( - '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action' - ) + await page.waitForResponse( + '**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/password/action' + ) - expect(interceptedRequest).toBeTruthy() + expect(interceptedRequest).toBeTruthy() + }) })