Skip to content

Commit 2d056fe

Browse files
committed
fix(web): stabilize flaky E2E tests
sessions.spec.js: Add per-RPC timeout (5s) to sendRpcFromPage so a stuck WebSocket call can't consume the entire 45s test timeout. Reduce retries from 40 to 10. sandboxes.spec.js: Wait for container row to render before clicking Delete. Add explicit timeout to "No containers found" assertion. settings-nav.spec.js: Wait for WebSocket connection in settings section load tests. Replace unreliable requestAnimationFrame waits with setTimeout(100) in mockChannelsStatus. Add explicit timeout to matrix senders visibility assertion.
1 parent 01c1acd commit 2d056fe

3 files changed

Lines changed: 15 additions & 10 deletions

File tree

crates/web/ui/e2e/specs/sandboxes.spec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ test.describe("Sandboxes page – Running Containers", () => {
238238

239239
await navigateAndWait(page, "/settings/sandboxes");
240240
await expect(page.getByRole("button", { name: "Refresh", exact: true })).toBeVisible();
241-
await expect(page.getByText("No containers found.")).toBeVisible();
241+
await expect(page.getByText("No containers found.")).toBeVisible({ timeout: 10_000 });
242242

243243
expect(pageErrors).toEqual([]);
244244
});
@@ -396,7 +396,8 @@ test.describe("Sandboxes page – Container error handling", () => {
396396
await navigateAndWait(page, "/settings/sandboxes");
397397
await expect.poll(() => containerListFetches, { timeout: 10_000 }).toBeGreaterThan(0);
398398

399-
// Click the delete button
399+
// Wait for the container row to render before clicking delete
400+
await expect(page.getByText("moltis-sandbox-ghost")).toBeVisible({ timeout: 10_000 });
400401
await page.getByRole("button", { name: "Delete", exact: true }).click();
401402

402403
// Error message should appear

crates/web/ui/e2e/specs/sessions.spec.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,33 @@ function isRetryableRpcError(message) {
1616

1717
async function sendRpcFromPage(page, method, params) {
1818
let lastResponse = null;
19-
for (let attempt = 0; attempt < 40; attempt++) {
19+
for (let attempt = 0; attempt < 10; attempt++) {
2020
if (attempt > 0) {
2121
await waitForWsConnected(page);
2222
}
2323
lastResponse = await page
2424
.evaluate(
25-
async ({ methodName, methodParams }) => {
25+
async ({ methodName, methodParams, timeoutMs }) => {
2626
var appScript = document.querySelector('script[type="module"][src*="js/app.js"]');
2727
if (!appScript) throw new Error("app module script not found");
2828
var appUrl = new URL(appScript.src, window.location.origin);
2929
var prefix = appUrl.href.slice(0, appUrl.href.length - "js/app.js".length);
3030
var helpers = await import(`${prefix}js/helpers.js`);
31-
return helpers.sendRpc(methodName, methodParams);
31+
var rpc = helpers.sendRpc(methodName, methodParams);
32+
var timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("RPC timeout")), timeoutMs));
33+
return Promise.race([rpc, timeout]);
3234
},
3335
{
3436
methodName: method,
3537
methodParams: params,
38+
timeoutMs: 5000,
3639
},
3740
)
3841
.catch((error) => ({ ok: false, error: { message: error?.message || String(error) } }));
3942

4043
if (lastResponse?.ok) return lastResponse;
41-
if (!isRetryableRpcError(lastResponse?.error?.message)) return lastResponse;
44+
if (!isRetryableRpcError(lastResponse?.error?.message) && !lastResponse?.error?.message?.includes("RPC timeout"))
45+
return lastResponse;
4246
}
4347
return lastResponse;
4448
}

crates/web/ui/e2e/specs/settings-nav.spec.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,7 @@ async function mockChannelsStatus(page, { channels, senders = [], allowRetryOwne
9090
} else {
9191
await channelsPage.prefetchChannels();
9292
}
93-
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
94-
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
93+
await new Promise((resolve) => setTimeout(resolve, 100));
9594
},
9695
{ channels, senders, allowRetryOwnership, label },
9796
);
@@ -224,6 +223,7 @@ test.describe("Settings navigation", () => {
224223
test(`settings/${section.id} loads without errors`, async ({ page }) => {
225224
const pageErrors = watchPageErrors(page);
226225
await navigateAndWait(page, `/settings/${section.id}`);
226+
await waitForWsConnected(page);
227227

228228
await expect(page).toHaveURL(new RegExp(`/settings/${section.id}$`));
229229

@@ -1305,10 +1305,10 @@ test.describe("Settings navigation", () => {
13051305
});
13061306
state.setConnected(true);
13071307
await channelsPage.prefetchChannels();
1308-
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
1308+
await new Promise((resolve) => setTimeout(resolve, 100));
13091309
});
13101310

1311-
await expect(page.getByText("Matrix (moltis-testbot)", { exact: true })).toBeVisible();
1311+
await expect(page.getByText("Matrix (moltis-testbot)", { exact: true })).toBeVisible({ timeout: 10_000 });
13121312
await page.getByRole("tab", { name: /Senders/ }).click();
13131313
await expect.poll(() => page.locator(".senders-table tbody tr").count(), { timeout: 10_000 }).toBe(1);
13141314
await expect(page.getByText("Alice", { exact: true })).toBeVisible();

0 commit comments

Comments
 (0)