Skip to content

ci: select desktop e2e specs through source reachability - #4583

Closed
ZenAlexa wants to merge 1 commit into
apache:mainfrom
ZenAlexa:ci/select-desktop-e2e-specs
Closed

ci: select desktop e2e specs through source reachability#4583
ZenAlexa wants to merge 1 commit into
apache:mainfrom
ZenAlexa:ci/select-desktop-e2e-specs

Conversation

@ZenAlexa

@ZenAlexa ZenAlexa commented Sep 2, 2026

Copy link
Copy Markdown

Summary

  • Derive each Desktop E2E spec's transitive repository source closure with the existing esbuild resolver.
  • Pass the selected workspace-relative spec paths to Playwright through a quoted Bash array.
  • Run one daily full CI execution for Electron process boundaries, generated sources, and separately bundled entry points.
  • Use Playwright's list reporter to expose individual test durations in every run.

The workflow exits before Xvfb allocation when the changed files reach zero specs. Selection-authority files and full plans run all 36 spec files.

Fixes #4478

Measured effect

A replay over the latest 300 first-parent commits found 169 E2E-triggering changes. Test invocations fell from 17,914 to 7,583, a 57.67% reduction. The selection histogram was 57 zero-spec changes, 30 single-spec changes, 10 two-spec changes, one three-spec change, one seven-spec change, and 70 full plans. The scheduled escape run adds one 106-test execution per day.

Verification

  • Node 24 focused planner, workflow, selector, and Windows closure suites: 82/82 passed
  • Full Desktop E2E: 102 passed, 4 skipped, 6.2 minutes
  • Selected e2e/sidebar-geometry.spec.ts: 2/2 passed
  • npm run format:check
  • npm run lint
  • npm run build
  • npm run typecheck
  • npx knip --workspace apps/desktop
  • npx knip --workspace packages/ui
  • npm run check:asf-headers
  • npm run check:release: 191/191 passed
  • actionlint .github/workflows/ci.yml
  • git diff --check

The repository-wide npm test run reached macOS environment failures in unmodified Runtime sandbox, Runtime Host fixture, and Desktop shell-env suites.

Copilot AI lite review requested due to automatic review settings September 2, 2026 18:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the effort/M Under 500 readable lines label Sep 2, 2026
@ZenAlexa

ZenAlexa commented Sep 3, 2026

Copy link
Copy Markdown
Author

e2311350 implements #4478 by selecting Desktop e2e specs from each spec's transitive source closure. Full and selection-authority changes continue to select every spec, and a nightly full run preserves broad coverage. The CI and Release Windows check suites are ready for approval; approving them will exercise the current head.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selection is sound as a filter, but a spec's static import closure is not the reachability relation an Electron e2e spec has with the app it drives, so this turns the Desktop e2e gate off for most desktop changes rather than narrowing it.

P1: Every spec imports ./fixtures and then launches a built Electron app. The app is loaded at runtime, so it is invisible to a static import closure. I computed the 36 spec closures at this head and compared them against the files that isE2eProductPath sets e2e=true for: apps/desktop/src/main 1 of 190 covered, src/renderer 29 of 493, src/preload 1 of 16, src/shared 1 of 8. (packages/ui/src is 125 of 128, but only because accessibility-coverage.spec.ts happens to import src/renderer/settings/settings-nav and drags the component tree in, which is accident rather than design.) Replaying the last 60 first-parent commits on main with this PR's own selector: of the 39 that trigger e2e, 11 now select zero specs and the step exits 0. One of them is e1e108a79 "move Settings to Usage into a renderer feature slice", which rewrote src/renderer/settings/settings-surface.tsx and settings/usage-settings-page.tsx, the exact surface settings.spec.ts, settings-row-focus-ring.spec.ts and accessibility-coverage.spec.ts drive in a real window. Others are 6cfb832e5 (shared Session Guest controls), 61cbda2fc (manual context compaction in Side Conversations), ea2457ae6 (connection snapshot during refresh). A concrete instance of the same gap: apps/desktop/package.json is not in FULL_SUITE_FILES, so an Electron version or main entry change sets e2e=true and selects zero specs. The 57 zero-spec changes in the PR's own histogram are this, not savings. The nightly full run turns each of these into a post-merge detection up to 24 hours later, on main.

I do not think there is a small patch here. Putting the desktop build entry points into every spec's closure makes the selection correct and collapses the reduction back to roughly zero, which is the honest reading: per-spec static reachability cannot select e2e specs. If selective e2e is worth it, coverage has to be declared by each spec (the feature slices it drives) or sampled from an instrumented full run.

