Test/e2e setup - #395
Conversation
Canonical spec for the Playwright + Electron E2E layer: packaged-build target (Android dropped, web-only impossible without the preload DB bridge), local-vs-CI launch modes, the e2e/ harness layout with reusable locators/flows, the fixture/seed contract, embedder-on-disk seeding, mock LLM, and the DB-first three-tier selector strategy. Unit + component discipline stays in code-conventions.md. Records the fixture prerequisite: seed IDs under a substitutable prefix must be real prefix_<uuid>, not mnemonics — substituteIds passes char_kael through untouched so turns fail on the placeholder return trip. Fix lands as a standalone slice next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mnemonic seed ids (char_kael) never match ID_PATTERN, so substituteIds passes them through untouched and a turn on seeded data dies on the placeholder return trip. buildSeedSteps now rewrites every substitutable id to a deterministic prefix_<uuid> (pure-JS hash, stable under both the Node seed script and Hermes reseed), correcting the two authored off-spec (fac→fact, thread→thr per data-model.md) and re-canonicalizing relationship pairs whose a_id<b_id order the uuid remap inverts. Non-substitutable ids (branch_, entry_, aw_) keep their readable mnemonics; conforming them to the data-model prefix table is a separate cleanup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fixture was hand-authored with ad-hoc prefixes that never matched the ID table in data-model.md: branch_→br_, e_→entry_, aw_→haw_, inv_→hinv_, ea_→ast_, and the seed_act_ action grouping→act_. Runtime code already mints these via generateId, so this was seed-only drift; correcting it keeps the fixture faithful if future code keys on the prefix. Readable suffixes stay (br_hero_main) since nothing requires the uuid shape for non-LLM ids; media assets, era flips, and the app_settings singleton have no spec prefix and are left as-is. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First cut of the Playwright + Electron harness from docs/testing.md: seed a throwaway userData (real db:seed), launch unpackaged main against a static-served dist, select the app window by origin (dodging the dev DevTools race), and assert through both the DOM (i18n-resolved locators) and the fixture DB. Proves the two open unknowns — i18next resolves standalone in Node, and the launch→seed→select→assert loop holds. Local mode only; packaged app:// tier, mock LLM, embedder seeding, and the wizard UI flow are the next increments. Requires a prior build:web + electron:compile. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the packaged launch mode to the harness (electron-builder --dir binary loading app://bundle, selected by scheme origin) behind AVENTURAS_E2E_MODE — dev stays the local default, packaged is the CI target of record. New e2e job builds the web bundle, compiles main, packages unpacked (Electron binary cached on the lockfile), then runs the suite under xvfb against a per-run seeded userData. Verified locally end-to-end in both modes; xvfb and fresh-install native deps are the CI-only unknowns to confirm on the first run. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The section described the id drift as an open bug and named UUIDv5; the fix shipped as a deterministic pure-JS hash (v4-shaped) in seed-ids.ts, plus the fac_→fact_ / thread_→thr_ corrections and relationship re-canonicalization. Reframe as the satisfied contract and correct the mechanism. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (13)
📝 WalkthroughWalkthroughAdds deterministic seed ID normalization, updates fixture contracts, and introduces a Playwright Electron harness covering seeded data, story creation, local embedding, mocked LLM turns, structured output, and packaged CI execution. ChangesSeeded E2E and fixture contracts
Estimated code review effort: 4 (Complex) | ~90 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant Playwright
participant Electron
participant RendererBridge
participant MockLlm
participant SQLite
Playwright->>Electron: launch with seeded userDataDir
Playwright->>RendererBridge: submit story turn
RendererBridge->>MockLlm: request streamed narrative
MockLlm-->>RendererBridge: return narrative SSE
RendererBridge->>MockLlm: request structured classifier
MockLlm-->>RendererBridge: return JSON response
RendererBridge->>SQLite: commit story entries
Playwright->>SQLite: assert persisted turn data
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/harness/launch.ts`:
- Around line 18-22: Replace the environment-variable E2E contract with explicit
checked-in runner/harness configuration: update e2e/harness/launch.ts lines
18-22 to receive mode and packaged app location through that configuration, and
lines 110-115 to pass the dev renderer endpoint through the same boundary; make
reporter selection deterministic or use the shared configuration in
playwright.config.ts line 15; update .github/workflows/ci.yml lines 141-144 to
invoke the packaged configuration directly without exporting AVENTURAS_E2E_MODE.
- Around line 89-125: Update the launch flow around electron.launch,
selectAppWindow, and the returned close methods to clean up resources on all
failures. Wrap each launch/setup path in try/catch, closing any started app and
removing userDataDir when initialization fails; for the development path, always
close the server and clean up user data in close() via finally, including when
app.close() rejects.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0c7b3d8d-a36d-42dd-9bb2-3ada03eac553
⛔ Files ignored due to path filters (4)
docs/README.mdis excluded by!docs/**docs/code-conventions.mdis excluded by!docs/**docs/testing.mdis excluded by!docs/**pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml,!pnpm-lock.yaml
📒 Files selected for processing (16)
.claude/rules/testing.md.github/workflows/ci.yml.gitignoreCLAUDE.mde2e/harness/db.tse2e/harness/i18n.tse2e/harness/launch.tse2e/harness/seed.tse2e/locators/home.tse2e/tests/home.spec.tslib/db/devtools/seed-dataset.test.tslib/db/devtools/seed-dataset.tslib/db/devtools/seed-ids.test.tslib/db/devtools/seed-ids.tspackage.jsonplaywright.config.ts
@huggingface/transformers pulls in sharp; its libvips native lib was packed inside the asar and failed to dlopen (ERR_DLOPEN_FAILED: libvips-cpp.so), so embedding threw an init error in every packaged build — release AppImage included — while dev worked from node_modules. Add sharp + @img to asarUnpack. Surfaced by the packaged E2E embedder test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harness provisions the default catalog model (download from HF at the pinned revision, sha256-verified, cached under ~/.cache/aventuras-e2e) and copies it into each run's userData/embedders. New spec drives the renderer embedder bridge: listInstalled sees the seeded model, and smokeTest embeds through the real renderer → IPC → main → onnxruntime path returning the catalog dim. CI caches the model dir keyed on the catalog. Unblocks the wizard flow, whose create-story path embeds the lead entity. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause was not the hypothesized wasm-backend / modelDir resolution but sharp's libvips native lib packed inside the asar (ERR_DLOPEN_FAILED); fixed in 74cb1d2 by adding sharp + @img to asarUnpack, verified on a built AppImage. Caught by the packaged E2E embedder test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drives the wizard UI end-to-end — New story → adventure mode (surfaces the lead) → calendar → opening + title → Finish — then asserts through the app's DB bridge that the story committed active, the lead entity persisted, and the real embedder populated its vec0 row (entities_vec_384). Exercises the wizard's hard embedder gate, the multi-step nav, and create-story's lead-embed path in one pass. All locators resolve via i18n role/name + placeholder, so no testIDs were needed; reusable flow lives in e2e/flows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/harness/embedder.ts`:
- Around line 39-44: The cacheRoot() configuration must not read the
AVENTURAS_E2E_MODEL_DIR environment variable. Remove this environment-based
override and use the repository-approved SQLite-backed configuration path,
preserving the existing default cache location when no configured value is
available.
- Around line 54-58: Update the cache-hit logic around sanitizeModelDirName and
the .e2e-complete marker to store catalog revision/hash metadata plus the
expected file manifest, then validate that metadata and all expected cached
files against the current model before returning. Treat missing, stale,
mismatched, or incomplete cache data as a cache miss and rerun model preparation
instead of trusting existsSync(marker).
- Around line 61-66: Update ensureEmbedderModel’s per-file fetch loop to use an
AbortSignal timeout for each Hugging Face download, and add bounded retries with
a clear final failure when the deadline or request fails. Preserve the existing
response validation and file-writing behavior, ensuring stalled downloads cannot
wait for the broader E2E timeout.
In `@e2e/tests/embedder.spec.ts`:
- Around line 23-30: Update the test setup around createSeededUserDataDir,
installEmbedderModel, and launchApp to retain userDataDir and clean it up when
setup throws before app is assigned. Add setup failure cleanup while preserving
normal app lifecycle cleanup and avoiding removal of user data still owned by
the running app.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 4236a18d-2b33-4bd5-b766-0fb9a1a53eca
⛔ Files ignored due to path filters (1)
docs/implementation/milestones/03-memory-floor/slices/01b-embedder-lifecycle.mdis excluded by!docs/**
📒 Files selected for processing (8)
.github/workflows/ci.ymle2e/flows/create-story.tse2e/harness/embedder.tse2e/locators/home.tse2e/locators/wizard.tse2e/tests/embedder.spec.tse2e/tests/wizard.spec.tspackage.json
🚧 Files skipped from review as they are similar to previous changes (2)
- e2e/locators/home.ts
- .github/workflows/ci.yml
| const { userDataDir } = createSeededUserDataDir() | ||
| ;({ modelId, dim: expectedDim } = await installEmbedderModel(userDataDir)) | ||
| app = await launchApp({ userDataDir, cleanupUserData: true }) | ||
| }) | ||
|
|
||
| test.afterAll(async () => { | ||
| await app?.close() | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Clean up temporary user data when setup fails.
If installEmbedderModel() or launchApp() throws, app is never assigned and afterAll cannot remove the directory created by createSeededUserDataDir(). Track userDataDir and remove it in a setup catch/finally path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/tests/embedder.spec.ts` around lines 23 - 30, Update the test setup
around createSeededUserDataDir, installEmbedderModel, and launchApp to retain
userDataDir and clean it up when setup throws before app is assigned. Add setup
failure cleanup while preserving normal app lifecycle cleanup and avoiding
removal of user data still owned by the running app.
A turn on seeded data failed preflight with config-resolver/no-profile-assigned: with no classifier profile assigned, the pipeline planned the piggyback fallback classifier, whose resolve had nothing to bind. Assign a classifier profile (kind agent) and mark the narrative model taggedBlockReliable so piggyback also rides in-band. Turns and the periodic classifier now resolve on seeded data — the payoff of the earlier fixture-id fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Local OpenAI-compatible mock: one endpoint, routed by the request — streaming SSE prose for the narrative call, JSON chat completion for the non-streaming structured classifier — with CORS so the renderer's cross-origin fetch isn't blocked in dev. setProviderEndpoint repoints the seeded provider at it before launch. New spec submits a user action in the seeded reader and asserts, through the app DB bridge, that both the user action and the streamed AI reply commit to the branch — the real transport + pipeline, only the LLM mocked. Green in dev and packaged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The structured route served one canned body, so a second structured agent would get a mismatched shape. Route instead by the exact TypeScript block the app injects (schemaToTypeScriptBlock over each agent's zod schema) — self-syncing since it reuses the app's own renderer, so adding an agent is one STRUCTURED_AGENTS entry. New spec disables piggyback to force the per-turn fallback classifier, then asserts a single turn fans out on one endpoint into a streamed narrative and a structured call the mock matched to the classifier schema. Exports schemaToTypeScriptBlock and fallbackClassifierSchema for the match. Covers the auto (prompt-injection) path we use; the force-on native path would match on response_format instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The section predated the implementation ("canned responses per prompt shape"). Describe what shipped: the dynamic endpoint override at seed time, CORS for the renderer's cross-origin fetch, and the one-endpoint routing — streaming SSE narrative vs structured calls matched to an agent by the exact schemaToTypeScriptBlock the app injects, with the auto-vs-force-on paths called out.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A force-on profile takes the native structured path, but the app never sets the openai-compatible provider's supportsStructuredOutputs, so the SDK emits response_format {type:json_object} with no schema and no prompt-injected block either. New spec forces the classifier call under a force-on profile and asserts exactly that, documenting why every other E2E relies on the prompt-embedded (auto) schema; it flags the change if the provider flag is ever wired. Doc updated, plus embedder-download flow marked manual-only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The profile flag exists and round-trips through the DB but has no UI and never reaches the provider (supportsStructuredOutputs unset), so force-on emits schema-less json_object. Captures the wiring + capability-gate + optional-to-nullable schema work, pointing at the structured-force-on spec as the guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
E2E enters the chain as a planning decision, not a dedicated agent: plan-slice gains a "Decide E2E coverage" step (does the slice change a cross-subsystem seam or user flow?), and when it does the plan carries an E2E task + pnpm test:e2e as a verification command. verification-before-completion adds the flow-works row; test-driven-development names E2E as the third layer beside unit and Storybook. The how already auto-loads via .claude/rules/testing.md, so any per-task subagent that touches e2e/ gets it — no new agent type, and most slices skip it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The E2E discipline had every anchor but this one — canonical doc, auto-loading rule, README index, code-conventions pointer, skill-chain gate — yet the doc itself wasn't in the always-loaded reading list beside its peers. Add it; clarify code-conventions covers unit/component testing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prettier turned the wrapped "Playwright + Electron" into a nested list item (the ' + '-separator wrap trap); use a slash so it stays one bullet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The discipline said what E2E owns and when a slice needs one, but never how much. Pin it: broad across flows, shallow within — every happy path, common alternative flows, and common edge cases that cross a seam; pure-logic branches and rare permutations stay in unit/Storybook. The boundary test is whether a running app is the only place the behavior is observable. plan-slice's gate now points at it so "carry an E2E task" carries the depth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (3)
e2e/harness/seed.ts (1)
24-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract shared read/mutate/write boilerplate.
setProviderEndpoint,setProfileStructuredOutput, anddisablePiggybackCapabilityeach repeat the same open → read column →JSON.parse→ mutate →UPDATE→closesequence againstapp_settings. Consider a small shared helper (e.g.withSingletonColumn(dbPath, column, mutate)) to remove the duplication.♻️ Example shared helper
+function withSingletonColumn<T>( + dbPath: string, + column: string, + mutate: (value: T) => void, +): void { + const db = new DatabaseSync(dbPath) + try { + const row = db.prepare(`SELECT ${column} FROM app_settings WHERE id = 'singleton'`).get() as + Record<string, string> + const value = JSON.parse(row[column]) as T + mutate(value) + db.prepare(`UPDATE app_settings SET ${column} = ? WHERE id = 'singleton'`).run( + JSON.stringify(value), + ) + } finally { + db.close() + } +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/harness/seed.ts` around lines 24 - 90, Extract the repeated singleton app_settings read/parse/mutate/update/close flow from setProviderEndpoint, setProfileStructuredOutput, and disablePiggybackCapability into a shared helper such as withSingletonColumn. Have the helper accept dbPath, the column name, and a mutation callback, then update each function to use it while preserving their existing mutations and cleanup behavior.e2e/tests/classifier.spec.ts (1)
13-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
dbRowshelper across specs. The exact same asyncdbRows(page, sql, params)helper (queryingwindow.aventurasDb.query) is defined independently in both spec files.
e2e/tests/classifier.spec.ts#L13-L26: move this helper into a shared harness module (e.g.e2e/harness/db.ts) and import it here.e2e/tests/turn.spec.ts#L9-L22: import the same shared helper instead of redefining it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@e2e/tests/classifier.spec.ts` around lines 13 - 26, The duplicated dbRows helper should be centralized in a shared harness module. Move the async dbRows implementation from e2e/tests/classifier.spec.ts lines 13-26 into e2e/harness/db.ts and import it there; in e2e/tests/turn.spec.ts lines 9-22, remove the local definition and import the same shared helper.lib/db/devtools/seed-dataset.ts (1)
1612-1649: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueKeep
'story_hero'references usable after remapping.remapSeedIds()leavesstory_herounchanged, but the seed comment says mnemonic story IDs are “off-spec,” so future additions/remaps to non-canonicalstory_*seeds could break these e2e selectors. Import/extract the canonicalstories.idvalue from the seed module or anchor these queries by a stable story attribute instead of hardcoding the memo mnemonic.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/db/devtools/seed-dataset.ts` around lines 1612 - 1649, Replace hardcoded story_hero selectors with the canonical stories.id value exported or extracted from the seed module, or use a stable story attribute for lookup. Update the affected references in lib/db/devtools/seed-dataset.ts (1612-1649), e2e/tests/classifier.spec.ts (72-80), and e2e/tests/turn.spec.ts (65-67); keep all selectors usable after remapSeedIds() and do not rely on mnemonic story IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@e2e/harness/seed.ts`:
- Around line 24-90: Extract the repeated singleton app_settings
read/parse/mutate/update/close flow from setProviderEndpoint,
setProfileStructuredOutput, and disablePiggybackCapability into a shared helper
such as withSingletonColumn. Have the helper accept dbPath, the column name, and
a mutation callback, then update each function to use it while preserving their
existing mutations and cleanup behavior.
In `@e2e/tests/classifier.spec.ts`:
- Around line 13-26: The duplicated dbRows helper should be centralized in a
shared harness module. Move the async dbRows implementation from
e2e/tests/classifier.spec.ts lines 13-26 into e2e/harness/db.ts and import it
there; in e2e/tests/turn.spec.ts lines 9-22, remove the local definition and
import the same shared helper.
In `@lib/db/devtools/seed-dataset.ts`:
- Around line 1612-1649: Replace hardcoded story_hero selectors with the
canonical stories.id value exported or extracted from the seed module, or use a
stable story attribute for lookup. Update the affected references in
lib/db/devtools/seed-dataset.ts (1612-1649), e2e/tests/classifier.spec.ts
(72-80), and e2e/tests/turn.spec.ts (65-67); keep all selectors usable after
remapSeedIds() and do not rely on mnemonic story IDs.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 05184ce9-c32f-4fca-bbd4-a110b97e83fb
⛔ Files ignored due to path filters (2)
docs/implementation/triage.mdis excluded by!docs/**docs/testing.mdis excluded by!docs/**
📒 Files selected for processing (13)
.agents/skills/aventuras-plan-slice/SKILL.md.agents/skills/aventuras-test-driven-development/SKILL.md.agents/skills/aventuras-verification-before-completion/SKILL.mdCLAUDE.mde2e/harness/mock-llm.tse2e/harness/seed.tse2e/tests/classifier.spec.tse2e/tests/structured-force-on.spec.tse2e/tests/turn.spec.tslib/ai/index.tslib/db/devtools/seed-dataset.tslib/pipeline/definitions/per-turn-piggyback.tslib/pipeline/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- CLAUDE.md
The suite is at happy-path core (one per seam); docs/testing.md sets the bar at thorough. Queue the known alt-flow and seam-crossing edge-case gaps on flows that already exist, so the backfill isn't assumed done — new slices meet the bar via the plan-slice gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harden the harness per review: launchApp cleans up the dev server + temp dir on launch/close failure (try/catch + finally, not sequential awaits); the embedder cache stores the catalog revision in its marker and re-downloads on a revision mismatch or missing file (a stale cache could otherwise pass the test against the wrong model); the download fetch gets a per-file timeout so a stalled HF connection fails cleanly; and createSeededUserDataDir self-cleans on seed failure while specs remove their temp dir in afterAll so a setup failure between seed and launch doesn't orphan it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getByText('E2E-FORCEON') matched both the AI reply and the user action ("E2E-FORCEON-USER …") — a strict-mode violation that surfaced intermittently as the two entries raced into the DOM. Give the reply a distinct E2E-FORCEON-REPLY token.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the AVENTURAS_E2E_MODE / AVENTURAS_E2E_APP_PATH / AVENTURAS_E2E_MODEL_DIR env contract (CodeRabbit: the repo bans env-driven config) with dev/packaged Playwright projects — the harness reads the project name, `pnpm test:e2e` runs dev, `test:e2e:packaged` runs packaged, and CI invokes the packaged script instead of exporting a var. Reporter is deterministic (list + github, the latter a no-op off Actions) and retries:1 absorbs a transient window/IPC hiccup. EXPO_WEB_URL stays — it's the app's own dev-mode contract passed to the Electron child. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary by CodeRabbit
pnpm test:e2eagainst the packaged build.