Skip to content

Commit a36e24a

Browse files
committed
Try to debug playwright failures
1 parent cabb265 commit a36e24a

File tree

3 files changed

+219
-7
lines changed

3 files changed

+219
-7
lines changed

web/src/app/admin/configuration/default-assistant/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ function DefaultAssistantConfig() {
7979
const persistConfiguration = async (
8080
updates: DefaultAssistantUpdateRequest
8181
) => {
82-
const response = await fetch("/api/admin/default-assistant/", {
82+
// Avoid trailing slash to prevent 307 redirect (breaks CORS in CI)
83+
const response = await fetch("/api/admin/default-assistant", {
8384
method: "PATCH",
8485
headers: { "Content-Type": "application/json" },
8586
body: JSON.stringify(updates),

web/tests/e2e/admin/default-assistant.spec.ts

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,40 @@ test.describe("Default Assistant Admin Page", () => {
1919
await page.waitForURL(
2020
"http://localhost:3000/admin/configuration/default-assistant"
2121
);
22+
23+
// Attach basic API logging for this spec
24+
page.on("response", async (resp) => {
25+
const url = resp.url();
26+
if (url.includes("/api/admin/default-assistant")) {
27+
const method = resp.request().method();
28+
const status = resp.status();
29+
let body = "";
30+
try {
31+
body = await resp.text();
32+
} catch {}
33+
console.log(
34+
`[api:response] ${method} ${url} => ${status} body=${body?.slice(0, 300)}`
35+
);
36+
}
37+
});
38+
39+
// Proactively log tool availability and current config
40+
try {
41+
const toolsResp = await page.request.get(
42+
"http://localhost:3000/api/admin/default-assistant/available-tools"
43+
);
44+
const cfgResp = await page.request.get(
45+
"http://localhost:3000/api/admin/default-assistant/configuration"
46+
);
47+
console.log(
48+
`[/available-tools] status=${toolsResp.status()} body=${(await toolsResp.text()).slice(0, 400)}`
49+
);
50+
console.log(
51+
`[/configuration] status=${cfgResp.status()} body=${(await cfgResp.text()).slice(0, 400)}`
52+
);
53+
} catch (e) {
54+
console.log(`[setup] Failed to fetch initial admin config: ${String(e)}`);
55+
}
2256
});
2357

2458
test("should load default assistant page for admin users", async ({
@@ -28,7 +62,8 @@ test.describe("Default Assistant Admin Page", () => {
2862
await expect(
2963
page.getByRole("heading", { name: "Default Assistant" })
3064
).toBeVisible();
31-
await expect(page.locator("text=Actions")).toBeVisible();
65+
// Avoid strict mode collision from multiple "Actions" elements
66+
await expect(page.getByText("Instructions", { exact: true })).toBeVisible();
3267
await expect(page.getByText("Instructions", { exact: true })).toBeVisible();
3368
});
3469

@@ -44,10 +79,32 @@ test.describe("Default Assistant Admin Page", () => {
4479

4580
// Get initial state
4681
const initialState = await searchToggle.getAttribute("data-state");
82+
const isDisabled = await searchToggle.isDisabled().catch(() => false);
83+
console.log(
84+
`[toggle] Internal Search initial data-state=${initialState} disabled=${isDisabled}`
85+
);
4786

4887
// Toggle it
4988
await searchToggle.click();
5089

90+
// Wait for PATCH to complete (or log if it didn't happen)
91+
const patchResp = await Promise.race([
92+
page.waitForResponse(
93+
(r) =>
94+
r.url().includes("/api/admin/default-assistant") &&
95+
r.request().method() === "PATCH",
96+
{ timeout: 8000 }
97+
),
98+
page.waitForTimeout(8500).then(() => null),
99+
]);
100+
if (patchResp) {
101+
console.log(
102+
`[toggle] Internal Search PATCH status=${patchResp.status()} body=${(await patchResp.text()).slice(0, 300)}`
103+
);
104+
} else {
105+
console.log(`[toggle] Internal Search did not observe PATCH response`);
106+
}
107+
51108
// Wait for the change to persist
52109
await page.waitForTimeout(1000);
53110

@@ -62,6 +119,7 @@ test.describe("Default Assistant Admin Page", () => {
62119
.locator('[role="switch"]')
63120
.first();
64121
const newState = await searchToggleAfter.getAttribute("data-state");
122+
console.log(`[toggle] Internal Search after reload data-state=${newState}`);
65123

66124
// State should have changed
67125
expect(initialState).not.toBe(newState);
@@ -83,9 +141,29 @@ test.describe("Default Assistant Admin Page", () => {
83141

84142
// Get initial state
85143
const initialState = await webSearchToggle.getAttribute("data-state");
144+
const isDisabled = await webSearchToggle.isDisabled().catch(() => false);
145+
console.log(
146+
`[toggle] Web Search initial data-state=${initialState} disabled=${isDisabled}`
147+
);
86148

87149
// Toggle it
88150
await webSearchToggle.click();
151+
const patchResp = await Promise.race([
152+
page.waitForResponse(
153+
(r) =>
154+
r.url().includes("/api/admin/default-assistant") &&
155+
r.request().method() === "PATCH",
156+
{ timeout: 8000 }
157+
),
158+
page.waitForTimeout(8500).then(() => null),
159+
]);
160+
if (patchResp) {
161+
console.log(
162+
`[toggle] Web Search PATCH status=${patchResp.status()} body=${(await patchResp.text()).slice(0, 300)}`
163+
);
164+
} else {
165+
console.log(`[toggle] Web Search did not observe PATCH response`);
166+
}
89167

90168
// Wait for the change to persist
91169
await page.waitForTimeout(1000);
@@ -101,6 +179,7 @@ test.describe("Default Assistant Admin Page", () => {
101179
.locator('[role="switch"]')
102180
.first();
103181
const newState = await webSearchToggleAfter.getAttribute("data-state");
182+
console.log(`[toggle] Web Search after reload data-state=${newState}`);
104183

105184
// State should have changed
106185
expect(initialState).not.toBe(newState);
@@ -122,9 +201,29 @@ test.describe("Default Assistant Admin Page", () => {
122201

123202
// Get initial state
124203
const initialState = await imageGenToggle.getAttribute("data-state");
204+
const isDisabled = await imageGenToggle.isDisabled().catch(() => false);
205+
console.log(
206+
`[toggle] Image Generation initial data-state=${initialState} disabled=${isDisabled}`
207+
);
125208

126209
// Toggle it
127210
await imageGenToggle.click();
211+
const patchResp = await Promise.race([
212+
page.waitForResponse(
213+
(r) =>
214+
r.url().includes("/api/admin/default-assistant") &&
215+
r.request().method() === "PATCH",
216+
{ timeout: 8000 }
217+
),
218+
page.waitForTimeout(8500).then(() => null),
219+
]);
220+
if (patchResp) {
221+
console.log(
222+
`[toggle] Image Generation PATCH status=${patchResp.status()} body=${(await patchResp.text()).slice(0, 300)}`
223+
);
224+
} else {
225+
console.log(`[toggle] Image Generation did not observe PATCH response`);
226+
}
128227

129228
// Wait for the change to persist
130229
await page.waitForTimeout(1000);
@@ -140,6 +239,9 @@ test.describe("Default Assistant Admin Page", () => {
140239
.locator('[role="switch"]')
141240
.first();
142241
const newState = await imageGenToggleAfter.getAttribute("data-state");
242+
console.log(
243+
`[toggle] Image Generation after reload data-state=${newState}`
244+
);
143245

144246
// State should have changed
145247
expect(initialState).not.toBe(newState);
@@ -167,6 +269,22 @@ test.describe("Default Assistant Admin Page", () => {
167269
// Save changes
168270
const saveButton = page.locator("text=Save Instructions");
169271
await saveButton.click();
272+
const patchResp = await Promise.race([
273+
page.waitForResponse(
274+
(r) =>
275+
r.url().includes("/api/admin/default-assistant") &&
276+
r.request().method() === "PATCH",
277+
{ timeout: 8000 }
278+
),
279+
page.waitForTimeout(8500).then(() => null),
280+
]);
281+
if (patchResp) {
282+
console.log(
283+
`[prompt] Save PATCH status=${patchResp.status()} body=${(await patchResp.text()).slice(0, 300)}`
284+
);
285+
} else {
286+
console.log(`[prompt] Did not observe PATCH response on save`);
287+
}
170288

171289
// Wait for success message
172290
await expect(
@@ -208,6 +326,14 @@ test.describe("Default Assistant Admin Page", () => {
208326
await textarea.fill("Temporary text");
209327
const tempSaveButton = page.locator("text=Save Instructions");
210328
await tempSaveButton.click();
329+
const patchResp1 = await page.waitForResponse(
330+
(r) =>
331+
r.url().includes("/api/admin/default-assistant") &&
332+
r.request().method() === "PATCH"
333+
);
334+
console.log(
335+
`[prompt-empty] Temp save PATCH status=${patchResp1.status()} body=${(await patchResp1.text()).slice(0, 300)}`
336+
);
211337
await expect(
212338
page.locator("text=Instructions updated successfully!")
213339
).toBeVisible();
@@ -220,6 +346,14 @@ test.describe("Default Assistant Admin Page", () => {
220346
// Save changes
221347
const saveButton = page.locator("text=Save Instructions");
222348
await saveButton.click();
349+
const patchResp2 = await page.waitForResponse(
350+
(r) =>
351+
r.url().includes("/api/admin/default-assistant") &&
352+
r.request().method() === "PATCH"
353+
);
354+
console.log(
355+
`[prompt-empty] Save empty PATCH status=${patchResp2.status()} body=${(await patchResp2.text()).slice(0, 300)}`
356+
);
223357

224358
// Wait for success message
225359
await expect(
@@ -241,6 +375,14 @@ test.describe("Default Assistant Admin Page", () => {
241375
await textareaAfter.fill(initialValue);
242376
const saveButtonAfter = page.locator("text=Save Instructions");
243377
await saveButtonAfter.click();
378+
const patchResp3 = await page.waitForResponse(
379+
(r) =>
380+
r.url().includes("/api/admin/default-assistant") &&
381+
r.request().method() === "PATCH"
382+
);
383+
console.log(
384+
`[prompt-empty] Restore PATCH status=${patchResp3.status()} body=${(await patchResp3.text()).slice(0, 300)}`
385+
);
244386
await expect(
245387
page.locator("text=Instructions updated successfully!")
246388
).toBeVisible();
@@ -272,6 +414,14 @@ test.describe("Default Assistant Admin Page", () => {
272414
// Save changes
273415
const saveButton = page.locator("text=Save Instructions");
274416
await saveButton.click();
417+
const patchResp = await page.waitForResponse(
418+
(r) =>
419+
r.url().includes("/api/admin/default-assistant") &&
420+
r.request().method() === "PATCH"
421+
);
422+
console.log(
423+
`[prompt-long] Save PATCH status=${patchResp.status()} body=${(await patchResp.text()).slice(0, 300)}`
424+
);
275425

276426
// Wait for success message
277427
await expect(
@@ -287,6 +437,14 @@ test.describe("Default Assistant Admin Page", () => {
287437
if (initialValue !== currentValue) {
288438
await textarea.fill(initialValue);
289439
await saveButton.click();
440+
const patchRespRestore = await page.waitForResponse(
441+
(r) =>
442+
r.url().includes("/api/admin/default-assistant") &&
443+
r.request().method() === "PATCH"
444+
);
445+
console.log(
446+
`[prompt-long] Restore PATCH status=${patchRespRestore.status()} body=${(await patchRespRestore.text()).slice(0, 300)}`
447+
);
290448
await expect(
291449
page.locator("text=Instructions updated successfully!")
292450
).toBeVisible();
@@ -315,7 +473,7 @@ test.describe("Default Assistant Admin Page", () => {
315473
// Use browser console to send invalid tool IDs
316474
// This simulates what would happen if someone tried to bypass the UI
317475
const response = await page.evaluate(async () => {
318-
const res = await fetch("/api/admin/default-assistant/", {
476+
const res = await fetch("/api/admin/default-assistant", {
319477
method: "PATCH",
320478
headers: { "Content-Type": "application/json" },
321479
body: JSON.stringify({
@@ -328,6 +486,21 @@ test.describe("Default Assistant Admin Page", () => {
328486
body: await res.text(),
329487
};
330488
});
489+
// Also try via page.request (uses storageState) to capture status in case page fetch fails
490+
try {
491+
const alt = await page.request.patch(
492+
"http://localhost:3000/api/admin/default-assistant",
493+
{
494+
data: { tool_ids: ["InvalidTool", "AnotherInvalidTool"] },
495+
headers: { "Content-Type": "application/json" },
496+
}
497+
);
498+
console.log(
499+
`[invalid-tools] page.request.patch status=${alt.status()} body=${(await alt.text()).slice(0, 300)}`
500+
);
501+
} catch (e) {
502+
console.log(`[invalid-tools] page.request.patch error: ${String(e)}`);
503+
}
331504

332505
// Check that the request failed with 400 or 422 (validation error)
333506
expect(response.ok).toBe(false);

web/tests/e2e/utils/auth.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,31 @@ export async function loginAs(
1818
? TEST_ADMIN2_CREDENTIALS
1919
: TEST_USER_CREDENTIALS;
2020

21+
console.log(`[loginAs] Navigating to /auth/login as ${userType}`);
2122
await page.goto("http://localhost:3000/auth/login");
2223

2324
await page.fill("#email", email);
2425
await page.fill("#password", password);
2526

2627
// Click the login button
2728
await page.click('button[type="submit"]');
29+
// Log any console errors during login
30+
page.on("console", (msg) => {
31+
if (msg.type() === "error") {
32+
console.log(`[page:console:error] ${msg.text()}`);
33+
}
34+
});
2835

2936
try {
30-
await page.waitForURL("http://localhost:3000/chat", { timeout: 4000 });
37+
await page.waitForURL("http://localhost:3000/chat", { timeout: 10000 });
38+
console.log(
39+
`[loginAs] Redirected to /chat for ${userType}. URL: ${page.url()}`
40+
);
3141
} catch (error) {
32-
console.log(`Timeout occurred. Current URL: ${page.url()}`);
42+
console.log(`[loginAs] Timeout to /chat. Current URL: ${page.url()}`);
3343

3444
// If redirect to /chat doesn't happen, go to /auth/login
45+
console.log(`[loginAs] Navigating to /auth/signup as fallback`);
3546
await page.goto("http://localhost:3000/auth/signup");
3647

3748
await page.fill("#email", email);
@@ -41,11 +52,38 @@ export async function loginAs(
4152
await page.click('button[type="submit"]');
4253

4354
try {
44-
await page.waitForURL("http://localhost:3000/chat", { timeout: 4000 });
55+
await page.waitForURL("http://localhost:3000/chat", { timeout: 10000 });
56+
console.log(
57+
`[loginAs] Fallback redirected to /chat for ${userType}. URL: ${page.url()}`
58+
);
4559
} catch (error) {
46-
console.log(`Timeout occurred again. Current URL: ${page.url()}`);
60+
console.log(
61+
`[loginAs] Fallback timeout again. Current URL: ${page.url()}`
62+
);
4763
}
4864
}
65+
66+
try {
67+
// Try to fetch current user info from the page context
68+
const me = await page.evaluate(async () => {
69+
try {
70+
const res = await fetch("/api/auth/me", { credentials: "include" });
71+
return {
72+
ok: res.ok,
73+
status: res.status,
74+
url: res.url,
75+
body: await res.text(),
76+
};
77+
} catch (e) {
78+
return { ok: false, status: 0, url: "", body: `error: ${String(e)}` };
79+
}
80+
});
81+
console.log(
82+
`[loginAs] /api/auth/me => ok=${me.ok} status=${me.status} url=${me.url}`
83+
);
84+
} catch (e) {
85+
console.log(`[loginAs] Failed to query /api/auth/me: ${String(e)}`);
86+
}
4987
}
5088
// Function to generate a random email and password
5189
const generateRandomCredentials = () => {

0 commit comments

Comments
 (0)