P3: The gate now carries two selection authorities that systematically disagree. isE2eProductPath says any apps/desktop change needs e2e; the selector says it does not, and the selector silently wins. Whichever survives should be the only one.

P3: Generalizing collectWorkspaceSourceClosure dropped the Windows-specific completeness argument (why static imports alone were sound there: the forked root-authority.test.ts fixture and the bundled worker-entry.ts are both free of win32). workspace-source-closure.mjs now only says callers need their own escape coverage, and the Windows call site no longer states its. Worth restating above windowsPackageSourceEntrypoints.

P3: reporter: 'list' in playwright.config.ts also changes local runs and is separable from selection. The nightly schedule is not separable, it is the escape hatch and belongs with the mechanism.

Verified as fine: playwright.config.ts and the two selector scripts force a full run; storybook catalog paths stay out of e2e; root package.json / package-lock.json / ci.yml / ci-test-plan.mjs reach plan.full; a selector failure exits non-zero under set -euo pipefail rather than failing open; the new test step sits after npm ci, so the prepare codegen the closure needs has already run; removing the packages/ filter changes nothing for the Windows lane today (its closure has no non-packages/ input and its 3 tests pass).

CI has not run at this head: only label reported, the CI and Release Windows suites are still awaiting approval.

if (plan.full || files.some((path) => SELECTION_AUTHORITY_FILES.has(path))) return specs;

const closures = options.closures ?? (await collectDesktopE2eSpecClosures(specs, repoRoot));
return specs.filter((spec) => files.some((path) => closures.get(spec)?.has(path)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This is the whole selection, and the relation it uses is wrong for e2e. A spec imports ./fixtures and then launches a built Electron app, so the app under test never appears in the spec's static closure. Measured at this head against the files that make the planner set e2e=true: apps/desktop/src/main 1 of 190 files reach any spec, src/renderer 29 of 493, src/preload 1 of 16, src/shared 1 of 8. Replaying the last 60 first-parent commits, 11 of the 39 e2e-triggering merges select zero specs, including e1e108a79, which rewrote src/renderer/settings/settings-surface.tsx while settings.spec.ts and settings-row-focus-ring.spec.ts drive that exact surface. The gate goes green with nothing run and the nightly catches it after merge. The smallest correct fix (adding the desktop main/preload/renderer entry points to every spec's closure) also removes essentially all of the reduction, which is the signal that static import reachability cannot select e2e specs. Selection needs coverage declared per spec or sampled from an instrumented run.

assert.ok(currentSpecs.length > 0);
for (const spec of currentSpecs) {
assert.ok(currentClosures.get(spec)?.has(spec), spec);
assert.ok(currentClosures.get(spec)?.has('apps/desktop/e2e/fixtures.ts'), spec);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The suite does not test the scenario the PR is for. Three of the four tests use the hand-written closures fixture and only prove the filter; this one uses real closures but asserts only scripts/ reachability and that every spec closure contains fixtures.ts. Nothing asserts anything about apps/desktop/src/**, which is where the selection actually fails. Add an assertion that every product source file under apps/desktop/src selects at least one spec. It fails today, and that failure is the evidence the design needs.

const files = [...new Set(changedFiles.map(normalizePath).filter(Boolean))];
const specs = options.specs ?? listDesktopE2eSpecs(repoRoot);
const plan = planTests(files, { repoRoot, forceFull: options.forceFull });
if (!plan.e2e) return [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Redundant with the caller. The Desktop e2e step is already gated on steps.plan.outputs.e2e and already branches on steps.plan.outputs.full, so plan.e2e, plan.full and the second changedFilesBetween here recompute a decision the same job made one step earlier. Let the selector take changed files and nothing else.

* the source consumes.
*
* This closure covers static imports. Spawned processes and generated or
* separately bundled entry points need caller-owned escape coverage.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The original comment on this function argued why static imports alone were complete for the Windows filter: root-authority.test.ts forks a fixture, worker-entry.ts is bundled rather than imported, and both are free of win32. Generalizing replaced that with "caller-owned escape coverage", and the Windows call site does not restate it, so the argument is now nowhere. Move it back above windowsPackageSourceEntrypoints.

@ZenAlexa ZenAlexa closed this Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/M Under 500 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Select Desktop e2e specs by what a change can reach, instead of all 103

3 participants