Skip to content

Commit e293a52

Browse files
authored
@W-20224360 Passkey login E2E tests (#3636)
* Add passkey login validation to E2E tests * Introduced a new function `validatePasskeyLogin` in `pageHelpers.js` to simulate passkey authentication using a virtual authenticator. * Added 60s timeout prior to running passkey login tests in both desktop and mobile test suites to handle authentication cooldowns.
1 parent a635542 commit e293a52

File tree

3 files changed

+449
-335
lines changed

3 files changed

+449
-335
lines changed

e2e/scripts/pageHelpers.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const {expect} = require('@playwright/test')
88
const config = require('../config')
99
const {getCreditCardExpiry, runAccessibilityTest} = require('../scripts/utils.js')
10+
const crypto = require('crypto')
1011
/**
1112
* Note: As a best practice, we should await the network call and assert on the network response rather than waiting for pageLoadState()
1213
* to avoid race conditions from lock in pageLoadState being released before network call resolves.
@@ -791,3 +792,85 @@ export const selectStoreFromPLP = async ({page}) => {
791792
await page.getByRole('button', {name: 'Close'}).click()
792793
}
793794
}
795+
796+
/**
797+
* Validates that a passkey login request is made to the /webAuthn/authenticate/finish endpoint.
798+
* We can't register an actual passkey in the E2E environment because registration requires a token verification.
799+
* Instead,we add a mock credential to the virtual authenticator to bypass the registration flow and verify the
800+
* request to the /webAuthn/authenticate/finish endpoint.
801+
*
802+
* @param {Object} options.page - Playwright page object representing a browser tab/window
803+
*/
804+
export const validatePasskeyLogin = async ({page}) => {
805+
// Start a CDP session to interact with WebAuthn
806+
const client = await page.context().newCDPSession(page)
807+
await client.send('WebAuthn.enable')
808+
// Create a virtual authenticator to simulate a hardware authenticator for testing
809+
const {authenticatorId} = await client.send('WebAuthn.addVirtualAuthenticator', {
810+
options: {
811+
protocol: 'ctap2',
812+
transport: 'internal',
813+
hasResidentKey: true,
814+
hasUserVerification: true,
815+
isUserVerified: true,
816+
// Enabling automaticPresenceSimulation automatically completes the device's passkey prompt without user interaction
817+
automaticPresenceSimulation: true
818+
}
819+
})
820+
821+
// Preload mock credential into the virtual authenticator
822+
const rpId = new URL(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME).hostname
823+
// Generate a valid EC key pair for WebAuthn (ES256/P-256)
824+
const {privateKey} = crypto.generateKeyPairSync('ec', {namedCurve: 'P-256'})
825+
const privateKeyBase64 = privateKey.export({format: 'der', type: 'pkcs8'}).toString('base64')
826+
827+
console.log('privateKeyBase64', privateKeyBase64)
828+
const credentialIdBuffer = Buffer.from('mock-credential-id-' + Date.now())
829+
const credentialIdBase64 = credentialIdBuffer.toString('base64') // For mock credential
830+
const credentialId = credentialIdBuffer.toString('base64url') // For verifying the request
831+
await client.send('WebAuthn.addCredential', {
832+
authenticatorId,
833+
credential: {
834+
credentialId: credentialIdBase64,
835+
isResidentCredential: true,
836+
rpId,
837+
privateKey: privateKeyBase64,
838+
userHandle: Buffer.from('test-user-handle').toString('base64'),
839+
signCount: 0,
840+
transports: ['internal']
841+
}
842+
})
843+
844+
let interceptedRequest = null
845+
846+
// Intercept the WebAuthn authenticate/finish endpoint to verify the request
847+
await page.route(
848+
'**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/webauthn/authenticate/finish',
849+
(route) => {
850+
interceptedRequest = route.request()
851+
route.continue()
852+
}
853+
)
854+
855+
await page.goto(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME + '/login')
856+
857+
// Wait for the WebAuthn authenticate/finish request
858+
await page.waitForResponse(
859+
'**/mobify/slas/private/shopper/auth/v1/organizations/*/oauth2/webauthn/authenticate/finish'
860+
)
861+
862+
// Verify the /webAuthn/authenticate/finish request
863+
expect(interceptedRequest).toBeTruthy()
864+
expect(interceptedRequest.method()).toBe('POST')
865+
const postData = interceptedRequest.postData()
866+
expect(postData).toBeTruthy()
867+
const requestBody = JSON.parse(postData)
868+
expect(requestBody).toBeTruthy()
869+
870+
// Verify the request body structure matches expected format
871+
expect(requestBody.client_id).toBeTruthy()
872+
expect(requestBody.channel_id).toBe(config.EXTRA_FEATURES_E2E_RETAIL_APP_HOME_SITE)
873+
expect(requestBody.credential.id).toBe(credentialId)
874+
expect(requestBody.credential.clientExtensionResults).toBeTruthy()
875+
expect(requestBody.credential.response).toBeTruthy()
876+
}

0 commit comments

Comments
 (0)