fix(e2e): recover harness after exhausted import retries

This commit is contained in:
rookiestar28
2026-04-18 21:31:10 +08:00
parent 10c0a60a39
commit 922299ae8a
2 changed files with 76 additions and 11 deletions
+27
View File
@@ -118,4 +118,31 @@ test.describe('OpenClaw Sidebar', () => {
.poll(() => page.evaluate(() => window.__openclawTestLoadAttempts))
.toBe(4);
});
test('recovers when the first harness boot exhausts transient entry fetch retries', async ({ page }) => {
let remainingFailures = 4;
let totalEntryRequests = 0;
await page.route('**/web/openclaw.js?openclaw_harness_attempt=*', async (route) => {
const url = new URL(route.request().url());
if (url.pathname !== '/web/openclaw.js') {
await route.fallback();
return;
}
totalEntryRequests += 1;
if (remainingFailures > 0) {
remainingFailures -= 1;
await route.abort('failed');
return;
}
await route.fallback();
});
await page.reload();
await waitForOpenClawReady(page);
await expect(page.locator('.openclaw-title')).toHaveText('OpenClaw');
await expect.poll(() => totalEntryRequests).toBe(5);
});
});
+49 -11
View File
@@ -24,6 +24,23 @@ function resolveUiTimeoutMs() {
return 30_000;
}
function resolveHarnessReloadBudget() {
const raw = process.env.OPENCLAW_E2E_HARNESS_RELOADS;
if (raw) {
const parsed = Number.parseInt(raw, 10);
if (Number.isInteger(parsed) && parsed >= 0) {
return parsed;
}
}
return 1;
}
function isTransientModuleFetchFailure(error) {
const message = String(error?.message || error || '');
return message.includes('Failed to fetch dynamically imported module');
}
function normalizeApiPath(pathname) {
if (typeof pathname !== 'string') return '';
const stripped = pathname.startsWith('/api/') ? pathname.slice(4) : pathname;
@@ -573,20 +590,41 @@ export async function mockRemoteAdminBaseline(
export async function waitForOpenClawReady(page) {
const timeoutMs = resolveUiTimeoutMs();
await page.waitForFunction(
() => window.__openclawTestReady === true || window.__openclawTestError,
null,
{ timeout: timeoutMs }
);
const maxHarnessReloads = resolveHarnessReloadBudget();
for (let reloadAttempt = 0; reloadAttempt <= maxHarnessReloads; reloadAttempt += 1) {
await page.waitForFunction(
() => window.__openclawTestReady === true || window.__openclawTestError,
null,
{ timeout: timeoutMs }
);
const [error, loadAttempts] = await Promise.all([
page.evaluate(() => window.__openclawTestError),
page.evaluate(() => window.__openclawTestLoadAttempts || 0),
]);
if (!error) {
// Basic sanity: header + tab bar exists
await expect(page.locator('.openclaw-header')).toBeVisible();
await expect(page.locator('.openclaw-tabs')).toBeVisible();
return;
}
// IMPORTANT: only recover via full-page reload after the in-page harness
// has already exhausted its own transient-fetch retry budget. This keeps
// CI-only fetch flakes recoverable without masking real module/runtime bugs.
if (
isTransientModuleFetchFailure(error) &&
loadAttempts >= 4 &&
reloadAttempt < maxHarnessReloads
) {
await page.reload({ waitUntil: 'load' });
continue;
}
const error = await page.evaluate(() => window.__openclawTestError);
if (error) {
throw new Error(`OpenClaw test harness failed to load: ${error?.message || error}`);
}
// Basic sanity: header + tab bar exists
await expect(page.locator('.openclaw-header')).toBeVisible();
await expect(page.locator('.openclaw-tabs')).toBeVisible();
}
export async function clickTab(page, title) {