# 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: user.integration.spec.ts >> Integration - User lifecycle >> login via Authentik (created user) issues internal JWT - Location: tests\integration\user.integration.spec.ts:140:7 # Error details ``` Error: No jwt cookie set after login (second test). Saved screenshot/cookies/page to tests/test-artifacts ``` # Test source ```ts 76 | 77 | test.beforeAll(async () => { 78 | // Integration tests assume test users are created in Authentik prior to running. 79 | // RayLab will create local user record upon first successful login via Authentik. 80 | }); 81 | 82 | test('login via Authentik creates local user and issues internal JWT', async () => { 83 | // use browser flow to login as the test user 84 | const browser = await chromium.launch({ headless: true }); 85 | const context = await browser.newContext(); 86 | const page = await context.newPage(); 87 | await page.goto(BASE_URL, { waitUntil: 'networkidle' }); 88 | 89 | try { 90 | await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 }); 91 | const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]'); 92 | if (usernameInput) await usernameInput.fill(TEST_USER_USERNAME); 93 | const passwordInput = await page.$('input[type="password"], input[name="password"]'); 94 | if (passwordInput) await passwordInput.fill(TEST_USER_PASSWORD); 95 | const submitButton = await page.$('button[type="submit"], input[type="submit"]'); 96 | if (submitButton) { 97 | await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]); 98 | } 99 | } catch (e) { 100 | // ignore if login form not present 101 | } 102 | 103 | await page.waitForTimeout(1000); 104 | const cookies = await context.cookies(); 105 | const jwtCookie = cookies.find(c => c.name === 'raylab_jwt'); 106 | const refreshCookie = cookies.find(c => c.name === 'raylab_refresh'); 107 | 108 | if (!jwtCookie) { 109 | const fs = require('fs'); 110 | const dir = 'tests/test-artifacts'; 111 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); 112 | const ts = Date.now(); 113 | await page.screenshot({ path: `${dir}/failed-login-${ts}.png`, fullPage: true }); 114 | fs.writeFileSync(`${dir}/failed-login-${ts}-cookies.json`, JSON.stringify(cookies, null, 2), 'utf-8'); 115 | const content = await page.content(); 116 | fs.writeFileSync(`${dir}/failed-login-${ts}-page.html`, content, 'utf-8'); 117 | await context.close(); 118 | await browser.close(); 119 | throw new Error(`No jwt cookie set after login. Saved screenshot/cookies/page to ${dir}`); 120 | } 121 | 122 | // ensure we have an internal jwt cookie 123 | expect(jwtCookie).toBeDefined(); 124 | expect(jwtCookie.value).toBeTruthy(); 125 | 126 | // validate /auth/me using the internal jwt 127 | const token = jwtCookie.value; 128 | const meUrl = new URL('/auth/me', BASE_URL).toString(); 129 | const meResp = await fetch(meUrl, { headers: { Authorization: `Bearer ${token}` } }); 130 | const meJson = await meResp.json(); 131 | expect(meResp.status).toBeLessThan(300); 132 | expect(meJson.success).toBe(true); 133 | expect(meJson.data).toBeDefined(); 134 | expect(meJson.data.email).toBe(TEST_USER_EMAIL); 135 | 136 | await context.close(); 137 | await browser.close(); 138 | }); 139 | 140 | test('login via Authentik (created user) issues internal JWT', async () => { 141 | // use browser flow to login as the created user 142 | const browser = await chromium.launch({ headless: true }); 143 | const context = await browser.newContext(); 144 | const page = await context.newPage(); 145 | await page.goto(BASE_URL, { waitUntil: 'networkidle' }); 146 | 147 | try { 148 | await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 }); 149 | const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]'); 150 | if (usernameInput) await usernameInput.fill(TEST_USER_USERNAME); 151 | const passwordInput = await page.$('input[type="password"], input[name="password"]'); 152 | if (passwordInput) await passwordInput.fill(TEST_USER_PASSWORD); 153 | const submitButton = await page.$('button[type="submit"], input[type="submit"]'); 154 | if (submitButton) { 155 | await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]); 156 | } 157 | } catch (e) { 158 | // ignore if login form not present 159 | } 160 | 161 | await page.waitForTimeout(1000); 162 | const cookies = await context.cookies(); 163 | const jwtCookie = cookies.find(c => c.name === 'raylab_jwt'); 164 | 165 | if (!jwtCookie) { 166 | const fs = require('fs'); 167 | const dir = 'tests/test-artifacts'; 168 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); 169 | const ts = Date.now(); 170 | await page.screenshot({ path: `${dir}/failed-login-2-${ts}.png`, fullPage: true }); 171 | fs.writeFileSync(`${dir}/failed-login-2-${ts}-cookies.json`, JSON.stringify(cookies, null, 2), 'utf-8'); 172 | const content = await page.content(); 173 | fs.writeFileSync(`${dir}/failed-login-2-${ts}-page.html`, content, 'utf-8'); 174 | await context.close(); 175 | await browser.close(); > 176 | throw new Error(`No jwt cookie set after login (second test). Saved screenshot/cookies/page to ${dir}`); | ^ Error: No jwt cookie set after login (second test). Saved screenshot/cookies/page to tests/test-artifacts 177 | } 178 | 179 | await context.close(); 180 | await browser.close(); 181 | 182 | expect(jwtCookie).toBeDefined(); 183 | expect(jwtCookie.value).toBeTruthy(); 184 | }); 185 | 186 | // Cleanup via API provisioning has been removed. If test environment requires cleanup, perform manually in Authentik. 187 | 188 | }); 189 | ```