From 22bc5c5cdea040ef0079b633373536f09ec7a70f Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Thu, 30 Jul 2026 12:32:58 +0200 Subject: [PATCH] Doubles frontend test findBy* timeouts to reduce CI flakiness 5000ms was occasionally too tight on the self-hosted Actions runner for lazy-loaded routes gated behind an async setup-status check (observed on SettingsPage.test.tsx, passed reliably locally). Bumps to 10000ms across all nine test files using that pattern, and raises vite.config.ts's global testTimeout from 15000 to 20000 to keep headroom above it. --- .../cms/AddCmsInstanceDialog.test.tsx | 2 +- .../components/cms/SetStatusDialog.test.tsx | 2 +- .../users/InviteUserDialog.test.tsx | 2 +- frontend/src/pages/AccessDeniedPage.test.tsx | 6 ++--- frontend/src/pages/CmsPage.test.tsx | 12 ++++----- frontend/src/pages/NotFoundPage.test.tsx | 6 ++--- frontend/src/pages/ProfilePage.test.tsx | 4 +-- frontend/src/pages/SettingsPage.test.tsx | 26 +++++++++---------- frontend/src/pages/UsersPage.test.tsx | 2 +- frontend/vite.config.ts | 2 +- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/cms/AddCmsInstanceDialog.test.tsx b/frontend/src/components/cms/AddCmsInstanceDialog.test.tsx index faa321b..7541120 100644 --- a/frontend/src/components/cms/AddCmsInstanceDialog.test.tsx +++ b/frontend/src/components/cms/AddCmsInstanceDialog.test.tsx @@ -16,7 +16,7 @@ beforeEach(() => { async function openDialog() { mockAuthenticated(); renderApp('/cms'); - await screen.findByTestId('cms-add-button', {}, { timeout: 5000 }); + await screen.findByTestId('cms-add-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('cms-add-button')); expect(screen.getByTestId('add-cms-name')).toBeInTheDocument(); } diff --git a/frontend/src/components/cms/SetStatusDialog.test.tsx b/frontend/src/components/cms/SetStatusDialog.test.tsx index f819b1b..0d93064 100644 --- a/frontend/src/components/cms/SetStatusDialog.test.tsx +++ b/frontend/src/components/cms/SetStatusDialog.test.tsx @@ -13,7 +13,7 @@ beforeEach(() => { async function openSetStatusDialog() { mockAuthenticated(); renderApp('/cms'); - const triggers = await screen.findAllByTestId('cms-instance-actions-trigger', {}, { timeout: 5000 }); + const triggers = await screen.findAllByTestId('cms-instance-actions-trigger', {}, { timeout: 10000 }); await userEvent.click(triggers[0]); await userEvent.click(await screen.findByTestId('cms-instance-set-status')); expect(screen.getByTestId('set-status-select')).toBeInTheDocument(); diff --git a/frontend/src/components/users/InviteUserDialog.test.tsx b/frontend/src/components/users/InviteUserDialog.test.tsx index adc8b07..46f79a2 100644 --- a/frontend/src/components/users/InviteUserDialog.test.tsx +++ b/frontend/src/components/users/InviteUserDialog.test.tsx @@ -14,7 +14,7 @@ beforeEach(() => { async function openDialog() { mockAuthenticated(); renderApp('/users'); - await screen.findByTestId('users-invite-button', {}, { timeout: 5000 }); + await screen.findByTestId('users-invite-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('users-invite-button')); expect(screen.getByTestId('invite-dialog-email')).toBeInTheDocument(); } diff --git a/frontend/src/pages/AccessDeniedPage.test.tsx b/frontend/src/pages/AccessDeniedPage.test.tsx index d5237de..772d293 100644 --- a/frontend/src/pages/AccessDeniedPage.test.tsx +++ b/frontend/src/pages/AccessDeniedPage.test.tsx @@ -12,21 +12,21 @@ describe('AccessDeniedPage', () => { it('renders the 403 title and message', async () => { renderApp('/403'); - expect(await screen.findByTestId('access-denied-title', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('access-denied-title', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('access-denied-message')).toBeInTheDocument(); }); it('renders the back to dashboard button', async () => { renderApp('/403'); - expect(await screen.findByTestId('access-denied-back-button', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('access-denied-back-button', {}, { timeout: 10000 })).toBeInTheDocument(); }); it('navigates to login when back button is clicked as guest', async () => { mockGuest(); renderApp('/403'); - const button = await screen.findByTestId('access-denied-back-button', {}, { timeout: 5000 }); + const button = await screen.findByTestId('access-denied-back-button', {}, { timeout: 10000 }); await userEvent.click(button); expect(await screen.findByTestId('login-form-submit-button')).toBeInTheDocument(); diff --git a/frontend/src/pages/CmsPage.test.tsx b/frontend/src/pages/CmsPage.test.tsx index fd98470..a36bca9 100644 --- a/frontend/src/pages/CmsPage.test.tsx +++ b/frontend/src/pages/CmsPage.test.tsx @@ -20,7 +20,7 @@ describe('CmsPage', () => { mockAuthenticated(); renderApp('/cms'); - expect(await screen.findByTestId('cms-title', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('cms-title', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('cms-add-button')).toBeInTheDocument(); }); @@ -28,7 +28,7 @@ describe('CmsPage', () => { mockAuthenticated(); renderApp('/cms'); - const rows = await screen.findAllByTestId('cms-instance-row', {}, { timeout: 5000 }); + const rows = await screen.findAllByTestId('cms-instance-row', {}, { timeout: 10000 }); expect(rows.length).toBe(2); }); @@ -37,7 +37,7 @@ describe('CmsPage', () => { server.use(http.get(CMS_URL, () => HttpResponse.json([]))); renderApp('/cms'); - expect(await screen.findByTestId('cms-empty-state', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('cms-empty-state', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('cms-empty-add-button')).toBeInTheDocument(); }); @@ -45,7 +45,7 @@ describe('CmsPage', () => { mockAuthenticated(); renderApp('/cms'); - await screen.findByTestId('cms-add-button', {}, { timeout: 5000 }); + await screen.findByTestId('cms-add-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('cms-add-button')); expect(await screen.findByTestId('add-cms-name')).toBeInTheDocument(); @@ -55,7 +55,7 @@ describe('CmsPage', () => { mockAuthenticated(); renderApp('/cms'); - await screen.findByTestId('cms-add-button', {}, { timeout: 5000 }); + await screen.findByTestId('cms-add-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('cms-add-button')); await userEvent.type(screen.getByTestId('add-cms-name'), 'New CMS'); @@ -63,7 +63,7 @@ describe('CmsPage', () => { await userEvent.type(screen.getByTestId('add-cms-apikey-input'), 'secret-key'); await userEvent.click(screen.getByTestId('add-cms-submit')); - const rows = await screen.findAllByTestId('cms-instance-row', {}, { timeout: 5000 }); + const rows = await screen.findAllByTestId('cms-instance-row', {}, { timeout: 10000 }); expect(rows.length).toBe(3); }); diff --git a/frontend/src/pages/NotFoundPage.test.tsx b/frontend/src/pages/NotFoundPage.test.tsx index fae2a14..7278e57 100644 --- a/frontend/src/pages/NotFoundPage.test.tsx +++ b/frontend/src/pages/NotFoundPage.test.tsx @@ -12,21 +12,21 @@ describe('NotFoundPage', () => { it('renders for an unknown route', async () => { renderApp('/this-route-does-not-exist'); - expect(await screen.findByTestId('not-found-title', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('not-found-title', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('not-found-message')).toBeInTheDocument(); }); it('renders the back to dashboard button', async () => { renderApp('/some/nonexistent/path'); - expect(await screen.findByTestId('not-found-back-button', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('not-found-back-button', {}, { timeout: 10000 })).toBeInTheDocument(); }); it('navigates to login when back button is clicked as guest', async () => { mockGuest(); renderApp('/this-does-not-exist'); - const button = await screen.findByTestId('not-found-back-button', {}, { timeout: 5000 }); + const button = await screen.findByTestId('not-found-back-button', {}, { timeout: 10000 }); await userEvent.click(button); expect(await screen.findByTestId('login-form-submit-button')).toBeInTheDocument(); diff --git a/frontend/src/pages/ProfilePage.test.tsx b/frontend/src/pages/ProfilePage.test.tsx index 6fa98f3..ca7484b 100644 --- a/frontend/src/pages/ProfilePage.test.tsx +++ b/frontend/src/pages/ProfilePage.test.tsx @@ -16,7 +16,7 @@ describe('ProfilePage', () => { mockAuthenticated(); renderApp('/profile'); - expect(await screen.findByTestId('profile-title', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('profile-title', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('profile-name-input')).toBeInTheDocument(); expect(screen.getByTestId('profile-email-input')).toBeInTheDocument(); expect(screen.getByTestId('profile-role-badge')).toBeInTheDocument(); @@ -61,7 +61,7 @@ describe('ProfilePage', () => { await userEvent.type(nameInput, 'New Name'); await userEvent.click(screen.getByTestId('profile-save-button')); - expect(await screen.findByText(/profile updated/i, {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByText(/profile updated/i, {}, { timeout: 10000 })).toBeInTheDocument(); }); it('shows validation error for invalid email', async () => { diff --git a/frontend/src/pages/SettingsPage.test.tsx b/frontend/src/pages/SettingsPage.test.tsx index f8ba4c1..c8e3331 100644 --- a/frontend/src/pages/SettingsPage.test.tsx +++ b/frontend/src/pages/SettingsPage.test.tsx @@ -16,21 +16,21 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - expect(await screen.findByTestId('settings-title', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('settings-title', {}, { timeout: 10000 })).toBeInTheDocument(); }); it('shows the current availability status badge', async () => { mockAuthenticated(); renderApp('/settings'); - expect(await screen.findByTestId('availability-badge', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('availability-badge', {}, { timeout: 10000 })).toBeInTheDocument(); }); it('renders all three availability mode buttons', async () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('availability-mode-selector', {}, { timeout: 5000 }); + await screen.findByTestId('availability-mode-selector', {}, { timeout: 10000 }); expect(screen.getByTestId('mode-option-Available')).toBeInTheDocument(); expect(screen.getByTestId('mode-option-Maintenance')).toBeInTheDocument(); expect(screen.getByTestId('mode-option-NotAvailable')).toBeInTheDocument(); @@ -40,7 +40,7 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('availability-mode-selector', {}, { timeout: 5000 }); + await screen.findByTestId('availability-mode-selector', {}, { timeout: 10000 }); const maintenanceBtn = screen.getByTestId('mode-option-Maintenance'); await userEvent.click(maintenanceBtn); @@ -51,10 +51,10 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('availability-save-button', {}, { timeout: 5000 }); + await screen.findByTestId('availability-save-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('availability-save-button')); - expect(await screen.findByText(/availability updated/i, {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByText(/availability updated/i, {}, { timeout: 10000 })).toBeInTheDocument(); }); it('shows error toast when save fails', async () => { @@ -66,10 +66,10 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('availability-save-button', {}, { timeout: 5000 }); + await screen.findByTestId('availability-save-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('availability-save-button')); - expect(await screen.findByText(/something went wrong/i, {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByText(/something went wrong/i, {}, { timeout: 10000 })).toBeInTheDocument(); }); it('disables the availability controls and shows a banner when master-controlled', async () => { @@ -86,7 +86,7 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - expect(await screen.findByTestId('availability-master-controlled-banner', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('availability-master-controlled-banner', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('mode-option-Available')).toBeDisabled(); expect(screen.getByTestId('mode-option-Maintenance')).toBeDisabled(); expect(screen.getByTestId('mode-option-NotAvailable')).toBeDisabled(); @@ -98,7 +98,7 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('availability-save-button', {}, { timeout: 5000 }); + await screen.findByTestId('availability-save-button', {}, { timeout: 10000 }); expect(screen.queryByTestId('availability-master-controlled-banner')).not.toBeInTheDocument(); expect(screen.getByTestId('availability-save-button')).not.toBeDisabled(); }); @@ -112,17 +112,17 @@ describe('SettingsPage', () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('availability-save-button', {}, { timeout: 5000 }); + await screen.findByTestId('availability-save-button', {}, { timeout: 10000 }); await userEvent.click(screen.getByTestId('availability-save-button')); - expect(await screen.findByText(/master cms controls this status/i, {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByText(/master cms controls this status/i, {}, { timeout: 10000 })).toBeInTheDocument(); }); it('renders all placeholder sections', async () => { mockAuthenticated(); renderApp('/settings'); - await screen.findByTestId('settings-title', {}, { timeout: 5000 }); + await screen.findByTestId('settings-title', {}, { timeout: 10000 }); expect(screen.getByTestId('placeholder-settings.modules.title')).toBeInTheDocument(); expect(screen.getByTestId('placeholder-settings.systemConfig.title')).toBeInTheDocument(); expect(screen.getByTestId('placeholder-settings.branding.title')).toBeInTheDocument(); diff --git a/frontend/src/pages/UsersPage.test.tsx b/frontend/src/pages/UsersPage.test.tsx index fa46b40..5d4a7dd 100644 --- a/frontend/src/pages/UsersPage.test.tsx +++ b/frontend/src/pages/UsersPage.test.tsx @@ -19,7 +19,7 @@ describe('UsersPage', () => { mockAuthenticated(); renderApp('/users'); - expect(await screen.findByTestId('users-page', {}, { timeout: 5000 })).toBeInTheDocument(); + expect(await screen.findByTestId('users-page', {}, { timeout: 10000 })).toBeInTheDocument(); expect(screen.getByTestId('users-invite-button')).toBeInTheDocument(); }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index d04e770..6502da0 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -42,7 +42,7 @@ export default defineConfig(({ command }) => ({ globals: true, environment: 'jsdom', setupFiles: ['./src/test/setup.ts'], - testTimeout: 15000, + testTimeout: 20000, css: true, coverage: { provider: 'v8',