7.2 KiB
7.2 KiB
Instructions
- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.
Test info
- Name: 1-login.spec.ts >> Login Workflow >> should remember login preference on revisit
- Location: e2e/workflows/1-login.spec.ts:165:7
Error details
Test timeout of 30000ms exceeded.
Error: page.fill: Test timeout of 30000ms exceeded.
Call log:
- waiting for locator('[data-testid="local-username-input"]')
Page snapshot
- generic [active] [ref=e1]:
- generic [ref=e3]:
- generic [ref=e4]:
- img [ref=e6]
- heading "Identity Check" [level=2] [ref=e9]
- paragraph [ref=e10]: Select operator profile or use direct login
- generic [ref=e11]:
- button "newuser user" [ref=e12] [cursor=pointer]:
- generic [ref=e13]:
- img [ref=e15]
- generic [ref=e18]:
- paragraph [ref=e19]: newuser
- paragraph [ref=e20]: user
- img [ref=e21]
- generic [ref=e23]:
- button "Enterprise" [ref=e24] [cursor=pointer]:
- img [ref=e25]
- text: Enterprise
- button "Manual Login" [ref=e28] [cursor=pointer]:
- img [ref=e29]
- text: Manual Login
- button "Open Next.js Dev Tools" [ref=e37] [cursor=pointer]:
- img [ref=e38]
- alert [ref=e41]
Test source
1 | import { Page, BrowserContext } from '@playwright/test';
2 |
3 | /**
4 | * Authentication fixture for E2E tests
5 | * Handles login flows and session management
6 | */
7 |
8 | export interface LoginCredentials {
9 | username: string;
10 | password: string;
11 | }
12 |
13 | export interface AuthSession {
14 | token: string;
15 | userId: string;
16 | email: string;
17 | isAdmin: boolean;
18 | }
19 |
20 | /**
21 | * Perform LDAP authentication
22 | */
23 | export async function loginWithLdap(
24 | page: Page,
25 | credentials: LoginCredentials,
26 | baseUrl: string = 'http://localhost'
27 | ): Promise<void> {
28 | await page.goto(`${baseUrl}`);
29 |
30 | // Wait for login form to appear
31 | await page.waitForSelector('[data-testid="login-form"]', { timeout: 5000 });
32 |
33 | // Fill in username
34 | await page.fill('[data-testid="username-input"]', credentials.username);
35 |
36 | // Fill in password
37 | await page.fill('[data-testid="password-input"]', credentials.password);
38 |
39 | // Click login button
40 | await page.click('[data-testid="login-submit"]');
41 |
42 | // Wait for navigation to inventory page
43 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' });
44 | }
45 |
46 | /**
47 | * Perform local user authentication
48 | */
49 | export async function loginWithLocalUser(
50 | page: Page,
51 | credentials: LoginCredentials,
52 | baseUrl: string = 'http://localhost'
53 | ): Promise<void> {
54 | await page.goto(`${baseUrl}`);
55 |
56 | // Wait for identity check overlay
57 | await page.waitForSelector('[data-testid="identity-check-overlay"]', { timeout: 5000 });
58 |
59 | // Click "Local User" tab if it exists
60 | const localUserTab = page.locator('[data-testid="local-user-tab"]');
61 | if (await localUserTab.isVisible()) {
62 | await localUserTab.click();
63 | }
64 |
65 | // Fill in username
> 66 | await page.fill('[data-testid="local-username-input"]', credentials.username);
| ^ Error: page.fill: Test timeout of 30000ms exceeded.
67 |
68 | // Fill in password
69 | await page.fill('[data-testid="local-password-input"]', credentials.password);
70 |
71 | // Click login button
72 | await page.click('[data-testid="local-login-submit"]');
73 |
74 | // Wait for navigation to inventory page
75 | await page.waitForURL(`${baseUrl}/*`, { waitUntil: 'networkidle' });
76 | }
77 |
78 | /**
79 | * Logout from the application
80 | */
81 | export async function logout(page: Page, baseUrl: string = 'http://localhost'): Promise<void> {
82 | // Click logout button or menu
83 | const logoutButton = page.locator('[data-testid="logout-button"]');
84 | if (await logoutButton.isVisible()) {
85 | await logoutButton.click();
86 | } else {
87 | // Try finding it in menu
88 | const menu = page.locator('[data-testid="user-menu"]');
89 | if (await menu.isVisible()) {
90 | await menu.click();
91 | await page.click('[data-testid="logout-menu-item"]');
92 | }
93 | }
94 |
95 | // Wait for redirect to login
96 | await page.waitForURL(`${baseUrl}/*`, { timeout: 5000 });
97 | }
98 |
99 | /**
100 | * Get authentication token from local storage
101 | */
102 | export async function getAuthToken(page: Page): Promise<string | null> {
103 | const token = await page.evaluate(() => {
104 | return localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token');
105 | });
106 | return token;
107 | }
108 |
109 | /**
110 | * Set authentication token in local storage
111 | */
112 | export async function setAuthToken(page: Page, token: string): Promise<void> {
113 | await page.evaluate((t) => {
114 | localStorage.setItem('auth_token', t);
115 | }, token);
116 | }
117 |
118 | /**
119 | * Clear authentication data
120 | */
121 | export async function clearAuth(page: Page): Promise<void> {
122 | await page.evaluate(() => {
123 | localStorage.removeItem('auth_token');
124 | sessionStorage.removeItem('auth_token');
125 | localStorage.removeItem('user_data');
126 | sessionStorage.removeItem('user_data');
127 | });
128 | }
129 |
130 | /**
131 | * Check if user is authenticated
132 | */
133 | export async function isAuthenticated(page: Page): Promise<boolean> {
134 | const token = await getAuthToken(page);
135 | return !!token;
136 | }
137 |
138 | /**
139 | * Get current user info from page
140 | */
141 | export async function getCurrentUser(page: Page): Promise<{ username: string; email: string } | null> {
142 | try {
143 | const userData = await page.evaluate(() => {
144 | const stored = localStorage.getItem('user_data') || sessionStorage.getItem('user_data');
145 | return stored ? JSON.parse(stored) : null;
146 | });
147 | return userData;
148 | } catch {
149 | return null;
150 | }
151 | }
152 |
153 | /**
154 | * Wait for authentication to complete
155 | */
156 | export async function waitForAuth(page: Page, timeout: number = 5000): Promise<void> {
157 | // Wait for either:
158 | // 1. Token to be in storage
159 | // 2. Navigation to happen
160 | // 3. Auth overlay to disappear
161 | await page.waitForFunction(
162 | () => {
163 | const token =
164 | typeof window !== 'undefined'
165 | ? localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token')
166 | : null;