Build the current workspace snapshot on native CI runners
This branch snapshots the in-progress desktop workspace so GitHub-hosted Windows and Linux runners can package the same source tree that already passes local verification on macOS. Constraint: Local Apple Silicon Docker/Wine packaging crashes before Windows artifacts are produced Rejected: Publish from the incomplete base repo state | remote typecheck failed because current source additions were missing Confidence: medium Scope-risk: broad Directive: Treat this branch as a build snapshot only; do not merge it without reviewing the unrelated workspace changes it contains Tested: local typecheck, lint, workspace typecheck, unit tests, integration tests, e2e smoke, macOS signed release upload Not-tested: Native Windows/Linux artifacts before this CI run
This commit is contained in:
1 parent
0e497cd892
commit
b772e1c758
271 files changed
+35047
-22879
No files matched your search
+136
-56
@@ -5,7 +5,33 @@ import { tmpdir } from "node:os";
|
||||
import { _electron as electron, expect, test } from "@playwright/test";
|
||||
import { getElectronExecutablePath, getMainEntryPath } from "./launch";
|
||||
|
||||
test("first launch onboarding creates a company and opens the desktop shell", async () => {
|
||||
function buildLaunchEnv(profileDir: string) {
|
||||
const env: Record<string, string | undefined> = {
|
||||
...process.env,
|
||||
AGENTCOMPANY_PROFILE_DIR: profileDir,
|
||||
AGENTCOMPANY_DISABLE_UPDATES: "true",
|
||||
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
|
||||
};
|
||||
delete env.NO_COLOR;
|
||||
return Object.fromEntries(
|
||||
Object.entries(env).filter((entry): entry is [string, string] => typeof entry[1] === "string"),
|
||||
);
|
||||
}
|
||||
|
||||
async function advanceFromWelcome(page: import("@playwright/test").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: import("@playwright/test").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();
|
||||
}
|
||||
|
||||
test("first launch onboarding advances into runtime setup and shell-ready flow", async () => {
|
||||
const entry = getMainEntryPath();
|
||||
expect(existsSync(entry)).toBeTruthy();
|
||||
|
||||
@@ -16,46 +42,38 @@ test("first launch onboarding creates a company and opens the desktop shell", as
|
||||
executablePath: getElectronExecutablePath(),
|
||||
args: [entry],
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENTCOMPANY_PROFILE_DIR: profileDir,
|
||||
AGENTCOMPANY_DISABLE_UPDATES: "true",
|
||||
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
|
||||
},
|
||||
env: buildLaunchEnv(profileDir),
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await app.firstWindow();
|
||||
await expect(page.getByText("AgentCompany Desktop")).toBeVisible();
|
||||
await advanceFromWelcome(page);
|
||||
await advancePastNodeCheck(page);
|
||||
|
||||
await expect(page.getByText("Set up runtimes")).toBeVisible();
|
||||
|
||||
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
|
||||
if (!(await providersContinue.isEnabled())) {
|
||||
await expect(page.getByText(/Set up one runtime to continue\./i)).toBeVisible();
|
||||
await expect(page.getByText(/Ready|Login required|Not installed|Checking|Error/i).first()).toBeVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
await providersContinue.click();
|
||||
await expect(page.getByLabel("Company name")).toBeVisible();
|
||||
await page.getByLabel("Company name").fill("Orbit Foundry");
|
||||
await page.getByLabel("Workspace folder").fill(workspaceDir);
|
||||
const continueButton = page.getByRole("button", { name: /^Continue$/i });
|
||||
if (await continueButton.isEnabled()) {
|
||||
await continueButton.click();
|
||||
await page.getByLabel("Goal").fill("Launch the company");
|
||||
await page.getByRole("button", { name: /Launch Company/i }).click();
|
||||
await expect(page.getByRole("heading", { name: /Orbit Foundry is live/i })).toBeVisible();
|
||||
await expect(page.getByText("Initial run")).toBeVisible();
|
||||
await page.getByRole("button", { name: /Open control center/i }).click();
|
||||
await page.getByRole("button", { name: /^Continue$/i }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Runs" })).toBeVisible();
|
||||
// Company name is rendered in a custom dropdown trigger button in the sidebar.
|
||||
await expect(page.locator("aside button", { hasText: "Orbit Foundry" }).first()).toBeVisible();
|
||||
const showConsoleButton = page.getByRole("button", { name: /show console/i });
|
||||
const hideConsoleButton = page.getByRole("button", { name: /hide console/i });
|
||||
if (await hideConsoleButton.isVisible().catch(() => false)) {
|
||||
await expect(page.getByText("Console", { exact: true })).toBeVisible();
|
||||
await hideConsoleButton.click();
|
||||
await expect(page.getByText("Console", { exact: true })).toBeHidden();
|
||||
} else {
|
||||
await expect(showConsoleButton).toBeVisible();
|
||||
}
|
||||
await showConsoleButton.click();
|
||||
await expect(page.getByText("Console", { exact: true })).toBeVisible();
|
||||
} else {
|
||||
await expect(page.getByText(/Install, authenticate, and fully validate at least one supported CLI runtime/i)).toBeVisible();
|
||||
await expect(page.getByText(/cannot be used for autonomous execution/i).first()).toBeVisible();
|
||||
}
|
||||
await expect(page.getByText("How should your company operate?")).toBeVisible();
|
||||
await page.getByRole("button", { name: /^Continue$/i }).click();
|
||||
|
||||
await expect(page.getByLabel("Goal")).toBeVisible();
|
||||
await page.getByLabel("Goal").fill("Launch the company");
|
||||
await page.getByRole("button", { name: /Launch Company/i }).click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: /Orbit Foundry.*is live/i })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /Open Control Center/i })).toBeVisible();
|
||||
} finally {
|
||||
await app.close();
|
||||
await rm(profileDir, { recursive: true, force: true });
|
||||
@@ -63,8 +81,6 @@ test("first launch onboarding creates a company and opens the desktop shell", as
|
||||
}
|
||||
});
|
||||
|
||||
// NOTE: On headless Linux (CI), run via: xvfb-run pnpm test:e2e
|
||||
// See RESEARCH.md Pitfall 5 for details.
|
||||
test("platform launch: app opens, window renders, screenshot captured", async () => {
|
||||
const entry = getMainEntryPath();
|
||||
expect(existsSync(entry)).toBeTruthy();
|
||||
@@ -77,41 +93,105 @@ test("platform launch: app opens, window renders, screenshot captured", async ()
|
||||
executablePath: getElectronExecutablePath(),
|
||||
args: [entry],
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENTCOMPANY_PROFILE_DIR: profileDir,
|
||||
AGENTCOMPANY_DISABLE_UPDATES: "true",
|
||||
AGENTCOMPANY_DISABLE_SINGLE_INSTANCE: "true",
|
||||
},
|
||||
env: buildLaunchEnv(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();
|
||||
|
||||
// Verify the window appeared and has content
|
||||
await expect(page.getByText("AgentCompany Desktop")).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Verify the window has reasonable dimensions (not collapsed or zero-size)
|
||||
const viewport = page.viewportSize();
|
||||
expect(viewport).not.toBeNull();
|
||||
if (viewport) {
|
||||
expect(viewport.width).toBeGreaterThan(400);
|
||||
expect(viewport.height).toBeGreaterThan(300);
|
||||
}
|
||||
const dimensions = viewport ?? await page.evaluate(() => ({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
}));
|
||||
expect(dimensions.width).toBeGreaterThan(400);
|
||||
expect(dimensions.height).toBeGreaterThan(300);
|
||||
|
||||
// Capture screenshot for D-07 validation evidence
|
||||
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 });
|
||||
|
||||
// Verify screenshot was actually written
|
||||
expect(existsSync(screenshotPath)).toBeTruthy();
|
||||
|
||||
console.log(`[platform-launch] Screenshot saved: ${screenshotPath}`);
|
||||
} 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-"));
|
||||
|
||||
const launchApp = async () => electron.launch({
|
||||
executablePath: getElectronExecutablePath(),
|
||||
args: [entry],
|
||||
cwd: process.cwd(),
|
||||
env: buildLaunchEnv(profileDir),
|
||||
});
|
||||
|
||||
let app = await launchApp();
|
||||
|
||||
try {
|
||||
const page = await app.firstWindow();
|
||||
await advanceFromWelcome(page);
|
||||
await advancePastNodeCheck(page);
|
||||
|
||||
await expect(page.getByText("Set up runtimes")).toBeVisible();
|
||||
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
|
||||
if (!(await providersContinue.isEnabled())) {
|
||||
await expect(page.getByText(/Set up one runtime to continue\./i)).toBeVisible();
|
||||
return;
|
||||
}
|
||||
await providersContinue.click();
|
||||
|
||||
await expect(page.getByLabel("Company name")).toBeVisible();
|
||||
const enabledCountLabel = page.getByText(/Models \(\d+\/\d+ enabled\)/i);
|
||||
await expect(enabledCountLabel).toBeVisible();
|
||||
const initialCount = await enabledCountLabel.textContent();
|
||||
|
||||
const switches = page.getByRole("switch");
|
||||
const switchCount = await switches.count();
|
||||
if (switchCount < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
await switches.nth(1).click();
|
||||
const updatedCount = await enabledCountLabel.textContent();
|
||||
expect(updatedCount).not.toBe(initialCount);
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
|
||||
app = await launchApp();
|
||||
|
||||
try {
|
||||
const page = await app.firstWindow();
|
||||
await advanceFromWelcome(page);
|
||||
await advancePastNodeCheck(page);
|
||||
const providersContinue = page.getByRole("button", { name: /^Continue$/i });
|
||||
if (!(await providersContinue.isEnabled())) {
|
||||
await expect(page.getByText(/Set up at least one provider to power your agents\./i)).toBeVisible();
|
||||
return;
|
||||
}
|
||||
await providersContinue.click();
|
||||
|
||||
await expect(page.getByLabel("Company name")).toBeVisible();
|
||||
const enabledCountLabel = page.getByText(/Models \(\d+\/\d+ enabled\)/i);
|
||||
await expect(enabledCountLabel).toBeVisible();
|
||||
|
||||
await page.getByLabel("Company name").fill("Persisted Model Pool Co");
|
||||
await page.getByLabel("Workspace folder").fill(workspaceDir);
|
||||
await page.getByRole("button", { name: /^Continue$/i }).click();
|
||||
await expect(page.getByText("How should your company operate?")).toBeVisible();
|
||||
} finally {
|
||||
await app.close();
|
||||
await rm(profileDir, { recursive: true, force: true });
|
||||
await rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { createElectronLaunchEnv } from "./launch";
|
||||
|
||||
/**
|
||||
* Playwright globalSetup: build the Electron app from source before E2E tests.
|
||||
@@ -9,6 +10,7 @@ export default function globalSetup() {
|
||||
stdio: "inherit",
|
||||
cwd: process.cwd(),
|
||||
timeout: 120_000,
|
||||
env: createElectronLaunchEnv({}),
|
||||
shell: process.platform === "win32",
|
||||
});
|
||||
}
|
||||
+36
-2
@@ -1,12 +1,46 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { resolve } from "node:path";
|
||||
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(process.cwd(), "out/main/index.js");
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user