diff --git a/app/test/e2e/specs/cron-jobs-flow.spec.ts b/app/test/e2e/specs/cron-jobs-flow.spec.ts index 47c3920b8..375097200 100644 --- a/app/test/e2e/specs/cron-jobs-flow.spec.ts +++ b/app/test/e2e/specs/cron-jobs-flow.spec.ts @@ -36,7 +36,7 @@ import { waitForText, } from '../helpers/element-helpers'; import { resetApp } from '../helpers/reset-app'; -import { navigateToSettings, navigateViaHash } from '../helpers/shared-flows'; +import { navigateToSettings, navigateViaHash, waitForHomePage } from '../helpers/shared-flows'; import { startMockServer, stopMockServer } from '../mock-server'; const USER_ID = 'e2e-cron-jobs'; @@ -51,18 +51,6 @@ function stepLog(message: string, context?: unknown): void { console.log(`[CronJobsE2E][${stamp}] ${message}`, JSON.stringify(context, null, 2)); } -/** Wait for an element matching one of several texts to be visible. */ -async function waitForAnyText(candidates: string[], timeoutMs = 10_000): Promise { - const deadline = Date.now() + timeoutMs; - while (Date.now() < deadline) { - for (const text of candidates) { - if (await textExists(text)) return text; - } - await browser.pause(500); - } - return null; -} - async function waitForCronPanel(timeoutMs = 5_000): Promise { try { await waitForTestId('cron-jobs-panel', timeoutMs); @@ -118,12 +106,14 @@ describe('Cron jobs settings panel (real UI flow)', () => { }); it('completing onboarding lands the user on the home screen', async () => { - // Home.tsx renders t('home.askAssistant') = 'Ask your assistant anything...' as the stable - // CTA button. Old strings ('Good morning', 'Message OpenHuman', etc.) are no longer rendered. - const home = await waitForAnyText( - ['Ask your assistant anything', 'Your device is connected'], - 15_000 - ); + // `/home` redirects to `/chat` (AppRoutes.tsx), so the landed surface can render + // either the CTA `home.askAssistant` ('Ask your assistant anything...') OR the + // `home.statusOk` hero ('Your assistant is ready when you are. Type something below + // to get started.'). Use the canonical waitForHomePage() helper — the same one + // resetApp() and every other spec use — which accepts the full marker set, instead + // of a stale subset that only matched the CTA and flaked on the slower macOS runner + // whenever the statusOk hero rendered first. + const home = await waitForHomePage(20_000); expect(home).toBeTruthy(); }); @@ -157,6 +147,10 @@ describe('Cron jobs settings panel (real UI flow)', () => { await waitForCronRow(MORNING_BRIEFING, 10_000); } expect(await textExists(MORNING_BRIEFING)).toBe(true); + // The 'Enabled' status badge (CoreJobList renders t('common.enabled')) can paint a + // beat after the row name. Poll for it instead of point-checking right away — the + // bare textExists() check raced the render on the slower macOS runner. + await waitForText('Enabled', 10_000); expect(await textExists('Enabled')).toBe(true); }); @@ -186,8 +180,10 @@ describe('Cron jobs settings panel (real UI flow)', () => { await clickNativeButton('Remove', 8_000); // UI assertion first — the row should disappear and the empty state appear. + // The removal RPC + optimistic re-render can take longer on the slower macOS + // runner, so poll for up to 20s rather than 10s before declaring the row stuck. const gone = await browser.waitUntil(async () => !(await textExists(MORNING_BRIEFING)), { - timeout: 10_000, + timeout: 20_000, interval: 500, timeoutMsg: 'morning_briefing row never disappeared', }); diff --git a/app/test/e2e/specs/telegram-channel-flow.spec.ts b/app/test/e2e/specs/telegram-channel-flow.spec.ts index 0ab9d52ea..255a8f00f 100644 --- a/app/test/e2e/specs/telegram-channel-flow.spec.ts +++ b/app/test/e2e/specs/telegram-channel-flow.spec.ts @@ -341,7 +341,13 @@ describe('Telegram channel — connect / receive / send / disconnect', () => { // ────────────────────────────────────────────────────────────────────────── it('C.5 inbound text message round-trip — inject update; observe or document reply path', async function () { - this.timeout(60_000); + // Internal budget is up to 30s (getUpdates poll) + 25s (reply) + connect/LLM + // overhead — that sums past a 60s ceiling on the slower macOS runner, so the + // working round-trip blew the Mocha `it` timeout instead of asserting. 90s + // (matching C.7) gives the observed flow headroom without masking a real hang: + // the getUpdates soft-pass at line ~386 still short-circuits if the listener + // never polls. + this.timeout(90_000); console.log(`${LOG_PREFIX} C.5: setting up inbound message round-trip`); // First ensure the bot is connected (writes credentials + TOML config). @@ -432,7 +438,10 @@ describe('Telegram channel — connect / receive / send / disconnect', () => { // ────────────────────────────────────────────────────────────────────────── it('C.6 unauthorized user — connect with allowlist; excluded sender gets approval prompt', async function () { - this.timeout(60_000); + // 30s getUpdates poll + 20s reply wait + connect/LLM overhead exceeds 60s on the + // slower macOS runner; 90s fits the working flow. The getUpdates soft-pass still + // guards against a genuinely dead listener. + this.timeout(90_000); console.log(`${LOG_PREFIX} C.6: connecting with allowlist excluding Bob`); // Connect with Alice in the allowlist — Bob is excluded. @@ -653,7 +662,10 @@ describe('Telegram channel — connect / receive / send / disconnect', () => { // ────────────────────────────────────────────────────────────────────────── it('C.10 remote /status command — bot replies with Thread: and Provider: markers', async function () { - this.timeout(60_000); + // 30s listener poll + 20s reply wait + connect/LLM overhead exceeds 60s on the + // slower macOS runner; 90s fits the working flow. The getUpdates soft-pass still + // guards against a genuinely dead listener. + this.timeout(90_000); console.log(`${LOG_PREFIX} C.10: setting up /status command scenario`); await connectTelegramBot({ botToken: BOT_TOKEN, allowedUsers: [ALICE_USERNAME] });