From 9597e052e08c26510f856633b48ca1317e5b79f6 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Wed, 4 Nov 2026 15:43:21 +0100 Subject: [PATCH 1/4] test(e2e): browser coverage for Inertia v3 client features via dev-host fixture pages Claude-Session: https://claude.ai/code/session_01FXHA3ZJ5EgoJN4zXTAhBR2 --- docs/e2e-testing.md | 27 +++ host/client_app/pages/E2eInertia/Deferred.tsx | 23 +++ host/client_app/pages/E2eInertia/Forms.tsx | 68 ++++++++ host/client_app/pages/E2eInertia/History.tsx | 37 +++++ host/client_app/pages/E2eInertia/Index.tsx | 38 +++++ host/client_app/pages/E2eInertia/Merge.tsx | 68 ++++++++ host/client_app/pages/E2eInertia/Once.tsx | 31 ++++ host/client_app/pages/E2eInertia/Partial.tsx | 47 ++++++ .../client_app/pages/E2eInertia/Redirects.tsx | 50 ++++++ host/client_app/pages/E2eInertia/Scroll.tsx | 40 +++++ host/client_app/pages/E2eInertia/Visible.tsx | 27 +++ .../pages/E2eInertia/components/E2ePage.tsx | 34 ++++ host/e2e_inertia/__init__.py | 42 +++++ host/e2e_inertia/constants.py | 17 ++ host/e2e_inertia/features.py | 157 ++++++++++++++++++ host/e2e_inertia/forms.py | 55 ++++++ host/e2e_inertia/navigation.py | 53 ++++++ host/e2e_inertia/probes.py | 19 +++ host/main.py | 3 + host/tests/test_e2e_inertia_gating.py | 35 ++++ pyproject.toml | 4 + tests/e2e/conftest.py | 8 + tests/e2e/inertia_support.py | 96 +++++++++++ tests/e2e/test_inertia_deferred.py | 51 ++++++ tests/e2e/test_inertia_forms.py | 79 +++++++++ tests/e2e/test_inertia_history.py | 94 +++++++++++ tests/e2e/test_inertia_merge.py | 79 +++++++++ tests/e2e/test_inertia_navigation.py | 66 ++++++++ tests/e2e/test_inertia_once.py | 57 +++++++ tests/e2e/test_inertia_partial.py | 71 ++++++++ tests/e2e/test_inertia_redirects.py | 74 +++++++++ tests/e2e/test_inertia_scroll.py | 73 ++++++++ tests/e2e/test_inertia_visible.py | 62 +++++++ 33 files changed, 1685 insertions(+) create mode 100644 host/client_app/pages/E2eInertia/Deferred.tsx create mode 100644 host/client_app/pages/E2eInertia/Forms.tsx create mode 100644 host/client_app/pages/E2eInertia/History.tsx create mode 100644 host/client_app/pages/E2eInertia/Index.tsx create mode 100644 host/client_app/pages/E2eInertia/Merge.tsx create mode 100644 host/client_app/pages/E2eInertia/Once.tsx create mode 100644 host/client_app/pages/E2eInertia/Partial.tsx create mode 100644 host/client_app/pages/E2eInertia/Redirects.tsx create mode 100644 host/client_app/pages/E2eInertia/Scroll.tsx create mode 100644 host/client_app/pages/E2eInertia/Visible.tsx create mode 100644 host/client_app/pages/E2eInertia/components/E2ePage.tsx create mode 100644 host/e2e_inertia/__init__.py create mode 100644 host/e2e_inertia/constants.py create mode 100644 host/e2e_inertia/features.py create mode 100644 host/e2e_inertia/forms.py create mode 100644 host/e2e_inertia/navigation.py create mode 100644 host/e2e_inertia/probes.py create mode 100644 host/tests/test_e2e_inertia_gating.py create mode 100644 tests/e2e/inertia_support.py create mode 100644 tests/e2e/test_inertia_deferred.py create mode 100644 tests/e2e/test_inertia_forms.py create mode 100644 tests/e2e/test_inertia_history.py create mode 100644 tests/e2e/test_inertia_merge.py create mode 100644 tests/e2e/test_inertia_navigation.py create mode 100644 tests/e2e/test_inertia_once.py create mode 100644 tests/e2e/test_inertia_partial.py create mode 100644 tests/e2e/test_inertia_redirects.py create mode 100644 tests/e2e/test_inertia_scroll.py create mode 100644 tests/e2e/test_inertia_visible.py diff --git a/docs/e2e-testing.md b/docs/e2e-testing.md index 1a60d32ce..07ee125f4 100644 --- a/docs/e2e-testing.md +++ b/docs/e2e-testing.md @@ -42,6 +42,33 @@ before the first `make dev` run. uv run pytest -m e2e tests/e2e ``` +## Inertia v3 fixture pages + +The framework's own pages never use partial reloads, deferred, merge or `once` +props, infinite scroll, error bags, history encryption, `WhenVisible`, polling +or prefetch. The dev host therefore ships fixture pages that do, so +`tests/e2e/test_inertia_*.py` can drive those client features in a real +browser: + +- Routes: [`host/e2e_inertia/`](../host/e2e_inertia/) under + `/__e2e/inertia/*`, plus loader counters at `GET /__e2e/inertia/_probes` + (reset with `POST /__e2e/inertia/_probes/reset`). +- Pages: `host/client_app/pages/E2eInertia/*.tsx`, rendered as + `E2eInertia/`. They are marked `i18n-exempt-file` and never shown to + users. +- Gating: `host/main.py` mounts the router only when `SM_ENVIRONMENT` is + `development`, and every route needs a signed-in user. In any other + environment the paths do not exist (`host/tests/test_e2e_inertia_gating.py`). +- Fixtures: `tests/e2e/inertia_support.py` signs in once per session through + the API and fails a test on any console or page error. Mark a test + `allow_resource_errors` when it provokes a 4xx/5xx on purpose. + +Run them against a development server (Vite required, as for every e2e test): + +```bash +uv run pytest -m e2e tests/e2e/test_inertia_*.py +``` + ## Configuration When you write e2e tests, read these environment variables (all optional): diff --git a/host/client_app/pages/E2eInertia/Deferred.tsx b/host/client_app/pages/E2eInertia/Deferred.tsx new file mode 100644 index 000000000..1a32881a8 --- /dev/null +++ b/host/client_app/pages/E2eInertia/Deferred.tsx @@ -0,0 +1,23 @@ +// i18n-exempt-file: e2e fixture page, never shown to users +import { Deferred as InertiaDeferred } from '@inertiajs/react'; +import { E2ePage, Value } from './components/E2ePage'; + +export default function Deferred({ fast, slow }: { fast?: number; slow?: number }) { + return ( + + Loading…

}> + +
+ Loading…

}> + +
+ Loading…

} + rescue={

Loader failed safely

} + > +

