# 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 creates local user and issues internal JWT - Location: tests\integration\user.integration.spec.ts:82:7 # Error details ``` Error: No jwt cookie set after login. Saved screenshot/cookies/page to tests/test-artifacts ``` # Test source ```ts 19 | // increase default timeout for slow integration flows 20 | test.setTimeout(60000); 21 | 22 | async function adminLogin() { 23 | const adminUser = process.env.ADMIN_USERNAME; 24 | const adminPass = process.env.ADMIN_PASSWORD; 25 | if (!adminUser || !adminPass) throw new Error('ADMIN_USERNAME/ADMIN_PASSWORD must be set in env for integration tests'); 26 | 27 | const browser = await chromium.launch({ headless: true }); 28 | const context = await browser.newContext(); 29 | const page = await context.newPage(); 30 | await page.goto(BASE_URL, { waitUntil: 'networkidle' }); 31 | 32 | try { 33 | await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 }); 34 | const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]'); 35 | if (usernameInput) await usernameInput.fill(adminUser); 36 | const passwordInput = await page.$('input[type="password"], input[name="password"]'); 37 | if (passwordInput) await passwordInput.fill(adminPass); 38 | const submitButton = await page.$('button[type="submit"], input[type="submit"]'); 39 | if (submitButton) { 40 | await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]); 41 | } 42 | } catch (e) { 43 | // ignore if login form not present 44 | } 45 | 46 | await page.waitForTimeout(1000); 47 | const cookies = await context.cookies(); 48 | const jwtCookie = cookies.find(c => c.name === 'raylab_jwt'); 49 | await context.close(); 50 | await browser.close(); 51 | 52 | if (!jwtCookie) throw new Error('Admin login failed: no session cookie'); 53 | return jwtCookie.value; 54 | } 55 | 56 | async function apiRequest(path, token, opts = {}) { 57 | const headers = Object.assign({ 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, opts.headers || {}); 58 | const method = opts.method || 'GET'; 59 | const body = opts.body ? JSON.stringify(opts.body) : undefined; 60 | let url; 61 | try { 62 | url = new URL(path, BASE_URL).toString(); 63 | } catch (e) { 64 | throw new Error(`Invalid BASE_URL for integration tests: ${BASE_URL}`); 65 | } 66 | const res = await fetch(url, { method, headers, body }); 67 | let json = null; 68 | try { json = await res.json(); } catch (e) { json = null; } 69 | return { status: res.status, body: json }; 70 | } 71 | 72 | 73 | test.describe('Integration - User lifecycle', () => { 74 | let adminToken; 75 | let createdUser = null; 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}`); | ^ Error: No jwt cookie set after login. Saved screenshot/cookies/page to tests/test-artifacts 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}`); 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 | ```