Conversation
Two-tier architecture (stubbed + integration) with shared factories, Docker-based E2E environment, and parallel CI pipeline for the OCMC dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
21 tasks across 5 chunks: factory layer, stubbed test refactoring, Docker E2E environment, integration tests, and CI pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…shboard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ction Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t helpers Rewrites boards_list, activity_feed, global_approvals, mobile_sidebar, organizations, skill_packs_sync, local_auth_login, and board_tasks to use the new factory layer and composable intercept helpers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rapper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…seed runner Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…targets Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds auth-protected-access, board-lifecycle, and task-lifecycle integration tests that run against the real backend. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Introduces a two-tier Cypress E2E framework (stubbed + real-backend integration) with shared factories/intercept helpers, plus Docker/CI orchestration to run the integration tier against an ephemeral stack.
Changes:
- Added shared Cypress factories and composable intercept helpers to de-duplicate stubbed test setup.
- Added an integration E2E tier (Cypress config + integration specs) backed by a Docker Compose
e2eprofile and a DB seed script. - Updated CI to split E2E into parallel
e2e-stubbedande2e-integrationjobs.
Reviewed changes
Copilot reviewed 35 out of 35 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/e2e_seed.py | Adds DB seeding for integration E2E prerequisites (user/org/membership). |
| frontend/cypress/support/testHooks.ts | Refactors legacy hook helper to delegate to new stubbing utilities. |
| frontend/cypress/support/intercepts/sse.ts | Adds reusable stubs for SSE endpoints. |
| frontend/cypress/support/intercepts/index.ts | Barrel export for intercept helpers. |
| frontend/cypress/support/intercepts/dashboard.ts | Adds reusable dashboard/metrics stubs. |
| frontend/cypress/support/intercepts/boards.ts | Adds reusable boards + board-groups stubs. |
| frontend/cypress/support/intercepts/auth.ts | Adds reusable auth bootstrap stubs (health + me + orgs + membership). |
| frontend/cypress/support/integration/api.ts | Adds thin real-API helper for integration test setup/verification. |
| frontend/cypress/support/factories/user.ts | Adds user entity factory for stubs. |
| frontend/cypress/support/factories/task.ts | Adds task + task comment factories and request input builders. |
| frontend/cypress/support/factories/reset.ts | Adds deterministic ID counter + reset hook for factories. |
| frontend/cypress/support/factories/org.ts | Adds org + org member factories. |
| frontend/cypress/support/factories/index.ts | Barrel export for factories. |
| frontend/cypress/support/factories/board.ts | Adds board + create-input + snapshot factory helpers. |
| frontend/cypress/support/factories/approval.ts | Adds approval factory helper. |
| frontend/cypress/support/e2e.ts | Adds global factory reset + “unstubbed API” logging intercept. |
| frontend/cypress/support/commands.ts | Makes local-auth login token env-aware for integration tier. |
| frontend/cypress/e2e/skill_packs_sync.cy.ts | Migrates stubbed spec to new intercept/factory approach. |
| frontend/cypress/e2e/organizations.cy.ts | Migrates org spec to stubAuth and trims inline stubs. |
| frontend/cypress/e2e/mobile_sidebar.cy.ts | Migrates dashboard sidebar spec to intercept helpers. |
| frontend/cypress/e2e/local_auth_login.cy.ts | Migrates local auth login spec to factories/intercepts. |
| frontend/cypress/e2e/global_approvals.cy.ts | Migrates approvals spec to factories/intercepts. |
| frontend/cypress/e2e/boards_list.cy.ts | Migrates boards list spec to factories/intercepts. |
| frontend/cypress/e2e/board_tasks.cy.ts | Migrates board task spec to factories + SSE helper. |
| frontend/cypress/e2e/activity_feed.cy.ts | Migrates activity feed spec to factories + SSE helper. |
| frontend/cypress/e2e-integration/task-lifecycle.cy.ts | Adds real-backend task lifecycle integration spec. |
| frontend/cypress/e2e-integration/board-lifecycle.cy.ts | Adds real-backend board lifecycle integration spec. |
| frontend/cypress/e2e-integration/auth-protected-access.cy.ts | Adds real-backend auth gate integration spec. |
| frontend/cypress.integration.config.ts | Adds Cypress config for integration tier (real API base + token). |
| docs/superpowers/specs/2026-03-12-e2e-testing-framework-design.md | Design spec for the new E2E architecture and rollout. |
| docs/superpowers/plans/2026-03-12-e2e-testing-framework.md | Detailed implementation plan for the E2E framework. |
| compose.yml | Adds --profile e2e services (db-test, backend-e2e, e2e-seed). |
| backend/app/main.py | Improves /readyz readiness probe to verify DB connectivity. |
| Makefile | Adds e2e-up, e2e-seed, e2e-integration, e2e-full orchestration targets. |
| .github/workflows/ci.yml | Splits E2E into parallel stubbed + integration jobs with artifacts/logs. |
You can also share your feedback on Copilot code review. Take the survey.
| getTasks(boardId: string) { | ||
| return cy.request({ | ||
| method: "GET", | ||
| url: `${apiBase()}/api/v1/boards/${boardId}/snapshot`, |
There was a problem hiding this comment.
api.getTasks() currently calls the same /boards/{id}/snapshot endpoint as api.getBoard(). The name suggests it fetches only tasks (or hits a /tasks endpoint), so this is easy to misuse later. Consider either renaming to reflect that it returns a snapshot, or changing the implementation to call an endpoint that actually returns tasks only (if available).
| url: `${apiBase()}/api/v1/boards/${boardId}/snapshot`, | |
| url: `${apiBase()}/api/v1/boards/${boardId}/tasks`, |
| async def seed(database_url: str) -> None: | ||
| engine: AsyncEngine = create_async_engine(database_url, echo=False) | ||
| session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) | ||
|
|
||
| async with session_maker() as session: | ||
| # Idempotent: check if user already exists. | ||
| existing = await session.get(User, USER_ID) | ||
| if existing: | ||
| print(f"Seed data already exists (user {USER_ID}). Skipping.") | ||
| return |
There was a problem hiding this comment.
seed() returns early when the user already exists, which (a) can leave the org/membership missing if the DB is in a partially-seeded state, and (b) skips engine.dispose() because the early return occurs before the dispose call. Consider making the seed idempotent per-entity (check/create org and membership too) and ensuring engine.dispose() runs via try/finally even on early exit.
| from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker, create_async_engine | ||
| from sqlmodel import SQLModel | ||
| from sqlmodel.ext.asyncio.session import AsyncSession |
There was a problem hiding this comment.
SQLModel is imported but never used in this script. Since model registration is already handled via from app import models as _models, this import should be removed to avoid dead code / confusion.
| it("positive: signed-in user can view /organization and sees correct invite permissions", () => { | ||
| // Semantically important: role is "member" (not default "owner") | ||
| // because invite button should be disabled for non-admins. | ||
| stubAuth(apiBase, { |
There was a problem hiding this comment.
stubAuth() is being called with a membership user_id of "u1", but no matching user: { id: "u1" } override is provided. Because stubAuth() will still return a default user (e.g. user-1) from /users/me, the stubs become internally inconsistent (current user id != membership.user_id), which can cause subtle permission/UI logic issues. Make the stubs consistent by either overriding the user id to "u1" or by not overriding member.user_id.
| stubAuth(apiBase, { | |
| stubAuth(apiBase, { | |
| user: { id: "u1" }, |
| // Fail stubbed tests if an API request escapes interception. | ||
| // Integration tests (which set Cypress.env("API_BASE")) skip this check. | ||
| if (!Cypress.env("API_BASE")) { | ||
| beforeEach(() => { | ||
| cy.intercept({ url: /\/api\/v1\//, middleware: true }, (req) => { | ||
| // If this middleware intercept is the ONLY handler, the request | ||
| // was not stubbed by a test-specific intercept. | ||
| // Log it so flaky leaks are visible in CI output. | ||
| Cypress.log({ | ||
| name: "UNSTUBBED API", | ||
| message: `${req.method} ${req.url}`, | ||
| consoleProps: () => ({ method: req.method, url: req.url }), | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
This block is documented as "Fail stubbed tests if an API request escapes interception", but the current intercept only logs and then allows the request to proceed. That means a missing stub could still yield a passing test if the app handles the response/error gracefully (or if a backend happens to be reachable locally). Consider explicitly failing the test for matched requests here (e.g., respond with an error status / throw) so unstubbed API calls are always surfaced as test failures.
| e2e-seed: | ||
| profiles: ["e2e"] | ||
| build: | ||
| context: . | ||
| dockerfile: backend/Dockerfile | ||
| depends_on: | ||
| db-test: | ||
| condition: service_healthy | ||
| environment: | ||
| DATABASE_URL: "postgresql+psycopg://ocmc:test@db-test:5432/ocmc_test" | ||
| command: ["python", "-m", "scripts.e2e_seed"] | ||
| restart: "no" |
There was a problem hiding this comment.
e2e-seed only depends on db-test being healthy, but the seed script assumes the schema/migrations are already applied (handled by backend-e2e via DB_AUTO_MIGRATE). If someone runs make e2e-seed (or docker compose run e2e-seed) without having started/awaited backend-e2e, this can fail with missing tables. Consider adding a depends_on: backend-e2e: { condition: service_healthy } (or running migrations in the seed container) to make seeding robust when invoked independently.
Task / context
Scope
Out of scope
Evidence / validation
make check(or explain what you ran instead)Screenshots (UI changes)
Docs impact
Risk / rollout notes
Checklist
origin/master(no unrelated commits)docs/reference/api.md)