1+ import { BROWSER_TIMEOUT } from '../setup/constants.js'
12import type { Page } from '@playwright/test'
23
34/**
@@ -23,24 +24,50 @@ async function fillSensitive(page: Page, selector: string, value: string): Promi
2324 * Completes the Shopify OAuth login flow on a Playwright page.
2425 */
2526export async function completeLogin ( page : Page , loginUrl : string , email : string , password : string ) : Promise < void > {
27+ // Disable WebAuthn so passkey/security key system dialogs never appear when headed
28+ const cdp = await page . context ( ) . newCDPSession ( page )
29+ await cdp . send ( 'WebAuthn.enable' , { enableUI : false } )
30+
2631 await page . goto ( loginUrl )
2732
2833 try {
2934 // Fill in email
30- await page . waitForSelector ( 'input[name="account[email]"], input[type="email"]' , { timeout : 60_000 } )
35+ await page . waitForSelector ( 'input[name="account[email]"], input[type="email"]' , { timeout : BROWSER_TIMEOUT . max } )
3136 await fillSensitive ( page , 'input[name="account[email]"], input[type="email"]' , email )
3237 await page . locator ( 'button[type="submit"]' ) . first ( ) . click ( )
3338
39+ // Handle passkey prompt — navigate to password login if needed
40+ const passwordInput = page . locator ( 'input[name="account[password]"], input[type="password"]' )
41+ const differentMethodBtn = page . locator ( 'text=Log in using a different method' )
42+
43+ // Wait for either password field or passkey page
44+ await Promise . race ( [
45+ passwordInput . waitFor ( { timeout : BROWSER_TIMEOUT . max } ) ,
46+ differentMethodBtn . waitFor ( { timeout : BROWSER_TIMEOUT . max } ) ,
47+ ] ) . catch ( ( ) => { } )
48+
49+ // If passkey page shown, navigate to password login
50+ if ( await differentMethodBtn . isVisible ( { timeout : BROWSER_TIMEOUT . short } ) . catch ( ( ) => false ) ) {
51+ await differentMethodBtn . click ( )
52+ await page . waitForTimeout ( BROWSER_TIMEOUT . short )
53+
54+ const continueWithPassword = page . locator ( 'text=Continue with password' )
55+ if ( await continueWithPassword . isVisible ( { timeout : BROWSER_TIMEOUT . medium } ) . catch ( ( ) => false ) ) {
56+ await continueWithPassword . click ( )
57+ await page . waitForTimeout ( BROWSER_TIMEOUT . short )
58+ }
59+ }
60+
3461 // Fill in password
35- await page . waitForSelector ( 'input[name="account[password]"], input[type="password"]' , { timeout : 60_000 } )
62+ await passwordInput . waitFor ( { timeout : BROWSER_TIMEOUT . max } )
3663 await fillSensitive ( page , 'input[name="account[password]"], input[type="password"]' , password )
3764 await page . locator ( 'button[type="submit"]' ) . first ( ) . click ( )
3865
3966 // Handle any confirmation/approval page
40- await page . waitForTimeout ( 3000 )
67+ await page . waitForTimeout ( BROWSER_TIMEOUT . medium )
4168 try {
4269 const btn = page . locator ( 'button[type="submit"]' ) . first ( )
43- if ( await btn . isVisible ( { timeout : 5000 } ) ) await btn . click ( )
70+ if ( await btn . isVisible ( { timeout : BROWSER_TIMEOUT . long } ) ) await btn . click ( )
4471 // eslint-disable-next-line no-catch-all/no-catch-all
4572 } catch ( _error ) {
4673 // No confirmation page — expected
0 commit comments