Files
kandev/apps/web/e2e/fixtures/docker-probe.ts
T
José Almeida b1f57cec6b test(docker-e2e): add real-Docker Playwright project + first spec
A new Playwright project — \`docker\` — runs container-bound E2E specs
against a backend with KANDEV_E2E_DOCKER=1. The fixture probes for a
Docker daemon and skips the worker entirely when none is reachable, so
contributors without Docker can still run the rest of the suite.

The fixture builds a tiny kandev-agent:e2e image once per machine
(node:22-slim + git/curl), pre-seeds a local_docker executor profile,
and seeds a local repo with a file:// origin so the executor's
clone-inside-container path resolves without network access. A small
WS-based session.launch helper is added to the api-client for tests
that need to launch additional sessions on an existing task.

First spec — docker-launch.spec.ts — covers the smoke path: a session
launches in a real container, the task environment records its
container_id, and \`docker inspect\` confirms the container exists.
Two follow-up specs (multi-session env reuse + recovery from external
container removal) are wired up but marked test.fixme with comments
pinpointing the backend gaps that block them: persistTaskEnvironment
isn't called for workflow-prepared sessions (so task_environment_id
stays empty on those rows), and DockerExecutor.reconnectToContainer
loses the agentctl handshake auth token across launches (so reconnect
401s and falls back to a fresh container every time).

Run locally with: \`pnpm --filter @kandev/web exec playwright test
--config e2e/playwright.config.ts --project=docker\`.
2026-05-04 21:09:39 +01:00

62 lines
2.0 KiB
TypeScript

import { execFileSync, spawnSync } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
/**
* Image used by the Docker E2E project. Built once per machine and reused.
* Tag is stable so layer caches survive across runs.
*/
export const E2E_IMAGE_TAG = "kandev-agent:e2e";
const E2E_DOCKERFILE = `FROM node:22-slim
RUN apt-get update \\
&& apt-get install -y --no-install-recommends git ca-certificates curl \\
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
`;
/**
* Returns true when a Docker daemon is reachable. Used by the docker fixture
* to skip the worker when the host has no Docker (CI without docker service,
* local dev without daemon running).
*/
export function hasDocker(): boolean {
const result = spawnSync("docker", ["info"], { stdio: "ignore" });
return result.status === 0;
}
/**
* Build the kandev-agent:e2e image. Idempotent: Docker layer caching makes
* repeated builds near-instant when the Dockerfile hasn't changed.
*/
export function buildE2EImage(): void {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "kandev-e2e-image-"));
try {
fs.writeFileSync(path.join(tmpDir, "Dockerfile"), E2E_DOCKERFILE);
execFileSync("docker", ["build", "-t", E2E_IMAGE_TAG, tmpDir], {
stdio: process.env.E2E_DEBUG ? "inherit" : "ignore",
});
} finally {
fs.rmSync(tmpDir, { recursive: true, force: true });
}
}
/**
* Remove all containers labeled as managed by kandev. Called at worker
* teardown to clean up anything the suite leaked. Safe to run on a host with
* no matching containers.
*/
export function removeKandevContainers(): void {
const list = spawnSync("docker", ["ps", "-aq", "--filter", "label=kandev.managed=true"], {
encoding: "utf8",
});
if (list.status !== 0) return;
const ids = list.stdout
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
if (ids.length === 0) return;
spawnSync("docker", ["rm", "-f", ...ids], { stdio: "ignore" });
}