Loader ready

+
+
+ ); +} diff --git a/host/client_app/pages/E2eInertia/Forms.tsx b/host/client_app/pages/E2eInertia/Forms.tsx new file mode 100644 index 000000000..e73d2b424 --- /dev/null +++ b/host/client_app/pages/E2eInertia/Forms.tsx @@ -0,0 +1,68 @@ +// i18n-exempt-file: e2e fixture page, never shown to users +import { useForm } from '@inertiajs/react'; +import { buttonClass, E2ePage } from './components/E2ePage'; + +function DemoForm({ bag }: { bag: 'default' | 'second' }) { + const form = useForm({ name: '', email: '' }); + return ( +
{ + event.preventDefault(); + form.post(`/__e2e/inertia/forms/${bag}`, { + errorBag: bag === 'second' ? 'second' : undefined, + }); + }} + > +

{bag === 'default' ? 'Default form' : 'Second form'}

+ {(['name', 'email'] as const).map((field) => ( +
+ + form.setData(field, event.target.value)} + /> + +
+ ))} + + {bag === 'default' && ( + + )} +
+ ); +} + +export default function Forms({ success }: { success: string }) { + return ( + +

{success ? `Saved ${success} form` : ''}

+ + +
+ ); +} diff --git a/host/client_app/pages/E2eInertia/History.tsx b/host/client_app/pages/E2eInertia/History.tsx new file mode 100644 index 000000000..cc9a7ee1a --- /dev/null +++ b/host/client_app/pages/E2eInertia/History.tsx @@ -0,0 +1,37 @@ +// i18n-exempt-file: e2e fixture page, never shown to users +import { Link, router } from '@inertiajs/react'; +import { buttonClass, E2ePage, Value } from './components/E2ePage'; + +export default function History({ mode }: { mode: string }) { + return ( + + + +