Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/e2e/agents-lifecycle.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ function connectedLiveEvents(page: Page) {

async function setDarkTheme(page: Page) {
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitemradio", { name: "Dark theme" }).click();
await page.getByRole("menuitem", { name: "Switch to dark theme" }).click();
}

async function openEnvironmentDialog(page: Page) {
Expand Down
5 changes: 3 additions & 2 deletions apps/web/e2e/core-connection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ async function resetFixture(request: APIRequestContext) {
async function boot(page: Page, request: APIRequestContext) {
await resetFixture(request);
await page.goto("/");
await expect(page.getByRole("button", { name: "Configure Agent Core connection" })).toBeVisible();
await expect(page.locator(".sidebar-footer .appearance-menu-trigger")).toBeVisible();
}

async function openConnection(page: Page) {
const trigger = page.getByRole("button", { name: "Configure Agent Core connection" });
const trigger = page.locator(".sidebar-footer .appearance-menu-trigger");
await trigger.click();
await page.getByRole("menuitem", { name: "Configure Agent Core connection" }).click();
const dialog = page.getByRole("dialog", { name: "Connect an Agent Core" });
await expect(dialog).toBeVisible();
return { dialog, trigger };
Expand Down
3 changes: 2 additions & 1 deletion apps/web/e2e/environment-templates.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ for (const operation of ["read", "write"] as const) {
// it lets an in-flight write finish after its owning page has unmounted.
await page.evaluate(() => { location.hash = "system"; });
await expect(page.getByRole("dialog", { name: "Edit Template", exact: true })).toHaveCount(0);
await page.getByRole("button", { name: "Configure Agent Core connection", exact: true }).click();
await page.locator(".sidebar-footer .appearance-menu-trigger").click();
await page.getByRole("menuitem", { name: "Configure Agent Core connection", exact: true }).click();
const connection = page.getByRole("dialog", { name: "Connect an Agent Core", exact: true });
await connection.getByRole("radio", { name: /Other compatible Core/ }).check();
await connection.getByLabel("Compatible Core base URL").fill(`${new URL(page.url()).origin}/v1`);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/e2e/first-run-keyboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test("moves focus from disappearing step actions to the new heading and retains
const accessHeading = page.getByRole("heading", { name: "Keep your sign-in details.", exact: true });
await expect(accessHeading).toBeVisible();
await expect(accessHeading).not.toBeFocused();
const continueButton = page.getByRole("button", { name: "I've saved it. Continue", exact: true });
const continueButton = page.getByRole("button", { name: "Continue to machines", exact: true });
await expect(continueButton).toBeEnabled();
await continueButton.focus();
await page.keyboard.press("Enter");
Expand All @@ -29,7 +29,7 @@ test("moves focus from disappearing step actions to the new heading and retains
await page.keyboard.press("Tab");
await expect(page.getByRole("button", { name: "Refresh sandbox state", exact: true })).toBeFocused();

await page.getByRole("button", { name: "Skip for now", exact: true }).focus();
await page.getByRole("button", { name: "Continue to the API", exact: true }).focus();
await page.keyboard.press("Enter");
const requestHeading = page.getByRole("heading", { name: "Make your first API request.", exact: true });
await expect(requestHeading).toBeFocused();
Expand Down
210 changes: 194 additions & 16 deletions apps/web/e2e/first-run.spec.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/web/e2e/fixture-sandbox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let provider = "docker";
let diagnostic = "";
let coreUrl = "";
export function resetSandboxFixture() {
nodes = [node("node-local", "Core server"), node("node-offline", "Offline host", false)]; calls = []; provider = "docker"; diagnostic = ""; coreUrl = "";
nodes = [node("node-local", "Core server"), node("node-offline", "Offline host", false)]; calls = []; provider = "docker"; diagnostic = ""; coreUrl = "https://core.example";
}
resetSandboxFixture();
export function handleSandboxFixture(request, response, url, sendJson, sendError) {
Expand Down
76 changes: 75 additions & 1 deletion apps/web/e2e/sandbox-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ test("add opens one command, copy works, existing hosts do not imply connection,
await context.grantPermissions(["clipboard-read", "clipboard-write"]);
await openManager(page);
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
const dialog = page.getByRole("dialog", { name: "Add node" });
const command = dialog.getByLabel("One-time enrollment command");
await expect(command).toHaveValue(/fixture-once-token/);
Expand All @@ -67,6 +72,11 @@ test("unavailable installer shows compact guidance and never creates an enrollme
await page.route("**/console/config", (route) => route.fulfill({ json: { sandbox_admin: true, node_installer: false } }));
await openManager(page);
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
const dialog = page.getByRole("dialog");
await expect(dialog).toContainText("Node installation is unavailable");
await expect(dialog.getByRole("textbox")).toHaveCount(0);
Expand Down Expand Up @@ -101,19 +111,23 @@ for (const value of ["node_unavailable", "resource_missing"]) {

test("Chinese actions, diagnostics, and enrollment are translated and language persists", async ({ page, request }) => {
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitem", { name: /Language/ }).click();
await page.getByRole("menuitemradio", { name: "简体中文" }).click();
await request.post(`${fixture}/__fixture/sandbox-diagnostic?value=resource_missing`);
await page.getByRole("button", { name: "托管沙箱管理", exact: true }).click();
await page.locator(".sandbox-topology-node").first().click();
await expect(page.getByRole("region", { name: "沙箱资源分配" }).first()).toContainText("沙箱资源缺失");
await page.getByRole("button", { name: "添加节点", exact: true }).click();
await page.getByLabel("新节点可访问的控制台地址").fill("https://console.example");
await page.getByRole("button", { name: "生成注册命令", exact: true }).click();
await expect(page.getByLabel("一次性注册命令")).toHaveValue(/fixture-once-token/);
await expect(page.getByRole("dialog")).toContainText("等待节点连接");
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "语言和外观" }).click();
await page.getByRole("menuitem", { name: /语言/ }).click();
await page.getByRole("menuitemradio", { name: "English" }).click();
await page.reload();
await expect(page.getByRole("button", { name: "Language and appearance" })).toContainText("EN");
await expect(page.getByRole("button", { name: "Language and appearance" })).toBeVisible();
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
});

Expand All @@ -128,12 +142,22 @@ test("an uncertain write is never retried; closing discards a late token and all
await route.fulfill({ json: { token: attempts === 1 ? "stale-token" : "fresh-token", expires_at: new Date(Date.now() + 600000).toISOString() } }).catch(() => {});
});
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
await expect.poll(() => attempts).toBe(1);
await page.keyboard.press("Escape");
release();
await expect(page.getByRole("dialog")).toHaveCount(0);
expect(attempts).toBe(1);
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
await expect(page.getByLabel("One-time enrollment command")).toHaveValue(/fresh-token/);
expect(attempts).toBe(2);
});
Expand All @@ -148,6 +172,11 @@ test("a connected node remains successful after its enrollment token expires", a
});
await openManager(page);
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
const dialog = page.getByRole("dialog", { name: "Add node" });
await expect(dialog.getByLabel("One-time enrollment command")).toHaveValue(/short-lived-token/);
await request.post(`${fixture}/__fixture/sandbox-add-node`);
Expand All @@ -171,6 +200,11 @@ test("expired commands and failed writes require an explicit retry", async ({ pa
return attempts === 1 ? route.fulfill({ json: { token: "expired-token", expires_at: "2020-01-01T00:00:00Z" } }) : route.fulfill({ status: 503, json: { error: { message: "Unavailable" } } });
});
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
await expect(page.getByRole("dialog")).toContainText("Command expired");
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
expect(attempts).toBe(1);
Expand Down Expand Up @@ -220,6 +254,11 @@ for (const width of [1280, 1440]) {
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await page.screenshot({ animations: "disabled", path: testInfo.outputPath(`nodes-${width}-${theme}.png`) });
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
await expect(page.getByLabel("One-time enrollment command")).toHaveValue(/fixture-once-token/);
const copyButton = page.getByRole("button", { name: "Copy node command" });
await expect(copyButton).toBeInViewport();
Expand Down Expand Up @@ -293,6 +332,7 @@ for (const theme of ["light", "dark"]) {
await page.setViewportSize({ width: 1440, height: 1000 });
await page.evaluate((theme) => document.documentElement.dataset.theme = theme, theme);
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitem", { name: /Language/ }).click();
await page.getByRole("menuitemradio", { name: "简体中文" }).click();
await page.getByRole("button", { name: "托管沙箱管理", exact: true }).click();
await expect(page.getByRole("region", { name: "沙箱节点", exact: true })).toContainText("可用");
Expand All @@ -302,3 +342,37 @@ for (const theme of ["light", "dark"]) {
await page.locator("#sandbox-selected-node").screenshot({ path: testInfo.outputPath(`node-details-zh-${theme}.png`), animations: "disabled" });
});
}

test("SSH tunnel enrollment requires a reachable console address before issuing a token", async ({ page, request }) => {
await openManager(page);
await page.getByRole("button", { name: "Add node", exact: true }).click();
const dialog = page.getByRole("dialog");
const source = dialog.getByLabel("Console address reachable from the new node");
const generate = dialog.getByRole("button", { name: "Generate enrollment command", exact: true });
await expect(source).toHaveValue("");
for (const invalid of ["http://127.0.0.1:14173", "https://localhost", "https://console.example/v1", "https://user:secret@console.example"]) {
await source.fill(invalid);
await expect(generate).toBeDisabled();
}
const before = (await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls;
expect(before.filter((call: { method: string }) => call.method === "POST")).toHaveLength(0);
await source.fill("https://console.example");
await generate.click();
const command = dialog.getByLabel("One-time enrollment command");
await expect(command).toHaveValue(/--source-url 'https:\/\/console.example'/);
await expect(command).toHaveValue(/--core-url 'https:\/\/core.example'/);
await expect(command).not.toHaveValue(/127\.0\.0\.1/);
});

test("a loopback deployment origin cannot issue a remote-node command", async ({ page, request }) => {
await page.route("**/core/v1/sandbox/deployment", async (route) => {
const deployment = await (await route.fetch()).json();
await route.fulfill({ json: { ...deployment, core_url: "http://127.0.0.1:8080" } });
});
await openManager(page);
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog")).toContainText("deployment needs an HTTPS Core address");
await expect(page.getByLabel("One-time enrollment command")).toHaveCount(0);
const calls = (await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls;
expect(calls.filter((call: { method: string }) => call.method === "POST")).toHaveLength(0);
});
1 change: 1 addition & 0 deletions apps/web/e2e/sandbox-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ for (const theme of ["light", "dark"]) {
await openManager(page);
await page.evaluate((theme) => document.documentElement.dataset.theme = theme, theme);
await page.getByRole("button", { name: "Language and appearance" }).click();
await page.getByRole("menuitem", { name: /Language/ }).click();
await page.getByRole("menuitemradio", { name: "简体中文" }).click();
await expect(page.getByRole("heading", { name: "部署运行后端", exact: true })).toBeVisible();
await expect(page.getByText("已配置", { exact: true })).toBeVisible();
Expand Down
20 changes: 18 additions & 2 deletions apps/web/e2e/sandbox-setup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ test("bundled console needs no extra admin key and provides one install command
await page.getByLabel("Sandbox provider").selectOption("docker");
await page.getByRole("button", { name: "Initialize sandbox deployment" }).click();
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
const command = page.getByLabel("One-time enrollment command");
await expect(command).toHaveValue(/fixture-once-token/);
await expect(command).toHaveValue(/\/node-install\/node-install.pyz/);
Expand Down Expand Up @@ -68,6 +73,11 @@ for (const provider of ["docker", "microsandbox"]) {
await expect(page.getByLabel("Sandbox provider")).toHaveCount(0);
await expect(page.getByText("No nodes registered. Add a node to provide hosted capacity.")).toBeVisible();
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
await expect(page.getByLabel("One-time enrollment command")).toHaveValue(/--core-url 'https:\/\/core.example'/);
expect(await page.evaluate(() => JSON.stringify({ local: { ...localStorage }, session: { ...sessionStorage } }))).not.toMatch(/fixture-admin-key|fixture-once-token/);
await request.post(`${fixture}/__fixture/sandbox-add-node`);
Expand Down Expand Up @@ -167,10 +177,16 @@ for (const operation of ["setup", "enrollment"] as const) {
await request.post(setupUrl, { data: { provider: "docker", core_url: "https://core.example" } });
await page.getByRole("button", { name: "Refresh sandbox state" }).click();
await page.getByRole("button", { name: "Add node", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add node" })).toBeVisible();
if (await page.getByLabel("Console address reachable from the new node").isVisible()) {
await page.getByLabel("Console address reachable from the new node").fill("https://console.example");
await page.getByRole("button", { name: "Generate enrollment command", exact: true }).click();
}
}
await expect.poll(() => page.evaluate(() => typeof (window as Window & { releaseSandboxResponse?: () => void }).releaseSandboxResponse)).toBe("function");
await page.evaluate(() => { location.hash = "system"; });
await page.getByRole("button", { name: "Configure Agent Core connection", exact: true }).click();
await page.locator(".sidebar-footer .appearance-menu-trigger").click();
await page.getByRole("menuitem", { name: "Configure Agent Core connection", exact: true }).click();
const connection = page.getByRole("dialog", { name: "Connect an Agent Core", exact: true });
await connection.getByRole("radio", { name: /Other compatible Core/ }).check();
await connection.getByLabel("Compatible Core base URL").fill(`${new URL(page.url()).origin}/v1`);
Expand All @@ -193,7 +209,7 @@ test("unpaired or unavailable consoles show setup guidance without admin credent
await page.route("**/console/config", (route) => route.fulfill(body ? { contentType: "application/json", body: JSON.stringify(body) } : { status: 404, body: "Not found" }));
await page.reload();
await page.getByRole("button", { name: "Hosted Sandbox Manager", exact: true }).click();
await expect(page.getByRole("alert")).toContainText("Sandbox administration is not configured");
await expect(page.getByRole("alert")).toContainText(body ? "Sandbox administration is not configured" : "Console access could not be checked");
await expect(page.getByLabel("Deployment admin key")).toHaveCount(0);
expect((await (await request.get(`${fixture}/__fixture/sandbox`)).json()).calls).toHaveLength(0);
}
Expand Down
Loading