From beedc8eced973594f95271256873186ed8c14ed1 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Thu, 12 Mar 2026 18:11:42 -0700 Subject: [PATCH] test(react-router): trying to make tests more reliable --- .../src/ReactRouter/StackManager.tsx | 8 +++--- .../react-router/test/base/cypress.config.ts | 4 +++ .../e2e/specs/index-param-priority.cy.js | 2 ++ .../tests/e2e/specs/index-route-reuse.cy.js | 17 +++++++----- .../tests/e2e/specs/nonlinear-forward.cy.js | 26 ++++++++----------- .../test/base/tests/e2e/support/commands.js | 16 ++++++++++++ 6 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/react-router/src/ReactRouter/StackManager.tsx b/packages/react-router/src/ReactRouter/StackManager.tsx index 79502eaa1b..e576a5a224 100644 --- a/packages/react-router/src/ReactRouter/StackManager.tsx +++ b/packages/react-router/src/ReactRouter/StackManager.tsx @@ -28,10 +28,12 @@ import { extractRouteChildren, getRoutesChildren, isNavigateElement } from './ut const VIEW_UNMOUNT_DELAY_MS = 250; /** - * Delay in milliseconds to wait for an IonPage element to be mounted before - * proceeding with a page transition. + * Delay (ms) to wait for an IonPage to mount before proceeding with a + * page transition. Only container routes (nested outlets with no direct + * IonPage) actually hit this timeout; normal routes clear it early via + * registerIonPage, so a larger value here doesn't affect the happy path. */ -const ION_PAGE_WAIT_TIMEOUT_MS = 50; +const ION_PAGE_WAIT_TIMEOUT_MS = 300; interface StackManagerProps { routeInfo: RouteInfo; diff --git a/packages/react-router/test/base/cypress.config.ts b/packages/react-router/test/base/cypress.config.ts index b2ec230fb0..6967447686 100644 --- a/packages/react-router/test/base/cypress.config.ts +++ b/packages/react-router/test/base/cypress.config.ts @@ -5,6 +5,10 @@ export default defineConfig({ pageLoadTimeout: 6000000000, screenshotOnRunFailure: false, defaultCommandTimeout: 10000, + retries: { + runMode: 2, + openMode: 0, + }, fixturesFolder: 'tests/e2e/fixtures', screenshotsFolder: 'tests/e2e/screenshots', videosFolder: 'tests/e2e/videos', diff --git a/packages/react-router/test/base/tests/e2e/specs/index-param-priority.cy.js b/packages/react-router/test/base/tests/e2e/specs/index-param-priority.cy.js index b1858d5ceb..ccb07376b5 100644 --- a/packages/react-router/test/base/tests/e2e/specs/index-param-priority.cy.js +++ b/packages/react-router/test/base/tests/e2e/specs/index-param-priority.cy.js @@ -141,6 +141,8 @@ describe('Index Param Priority', () => { cy.get('[data-testid="notfound-page-label"]').should('contain', 'Page not found'); cy.get('#back-to-index-from-notfound').click(); + cy.url().should('include', '/index-param-priority'); + cy.wait(300); cy.ionPageVisible('index-param-priority-index'); cy.get('[data-testid="index-page-label"]').should('contain', 'This is the index page'); }); diff --git a/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js b/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js index fb4358b263..7ebfa89f98 100644 --- a/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js +++ b/packages/react-router/test/base/tests/e2e/specs/index-route-reuse.cy.js @@ -23,12 +23,10 @@ describe('Index Route Reuse - Nested Outlet Index Routes', () => { // Switch to Tab 2 cy.ionTabClick('Tab 2'); + cy.url().should('include', '/index-route-reuse/tab2'); cy.ionPageVisible('irr-tab2-home'); cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab2-home-content"]').should('contain', 'Tab 2 Index Route Content'); - - // Verify URL changed to tab2 - cy.url().should('include', '/index-route-reuse/tab2'); }); it('should show tab3 index content when switching to tab3', () => { @@ -37,12 +35,10 @@ describe('Index Route Reuse - Nested Outlet Index Routes', () => { // Switch to Tab 3 cy.ionTabClick('Tab 3'); + cy.url().should('include', '/index-route-reuse/tab3'); cy.ionPageVisible('irr-tab3-home'); cy.get('[data-testid="irr-tab3-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab3-home-content"]').should('contain', 'Tab 3 Index Route Content'); - - // Verify URL changed to tab3 - cy.url().should('include', '/index-route-reuse/tab3'); }); it('should correctly show each tab index when cycling through all tabs', () => { @@ -52,18 +48,21 @@ describe('Index Route Reuse - Nested Outlet Index Routes', () => { // Tab 1 -> Tab 2 cy.ionTabClick('Tab 2'); + cy.url().should('include', '/index-route-reuse/tab2'); cy.ionPageVisible('irr-tab2-home'); cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab2-home-content"]').should('contain', 'Tab 2 Index Route Content'); // Tab 2 -> Tab 3 cy.ionTabClick('Tab 3'); + cy.url().should('include', '/index-route-reuse/tab3'); cy.ionPageVisible('irr-tab3-home'); cy.get('[data-testid="irr-tab3-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab3-home-content"]').should('contain', 'Tab 3 Index Route Content'); // Tab 3 -> Tab 1 (back to start) cy.ionTabClick('Tab 1'); + cy.url().should('include', '/index-route-reuse/tab1'); cy.ionPageVisible('irr-tab1-home'); cy.get('[data-testid="irr-tab1-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab1-home-content"]').should('contain', 'Tab 1 Index Route Content'); @@ -80,11 +79,13 @@ describe('Index Route Reuse - Nested Outlet Index Routes', () => { // Switch to Tab 2 cy.ionTabClick('Tab 2'); + cy.url().should('include', '/index-route-reuse/tab2'); cy.ionPageVisible('irr-tab2-home'); cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); // Switch back to Tab 1 - should show detail (preserved history) cy.ionTabClick('Tab 1'); + cy.url().should('include', '/index-route-reuse/tab1'); cy.ionPageVisible('irr-tab1-detail'); cy.get('[data-testid="irr-tab1-detail-content"]').should('be.visible'); }); @@ -95,17 +96,21 @@ describe('Index Route Reuse - Nested Outlet Index Routes', () => { // Rapid switching: Tab1 -> Tab2 -> Tab3 -> Tab2 -> Tab1 cy.ionTabClick('Tab 2'); + cy.url().should('include', '/index-route-reuse/tab2'); cy.ionPageVisible('irr-tab2-home'); cy.ionTabClick('Tab 3'); + cy.url().should('include', '/index-route-reuse/tab3'); cy.ionPageVisible('irr-tab3-home'); cy.ionTabClick('Tab 2'); + cy.url().should('include', '/index-route-reuse/tab2'); cy.ionPageVisible('irr-tab2-home'); cy.get('[data-testid="irr-tab2-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab2-home-content"]').should('contain', 'Tab 2 Index Route Content'); cy.ionTabClick('Tab 1'); + cy.url().should('include', '/index-route-reuse/tab1'); cy.ionPageVisible('irr-tab1-home'); cy.get('[data-testid="irr-tab1-home-content"]').should('be.visible'); cy.get('[data-testid="irr-tab1-home-content"]').should('contain', 'Tab 1 Index Route Content'); diff --git a/packages/react-router/test/base/tests/e2e/specs/nonlinear-forward.cy.js b/packages/react-router/test/base/tests/e2e/specs/nonlinear-forward.cy.js index 3238bf9054..6a01a95785 100644 --- a/packages/react-router/test/base/tests/e2e/specs/nonlinear-forward.cy.js +++ b/packages/react-router/test/base/tests/e2e/specs/nonlinear-forward.cy.js @@ -17,15 +17,14 @@ describe('Non-linear POP Forward Navigation', () => { // Browser back - this is a non-linear POP because settings route has no pushedByRoute. // The else branch should push the current location key onto forwardStack. - cy.go('back'); + cy.ionGoBack('/routing/tabs/home/details/1'); cy.ionPageVisible('home-details-page-1'); cy.get('ion-tab-button.tab-selected').contains('Home'); // Browser forward - should be detected as forward navigation via forwardStack. // Without the fix, forwardStack is empty so this falls into the wrong branch // (else-if, treating it as back navigation with wrong animation). - cy.go('forward'); - cy.wait(500); + cy.ionGoForward('/routing/tabs/settings'); cy.ionPageVisible('settings-page'); cy.get('ion-tab-button.tab-selected').contains('Settings'); }); @@ -43,27 +42,25 @@ describe('Non-linear POP Forward Navigation', () => { cy.ionPageVisible('settings-page'); // Browser back (non-linear POP) - cy.go('back'); + cy.ionGoBack('/routing/tabs/home/details/1'); cy.ionPageVisible('home-details-page-1'); // Browser forward - cy.go('forward'); - cy.wait(500); + cy.ionGoForward('/routing/tabs/settings'); cy.ionPageVisible('settings-page'); // Browser back again - without the fix, the forward stack is corrupted from // the previous misclassification, causing this back to be treated as forward. - cy.go('back'); + cy.ionGoBack('/routing/tabs/home/details/1'); cy.ionPageVisible('home-details-page-1'); cy.get('ion-tab-button.tab-selected').contains('Home'); // One more forward/back cycle to verify stack integrity - cy.go('forward'); - cy.wait(500); + cy.ionGoForward('/routing/tabs/settings'); cy.ionPageVisible('settings-page'); cy.get('ion-tab-button.tab-selected').contains('Settings'); - cy.go('back'); + cy.ionGoBack('/routing/tabs/home/details/1'); cy.ionPageVisible('home-details-page-1'); cy.get('ion-tab-button.tab-selected').contains('Home'); }); @@ -81,22 +78,21 @@ describe('Non-linear POP Forward Navigation', () => { cy.ionPageVisible('settings-page'); // Browser back (non-linear POP) - cy.go('back'); + cy.ionGoBack('/routing/tabs/home/details/1'); cy.ionPageVisible('home-details-page-1'); // Browser forward to Settings - cy.go('forward'); - cy.wait(500); + cy.ionGoForward('/routing/tabs/settings'); cy.ionPageVisible('settings-page'); // Browser back to D1 - cy.go('back'); + cy.ionGoBack('/routing/tabs/home/details/1'); cy.ionPageVisible('home-details-page-1'); // Browser back to Home. The D1 route lost its pushedByRoute when it was // recreated via a non-linear POP, so this back also goes through the // non-linear else branch. - cy.go('back'); + cy.ionGoBack('/routing/tabs/home'); cy.ionPageVisible('home-page'); cy.contains('[data-pageid=home-page]', '"routeAction":"pop"'); cy.contains('[data-pageid=home-page]', '"pathname":"/routing/tabs/home"'); diff --git a/packages/react-router/test/base/tests/e2e/support/commands.js b/packages/react-router/test/base/tests/e2e/support/commands.js index 6e9ce11c45..c14ea8bda0 100644 --- a/packages/react-router/test/base/tests/e2e/support/commands.js +++ b/packages/react-router/test/base/tests/e2e/support/commands.js @@ -89,6 +89,22 @@ Cypress.Commands.add('ionTabClick', (tabText) => { cy.contains('ion-tab-button', tabText).click({ force: true }); }); +Cypress.Commands.add('ionGoBack', (expectedUrlPart) => { + cy.go('back'); + if (expectedUrlPart) { + cy.url().should('include', expectedUrlPart); + } + cy.wait(300); +}); + +Cypress.Commands.add('ionGoForward', (expectedUrlPart) => { + cy.go('forward'); + if (expectedUrlPart) { + cy.url().should('include', expectedUrlPart); + } + cy.wait(300); +}); + Cypress.Commands.add('ionBackClick', (pageId) => { cy.get(`div.ion-page[data-pageid=${pageId}]`) .should('be.visible', true)