11import { Page , APIRequestContext } from '@playwright/test' ;
22import testUsers from '../config/test-users.json' ;
3+ import { APP_URLS } from '../config/test-config' ;
34
45/**
56 * Authentication Fixtures
@@ -31,30 +32,75 @@ export async function loginAs(
3132 await page . goto ( '/' ) ;
3233 await page . waitForLoadState ( 'networkidle' ) ;
3334
35+ // If we're already authenticated for some reason, log out first
36+ if ( await isAuthenticated ( page ) ) {
37+ // the logout helper already waits for navigation etc.
38+ await logout ( page ) ;
39+ await page . goto ( '/' ) ;
40+ await page . waitForLoadState ( 'networkidle' ) ;
41+ }
42+
3443 // Clear any existing auth tokens to ensure clean state
3544 await clearAuthTokens ( page ) ;
3645
3746 // Reload page after clearing tokens to ensure Guest state
3847 await page . reload ( ) ;
3948 await page . waitForLoadState ( 'networkidle' ) ;
4049
41- // Wait for page to fully render
50+ // Pause briefly to allow Angular to render guest UI
4251 await page . waitForTimeout ( 1000 ) ;
4352
44- // Click user icon in upper right corner
45- const userIcon = page . locator ( 'button[aria-label="User menu"], button mat-icon:has-text("account_circle"), header button:has(mat-icon)' ) . last ( ) ;
46- await userIcon . click ( ) ;
53+ // Click user icon in upper right corner to open menu
54+ const userIcon = page . locator (
55+ 'button[aria-label="User menu"], button mat-icon:has-text("account_circle"), header button:has(mat-icon)'
56+ ) . last ( ) ;
57+ await userIcon . waitFor ( { state : 'visible' , timeout : 10000 } ) ;
58+
59+ // perform click; in some browsers the click may hang, so retry with force if needed
60+ try {
61+ await userIcon . click ( { timeout : 5000 } ) ;
62+ } catch ( err ) {
63+ console . warn ( 'userIcon.click() failed, retrying with force:' , err ) ;
64+ await userIcon . click ( { force : true } ) ;
65+ }
66+
4767 await page . waitForTimeout ( 500 ) ;
4868
4969 // Click "Login" option from dropdown menu
50- const loginOption = page . locator ( 'button:has-text("Login"), a:has-text("Login"), [role="menuitem"]:has-text("Login")' ) . first ( ) ;
70+ const loginOption = page . locator (
71+ 'button:has-text("Login"), a:has-text("Login"), [role="menuitem"]:has-text("Login")'
72+ ) . first ( ) ;
73+
74+ // Make sure the login option actually exists; otherwise abort early
75+ const optionCount = await loginOption . count ( ) ;
76+ if ( optionCount === 0 ) {
77+ const currentUrl = page . url ( ) ;
78+ throw new Error (
79+ `Unable to find Login option in user menu. Current url: ${ currentUrl } `
80+ ) ;
81+ }
5182
52- // Wait for login option to be visible before clicking
83+ // Ensure it's clickable and visible before firing the click
84+ await loginOption . scrollIntoViewIfNeeded ( ) ;
5385 await loginOption . waitFor ( { state : 'visible' , timeout : 5000 } ) ;
54- await loginOption . click ( ) ;
86+ await loginOption . click ( { timeout : 5000 } ) ;
5587
56- // Wait for redirect to IdentityServer login page
57- await page . waitForURL ( / s t s \. s k o r u b a \. l o c a l .* / , { timeout : 10000 } ) ;
88+ // After clicking we expect to leave the Angular app. The redirect may go to
89+ // the configured identity server or an in-app login page; wait for either.
90+ const idHost = new URL ( APP_URLS . identityServer ) . host . replace ( / \. / g, '\\.' ) ;
91+ const idRegex = new RegExp ( `${ idHost } .*` ) ;
92+ try {
93+ await Promise . race ( [
94+ page . waitForURL ( idRegex , { timeout : 30000 } ) ,
95+ page . waitForSelector ( 'input[name="Username"]' , { timeout : 30000 } )
96+ ] ) ;
97+ } catch ( err ) {
98+ const currentUrl = page . url ( ) ;
99+ throw new Error (
100+ `Timed out waiting for login redirect after clicking login (30s). ` +
101+ `Current url: ${ currentUrl } `
102+ ) ;
103+ }
58104
59105 // Fill in login credentials
60106 await page . fill ( 'input[name="Username"]' , username ) ;
@@ -64,10 +110,13 @@ export async function loginAs(
64110 await page . click ( 'button:has-text("Login")' ) ;
65111
66112 // Wait for OAuth callback redirect back to Angular app
67- await page . waitForURL ( / l o c a l h o s t : 4 2 0 0 .* / , { timeout : 15000 } ) ;
113+ await page . waitForURL ( / l o c a l h o s t : 4 2 0 0 .* / , { timeout : 30000 } ) ;
68114
69115 // Wait for dashboard to load (indicating successful authentication)
70- await page . waitForSelector ( 'h1:has-text("Dashboard"), h2:has-text("Dashboard"), .matero-page-title' , { timeout : 10000 } ) ;
116+ await page . waitForSelector (
117+ 'h1:has-text("Dashboard"), h2:has-text("Dashboard"), .matero-page-title' ,
118+ { timeout : 10000 }
119+ ) ;
71120}
72121
73122/**
@@ -110,7 +159,7 @@ export async function getApiToken(
110159 username : string ,
111160 password : string
112161) : Promise < string > {
113- const tokenEndpoint = 'https://sts.skoruba.local /connect/token' ;
162+ const tokenEndpoint = ` ${ APP_URLS . identityServer } /connect/token` ;
114163
115164 const response = await request . post ( tokenEndpoint , {
116165 form : {
@@ -172,10 +221,18 @@ export async function logout(page: Page): Promise<void> {
172221 const logoutOption = page . locator ( 'button:has-text("Logout"), a:has-text("Logout"), [role="menuitem"]:has-text("Logout")' ) . first ( ) ;
173222 await logoutOption . click ( ) ;
174223
175- // Wait for redirect to IdentityServer logout screen
176- await page . waitForURL ( / s t s \. s k o r u b a \. l o c a l .* / , { timeout : 10000 } ) ;
224+ // Wait for redirect to logout page. Depending on environment this may go to
225+ // the configured identity server host or local /Account/Logout.
226+ const idHost = new URL ( APP_URLS . identityServer ) . host . replace ( / \. / g, '\\.' ) ;
227+ const logoutRegex = new RegExp ( `(${ idHost } .*|localhost\/Account\/Logout.*)` ) ;
228+ try {
229+ await page . waitForURL ( logoutRegex , { timeout : 15000 } ) ;
230+ } catch ( err ) {
231+ const currentUrl = page . url ( ) ;
232+ console . warn ( `logout(): expected external logout page but still at ${ currentUrl } ` ) ;
233+ }
177234
178- // Wait a moment for STS logout screen to load
235+ // Wait a moment for logout screen to render
179236 await page . waitForTimeout ( 1000 ) ;
180237
181238 // Look for the "click here" link to return to Angular
0 commit comments