Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions scripts/build-pages-report.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -510,21 +510,32 @@ async function restoreReport(baseUrl, siteDirectory, report) {
}
}
await Promise.all(
Array.from({ length: Math.min(8, files.length) }, () => restoreNextFiles()),
Array.from({ length: Math.min(4, files.length) }, () => restoreNextFiles()),
);
if (failure) throw failure;
}

async function restoreFile(baseUrl, siteDirectory, report, file) {
let response;
for (let attempt = 0; attempt < 3; attempt++) {
for (let attempt = 0; attempt < 6; attempt++) {
response = await fetch(new URL(file, baseUrl), {
redirect: 'follow',
signal: AbortSignal.timeout(30_000),
});
if (response.status !== 503 || attempt === 2) break;
const retryable =
(response.status === 503 && attempt < 2) ||
(response.status === 429 && attempt < 5);
if (!retryable) break;
const retryAfterHeader = response.headers.get('retry-after');
const retryAfter = Number(retryAfterHeader);
const delayMs =
response.status === 429
? retryAfterHeader !== null && Number.isFinite(retryAfter) && retryAfter >= 0
? Math.min(retryAfter * 1000, 30_000)
: Math.min(1000 * 2 ** (attempt + 1), 30_000)
: 1000 * (attempt + 1);
await response.body?.cancel();
await new Promise((done) => setTimeout(done, 1000 * (attempt + 1)));
await new Promise((done) => setTimeout(done, delayMs));
}
if (!response.ok) {
throw new Error(
Expand Down
43 changes: 43 additions & 0 deletions tests/contracts/test_pages_report_history.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,49 @@ test('restores retained history before adding the new run', async (context) => {
);
});

test('retries rate-limited history files before failing the report build', async (context) => {
const root = await mkdtemp(path.join(os.tmpdir(), 'pages-rate-limit-'));
const reportDirectory = await fixtureDirectory(root);
const siteDirectory = path.join(root, 'site');
const file = 'reports/100/index.html';
let attempts = 0;
const server = await startServer((request, response) => {
if (request.url === '/reports/manifest.json') {
response.setHeader('content-type', 'application/json');
response.end(JSON.stringify({
version: 1,
reports: [{
runId: '100',
generatedAt: '2026-09-14T12:00:00.000Z',
label: 'Omarchy 4.0.3',
successRate: 100,
testCount: 1,
averageDurationMs: 4000,
modelCallCount: 1,
tokenUsage: null,
workflowUrl: 'https://github.com/quanru/doubao-say/actions/runs/100',
reportPath: file,
}],
}));
} else if (request.url === `/${file}`) {
attempts++;
if (attempts === 1) {
response.writeHead(429, { 'retry-after': '0' }).end();
} else {
response.writeHead(200, { 'content-type': 'text/html' });
response.end('<html>restored</html>');
}
} else {
response.writeHead(404).end();
}
});
context.after(server.close);

await buildPagesReport(options(reportDirectory, siteDirectory, server.url));
assert.equal(attempts, 2);
assert.equal(await readFile(path.join(siteDirectory, file), 'utf8'), '<html>restored</html>');
});

test('restores large report histories with bounded parallel downloads', async (context) => {
const root = await mkdtemp(path.join(os.tmpdir(), 'pages-parallel-history-'));
const reportDirectory = await fixtureDirectory(root);
Expand Down
9 changes: 3 additions & 6 deletions tests/e2e/cases/runtime.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,15 @@ cases:
- aiWaitFor:
prompt: The recording overlay is visible and the target field is still empty.
options: { timeoutMs: 30000, checkIntervalMs: 1000 }
- aiAssert: The recording overlay is visible and the target field is still empty.
- computer.keyPress: { keyName: Escape }
- aiWaitFor:
prompt: The overlay is gone and the status says Cancelled, no text delivered, and ready to try again.
prompt: The large Synthetic dictation delivery target window shows an empty text field and the status Cancelled · no text delivered · ready to try again. The small floating recording panel with Listening to synthetic speech is not visible.
options: { timeoutMs: 30000, checkIntervalMs: 1000 }
- aiAssert: The target field remains empty and the status says Cancelled, no text delivered, and ready to try again.
- computer.keyPress: { keyName: F8 }
- aiWaitFor:
prompt: A new recording overlay is visible.
prompt: A small floating recording panel says Listening to synthetic speech and Synthetic dictation is ready to deliver.
options: { timeoutMs: 30000, checkIntervalMs: 1000 }
- computer.keyPress: { keyName: F8 }
- aiWaitFor:
prompt: The new attempt has delivered exactly one synthetic sentence and the status reports 1 delivery.
prompt: The Synthetic dictation delivery target window shows Synthetic dictation delivered exactly once. in its text field and Delivered once · 1 delivery in its status.
options: { timeoutMs: 30000, checkIntervalMs: 1000 }
- aiAssert: A new attempt succeeded with exactly one delivered synthetic sentence and the status reports 1 delivery.
10 changes: 7 additions & 3 deletions tests/e2e/gtk_runtime_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ def delivery_changed(value):
worker=ImmediateWorker(),
)

def restore_target_focus():
window.present()
window.set_focus(target)
target.grab_focus()

def key_pressed(_controller, keyval, _keycode, _modifiers):
if keyval == Gdk.KEY_F8:
if state["recording"]:
Expand All @@ -88,9 +93,7 @@ def key_pressed(_controller, keyval, _keycode, _modifiers):

def close_overlay_and_restore_target():
overlay.hide()
window.present()
window.set_focus(target)
target.grab_focus()
restore_target_focus()
status.set_text(
f"Delivered once · {state['deliveries']} delivery"
)
Expand All @@ -105,6 +108,7 @@ def close_overlay_and_restore_target():
if keyval == Gdk.KEY_Escape and state["recording"]:
state["recording"] = False
overlay.hide()
restore_target_focus()
status.set_text("Cancelled · no text delivered · ready to try again")
return True
return False
Expand Down
Loading