diff --git a/scripts/e2e/parallels/snapshots.ts b/scripts/e2e/parallels/snapshots.ts index 3e216eb2b172..b406f1f292a4 100644 --- a/scripts/e2e/parallels/snapshots.ts +++ b/scripts/e2e/parallels/snapshots.ts @@ -38,9 +38,13 @@ export function resolveSnapshot(vmName: string, hint: string): SnapshotInfo { `prlctl snapshot-list ${vmName} --json returned no snapshots; create/restore a snapshot or set ${SKIP_SNAPSHOT_RESTORE_ENV}=1 for an already-started guest`, ); } - const payload = JSON.parse(output) as Record; + const payload = JSON.parse(output) as Record< + string, + { date?: string; name?: string; state?: string } + >; let best: SnapshotInfo | null = null; let bestScore = -1; + let bestDate = ""; const aliases = (name: string): string[] => { const values = [name]; for (const pattern of [/^(.*)-poweroff$/, /^(.*)-poweroff-\d{4}-\d{2}-\d{2}$/]) { @@ -78,8 +82,12 @@ export function resolveSnapshot(vmName: string, hint: string): SnapshotInfo { if ((meta.state ?? "").toLowerCase() === "poweroff") { score += 0.5; } - if (score > bestScore) { + const date = (meta.date ?? "").trim(); + // Parallels lists snapshots oldest-first. Prefer the newest reusable baseline when fuzzy + // names tie, while preserving the original order when date metadata is unavailable. + if (score > bestScore || (score === bestScore && bestDate && date && date > bestDate)) { bestScore = score; + bestDate = date; best = { id, name, state: (meta.state ?? "").trim() }; } } diff --git a/scripts/e2e/parallels/windows-smoke.ts b/scripts/e2e/parallels/windows-smoke.ts index 9b7ded291b70..78b70daca933 100755 --- a/scripts/e2e/parallels/windows-smoke.ts +++ b/scripts/e2e/parallels/windows-smoke.ts @@ -111,7 +111,7 @@ const defaultOptions = (): WindowsOptions => ({ npmRegistry: undefined, provider: "openai", skipLatestRefCheck: false, - snapshotHint: "pre-openclaw-native-e2e-2026-03-12", + snapshotHint: "pre-openclaw-native-e2e-", targetPackageSpec: "", upgradeFromPackedMain: false, vmName: "Windows 11", @@ -127,7 +127,7 @@ function usage(): string { Options: --vm Parallels VM name. Default: "Windows 11" --snapshot-hint Snapshot name substring/fuzzy match. - Default: "pre-openclaw-native-e2e-2026-03-12" + Default: newest "pre-openclaw-native-e2e-*" snapshot --mode --provider --model Override the model used for the agent-turn smoke. diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index 02f39721e8b4..ba642184b160 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -371,6 +371,7 @@ fetch_host_metadata "https://example.test/metadata"`, expect(parseMacosSmokeArgs(["--host-port", "65535"]).hostPort).toBe(65535); expect(parseLinuxSmokeArgs(["--host-port", "65535"]).hostPort).toBe(65535); expect(parseWindowsSmokeArgs(["--host-port", "65535"]).hostPort).toBe(65535); + expect(parseWindowsSmokeArgs([]).snapshotHint).toBe("pre-openclaw-native-e2e-"); for (const parseArgs of [parseMacosSmokeArgs, parseLinuxSmokeArgs, parseWindowsSmokeArgs]) { expect(parseArgs(["--npm-registry", "http://192.0.2.2:48123"]).npmRegistry).toBe( "http://192.0.2.2:48123", @@ -764,6 +765,10 @@ if [[ "$1" == "snapshot-list" ]]; then { "{older}": {"name": "fresh", "state": "running"}, "{wanted}": {"name": "fresh-poweroff-2026-04-01", "state": "poweroff"}, + "{old-e2e}": {"name": "pre-openclaw-native-e2e-2026-03-12", "state": "poweroff", "date": "2026-03-12 22:32:24"}, + "{new-e2e}": {"name": "pre-openclaw-native-e2e-2026-07-26", "state": "poweroff", "date": "2026-07-26 11:52:02"}, + "{undated-first}": {"name": "undated-family-1", "state": "poweroff"}, + "{dated-later}": {"name": "undated-family-2", "state": "poweroff", "date": "2026-07-26 11:52:02"}, "{other}": {"name": "unrelated", "state": "poweroff"} } JSON @@ -780,6 +785,10 @@ if (isPrlctl) { console.log(JSON.stringify({ "{older}": { name: "fresh", state: "running" }, "{wanted}": { name: "fresh-poweroff-2026-04-01", state: "poweroff" }, + "{old-e2e}": { name: "pre-openclaw-native-e2e-2026-03-12", state: "poweroff", date: "2026-03-12 22:32:24" }, + "{new-e2e}": { name: "pre-openclaw-native-e2e-2026-07-26", state: "poweroff", date: "2026-07-26 11:52:02" }, + "{undated-first}": { name: "undated-family-1", state: "poweroff" }, + "{dated-later}": { name: "undated-family-2", state: "poweroff", date: "2026-07-26 11:52:02" }, "{other}": { name: "unrelated", state: "poweroff" }, })); process.exit(0); @@ -792,11 +801,20 @@ if (isPrlctl) { try { const output = withEnv(fakePrlctlEnv(tempDir), () => { const snapshot = resolveSnapshot("vm", "fresh"); - return `${shellQuote("it's ok")}\n${[snapshot.id, snapshot.state, snapshot.name].join("\t")}`; + const latestE2e = resolveSnapshot("vm", "pre-openclaw-native-e2e-"); + const missingDate = resolveSnapshot("vm", "undated-family-"); + return [ + shellQuote("it's ok"), + [snapshot.id, snapshot.state, snapshot.name].join("\t"), + [latestE2e.id, latestE2e.state, latestE2e.name].join("\t"), + [missingDate.id, missingDate.state, missingDate.name].join("\t"), + ].join("\n"); }); expect(output.split("\n")[0]).toBe("'it'\"'\"'s ok'"); expect(output).toContain("{wanted}\tpoweroff\tfresh-poweroff-2026-04-01"); + expect(output).toContain("{new-e2e}\tpoweroff\tpre-openclaw-native-e2e-2026-07-26"); + expect(output).toContain("{undated-first}\tpoweroff\tundated-family-1"); } finally { rmSync(tempDir, { force: true, recursive: true }); }