Skip to content

test(e2e): split @lifecycle into @ci + @real tiers (#621)#640

Draft
Kosinkadink wants to merge 13 commits into
mainfrom
fix/e2e-tier-rename-621
Draft

test(e2e): split @lifecycle into @ci + @real tiers (#621)#640
Kosinkadink wants to merge 13 commits into
mainfrom
fix/e2e-tier-rename-621

Conversation

@Kosinkadink

Copy link
Copy Markdown
Member

Closes #621 (partial - Phase 1 of the plan).

What this PR does

Retires the overloaded @lifecycle tag and replaces it with two explicit, independent tiers:

  • @ci - every-PR matrix (Windows + macOS + Linux). Seeded harness, __e2e backdoor, window.api bridge, fixtures on disk all allowed. Fast, deterministic, covers renderer + IPC contracts.
  • @real - real lifecycle. pnpm run test:e2e:real. No seeds, no __e2e mutations, no window.api shortcuts, no fake fixture installs. Behaves as if a human had booted the app and clicked through the UI.

OS axis stays independent: @ci defaults to all three OSes; @windows-only / @macos-only / @linux-only opt out when behavior is genuinely platform-specific. quit-flow.spec.ts is the lone opt-out today.

See e2e/AGENTS.md for the contract.

Tag inventory

  • 2 files @real: lifecycle.test.ts (29-test install/update/snapshot/copy/delete chain), lifecycle-cloud.test.ts.
  • 33 files @ci (1 with @macos-only).
  • 0 untagged.

Broken tests

Four @ci tests are currently broken; each is marked with a TODO(#621) comment so they stay visible and don't silently rot:

Test Marker Why
picker-stop-confirm.test.ts:159 skip Forwarded update-comfyui never reaches run-action.
lifecycle-snapshot-restore.test.ts:274 skip Picker-driven snapshot row never appears.
lifecycle-update-check.test.ts:218 fail Product bug from #595: auto-refresh fires once against fresh cache. test.fail keeps the regression visible without breaking CI.
lifecycle-update-check.test.ts:247 skip ageReleaseCache e2e hook no longer mutates the map main reads from.

Follow-up PRs address each. This PR is intentionally rename-only.

CI

  • Every-PR matrix (test:e2e:{windows,macos,linux}) is unchanged in scope but now driven by grep: /@ci/ with OS opt-out exclusions.
  • New .github/workflows/ci-real.yml workflow runs @real on workflow_dispatch + nightly cron (09:00 UTC) on windows-latest with a 90-min budget.

Validation

  • pnpm run typecheck ?
  • pnpm run lint ?
  • pnpm run build ?
  • pnpm run test:e2e:windows: 88 passed, 4 skipped (the TODOs), 0 failed, ~1.8 min.

The pre-existing src/main/lib/release-cache.test.ts vitest collection failure (__vite-browser-external:fs) reproduces on origin/main and is unrelated to this PR.

Kosinkadink and others added 13 commits May 27, 2026 21:54
The @lifecycle Playwright tag had drifted to mean three different
things at once (slow, real-ish, lifecycle-adjacent) and the project
was excluded from CI entirely. Result: 81 `@lifecycle` tests
across 28 files, never verified on PR, some silently broken.

Split into two explicit tiers on independent axes:

  - @ci   — every-PR matrix (Windows + macOS + Linux). Seeded
            harness, __e2e backdoor, window.api bridge, fixtures
            on disk all allowed. Fast, deterministic, covers
            renderer + IPC contracts.

  - @real — real lifecycle. `pnpm run test:e2e:real`. No seeds,
            no __e2e mutations, no window.api shortcuts, no fake
            fixture installs. Behaves as if a human had booted
            the app and clicked through the UI.

OS axis stays independent: @ci defaults to all three OSes;
@windows-only / @macos-only / @linux-only opt out when behavior
is genuinely platform-specific. quit-flow.spec.ts is the lone
opt-out today (@ci @macos-only — tray-close mode is macOS).

Tag inventory after this change:

  - 2 files @real: lifecycle.test.ts, lifecycle-cloud.test.ts
  - 33 files @ci (1 with @macos-only)
  - 0 files untagged

Four tests are currently broken and marked with TODO(#621):

  - picker-stop-confirm.test.ts:159 (skip) — forwarded
    update-comfyui never reaches run-action.
  - lifecycle-snapshot-restore.test.ts:274 (skip) — picker-driven
    snapshot row never appears.
  - lifecycle-update-check.test.ts:218 (fail) — PRODUCT BUG from
    #595: auto-refresh fires once against fresh cache. `test.fail`
    keeps the regression visible without breaking CI.
  - lifecycle-update-check.test.ts:247 (skip) — ageReleaseCache
    e2e hook no longer mutates the map main reads from.

Also:

  - e2e/AGENTS.md: codifies the @real contract + skip discipline.
  - .github/workflows/ci-real.yml: nightly + workflow_dispatch
    runner for the @real suite (windows-latest, 90 min budget).
  - test:e2e:real npm script.

Validation on this branch:

  - pnpm run typecheck ✓
  - pnpm run lint ✓
  - pnpm run build ✓
  - pnpm run test:e2e:windows: 88 passed, 4 skipped (the TODOs),
    0 failed, ~1.8 min.

The pre-existing src/main/lib/release-cache.test.ts vitest collection
failure (`__vite-browser-external:fs`) reproduces on origin/main and
is unrelated to this PR.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6c7e-bdf2-717b-be9f-156eb68dd55a
Co-authored-by: Amp <amp@ampcode.com>
Adds e2e/support/realPrereqs.ts exposing freshInstallStandaloneCpu, comfyFrontendIsLoaded, launchComfyByClickingTile, returnToDashboardViaFileMenu, and ensureInstalledAndLaunched. All helpers drive prereqs via real DOM clicks against the visible webContents (panel / title bar / popup) — no \__e2e.*\ mutations, no \window.api.*\ writes, no synthetic IPC. Mirrors the @real contract codified in e2e/AGENTS.md.

e2e/lifecycle.test.ts changes:

- Replace the bespoke beforeAll hydration block with ensureInstalledAndLaunched(ctx) when LIFECYCLE_REUSE_DIR points at an existing install. Module-level _updateInstallId / _updateInstallPath / _comfyUIDir / _installedCommit are populated from the returned HydratedInstall; read-only snapshot rehydration stays as-is.

- Replace every returnFirstInstallHostToDashboard(ctx.app) backdoor call (4 sites) with returnToDashboardViaFileMenu(ctx), which opens the title-bar file menu and clicks the 'Return to Dashboard' item.

- Add prereq launch safety to running-comfy-dependent tests (stop-before-update, re-launch-after-update, snapshot capture, cross-channel update, snapshot restore, compact-row Restart, pin-bottom Restart, pin-bottom Copy, stop-before-delete) so each can be greped individually against a hydrated profile.

- Mark every window.api.runAction(...) / window.api.openInstancePicker(...) SUT-call shortcut with // TODO(#621-phase3) — those remain as Phase 3 follow-ups.

- Remove unused returnFirstInstallHostToDashboard import from devHooks and unused clickInstallTile import from chooserHelpers.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6d01-75c8-709e-a16b-1ede7cd787a7
Co-authored-by: Amp <amp@ampcode.com>
returnToDashboard funnels through consultPanelRendererReturnToDashboard,
which short-circuits 'cleared' whenever the panelView is missing or its
webContents is destroyed. The chooser-pick attach in index.ts:393 calls
destroyPanelView(claimed), so until the user opens Settings or the
lifecycle body the install-backed panel has no webContents — meaning the
short-circuit also swallowed the 'Stop & Return' confirm that lives in
PanelApp.vue's onReturnToDashboardRequest handler. Clicking the picker
Home icon (or File > Return to Dashboard) on a running local install
tore the ComfyUI session down silently.

When the panelView is alive the existing renderer consult still owns
the prompt (it also layers Tier 2/3 overlay cancel-prompts on top); when
it's gone, fall back to a shell system modal with the same 'Stop & Return'
copy. The fallback mirrors useReturnToDashboardConfirm's gates: non-local
installs and already-stopped sessions clear silently.

Test side: returnToDashboardViaPickerHome now polls for the BaseAlert
confirm on either ctx.panel or the comfySystemModal popup, since the
WebContentsPage.exists call throws while the webContents doesn't exist
yet. Issue #621.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6d78-cf01-710a-821c-981fca2e7c22
Co-authored-by: Amp <amp@ampcode.com>
…nd updates it

Adds the missing @real coverage for the per-channel copy-update action.
Drives the same picker ChannelPicker surface as the existing cross-channel
update-comfyui @real test, but invokes the copy-update sibling — drafts
latest, clicks update-action-copy-update, fills the new-install-name
prompt, waits for the ~500MB copy + git update on the copy + the
open-install-window IPC for the new install.

Asserts:
- New install's ComfyUI HEAD moved past the source HEAD (cross-channel
  copy-update bumps the copy to master tip).
- Source HEAD did NOT move (copy-update writes only to the copy).
- New install record.updateChannel = latest; source stays on stable.
- run-action IPC carried actionData.channel=latest (same Vue-reactive-
  proxy deep-clone bug class the cross-channel update-comfyui test pins).
- Disk shape on both installs (full standalone tree on the copy, source
  untouched).

Sequenced before the cross-channel update-comfyui test so the source is
still on stable when both run — same-channel copy-update would resolve to
'no update available' since the prior update-comfyui test already pushed
the source to the latest stable tag. Paired cleanup test deletes the
~500MB copy before the cross-channel update test runs so disk doesn't
accumulate. Issue #621.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6d78-cf01-710a-821c-981fca2e7c22
Co-authored-by: Amp <amp@ampcode.com>
Picker-driven mutating ops (copy, copy-update, delete, release-update,
launch, ...) invoked via comfy-titlepopup:start-background-op were
calling handleDelegateToSource directly. Session-level action ids
aren't registered in any source's handleAction, so they failed with
'Action not yet implemented' before reaching the real handler.

Extract the existing run-action switch into dispatchSessionAction and
call it from both the run-action IPC handler and the picker's
background-op stub. Session ids land on their handlers; source-specific
ids (update-comfyui, snapshot-restore, ...) still fall through to the
source via handleDelegateToSource.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6dc6-0f96-7659-9107-0864be5cbe04
Co-authored-by: Amp <amp@ampcode.com>
BasePrompt is the surface useDialogs().prompt() routes through (the
picker's Copy / Copy & Update / Snapshot Save name prompts). Its
testids were string literals; promote them to the shared TID family
so @real tests can target them without duplicating selectors and so
they're discoverable next to the modalPrompt* (legacy ModalDialog)
counterparts.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6dc6-0f96-7659-9107-0864be5cbe04
Co-authored-by: Amp <amp@ampcode.com>
Three related @real fixes:

- cross-channel copy-update test now drives the basePrompt* inputs
  (picker uses dialogs.prompt → BasePrompt, not the legacy
  ModalDialog) and reads run-action from comfy-titlepopup:start-background-op
  since copy-update routes through inline-picker progress, not the
  open-install-window branch. Adds standalone-rehydration setup to the
  cleanup test so it works under --grep.

- picker pin-bottom Copy + dashboard kebab Copy tests switch from
  modalPromptInput/modalConfirm to basePromptInput/basePromptAction
  for the same reason.

- captureInstallStateFromDisk: when multiple installs match, prefer
  ones whose disk state is actually launchable
  (ComfyUI/main.py + .venv/Scripts/python.exe). Skips silently
  hydrating a partial-delete leftover from a prior run.

- ensureInstalledAndLaunched: detects unlaunchable hydration and falls
  through to a fresh install with a [lifecycle-harness] log line,
  preventing the cryptic 'no Python environment found' timeout.

Amp-Thread-ID: https://ampcode.com/threads/T-019e6dc6-0f96-7659-9107-0864be5cbe04
Co-authored-by: Amp <amp@ampcode.com>
vitest 4.x's happy-dom environment (the global default) resolves bare
'fs' through __vite-browser-external, so vi.mock('fs') fails before
any test runs with 'argument filename must be a file URL object'.

Two targeted fixes:
- Add // @vitest-environment node so this single file gets the node
  runner without changing the global default that happy-dom-using
  Vue component tests need.
- Mock ./safe-file so set() doesn't flush a release-cache.json into
  the repo cwd (dataDir() is '' under the mocked Electron app).

Amp-Thread-ID: https://ampcode.com/threads/T-019e6dc6-0f96-7659-9107-0864be5cbe04
Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019e6e8e-e90f-720a-b3b2-d2f392a77a04
Co-authored-by: Amp <amp@ampcode.com>

# Conflicts:
#	e2e/update-pills.test.ts
#	src/main/lib/release-cache.test.ts
#	src/renderer/src/components/ui/BasePrompt.vue
@Kosinkadink Kosinkadink marked this pull request as draft May 28, 2026 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix lifecycle tests that currently fail

1 participant