Promote AgentCompany-New to the repository root and retire the old app

The legacy application - its src, tests, landing site, promo assets, docs,
release tooling, CI workflows, and root configs - leaves the tree; it stays
reachable in history. Everything under AgentCompany-New moves up unchanged:
same package name, so the production userData/database path is untouched,
and e2e/build scripts already resolved paths relative to the project. The
developer cred.txt stays gitignored at its new location. Full verify:ci
passes from the new root.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 00:04:13 -04:00
1 parent 2350f744e3
commit 07d0b4587d
1904 files changed
+1133 -282075

No files matched your search

-212
View File
@@ -1,212 +0,0 @@
import { existsSync } from "node:fs";
import { mkdir, mkdtemp, rm } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { _electron as electron, expect, test } from "@playwright/test";
import type { ElectronApplication, Page } from "@playwright/test";
import { createElectronLaunchEnv, getElectronExecutablePath, getMainEntryPath } from "./launch";
import {
E2E_READY_CONNECTOR_ID,
E2E_READY_CONNECTOR_MODEL_ID,
E2E_PROVIDER_API_KEY,
readOnboardingProfileState,
} from "./onboarding-fixture";
function buildLaunchEnv(profileDir: string) {
return createElectronLaunchEnv({
AGENTCOMPANY_PROFILE_DIR: profileDir,
AGENTCOMPANY_DISABLE_UPDATES: "true",
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
AGENTCOMPANY_ALLOW_INSECURE_VAULT: "true",
});
}
async function launchDesktopApp(profileDir: string): Promise<ElectronApplication> {
return electron.launch({
executablePath: getElectronExecutablePath(),
args: [getMainEntryPath()],
cwd: process.cwd(),
env: buildLaunchEnv(profileDir),
});
}
async function advanceFromWelcome(page: Page) {
await expect(page.getByText("AgentCompany")).toBeVisible();
await expect(page.getByText("Build your AI company here.")).toBeVisible();
await page.getByRole("button", { name: /Get Started/i }).click();
}
async function advancePastNodeCheck(page: Page) {
const continueButton = page.getByRole("button", { name: /^Continue$/i });
await expect(page.getByText("Check Node.js")).toBeVisible();
await expect(continueButton).toBeEnabled({ timeout: 15_000 });
await continueButton.click();
}
async function configureReadyProvider(page: Page) {
await expect(page.getByText("Anthropic API")).toBeVisible();
await page.getByRole("button", { name: /Anthropic API/i }).click();
const apiKeyInput = page.locator('input[type="password"]').first();
await expect(apiKeyInput).toBeVisible();
await apiKeyInput.fill(E2E_PROVIDER_API_KEY);
await page.getByRole("button", { name: /^Save$/i }).click();
await expect(page.getByText("Saved.")).toBeVisible({ timeout: 15_000 });
await expect(page.getByText("Connected").first()).toBeVisible({ timeout: 15_000 });
}
async function completeOnboardingToLaunch(
page: Page,
input: {
companyName: string;
workspaceDir: string;
goalTitle: string;
toggleSecondModel?: boolean;
},
) {
await advanceFromWelcome(page);
await advancePastNodeCheck(page);
await expect(page.getByText("Connect a provider")).toBeVisible();
await configureReadyProvider(page);
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
await expect(providersContinue).toBeEnabled({ timeout: 30_000 });
await providersContinue.click();
await expect(page.getByLabel("Company name")).toBeVisible();
await page.getByLabel("Company name").fill(input.companyName);
await page.getByLabel("Workspace folder").fill(input.workspaceDir);
await expect(page.getByLabel("Provider")).toHaveValue(E2E_READY_CONNECTOR_ID);
await expect(page.getByLabel("Model")).toHaveValue(E2E_READY_CONNECTOR_MODEL_ID);
await page.getByRole("button", { name: /^Continue$/i }).click();
await expect(page.getByText("Choose team models")).toBeVisible();
const enabledCountLabel = page.getByText(/\d+ \/ \d+ models enabled/i).first();
await expect(enabledCountLabel).toBeVisible();
const initialEnabledCount = await enabledCountLabel.textContent();
const switches = page.getByRole("switch");
await expect(switches.nth(1)).toBeVisible();
if (input.toggleSecondModel) {
await switches.nth(1).click();
await expect.poll(async () => enabledCountLabel.textContent()).not.toBe(initialEnabledCount);
}
await page.getByRole("button", { name: /^Continue$/i }).click();
await expect(page.getByText("How should it run?")).toBeVisible();
await page.getByRole("button", { name: /^Continue$/i }).click();
await expect(page.getByLabel("Goal")).toBeVisible();
await page.getByLabel("Goal").fill(input.goalTitle);
await page.getByRole("button", { name: /Launch Company/i }).click();
await expect(page.getByRole("heading", { name: new RegExp(`${input.companyName}.*is live`, "i") })).toBeVisible();
await expect(page.getByText("Queued in orchestration")).toBeVisible();
await expect(page.getByRole("button", { name: /Open Control Center/i })).toBeVisible();
}
test("first launch onboarding advances into runtime setup and shell-ready flow", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
const profileDir = await mkdtemp(join(tmpdir(), "agentcompany-e2e-"));
const workspaceDir = await mkdtemp(join(tmpdir(), "agentcompany-workspace-"));
let app: ElectronApplication | null = await launchDesktopApp(profileDir);
try {
const page = await app.firstWindow();
await completeOnboardingToLaunch(page, {
companyName: "Orbit Foundry",
workspaceDir,
goalTitle: "Launch the company",
});
await page.getByRole("button", { name: /Open Control Center/i }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible({ timeout: 30_000 });
await expect(page.getByRole("button", { name: /Orbit Foundry/i })).toBeVisible();
await app.close();
app = null;
const state = readOnboardingProfileState(profileDir);
expect(state.companyCount).toBe(1);
expect(state.currentCompanyName).toBe("Orbit Foundry");
expect(state.leadAgentConnectorId).toBe(E2E_READY_CONNECTOR_ID);
expect(state.leadAgentModel).toBe(E2E_READY_CONNECTOR_MODEL_ID);
expect(state.taskCount).toBeGreaterThanOrEqual(1);
expect(state.runCount).toBeGreaterThanOrEqual(1);
} finally {
await app?.close();
await rm(profileDir, { recursive: true, force: true });
await rm(workspaceDir, { recursive: true, force: true });
}
});
test("platform launch: app opens, window renders, screenshot captured", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
const profileDir = await mkdtemp(join(tmpdir(), "agentcompany-launch-"));
const screenshotDir = join(process.cwd(), "build", "screenshots");
await mkdir(screenshotDir, { recursive: true });
const app = await launchDesktopApp(profileDir);
try {
const page = await app.firstWindow();
await expect(page.getByText("AgentCompany")).toBeVisible({ timeout: 30_000 });
await expect(page.getByText("Build your AI company here.")).toBeVisible();
const viewport = page.viewportSize();
const dimensions = viewport ?? await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
}));
expect(dimensions.width).toBeGreaterThan(400);
expect(dimensions.height).toBeGreaterThan(300);
const platformName = process.platform === "darwin" ? "macos"
: process.platform === "win32" ? "windows"
: "linux";
const screenshotPath = join(screenshotDir, `launch-${platformName}.png`);
await page.screenshot({ path: screenshotPath, fullPage: true });
expect(existsSync(screenshotPath)).toBeTruthy();
} finally {
await app.close();
await rm(profileDir, { recursive: true, force: true });
}
});
test("onboarding model pool persists across relaunch for a ready connector", async () => {
const entry = getMainEntryPath();
expect(existsSync(entry)).toBeTruthy();
const profileDir = await mkdtemp(join(tmpdir(), "agentcompany-model-pool-"));
const workspaceDir = await mkdtemp(join(tmpdir(), "agentcompany-model-pool-workspace-"));
let app: ElectronApplication | null = await launchDesktopApp(profileDir);
try {
const page = await app.firstWindow();
await completeOnboardingToLaunch(page, {
companyName: "Persisted Model Pool Co",
workspaceDir,
goalTitle: "Keep model pool choices after relaunch",
toggleSecondModel: true,
});
await page.getByRole("button", { name: /Open Control Center/i }).click();
await expect(page.getByRole("heading", { name: "Dashboard" })).toBeVisible({ timeout: 30_000 });
await app.close();
app = null;
const persistedState = readOnboardingProfileState(profileDir);
expect(persistedState.currentCompanyName).toBe("Persisted Model Pool Co");
expect(persistedState.disabledModelCount).toBe(1);
app = await launchDesktopApp(profileDir);
const relaunchedPage = await app.firstWindow();
await expect(relaunchedPage.getByRole("heading", { name: "Dashboard" })).toBeVisible({ timeout: 30_000 });
await expect(relaunchedPage.getByRole("button", { name: /Persisted Model Pool Co/i })).toBeVisible();
} finally {
await app?.close();
await rm(profileDir, { recursive: true, force: true });
await rm(workspaceDir, { recursive: true, force: true });
}
});
+226
View File
@@ -0,0 +1,226 @@
import { _electron as electron, expect, test, type Page } from "@playwright/test";
import { mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { DatabaseSync } from "node:sqlite";
test("onboards, hires, and creates work in the Electron shell", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-e2e-${randomUUID()}`);
mkdirSync(profileDir, { recursive: true });
const app = await electron.launch({
args: [resolve("out/main/index.js")],
env: {
...process.env,
AGENTCOMPANY_NEW_PROFILE_DIR: profileDir,
AGENTCOMPANY_NEW_AUTORUN: "0",
},
});
try {
const page = await app.firstWindow();
await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible();
await page.getByRole("button", { name: "Get started" }).click();
await page.getByLabel("API key").fill("e2e-secret");
await page.getByLabel("Model").fill("gpt-5.4");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Company name").fill("Acme Operations");
await page.getByLabel("Your name").fill("Dana Lee");
await page.getByLabel("Description").fill("Coordinate agents with auditable approvals.");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Goal").fill("Ship a verified control plane.");
await page.getByLabel("Acceptance criteria").fill("The CEO creates the kickoff operating loop.");
await page.getByRole("button", { name: "Launch" }).click();
await expect(page.getByRole("button", { name: /Acme Operations/ })).toBeVisible();
await page.getByRole("button", { name: "Work", exact: true }).click();
await expect(page.getByText("Projects", { exact: true })).toBeVisible();
await expect(page.getByText("Execution plan: Ship a verified control plane").first()).toBeVisible();
await expect(page.getByText("#execution-plan-ship-a-verified-control-plane").first()).toBeVisible();
await page.getByRole("button", { name: "Goal", exact: true }).click();
await page.getByLabel("Title").fill("Open the Berlin market");
await page.getByLabel("Description").fill("Win the first ten customers.");
await page.locator(".modal").getByRole("button", { name: "Create" }).click();
await expect(page.getByText("Operationalize goal: Open the Berlin market").first()).toBeVisible();
await page.getByRole("button", { name: "Team" }).click();
await page.getByRole("button", { name: "Hire" }).click();
await page.getByLabel("Name").fill("Alex Chen");
await page.getByLabel("Role").fill("CTO");
await page.getByLabel("Department").fill("Engineering");
await page.getByLabel("Monthly budget").fill("250");
await page.locator(".modal").getByRole("button", { name: "Hire" }).click();
await expect(page.getByText("Alex Chen").first()).toBeVisible();
const hiredAgent = await page.evaluate(async () => {
const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<unknown> } }).agentCompany;
const result = await api.loadSnapshot() as {
ok: true;
data: {
agents: Array<{ name: string; modelProviderId: string | null; modelId: string | null; workspaceId: string | null; heartbeatEnabled: boolean; heartbeatIntervalSec: number }>;
workspaces: Array<{ id: string; isPrimary: boolean }>;
};
};
const agent = result.data.agents.find((item) => item.name === "Alex Chen");
const primaryWorkspace = result.data.workspaces.find((item) => item.isPrimary) ?? null;
return {
modelProviderId: agent?.modelProviderId ?? null,
modelId: agent?.modelId ?? null,
workspaceId: agent?.workspaceId ?? null,
heartbeatEnabled: agent?.heartbeatEnabled ?? false,
heartbeatIntervalSec: agent?.heartbeatIntervalSec ?? 0,
primaryWorkspaceId: primaryWorkspace?.id ?? null,
};
});
expect(hiredAgent.modelProviderId).toBeTruthy();
expect(hiredAgent.modelId).toBe("gpt-5.4");
expect(hiredAgent.workspaceId).toBe(hiredAgent.primaryWorkspaceId);
expect(hiredAgent.heartbeatEnabled).toBe(true);
expect(hiredAgent.heartbeatIntervalSec).toBe(120);
await page.getByRole("button", { name: "Work", exact: true }).click();
await page.getByRole("button", { name: "New task" }).click();
await page.getByLabel("Title").fill("Production deploy");
await page.getByLabel("Description").fill("Deploy a high-risk change.");
await page.getByLabel("Risk").selectOption("high");
await page.locator(".modal").getByRole("button", { name: "Create" }).click();
await expect(page.getByText("Production deploy").first()).toBeVisible();
await page.getByRole("button", { name: "Search" }).click();
await page.getByRole("textbox", { name: "Search query" }).fill("production");
await expect(page.getByText("Production deploy").first()).toBeVisible();
await page.getByRole("dialog", { name: "Search" }).getByRole("button").filter({ hasText: "Production deploy" }).first().click();
await page.getByRole("button", { name: "Approvals" }).click();
await expect(page.getByText("Production deploy").first()).toBeVisible();
await page.getByRole("button", { name: "Messages" }).click();
await page.getByRole("button", { name: "New space" }).click();
await page.getByLabel("Name").fill("release");
await page.getByLabel("Topic").fill("Release coordination");
await page.locator(".modal").getByRole("button", { name: "Create" }).click();
await expect(page.getByText("release").first()).toBeVisible();
await page.getByRole("textbox", { name: "Message" }).fill("/escalate @Board review release plan");
await page.getByRole("button", { name: "Send" }).click();
await expect(page.getByText("@Board").first()).toBeVisible();
await expect(page.getByText("/escalate").first()).toBeVisible();
await page.getByRole("button", { name: "Open thread" }).first().click();
await expect(page.getByRole("textbox", { name: "Thread reply" })).toBeVisible();
await page.getByRole("button", { name: "Approvals" }).click();
await expect(page.getByText("Escalate release").first()).toBeVisible();
// The Connectors page shell renders whether or not the Composio catalog
// is reachable from the test machine.
await page.getByRole("button", { name: "Connectors", exact: true }).click();
await expect(page.getByRole("heading", { name: "Connectors" })).toBeVisible();
await expect(page.getByRole("button", { name: "Refresh" })).toBeVisible();
await page.getByRole("button", { name: "Settings" }).click();
await expect(page.getByText("AI CEO", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Providers" }).click();
await page.getByLabel("OpenAI background execution").check();
await page.getByRole("button", { name: "Save OpenAI policy" }).click();
// The operator request console lives under Advanced.
await page.getByRole("button", { name: "Advanced" }).click();
await expect(page.getByText("Requests", { exact: true }).first()).toBeVisible();
await expect(page.getByLabel("Request provider")).toBeVisible();
await expect(page.getByRole("button", { name: "Background" })).toBeVisible();
await page.getByLabel("Request prompt").fill("Draft a provider control memo.");
await page.getByLabel("Request instructions").fill("Use direct language.");
await expect(page.getByLabel("Composio API key")).toBeVisible();
await page.getByRole("button", { name: "Providers" }).click();
await page.getByLabel("OpenAI data policy").selectOption("zero_data_retention");
await expect(page.getByLabel("OpenAI background execution")).not.toBeChecked();
await page.getByLabel("OpenAI monthly request limit").fill("25");
await page.getByRole("button", { name: "Save OpenAI policy" }).click();
await expect(page.getByText("0 / 25").first()).toBeVisible();
await page.getByRole("button", { name: "Skills" }).click();
await page.getByRole("button", { name: "Import" }).click();
await page.getByLabel("Directory path").fill(createSkillDirectory());
await page.locator(".modal").getByRole("button", { name: "Import" }).click();
await expect(page.getByText("Release Gate").first()).toBeVisible();
await expect(page.getByText("Never run").first()).toBeVisible();
await page.getByRole("button", { name: "Run tests" }).click();
await expect(page.getByText("1/1 passed").first()).toBeVisible();
const legacyPath = createLegacyDatabase();
await page.getByRole("button", { name: "Advanced" }).click();
await page.getByRole("button", { name: "Report" }).click();
await page.getByLabel("Source database path").fill(legacyPath);
await page.locator(".modal").getByRole("button", { name: "Create" }).click();
await expect(page.getByText("organizations: 1").first()).toBeVisible();
await page.getByRole("button", { name: "Snapshot" }).click();
await expect(page.getByText("Rollback snapshots", { exact: true })).toBeVisible();
await expect(page.getByText(/\d+ workspaces/).first()).toBeVisible();
await page.getByRole("button", { name: "Set", exact: true }).click();
await expect(page.getByRole("button", { name: "active", exact: true })).toBeDisabled();
// Virtual office page
await page.getByRole("button", { name: "Office" }).click();
await expect(page.locator("canvas")).toBeVisible();
// @mention with canonical handle in chat
await page.getByRole("button", { name: "Messages" }).click();
await page.getByText("general").first().click();
await page.getByRole("textbox", { name: "Message" }).fill("@alexchen what is the status?");
await page.getByRole("button", { name: "Send" }).click();
await expect(page.getByText("Alex Chen").first()).toBeVisible();
await verifyPagesDoNotOverflow(page);
} finally {
await app.close();
}
});
function createLegacyDatabase(): string {
const legacyPath = join(tmpdir(), `agentcompany-legacy-${randomUUID()}.db`);
const legacy = new DatabaseSync(legacyPath);
legacy.exec("create table organizations(id text primary key); insert into organizations(id) values ('legacy-org');");
legacy.close();
return legacyPath;
}
function createSkillDirectory(): string {
const root = join(tmpdir(), `agentcompany-skill-${randomUUID()}`);
mkdirSync(join(root, "checks"), { recursive: true });
writeFileSync(join(root, "SKILL.md"), "Review release evidence and produce a concise gate decision.\n", "utf8");
writeFileSync(join(root, "checks", "acceptance.txt"), "release evidence accepted\n", "utf8");
writeFileSync(join(root, "skill.manifest.json"), JSON.stringify({
name: "Release Gate",
version: "1.0.0",
entrypoint: "SKILL.md",
capabilities: ["release-review"],
required_tools: ["git"],
compatible_models: ["gpt-5.4"],
risk_level: "low",
tests: ["checks/acceptance.txt"],
inputs: {},
outputs: {},
}, null, 2), "utf8");
return root;
}
async function verifyPagesDoNotOverflow(page: Page): Promise<void> {
for (const size of [{ width: 1280, height: 860 }, { width: 980, height: 700 }]) {
await page.setViewportSize(size);
for (const nav of ["Home", "Team", "Work", "Messages", "Files", "Approvals", "Connectors", "Office", "Settings"]) {
await page.getByRole("button", { name: nav, exact: true }).click();
await expectPageToFit(page);
}
for (const tab of ["General", "Providers", "Skills", "Advanced"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab, exact: true }).click();
await expectPageToFit(page);
}
}
}
async function expectPageToFit(page: Page): Promise<void> {
const dimensions = await page.evaluate(() => ({
documentScrollWidth: document.documentElement.scrollWidth,
documentClientWidth: document.documentElement.clientWidth,
bodyScrollWidth: document.body.scrollWidth,
bodyClientWidth: document.body.clientWidth,
}));
expect(dimensions.documentScrollWidth).toBeLessThanOrEqual(dimensions.documentClientWidth + 1);
expect(dimensions.bodyScrollWidth).toBeLessThanOrEqual(dimensions.bodyClientWidth + 1);
}
-16
View File
@@ -1,16 +0,0 @@
import { execFileSync } from "node:child_process";
import { createElectronLaunchEnv } from "./launch";
/**
* Playwright globalSetup: build the Electron app from source before E2E tests.
* Uses execFileSync and enables shell mode on Windows so the pnpm shim resolves correctly.
*/
export default function globalSetup() {
execFileSync("pnpm", ["electron-vite", "build"], {
stdio: "inherit",
cwd: process.cwd(),
timeout: 120_000,
env: createElectronLaunchEnv({}),
shell: process.platform === "win32",
});
}
-46
View File
@@ -1,46 +0,0 @@
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const require = createRequire(import.meta.url);
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
const EXPERIMENTAL_WARNING_FLAG = "--disable-warning=ExperimentalWarning";
export function getElectronExecutablePath() {
return require("electron");
}
export function getMainEntryPath() {
return resolve(repoRoot, "out/main/index.js");
}
export function createElectronLaunchEnv(
overrides: Record<string, string>,
) : Record<string, string> {
const env: Record<string, string> = {};
for (const [key, value] of Object.entries(process.env)) {
if (typeof value === "string") {
env[key] = value;
}
}
Object.assign(env, overrides);
if (env.FORCE_COLOR && env.NO_COLOR) {
delete env.NO_COLOR;
}
const existingNodeOptions = env.NODE_OPTIONS?.trim();
if (!existingNodeOptions) {
env.NODE_OPTIONS = EXPERIMENTAL_WARNING_FLAG;
return env;
}
if (!existingNodeOptions.includes(EXPERIMENTAL_WARNING_FLAG)) {
env.NODE_OPTIONS = `${existingNodeOptions} ${EXPERIMENTAL_WARNING_FLAG}`;
}
return env;
}
+106
View File
@@ -0,0 +1,106 @@
import { _electron as electron, expect, test, type Page } from "@playwright/test";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { buildFloorPlan } from "../../src/renderer/src/pages/office/floor-plan";
import { cameraFor } from "../../src/renderer/src/pages/office/simulation";
test("the office floor opens agent details on click", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-office-${randomUUID()}`);
mkdirSync(profileDir, { recursive: true });
const app = await electron.launch({
args: [resolve("out/main/index.js")],
env: { ...process.env, AGENTCOMPANY_NEW_PROFILE_DIR: profileDir, AGENTCOMPANY_NEW_AUTORUN: "0" },
});
try {
const page = await app.firstWindow();
await page.setViewportSize({ width: 1440, height: 900 });
await page.getByRole("button", { name: "Get started" }).click();
await page.getByLabel("API key").fill("office-e2e-key");
await page.getByLabel("Model").fill("gpt-5.4");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Company name").fill("Pixel Works");
await page.getByLabel("Your name").fill("Morgan Reyes");
await page.getByLabel("Description").fill("Office e2e coverage.");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Goal").fill("Verify the office floor.");
await page.getByLabel("Acceptance criteria").fill("Clicking an agent opens their card.");
await page.getByRole("button", { name: "Launch" }).click();
await expect(page.getByRole("button", { name: /Pixel Works/ })).toBeVisible();
await page.getByRole("button", { name: "Office", exact: true }).click();
const canvas = page.locator("canvas");
await expect(canvas).toBeVisible();
await page.waitForTimeout(300);
// Onboarding seeds exactly one agent (the CEO), so the floor plan and
// camera are fully determined by the same pure modules the app uses.
const box = await canvas.boundingBox();
expect(box).not.toBeNull();
const plan = buildFloorPlan(0);
const camera = cameraFor(plan.world, box!.width, box!.height, plan.entrance);
const clickX = box!.x + plan.ceoSeat.x - camera.x;
const clickY = box!.y + plan.ceoSeat.y - 20 - camera.y;
await page.mouse.click(clickX, clickY);
const card = page.getByRole("dialog", { name: "Agent CEO" });
await expect(card).toBeVisible();
await expect(card.getByText("CEO", { exact: true })).toBeVisible();
await expect(card.getByText("Chief Executive Officer")).toBeVisible();
await expect(card.getByText("Executive", { exact: true })).toBeVisible();
await page.keyboard.press("Escape");
await expect(card).not.toBeVisible();
// Clicking empty corridor floor selects nothing.
await page.mouse.click(box!.x + 500 - camera.x, box!.y + 320 - camera.y);
await expect(page.getByRole("dialog", { name: /Agent / })).not.toBeVisible();
// WASD input is accepted without disturbing the page.
for (const key of ["KeyD", "KeyS", "KeyA", "KeyW"]) {
await page.keyboard.down(key);
await page.waitForTimeout(120);
await page.keyboard.up(key);
}
await expect(canvas).toBeVisible();
// Hired agents take open-floor desks in hire order and are clickable too.
await hireAgent(page, "Ava Patel", "Designer", "Design");
await hireAgent(page, "Ken Ito", "Engineer", "Engineering");
await page.getByRole("button", { name: "Office", exact: true }).click();
await expect(canvas).toBeVisible();
await page.waitForTimeout(300);
const populated = buildFloorPlan(2);
const populatedBox = await canvas.boundingBox();
const populatedCamera = cameraFor(populated.world, populatedBox!.width, populatedBox!.height, populated.entrance);
const firstDesk = populated.desks[0].seat;
await page.mouse.click(
populatedBox!.x + firstDesk.x - populatedCamera.x,
populatedBox!.y + firstDesk.y - 20 - populatedCamera.y,
);
const avaCard = page.getByRole("dialog", { name: "Agent Ava Patel" });
await expect(avaCard).toBeVisible();
await expect(avaCard.getByText("Design", { exact: true })).toBeVisible();
const outDir = resolve("test-results/screenshots");
mkdirSync(outDir, { recursive: true });
await page.screenshot({ path: join(outDir, "19-office-populated.png"), fullPage: false });
} finally {
await app.close();
}
});
async function hireAgent(page: Page, name: string, role: string, department: string): Promise<void> {
await page.getByRole("button", { name: "Team", exact: true }).click();
await page.getByRole("button", { name: "Hire", exact: true }).click();
await page.getByLabel("Name").fill(name);
await page.getByLabel("Role").fill(role);
await page.getByLabel("Department").fill(department);
await page.getByLabel("Monthly budget").fill("100");
await page.locator(".modal").getByRole("button", { name: "Hire" }).click();
await expect(page.getByText(name).first()).toBeVisible();
}
-66
View File
@@ -1,66 +0,0 @@
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import type { ConnectorId } from "../../src/shared/types";
export const E2E_READY_CONNECTOR_ID = "anthropic" as ConnectorId;
export const E2E_READY_CONNECTOR_MODEL_ID = "claude-sonnet-4-5";
export const E2E_PROVIDER_API_KEY = "sk-ant-e2e-key-do-not-use-000000000000";
export interface OnboardingProfileState {
companyCount: number;
currentCompanyName: string | null;
leadAgentConnectorId: string | null;
leadAgentModel: string | null;
taskCount: number;
runCount: number;
disabledModelCount: number;
}
function first<T>(db: DatabaseSync, sql: string, ...params: Array<string | number | null>): T | null {
return (db.prepare(sql).get(...params) as T | undefined) ?? null;
}
export function readOnboardingProfileState(profileDir: string): OnboardingProfileState {
const db = new DatabaseSync(join(profileDir, "profile.sqlite"));
try {
const companyCount =
first<{ count: number }>(db, "select count(*) as count from companies")?.count ?? 0;
const currentCompanyId =
first<{ value: string }>(db, "select value from settings where key = 'current_company_id'")?.value ?? null;
const currentCompany = currentCompanyId
? first<{ id: string; name: string }>(db, "select id, name from companies where id = ?", currentCompanyId)
: first<{ id: string; name: string }>(db, "select id, name from companies order by created_at asc limit 1");
const companyId = currentCompany?.id ?? null;
const leadAgent = companyId
? first<{ connector_id: string; model: string | null }>(
db,
"select connector_id, model from agents where company_id = ? and reports_to is null limit 1",
companyId,
)
: null;
const disabledModelsJson = companyId
? first<{ disabled_models_json: string }>(
db,
"select disabled_models_json from connector_model_pool_settings where company_id = ? and connector_id = ?",
companyId,
E2E_READY_CONNECTOR_ID,
)?.disabled_models_json ?? "[]"
: "[]";
const disabledModels = JSON.parse(disabledModelsJson) as unknown[];
return {
companyCount,
currentCompanyName: currentCompany?.name ?? null,
leadAgentConnectorId: leadAgent?.connector_id ?? null,
leadAgentModel: leadAgent?.model ?? null,
taskCount: companyId
? first<{ count: number }>(db, "select count(*) as count from tasks where company_id = ?", companyId)?.count ?? 0
: 0,
runCount: companyId
? first<{ count: number }>(db, "select count(*) as count from runs where company_id = ?", companyId)?.count ?? 0
: 0,
disabledModelCount: disabledModels.length,
};
} finally {
db.close();
}
}
+2 -13
View File
@@ -1,20 +1,9 @@
import { join } from "node:path";
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: ".",
testMatch: ["desktop.smoke.spec.ts"],
timeout: 120_000,
fullyParallel: false,
timeout: 60_000,
retries: 0,
workers: 1,
reporter: "list",
outputDir: join(process.cwd(), "build", "test-results"),
use: {
trace: "on-first-retry",
screenshot: "only-on-failure",
},
// Build from current source before running e2e tests.
// Uses globalSetup (not webServer) because electron-vite build is a
// one-shot build command, not a long-running server process.
globalSetup: "./e2e-global-setup.ts",
});
+76
View File
@@ -0,0 +1,76 @@
import { _electron as electron, expect, test, type Page } from "@playwright/test";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
test("captures every page for visual review", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-shots-${randomUUID()}`);
mkdirSync(profileDir, { recursive: true });
const app = await electron.launch({
args: [resolve("out/main/index.js")],
env: { ...process.env, AGENTCOMPANY_NEW_PROFILE_DIR: profileDir, AGENTCOMPANY_NEW_AUTORUN: "0" },
});
const outDir = resolve("test-results/screenshots");
mkdirSync(outDir, { recursive: true });
try {
const page = await app.firstWindow();
await page.setViewportSize({ width: 1440, height: 900 });
await page.screenshot({ path: join(outDir, "00-onboarding-welcome.png"), fullPage: false });
await page.getByRole("button", { name: "Get started" }).click();
await page.screenshot({ path: join(outDir, "01-onboarding-provider.png"), fullPage: false });
await page.getByLabel("API key").fill("local-dev-key");
await page.getByLabel("Model").fill("gpt-5.4");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Company name").fill("OrbitOps");
await page.getByLabel("Your name").fill("Yu Chen");
await page.getByLabel("Description").fill("Multi-agent operations platform.");
await page.screenshot({ path: join(outDir, "02-onboarding-company.png"), fullPage: false });
await page.getByRole("button", { name: "Continue" }).click();
await page.screenshot({ path: join(outDir, "03-onboarding-mode.png"), fullPage: false });
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Goal").fill("Ship the v1 launch by July.");
await page.getByLabel("Acceptance criteria").fill("Pricing model + landing page + 3 pilot signups.");
await page.screenshot({ path: join(outDir, "04-onboarding-goal.png"), fullPage: false });
await page.getByRole("button", { name: "Launch" }).click();
await expect(page.getByRole("button", { name: /OrbitOps/ })).toBeVisible();
await snap(page, outDir, "10-home");
await go(page, "Team", outDir, "11-team");
await go(page, "Work", outDir, "12-work");
await go(page, "Messages", outDir, "13-messages");
await go(page, "Files", outDir, "14-files");
await go(page, "Approvals", outDir, "15-approvals");
await go(page, "Connectors", outDir, "16-connectors");
await go(page, "Settings", outDir, "17-settings");
await go(page, "Office", outDir, "18-office");
for (const tab of ["General", "Providers", "Skills", "Advanced"]) {
await page.getByRole("button", { name: "Settings" }).click();
await page.getByRole("button", { name: tab }).first().click();
await snap(page, outDir, `settings-${tab.toLowerCase()}`);
}
await page.setViewportSize({ width: 980, height: 700 });
await go(page, "Home", outDir, "20-narrow-home");
await go(page, "Messages", outDir, "21-narrow-messages");
await go(page, "Files", outDir, "22-narrow-files");
} finally {
await app.close();
}
});
async function go(page: Page, nav: string, dir: string, name: string): Promise<void> {
await page.getByRole("button", { name: nav, exact: true }).click();
await snap(page, dir, name);
}
async function snap(page: Page, dir: string, name: string): Promise<void> {
await page.waitForTimeout(120);
await page.screenshot({ path: join(dir, `${name}.png`), fullPage: false });
}