The application has broad Playwright coverage for browser behavior, but those tests intercept /api and provide fabricated responses. They can prove Angular behavior across viewports, yet they cannot detect broken Nginx proxying, OAuth callbacks, Spring Security sessions and CSRF, HTTP serialization, MongoDB transactions and persistence, attachment storage, ImageMagick processing, or PDF generation.
Backend integration tests exercise the Spring application and a real MongoDB replica set through MockMvc, but they do not run a browser, packaged frontend, Nginx, or real OIDC authorization-code flow. A commit can therefore pass both suites while the deployed application is unusable across their boundary.
The project needs a small, deterministic real-stack E2E gate that complements rather than replaces the existing fast suites. It must run on every commit, remain reproducible locally, and fail for actual integration breakage without depending on shared state or live third-party services.
Solution
Add a separate real-backend Playwright suite containing eight independent, critical business journeys. Run it through a disposable, production-like Compose stack containing the packaged Angular/Nginx frontend, Spring Boot backend, MongoDB replica set, Dex OIDC provider, deterministic local INSEE fixture service, and Playwright runner.
The suite will authenticate through a real OIDC authorization-code flow, reuse authenticated browser state for setup and journeys, create prerequisites through the application's public HTTP API, perform the behavior under test through the browser, and assert only externally observable results. It will never intercept application API calls or inspect MongoDB collections directly.
The existing mocked Playwright suite remains the broad and responsive browser contract. The new suite is a desktop-only, one-worker, zero-retry integration gate on every commit. Every job receives fresh MongoDB, identity, and attachment state and destroys it afterward.
User Stories
As a developer, I want a browser test to reach the packaged frontend and real backend, so that frontend/backend integration failures are caught before merge.
As a maintainer, I want the real-stack suite to complement the mocked browser suite, so that broad UI coverage remains fast while critical boundaries receive production-like validation.
As a maintainer, I want the real-stack job to run on every commit, so that integration regressions are detected at the commit that introduced them.
As a developer, I want one local command to run the same stack as CI, so that I can reproduce failures without reconstructing CI manually.
As a developer, I want all services to be disposable per job, so that previous runs cannot influence current results.
As a developer, I want the stack cleaned up after success or failure, so that local and CI machines do not accumulate databases, files, or networks.
As a security maintainer, I want one real OIDC authorization-code login, so that redirects, callback handling, session cookies, and claims mapping are validated.
As a security maintainer, I want Spring's real CSRF behavior exercised, so that protected writes cannot appear healthy only because tests bypass security.
As a developer, I want authenticated browser state reused after login, so that business journeys do not repeat slow identity-provider UI steps.
As a developer, I want two deterministic test users, so that ownership and authorization behavior can be exercised without external accounts.
As a Bailleur, I want my portfolio isolated from another Bailleur, so that another authenticated OIDC subject cannot read or mutate my data.
As a Bailleur, I want my Profil bailleur to persist after login and reload, so that the identity-to-Bailleur boundary is proven end to end.
As a Bailleur, I want to create an Appartement through the real application and see it after reload, so that HTTP mapping and MongoDB persistence are validated.
As a Bailleur, I want to create a Locataire through the real application and see it after reload, so that Locataire persistence is validated.
As a Bailleur, I want to create a draft Bail for an Appartement and Locataire and reopen it, so that aggregate references and contractual snapshots persist correctly.
As a Bailleur, I want uploaded Appartement documents and Locataire attachments to download with their original metadata, so that multipart handling and filesystem storage work together.
As a Bailleur, I want a stored document to be replaceable or removable through supported application behavior, so that metadata and file lifecycle stay coherent.
As a Bailleur, I want a Garantie Visale declaration and its required documents to survive reload, so that the lease guarantee boundary is exercised with real storage.
As a Bailleur, I want to complete the Inventaire du mobilier through the real application, so that its contract is usable by the later lease workflow.
As a Bailleur, I want to create an État des lieux d'entrée with a real uploaded photo, so that browser upload, Spring multipart handling, ImageMagick normalization, storage, and MongoDB metadata work together.
As a Bailleur, I want to generate and download a real lease package, so that PDF generation and Nginx download proxying are validated.
As a Bailleur, I want to finalize the Bail with its signed package, so that the cross-aggregate transaction and immutable document lifecycle work in the deployed topology.
As a Bailleur, I want finalized Bail data to remain visible after reload, so that activation persisted rather than merely updating browser state.
As a Bailleur, I want generated Échéances de loyer to appear after Bail finalization, so that lifecycle side effects are visible through the public contract.
As a Bailleur, I want to record a Règlement de loyer and Affectation de règlement, so that real financial writes and MongoDB transactions are validated.
As a Bailleur, I want to download a Quittance de loyer and Reçu de règlement, so that financial document generation works through the real stack.
As a Bailleur, I want to record a Remboursement de trop-perçu and a Contrepassation de règlement, so that immutable correction workflows remain coherent end to end.
As a Bailleur, I want a Révision IRL proposal to use deterministic local index values, so that the complete workflow is tested without live INSEE availability.
As a Bailleur, I want to generate and download the revision letter and record its transmission, so that the revision document and persistence boundaries are exercised.
As a Bailleur, I want to record a Congé du locataire and its Justificatif du congé, so that lease-ending facts and stored evidence work through the public UI.
As a Bailleur, I want to complete and finalize the État des lieux de sortie, so that exit photos, generated files, and contract-ending rules are exercised.
As a Bailleur, I want to complete the Clôture du bail only after its prerequisites, so that the final lifecycle transition is proven through real persistence.
As a developer, I want prerequisites created through public APIs rather than direct MongoDB inserts, so that tests respect application contracts.
As a developer, I want each journey to use unique data and remain independent, so that one failed test does not corrupt another test's assumptions.
As a developer, I want browser and API assertions to observe only public behavior, so that private MongoDB adapter schemas can evolve without rewriting E2E tests.
As a maintainer, I want non-authentication third parties replaced by local deterministic services, so that CI does not depend on internet availability or mutable remote data.
As a maintainer, I want running E2E services denied internet egress, so that accidental external calls fail immediately.
As a maintainer, I want a fixed backend clock, so that legal dates, lease windows, rent dates, and audit timestamps remain deterministic.
As a maintainer, I want production security, repositories, transactions, filesystem behavior, ImageMagick, and PDF generation unchanged in E2E, so that the test profile does not create a fake backend.
As a maintainer, I want the packaged Angular and Spring Boot images tested, so that production builds and Nginx routing are part of the gate.
As a maintainer, I want exact external image versions pinned, so that an upstream latest tag cannot silently change test behavior.
As a contributor, I want committed, clearly test-only Dex users and client credentials, so that forks and local runs require no secret provisioning.
As a security maintainer, I want test credentials isolated from production configuration, so that the E2E setup cannot weaken deployed authentication.
As a developer, I want zero automatic retries, so that flaky behavior and state leakage fail visibly instead of being hidden.
As a developer, I want traces, screenshots, browser console, and service logs on failure, so that the failed boundary can be diagnosed from CI artifacts.
As a maintainer, I want failure artifacts retained for seven days, so that evidence is available without indefinitely storing test output.
As a maintainer, I want database and attachment volumes excluded from artifacts, so that diagnostics remain small and reproducible.
As a developer, I want generated downloads checked structurally rather than byte-for-byte, so that real corruption is detected without brittle timestamp or renderer snapshots.
As a maintainer, I want the real-stack job to target eight minutes and stop after fifteen, so that every-commit feedback remains practical.
As a developer, I want Playwright filtering forwarded by the local runner, so that one failing journey can be reproduced quickly against the same stack.
As a maintainer, I want the first rollout commit to contain a working authentication/Profil bailleur smoke and CI gate, so that no commit introduces permanently red infrastructure.
As a maintainer, I want each remaining journey added in a separate passing commit, so that regressions and design changes are reviewable.
Implementation Decisions
Preserve the existing mocked browser suite and backend integration suite. The real-backend suite is a third, complementary layer rather than a replacement.
Use one primary E2E seam: Playwright drives the packaged Angular/Nginx frontend, which calls the real Spring Boot HTTP API backed by real MongoDB and filesystem adapters. Setup may use the same public HTTP API with authenticated state; assertions remain browser or public-API observations.
Place real-backend tests in a separate Playwright suite with separate configuration. The suite must not import mock-authentication or mocked-API helpers from the existing browser suite.
Prohibit browser-level interception of application API traffic in the real suite. External INSEE behavior is replaced behind the backend's configurable HTTP boundary, not with page.route.
Run a dedicated Compose topology containing:
MongoDB 8 configured as a replica set;
Dex configured declaratively with two static test users and one OIDC client;
the real Spring Boot backend image;
the packaged Angular/Nginx frontend image;
a deterministic local HTTP fixture service for INSEE IRL responses;
the version-matched Playwright runner.
Put every runtime service on an internal Compose network with no published host ports and no internet egress. The Playwright browser resolves frontend, Dex, and other services by Compose DNS.
Pin MongoDB, Dex, Playwright, and fixture-service image versions. Do not use floating latest tags.
Configure the OIDC callback through the frontend/Nginx origin so that Nginx proxy headers, Spring redirect construction, callback handling, and session cookies are exercised.
Use Dex for E2E because it supports deterministic declarative users. Keep VoidAuth as the deployment identity provider; using another standards-compliant provider in CI validates the pluggable OIDC decision rather than replacing it.
Commit conspicuously test-only Dex users, password hashes, client ID, and client secret. They are not secrets and must never be reused outside the isolated E2E stack.
Authenticate both users once in Playwright setup and persist separate storage states. Business journeys reuse those states; the authentication journey itself still visibly exercises login and logout.
API setup helpers use authenticated HTTP state and Spring's real CSRF contract. They do not add test-only authentication endpoints or bypass filters.
Create an E2E Spring profile whose only behavioral override is a fixed Clock. Configure MongoDB, OIDC, INSEE base URI, attachment root, and other existing ports through normal typed/environment configuration.
Do not add direct database seeders, cleanup controllers, compatibility adapters, or production authentication bypasses. Each job starts empty and is destroyed afterward.
Build prerequisites through public APIs and assign unique, deterministic test data per journey. Tests may share authenticated storage state but not mutable domain fixtures.
Keep test cases independent and run them with one Playwright worker. Ordered steps inside one test are allowed when they express one business lifecycle, but no test may depend on another test's state or success. Do not use Playwright serial mode that skips unrelated tests after one failure.
Before every journey, idempotently establish complete baseline Profils bailleurs for both static users through the public API. Business journeys must not depend on mutations performed by the authentication/Profil bailleur journey.
Run only the desktop Playwright project in the real suite. Responsive behavior remains the responsibility of the mocked mobile/tablet/desktop suite.
Disable Playwright retries for the real suite. A manual pipeline rerun creates a new disposable environment.
Validate files by status, expected filename, content type, non-zero size, format signature, and persistence after reload. Do not snapshot exact PDF bytes or hashes.
Provide one local runner command that invokes the same Compose definition and Playwright image as CI, forwards optional Playwright filters, streams service output, and removes containers, networks, and volumes in a trap/after-step.
Add a required GitLab CI job for every commit. It uses the Compose runner exit code, a fifteen-minute timeout, and caching/build-layer reuse aimed at an eight-minute normal runtime.
On failure, collect Playwright traces and screenshots, browser console output, and logs from frontend, backend, Dex, MongoDB, INSEE fixture, and runner containers. Retain artifacts for seven days. Do not archive MongoDB or attachment volumes.
Add health checks for MongoDB replica-set readiness, Dex discovery, frontend reachability, INSEE fixture availability, and backend readiness through the existing unauthenticated /api/user endpoint. Playwright starts only after dependencies are healthy; do not add Actuator solely for this suite.
Record the layered real-stack E2E architecture and its trade-offs in a new ADR. No domain glossary entry is needed because this work introduces no business-domain term.
Roll out incrementally: first land the complete stack, authentication/Profil bailleur journey, local runner, and green required CI job; then add each remaining independent journey in its own green commit.
Testing Decisions
A good real-backend E2E test observes public behavior at the highest available seam. It drives the browser for the behavior under test, uses public HTTP only for prerequisite setup or independent verification, and never asserts internal classes, MongoDB collections, filesystem paths, or Angular component implementation.
The highest seam is the packaged frontend origin. It includes Nginx routing, OAuth initiation and callback, session/CSRF cookies, Spring MVC serialization and validation, application/domain services, MongoDB transactions, filesystem storage, ImageMagick, PDF rendering, and browser-visible results.
The eight required journeys are:
OIDC login, Profil bailleur persistence, and logout.
Two-user portfolio isolation for protected reads and writes.
Appartement, Locataire, and draft Bail creation, reload, and reopen.
Appartement document and Locataire attachment upload, download, replacement/removal, and reload.
Garantie Visale documents, Inventaire du mobilier, État des lieux d'entrée with a real photo, lease-package generation, and Bail finalization.
Generated Échéances de loyer, Règlement de loyer, Affectation de règlement, Quittance de loyer, Reçu de règlement, Remboursement de trop-perçu, and Contrepassation de règlement.
Révision IRL using deterministic local INSEE data, revision-letter download, transmission evidence, and persisted application.
Congé du locataire with evidence, État des lieux de sortie with a real photo and generated document, and Clôture du bail.
Each journey gets its own test and unique data. Preconditions that are not the subject of the journey are created through authenticated public APIs to keep the browser flow focused and failures diagnosable.
The portfolio-isolation journey uses both Dex users and verifies denial through public responses and browser-visible behavior. It must not infer isolation from absent database records.
Multipart journeys use small, valid committed fixtures. The photo journey verifies that the normalized stored image can be rendered or downloaded, exercising the real ImageMagick path.
PDF and archive journeys assert structural validity and user-visible metadata. Detailed document content remains covered by existing focused backend tests.
The local INSEE fixture returns the same deterministic protocol payload expected by the production adapter, allowing the real HTTP adapter, parsing, cache, and Révision IRL application flow to run without internet access.
The fixed clock and browser timezone are explicit and shared across tests. Test dates are valid relative to that instant rather than today's wall clock.
Existing prior art remains authoritative:
the mocked Playwright suite provides browser locators, accessibility assertions, viewport coverage, and UI branch coverage;
Spring Boot integration tests provide real MongoDB replica-set setup and cross-aggregate journey knowledge;
focused web tests provide authentication/CSRF contract examples;
Docker acceptance checks provide real ImageMagick behavior;
production Compose and container definitions provide the packaging and proxy topology.
The real suite runs desktop-only, one worker, and without retries. Tests may continue after another independent test fails, but no test depends on another test's domain state.
CI success requires all eight journeys to pass in the single fresh environment. A preflight must prove that internal services are reachable while a known external destination is unreachable; the job fails if egress unexpectedly succeeds. Service startup or health-check failure is also a job failure.
The local/CI runner must trap non-zero exits, write Playwright diagnostics and every service's logs to known artifact paths, validate that the expected diagnostic files exist, and then return the original non-zero status for GitLab artifact upload.
Formatting, linting, frontend type checking, backend verification, mocked Playwright tests, container builds, and the new real-backend job all remain required gates.
Out of Scope
Replacing the existing mocked Playwright suite or rerunning all 62 mocked journeys against the backend.
Mobile and tablet projects in the real-stack suite.
Parallel real-stack execution or per-test Compose environments.
Automatic retries against mutated job state.
Live calls to INSEE or any non-authentication third party.
Running VoidAuth itself in CI; deployment compatibility remains based on standard OIDC and is separately configured.
Direct MongoDB setup/assertions, filesystem-path assertions, cleanup endpoints, or test-only authentication bypasses.
Exact PDF rendering snapshots, byte-for-byte document hashes, or visual-regression testing.
Load, soak, chaos, browser-compatibility, penetration, and production deployment tests.
Preserving E2E data or volumes after the job.
Schema migrations or backward-compatibility work; development and E2E data remain disposable.
Expanding the application's business functionality while adding the test harness.
Further Notes
The current Playwright server starts Angular without the backend proxy and the existing tests intentionally mock /api; this is why a separate configuration and Compose topology are required.
The backend already externalizes the INSEE base URI and attachment path and already injects Clock into date-sensitive services. The new profile should use those existing seams rather than introduce parallel adapters.
Backend integration tests already require Docker through Testcontainers and a MongoDB replica set, so the new job builds on an existing CI capability. The CI runner must expose Docker/Compose to the job; absence of that capability is an infrastructure prerequisite, not a reason to weaken the test seam.
ADR-0004 selects pluggable OIDC with VoidAuth as the primary deployment provider. A declarative Dex test provider is consistent with that decision and supplies deterministic CI bootstrap that VoidAuth's documented first-run administrative flow does not currently provide.
ADR-0013 makes OIDC-subject portfolio isolation a core security rule, which is why the two-user journey is mandatory.
ADR-0014 requires a MongoDB replica set for transactions and keeps filesystem storage outside MongoDB transactions, which is why both real MongoDB and attachment storage are part of the stack.
ADR-0022 confirms that development data is disposable. The E2E architecture independently applies the same no-preservation posture to test data through fresh per-job volumes.
The architectural choice should be captured as ADR-0023 because the layered suite, test identity provider, production-like topology, and explicit non-goals are a durable trade-off future maintainers might otherwise undo.
## Problem Statement
The application has broad Playwright coverage for browser behavior, but those tests intercept `/api` and provide fabricated responses. They can prove Angular behavior across viewports, yet they cannot detect broken Nginx proxying, OAuth callbacks, Spring Security sessions and CSRF, HTTP serialization, MongoDB transactions and persistence, attachment storage, ImageMagick processing, or PDF generation.
Backend integration tests exercise the Spring application and a real MongoDB replica set through MockMvc, but they do not run a browser, packaged frontend, Nginx, or real OIDC authorization-code flow. A commit can therefore pass both suites while the deployed application is unusable across their boundary.
The project needs a small, deterministic real-stack E2E gate that complements rather than replaces the existing fast suites. It must run on every commit, remain reproducible locally, and fail for actual integration breakage without depending on shared state or live third-party services.
## Solution
Add a separate real-backend Playwright suite containing eight independent, critical business journeys. Run it through a disposable, production-like Compose stack containing the packaged Angular/Nginx frontend, Spring Boot backend, MongoDB replica set, Dex OIDC provider, deterministic local INSEE fixture service, and Playwright runner.
The suite will authenticate through a real OIDC authorization-code flow, reuse authenticated browser state for setup and journeys, create prerequisites through the application's public HTTP API, perform the behavior under test through the browser, and assert only externally observable results. It will never intercept application API calls or inspect MongoDB collections directly.
The existing mocked Playwright suite remains the broad and responsive browser contract. The new suite is a desktop-only, one-worker, zero-retry integration gate on every commit. Every job receives fresh MongoDB, identity, and attachment state and destroys it afterward.
## User Stories
1. As a developer, I want a browser test to reach the packaged frontend and real backend, so that frontend/backend integration failures are caught before merge.
2. As a maintainer, I want the real-stack suite to complement the mocked browser suite, so that broad UI coverage remains fast while critical boundaries receive production-like validation.
3. As a maintainer, I want the real-stack job to run on every commit, so that integration regressions are detected at the commit that introduced them.
4. As a developer, I want one local command to run the same stack as CI, so that I can reproduce failures without reconstructing CI manually.
5. As a developer, I want all services to be disposable per job, so that previous runs cannot influence current results.
6. As a developer, I want the stack cleaned up after success or failure, so that local and CI machines do not accumulate databases, files, or networks.
7. As a security maintainer, I want one real OIDC authorization-code login, so that redirects, callback handling, session cookies, and claims mapping are validated.
8. As a security maintainer, I want Spring's real CSRF behavior exercised, so that protected writes cannot appear healthy only because tests bypass security.
9. As a developer, I want authenticated browser state reused after login, so that business journeys do not repeat slow identity-provider UI steps.
10. As a developer, I want two deterministic test users, so that ownership and authorization behavior can be exercised without external accounts.
11. As a Bailleur, I want my portfolio isolated from another Bailleur, so that another authenticated OIDC subject cannot read or mutate my data.
12. As a Bailleur, I want my Profil bailleur to persist after login and reload, so that the identity-to-Bailleur boundary is proven end to end.
13. As a Bailleur, I want to create an Appartement through the real application and see it after reload, so that HTTP mapping and MongoDB persistence are validated.
14. As a Bailleur, I want to create a Locataire through the real application and see it after reload, so that Locataire persistence is validated.
15. As a Bailleur, I want to create a draft Bail for an Appartement and Locataire and reopen it, so that aggregate references and contractual snapshots persist correctly.
16. As a Bailleur, I want uploaded Appartement documents and Locataire attachments to download with their original metadata, so that multipart handling and filesystem storage work together.
17. As a Bailleur, I want a stored document to be replaceable or removable through supported application behavior, so that metadata and file lifecycle stay coherent.
18. As a Bailleur, I want a Garantie Visale declaration and its required documents to survive reload, so that the lease guarantee boundary is exercised with real storage.
19. As a Bailleur, I want to complete the Inventaire du mobilier through the real application, so that its contract is usable by the later lease workflow.
20. As a Bailleur, I want to create an État des lieux d'entrée with a real uploaded photo, so that browser upload, Spring multipart handling, ImageMagick normalization, storage, and MongoDB metadata work together.
21. As a Bailleur, I want to generate and download a real lease package, so that PDF generation and Nginx download proxying are validated.
22. As a Bailleur, I want to finalize the Bail with its signed package, so that the cross-aggregate transaction and immutable document lifecycle work in the deployed topology.
23. As a Bailleur, I want finalized Bail data to remain visible after reload, so that activation persisted rather than merely updating browser state.
24. As a Bailleur, I want generated Échéances de loyer to appear after Bail finalization, so that lifecycle side effects are visible through the public contract.
25. As a Bailleur, I want to record a Règlement de loyer and Affectation de règlement, so that real financial writes and MongoDB transactions are validated.
26. As a Bailleur, I want to download a Quittance de loyer and Reçu de règlement, so that financial document generation works through the real stack.
27. As a Bailleur, I want to record a Remboursement de trop-perçu and a Contrepassation de règlement, so that immutable correction workflows remain coherent end to end.
28. As a Bailleur, I want a Révision IRL proposal to use deterministic local index values, so that the complete workflow is tested without live INSEE availability.
29. As a Bailleur, I want to generate and download the revision letter and record its transmission, so that the revision document and persistence boundaries are exercised.
30. As a Bailleur, I want to record a Congé du locataire and its Justificatif du congé, so that lease-ending facts and stored evidence work through the public UI.
31. As a Bailleur, I want to complete and finalize the État des lieux de sortie, so that exit photos, generated files, and contract-ending rules are exercised.
32. As a Bailleur, I want to complete the Clôture du bail only after its prerequisites, so that the final lifecycle transition is proven through real persistence.
33. As a developer, I want prerequisites created through public APIs rather than direct MongoDB inserts, so that tests respect application contracts.
34. As a developer, I want each journey to use unique data and remain independent, so that one failed test does not corrupt another test's assumptions.
35. As a developer, I want browser and API assertions to observe only public behavior, so that private MongoDB adapter schemas can evolve without rewriting E2E tests.
36. As a maintainer, I want non-authentication third parties replaced by local deterministic services, so that CI does not depend on internet availability or mutable remote data.
37. As a maintainer, I want running E2E services denied internet egress, so that accidental external calls fail immediately.
38. As a maintainer, I want a fixed backend clock, so that legal dates, lease windows, rent dates, and audit timestamps remain deterministic.
39. As a maintainer, I want production security, repositories, transactions, filesystem behavior, ImageMagick, and PDF generation unchanged in E2E, so that the test profile does not create a fake backend.
40. As a maintainer, I want the packaged Angular and Spring Boot images tested, so that production builds and Nginx routing are part of the gate.
41. As a maintainer, I want exact external image versions pinned, so that an upstream `latest` tag cannot silently change test behavior.
42. As a contributor, I want committed, clearly test-only Dex users and client credentials, so that forks and local runs require no secret provisioning.
43. As a security maintainer, I want test credentials isolated from production configuration, so that the E2E setup cannot weaken deployed authentication.
44. As a developer, I want zero automatic retries, so that flaky behavior and state leakage fail visibly instead of being hidden.
45. As a developer, I want traces, screenshots, browser console, and service logs on failure, so that the failed boundary can be diagnosed from CI artifacts.
46. As a maintainer, I want failure artifacts retained for seven days, so that evidence is available without indefinitely storing test output.
47. As a maintainer, I want database and attachment volumes excluded from artifacts, so that diagnostics remain small and reproducible.
48. As a developer, I want generated downloads checked structurally rather than byte-for-byte, so that real corruption is detected without brittle timestamp or renderer snapshots.
49. As a maintainer, I want the real-stack job to target eight minutes and stop after fifteen, so that every-commit feedback remains practical.
50. As a developer, I want Playwright filtering forwarded by the local runner, so that one failing journey can be reproduced quickly against the same stack.
51. As a maintainer, I want the first rollout commit to contain a working authentication/Profil bailleur smoke and CI gate, so that no commit introduces permanently red infrastructure.
52. As a maintainer, I want each remaining journey added in a separate passing commit, so that regressions and design changes are reviewable.
## Implementation Decisions
- Preserve the existing mocked browser suite and backend integration suite. The real-backend suite is a third, complementary layer rather than a replacement.
- Use one primary E2E seam: Playwright drives the packaged Angular/Nginx frontend, which calls the real Spring Boot HTTP API backed by real MongoDB and filesystem adapters. Setup may use the same public HTTP API with authenticated state; assertions remain browser or public-API observations.
- Place real-backend tests in a separate Playwright suite with separate configuration. The suite must not import mock-authentication or mocked-API helpers from the existing browser suite.
- Prohibit browser-level interception of application API traffic in the real suite. External INSEE behavior is replaced behind the backend's configurable HTTP boundary, not with `page.route`.
- Run a dedicated Compose topology containing:
- MongoDB 8 configured as a replica set;
- Dex configured declaratively with two static test users and one OIDC client;
- the real Spring Boot backend image;
- the packaged Angular/Nginx frontend image;
- a deterministic local HTTP fixture service for INSEE IRL responses;
- the version-matched Playwright runner.
- Put every runtime service on an internal Compose network with no published host ports and no internet egress. The Playwright browser resolves frontend, Dex, and other services by Compose DNS.
- Pin MongoDB, Dex, Playwright, and fixture-service image versions. Do not use floating `latest` tags.
- Configure the OIDC callback through the frontend/Nginx origin so that Nginx proxy headers, Spring redirect construction, callback handling, and session cookies are exercised.
- Use Dex for E2E because it supports deterministic declarative users. Keep VoidAuth as the deployment identity provider; using another standards-compliant provider in CI validates the pluggable OIDC decision rather than replacing it.
- Commit conspicuously test-only Dex users, password hashes, client ID, and client secret. They are not secrets and must never be reused outside the isolated E2E stack.
- Authenticate both users once in Playwright setup and persist separate storage states. Business journeys reuse those states; the authentication journey itself still visibly exercises login and logout.
- API setup helpers use authenticated HTTP state and Spring's real CSRF contract. They do not add test-only authentication endpoints or bypass filters.
- Create an E2E Spring profile whose only behavioral override is a fixed `Clock`. Configure MongoDB, OIDC, INSEE base URI, attachment root, and other existing ports through normal typed/environment configuration.
- Do not add direct database seeders, cleanup controllers, compatibility adapters, or production authentication bypasses. Each job starts empty and is destroyed afterward.
- Build prerequisites through public APIs and assign unique, deterministic test data per journey. Tests may share authenticated storage state but not mutable domain fixtures.
- Keep test cases independent and run them with one Playwright worker. Ordered steps inside one test are allowed when they express one business lifecycle, but no test may depend on another test's state or success. Do not use Playwright serial mode that skips unrelated tests after one failure.
- Before every journey, idempotently establish complete baseline Profils bailleurs for both static users through the public API. Business journeys must not depend on mutations performed by the authentication/Profil bailleur journey.
- Run only the desktop Playwright project in the real suite. Responsive behavior remains the responsibility of the mocked mobile/tablet/desktop suite.
- Disable Playwright retries for the real suite. A manual pipeline rerun creates a new disposable environment.
- Validate files by status, expected filename, content type, non-zero size, format signature, and persistence after reload. Do not snapshot exact PDF bytes or hashes.
- Provide one local runner command that invokes the same Compose definition and Playwright image as CI, forwards optional Playwright filters, streams service output, and removes containers, networks, and volumes in a trap/after-step.
- Add a required GitLab CI job for every commit. It uses the Compose runner exit code, a fifteen-minute timeout, and caching/build-layer reuse aimed at an eight-minute normal runtime.
- On failure, collect Playwright traces and screenshots, browser console output, and logs from frontend, backend, Dex, MongoDB, INSEE fixture, and runner containers. Retain artifacts for seven days. Do not archive MongoDB or attachment volumes.
- Add health checks for MongoDB replica-set readiness, Dex discovery, frontend reachability, INSEE fixture availability, and backend readiness through the existing unauthenticated `/api/user` endpoint. Playwright starts only after dependencies are healthy; do not add Actuator solely for this suite.
- Record the layered real-stack E2E architecture and its trade-offs in a new ADR. No domain glossary entry is needed because this work introduces no business-domain term.
- Roll out incrementally: first land the complete stack, authentication/Profil bailleur journey, local runner, and green required CI job; then add each remaining independent journey in its own green commit.
## Testing Decisions
- A good real-backend E2E test observes public behavior at the highest available seam. It drives the browser for the behavior under test, uses public HTTP only for prerequisite setup or independent verification, and never asserts internal classes, MongoDB collections, filesystem paths, or Angular component implementation.
- The highest seam is the packaged frontend origin. It includes Nginx routing, OAuth initiation and callback, session/CSRF cookies, Spring MVC serialization and validation, application/domain services, MongoDB transactions, filesystem storage, ImageMagick, PDF rendering, and browser-visible results.
- The eight required journeys are:
1. OIDC login, Profil bailleur persistence, and logout.
2. Two-user portfolio isolation for protected reads and writes.
3. Appartement, Locataire, and draft Bail creation, reload, and reopen.
4. Appartement document and Locataire attachment upload, download, replacement/removal, and reload.
5. Garantie Visale documents, Inventaire du mobilier, État des lieux d'entrée with a real photo, lease-package generation, and Bail finalization.
6. Generated Échéances de loyer, Règlement de loyer, Affectation de règlement, Quittance de loyer, Reçu de règlement, Remboursement de trop-perçu, and Contrepassation de règlement.
7. Révision IRL using deterministic local INSEE data, revision-letter download, transmission evidence, and persisted application.
8. Congé du locataire with evidence, État des lieux de sortie with a real photo and generated document, and Clôture du bail.
- Each journey gets its own test and unique data. Preconditions that are not the subject of the journey are created through authenticated public APIs to keep the browser flow focused and failures diagnosable.
- The portfolio-isolation journey uses both Dex users and verifies denial through public responses and browser-visible behavior. It must not infer isolation from absent database records.
- Multipart journeys use small, valid committed fixtures. The photo journey verifies that the normalized stored image can be rendered or downloaded, exercising the real ImageMagick path.
- PDF and archive journeys assert structural validity and user-visible metadata. Detailed document content remains covered by existing focused backend tests.
- The local INSEE fixture returns the same deterministic protocol payload expected by the production adapter, allowing the real HTTP adapter, parsing, cache, and Révision IRL application flow to run without internet access.
- The fixed clock and browser timezone are explicit and shared across tests. Test dates are valid relative to that instant rather than today's wall clock.
- Existing prior art remains authoritative:
- the mocked Playwright suite provides browser locators, accessibility assertions, viewport coverage, and UI branch coverage;
- Spring Boot integration tests provide real MongoDB replica-set setup and cross-aggregate journey knowledge;
- focused web tests provide authentication/CSRF contract examples;
- Docker acceptance checks provide real ImageMagick behavior;
- production Compose and container definitions provide the packaging and proxy topology.
- The real suite runs desktop-only, one worker, and without retries. Tests may continue after another independent test fails, but no test depends on another test's domain state.
- CI success requires all eight journeys to pass in the single fresh environment. A preflight must prove that internal services are reachable while a known external destination is unreachable; the job fails if egress unexpectedly succeeds. Service startup or health-check failure is also a job failure.
- The local/CI runner must trap non-zero exits, write Playwright diagnostics and every service's logs to known artifact paths, validate that the expected diagnostic files exist, and then return the original non-zero status for GitLab artifact upload.
- Formatting, linting, frontend type checking, backend verification, mocked Playwright tests, container builds, and the new real-backend job all remain required gates.
## Out of Scope
- Replacing the existing mocked Playwright suite or rerunning all 62 mocked journeys against the backend.
- Mobile and tablet projects in the real-stack suite.
- Parallel real-stack execution or per-test Compose environments.
- Automatic retries against mutated job state.
- Live calls to INSEE or any non-authentication third party.
- Running VoidAuth itself in CI; deployment compatibility remains based on standard OIDC and is separately configured.
- Direct MongoDB setup/assertions, filesystem-path assertions, cleanup endpoints, or test-only authentication bypasses.
- Exact PDF rendering snapshots, byte-for-byte document hashes, or visual-regression testing.
- Load, soak, chaos, browser-compatibility, penetration, and production deployment tests.
- Preserving E2E data or volumes after the job.
- Schema migrations or backward-compatibility work; development and E2E data remain disposable.
- Expanding the application's business functionality while adding the test harness.
## Further Notes
- The current Playwright server starts Angular without the backend proxy and the existing tests intentionally mock `/api`; this is why a separate configuration and Compose topology are required.
- The backend already externalizes the INSEE base URI and attachment path and already injects `Clock` into date-sensitive services. The new profile should use those existing seams rather than introduce parallel adapters.
- Backend integration tests already require Docker through Testcontainers and a MongoDB replica set, so the new job builds on an existing CI capability. The CI runner must expose Docker/Compose to the job; absence of that capability is an infrastructure prerequisite, not a reason to weaken the test seam.
- ADR-0004 selects pluggable OIDC with VoidAuth as the primary deployment provider. A declarative Dex test provider is consistent with that decision and supplies deterministic CI bootstrap that VoidAuth's documented first-run administrative flow does not currently provide.
- ADR-0013 makes OIDC-subject portfolio isolation a core security rule, which is why the two-user journey is mandatory.
- ADR-0014 requires a MongoDB replica set for transactions and keeps filesystem storage outside MongoDB transactions, which is why both real MongoDB and attachment storage are part of the stack.
- ADR-0022 confirms that development data is disposable. The E2E architecture independently applies the same no-preservation posture to test data through fresh per-job volumes.
- The architectural choice should be captured as ADR-0023 because the layered suite, test identity provider, production-like topology, and explicit non-goals are a durable trade-off future maintainers might otherwise undo.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem Statement
The application has broad Playwright coverage for browser behavior, but those tests intercept
/apiand provide fabricated responses. They can prove Angular behavior across viewports, yet they cannot detect broken Nginx proxying, OAuth callbacks, Spring Security sessions and CSRF, HTTP serialization, MongoDB transactions and persistence, attachment storage, ImageMagick processing, or PDF generation.Backend integration tests exercise the Spring application and a real MongoDB replica set through MockMvc, but they do not run a browser, packaged frontend, Nginx, or real OIDC authorization-code flow. A commit can therefore pass both suites while the deployed application is unusable across their boundary.
The project needs a small, deterministic real-stack E2E gate that complements rather than replaces the existing fast suites. It must run on every commit, remain reproducible locally, and fail for actual integration breakage without depending on shared state or live third-party services.
Solution
Add a separate real-backend Playwright suite containing eight independent, critical business journeys. Run it through a disposable, production-like Compose stack containing the packaged Angular/Nginx frontend, Spring Boot backend, MongoDB replica set, Dex OIDC provider, deterministic local INSEE fixture service, and Playwright runner.
The suite will authenticate through a real OIDC authorization-code flow, reuse authenticated browser state for setup and journeys, create prerequisites through the application's public HTTP API, perform the behavior under test through the browser, and assert only externally observable results. It will never intercept application API calls or inspect MongoDB collections directly.
The existing mocked Playwright suite remains the broad and responsive browser contract. The new suite is a desktop-only, one-worker, zero-retry integration gate on every commit. Every job receives fresh MongoDB, identity, and attachment state and destroys it afterward.
User Stories
latesttag cannot silently change test behavior.Implementation Decisions
page.route.latesttags.Clock. Configure MongoDB, OIDC, INSEE base URI, attachment root, and other existing ports through normal typed/environment configuration./api/userendpoint. Playwright starts only after dependencies are healthy; do not add Actuator solely for this suite.Testing Decisions
Out of Scope
Further Notes
/api; this is why a separate configuration and Compose topology are required.Clockinto date-sensitive services. The new profile should use those existing seams rather than introduce parallel adapters.mentioned in commit b66762e37aa111316ef38c61b019bdf38528b3d7
mentioned in commit cd030918558a1a55cb86c6efa32f171069c03caa
mentioned in commit 9adad23c16d94c76305f16d6b4409ba57133b159
mentioned in commit 1c31166795f1a8b762427987f27fd0b469ef8186
mentioned in commit ba90a6a1a14b5cc213812e06a3a96ce1b0679b1d
mentioned in commit 50043da984eab195a839f70403c9b35a37d6b40b
mentioned in commit 1dc95b621be4c8a9af3507fd57e87e172ea3e0e8
mentioned in commit beb2c7aa8b8cfe1467a79efb553458eee1c9c05d
mentioned in commit a1d55d1a0d2b9de2ff9e012086d3074cb9e46b9b
mentioned in commit 8f17601f546cd4ad2e8baf6caa1aebbe8781ce68
mentioned in commit
ff5975fefbmentioned in commit
88291aa0a5mentioned in commit
ff212b2624mentioned in commit
108fc61594mentioned in commit
f1bb1d2b3dmentioned in commit
98d82d60a2mentioned in commit
98f4151ff7mentioned in commit
efb4c94065mentioned in commit
5b03aa9d21mentioned in commit
d4f8f28fafPreserved GitLab #88 history: source attribution, exact timestamps, discussion grouping and review positions. Historical diff snapshots: 2; visible source notes: 20.
gitlab-47-issue-88.md (SHA-256
4d5611058b5db231b50325a8c21e3c7a4635c0c8d6a1f8f9d846296dfd71eea4)gitlab-47-issue-88.json (SHA-256
698da7d05e900b57edd05232ee8acb6f10ae8af7c9d7b1ec8b512fd1ee41a596)Old CI job execution and reports are omitted. Original source files and rollback backups remain protected.