Files
software-mansion__argent/packages/argent-cli/test/flow-deviceless-render.test.ts
Jakub Piasecki 6d907cc86b feat(flow): boot a Chromium instance per launch step (#585)
A `launch:` on Chromium now starts its own Electron instance, so nested
e2e flows and mid-flow relaunches work as they do on iOS/Android.
Previously the runner booted one app per run and rejected any second
chromium launch.

A chromium device id _is_ its CDP port, so a launch that starts an app
from scratch has to boot one and move the run onto it: `state.device`
became reassignable (steps already read it per call), and `state.owned`
tracks what the runner booted, torn down in reverse at run end. An
instance the runner did not boot — pinned via `--device`, or
auto-detected — is attached to, never killed. Relaunching the same app
kills its instance and waits for the process to actually exit, or the
replacement loses a race against the app's single-instance lock.

The runner needs a device before step 1, so `resolveRunDevice` follows a
leading `run:` chain to find the launch the run _begins_ with: a
fragment composing a chromium e2e flow now boots instead of failing with
"No booted chromium device found". A launch declaring no app id for the
run's platform stays an error — the run's platform is fixed start to
finish.

Also fixes a pre-existing teardown leak: the spawned handle is the npm
`electron` node wrapper, not Electron, so signalling it left the app and
its helpers running (reparented to launchd, still in the dock). Teardown
now sweeps the child's process group.

**Behavior change:** a fragment leading with `run:` into a chromium e2e
flow now boots a fresh instance where it previously attached to a
running one; pass `--device` to keep attaching.

## Tests

Cover the boot/attach/settle three-way, teardown ordering and the group
sweep, device rebinding after a nested launch, same-app retire ordering,
cancellation mid-boot, both "no chromium app declared" guards, and the
leading-`run:` chain (multi-hop, cyclic, no-launch). The guard,
exit-wait and sweep tests are mutation-checked.
2026-08-06 07:28:27 +02:00

64 lines
2.2 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { renderReport, renderSummary, type FlowReport } from "../src/flow.js";
function report(overrides: Partial<FlowReport> = {}): FlowReport {
return {
flow: "echo-only",
device: "",
ok: true,
passed: 0,
failed: 0,
skipped: 0,
errored: 0,
steps: [],
...overrides,
} as FlowReport;
}
describe("rendering a run that resolved no device", () => {
it("does not claim the run happened on a device", () => {
const line = renderSummary(report(), { withDevice: true });
expect(line).not.toContain(" on ");
expect(line).toBe("PASS — 0 passed, 0 failed, 0 errored, 0 skipped (no test steps)");
});
it("omits the device from the report header too", () => {
const lines = renderReport(report()).split("\n");
expect(lines[0]).toBe('Flow "echo-only"');
});
it("still names a device when the run had one", () => {
const line = renderSummary(report({ device: "UDID-1", passed: 2 }), { withDevice: true });
expect(line).toBe("PASS (started on UDID-1) — 2 passed, 0 failed, 0 errored, 0 skipped");
});
});
describe("the no-test-steps note", () => {
it("explains a passing run whose counters are all zero", () => {
expect(renderSummary(report())).toContain("(no test steps)");
});
it("is absent whenever anything was counted", () => {
expect(renderSummary(report({ passed: 1 }))).not.toContain("(no test steps)");
expect(renderSummary(report({ skipped: 1 }))).not.toContain("(no test steps)");
});
it("is absent on a failure, where the counts are not what needs explaining", () => {
// A cancelled run can be a FAIL with every counter still zero; calling that
// "no test steps" would read as though the failure had no cause.
const line = renderSummary(report({ ok: false }));
expect(line).toBe("FAIL — 0 passed, 0 failed, 0 errored, 0 skipped");
expect(line).not.toContain("(no test steps)");
});
it("leaves an ordinary summary byte-for-byte unchanged", () => {
const line = renderSummary(report({ ok: false, passed: 2, failed: 1, skipped: 1 }));
expect(line).toBe("FAIL — 2 passed, 1 failed, 0 errored, 1 skipped");
});
});