diff --git a/test/e2e-scenario/fixtures/phases/lifecycle.ts b/test/e2e-scenario/fixtures/phases/lifecycle.ts index a073ba4da5..39adedab58 100644 --- a/test/e2e-scenario/fixtures/phases/lifecycle.ts +++ b/test/e2e-scenario/fixtures/phases/lifecycle.ts @@ -90,10 +90,14 @@ function stripAnsi(text: string): string { } function outputContainsReadySandbox(result: ShellProbeResult, sandboxName: string): boolean { - const escaped = sandboxName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - return new RegExp(`${escaped}.*\\bReady\\b`, "i").test( - stripAnsi(`${result.stdout}\n${result.stderr}`), - ); + return stripAnsi(`${result.stdout}\n${result.stderr}`) + .split(/\r?\n/) + .some((line) => { + const trimmed = line.trim(); + if (!trimmed) return false; + const [name] = trimmed.split(/\s+/); + return name === sandboxName && /\bReady\b/i.test(trimmed); + }); } export class LifecyclePhaseFixture { diff --git a/test/e2e-scenario/fixtures/phases/state-validation.ts b/test/e2e-scenario/fixtures/phases/state-validation.ts index 488cfb0c00..26106acc51 100644 --- a/test/e2e-scenario/fixtures/phases/state-validation.ts +++ b/test/e2e-scenario/fixtures/phases/state-validation.ts @@ -346,7 +346,7 @@ export class StateValidationPhaseFixture { options: MarkerFileOptions = {}, ): Promise { const actual = await this.readMarkerFile(instance, markerPath, options); - if (actual !== expected && actual.trim() !== expected) { + if (actual !== expected) { const sandboxName = typeof instance === "string" ? instance : instance.sandboxName; throw new Error( `marker ${markerPath} in ${sandboxName} did not match expected content: got ${JSON.stringify( diff --git a/test/e2e-scenario/live/sandbox-rebuild.test.ts b/test/e2e-scenario/live/sandbox-rebuild.test.ts index 5dc15a1069..8acb4c7d74 100644 --- a/test/e2e-scenario/live/sandbox-rebuild.test.ts +++ b/test/e2e-scenario/live/sandbox-rebuild.test.ts @@ -125,7 +125,12 @@ test.skipIf(!shouldRunLiveE2EScenarios())( }); const stateSnapshot = snapshotRegistryAndSession(); - const backupRoot = path.join(os.homedir(), ".nemoclaw", "rebuild-backups", SANDBOX_NAME); + const backupRoot = path.join( + process.env.HOME ?? os.homedir(), + ".nemoclaw", + "rebuild-backups", + SANDBOX_NAME, + ); cleanup.add(`restore NemoClaw state files for ${SANDBOX_NAME}`, () => { restoreRegistryAndSession(stateSnapshot); fs.rmSync(backupRoot, { recursive: true, force: true }); diff --git a/test/e2e-scenario/support-tests/e2e-phase-lifecycle.test.ts b/test/e2e-scenario/support-tests/e2e-phase-lifecycle.test.ts index d1c265ec77..15a5203c20 100644 --- a/test/e2e-scenario/support-tests/e2e-phase-lifecycle.test.ts +++ b/test/e2e-scenario/support-tests/e2e-phase-lifecycle.test.ts @@ -229,6 +229,21 @@ describe("LifecyclePhaseFixture rebuild helpers", () => { args: ["sandbox", "list"], }); }); + + it("requires an exact sandbox-name match when waiting after rebuild", async () => { + const runner = new FakeRunner(); + runner.enqueue(shellResult(0, "NAME PHASE\ne2e-x-dev Ready\n")); + runner.enqueue(shellResult(0, "NAME PHASE\ne2e-x Ready\n")); + const cleanup = new FakeCleanup(); + + const result = await fixture(runner, cleanup).assertSandboxReadyAfterRebuild("e2e-x", { + attempts: 2, + delayMs: 0, + }); + + expect(result.stdout).toContain("e2e-x Ready"); + expect(runner.calls).toHaveLength(2); + }); }); describe("LifecyclePhaseFixture gateway runtime restart helpers", () => { diff --git a/test/e2e-scenario/support-tests/e2e-phase-state-validation.test.ts b/test/e2e-scenario/support-tests/e2e-phase-state-validation.test.ts index 8f20b55470..1501991dad 100644 --- a/test/e2e-scenario/support-tests/e2e-phase-state-validation.test.ts +++ b/test/e2e-scenario/support-tests/e2e-phase-state-validation.test.ts @@ -710,6 +710,20 @@ describe("state-validation host-side probes", () => { ]); }); + it("keeps marker content comparisons exact", async () => { + const runner = new FakeRunner(); + runner.enqueue(shellResult(0, " marker-value ")); + const fx = fixture(runner); + + await expect( + fx.expectMarkerFileContent( + "e2e-marker", + "/sandbox/.openclaw/workspace/rebuild-marker.txt", + "marker-value", + ), + ).rejects.toThrow(/did not match expected content/); + }); + it("patches registry entries and validates refreshed agentVersion", () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "e2e-registry-")); const registryPath = path.join(tmp, "sandboxes.json"); diff --git a/tools/e2e-scenarios/workflow-boundary.mts b/tools/e2e-scenarios/workflow-boundary.mts index 1b000ced28..f2041de062 100644 --- a/tools/e2e-scenarios/workflow-boundary.mts +++ b/tools/e2e-scenarios/workflow-boundary.mts @@ -807,6 +807,8 @@ function validateSandboxRebuildVitestJob(errors: string[], jobs: WorkflowRecord) const installOpenShell = requireJobStep(errors, jobName, steps, "Install OpenShell"); requireRunContains(errors, installOpenShell, "bash scripts/install-openshell.sh"); requireRunContains(errors, installOpenShell, "env -u DOCKER_CONFIG"); + requireRunContains(errors, installOpenShell, "-u DOCKERHUB_USERNAME"); + requireRunContains(errors, installOpenShell, "-u DOCKERHUB_TOKEN"); requireRunContains(errors, installOpenShell, "-u NVIDIA_API_KEY"); requireRunContains(errors, installOpenShell, "-u GITHUB_TOKEN");