Files
s1mba 731cc340d8 [packages/cli] Add comprehensive CLI test suite (#128)
* [packages/cli]test: add comprehensive CLI test suite with E2E browser tests

- Add unit tests for wrapper (actionbook.js), postinstall, config, profile,
  search, get, and sources commands
- Add Tier 1 browser tests (arg validation, help output, defaults)
- Add Tier 2 browser E2E tests (navigation, interaction, screenshots,
  cookies, tabs, error handling, JSON output)
- Fix E2E test reliability: add --wait for element readiness after navigation,
  use eval-based form submit for login flow, match actual CLI exit codes
- Suppress macOS Keychain dialogs in tests via --use-mock-keychain config
- Set testTimeout to 60s for browser E2E operations
- Export modules from actionbook.js and postinstall.js for testability

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* [packages/cli]fix: resolve Windows .exe suffix when locating test CLI binary

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* [packages/cli]fix: skip shell-script mock tests on Windows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 14:36:15 +08:00

178 lines
5.9 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import { getActionbookBinary, runCli } from "./helpers/binary.js";
import { createIsolatedEnv } from "./helpers/config.js";
const binary = getActionbookBinary();
describe.skipIf(!binary)("profile command", () => {
let isolatedEnv: ReturnType<typeof createIsolatedEnv>;
beforeEach(() => {
isolatedEnv = createIsolatedEnv();
});
// ── profile list ───────────────────────────────────────────────────
describe("profile list", () => {
it("lists profiles in text format", async () => {
const result = await runCli(["profile", "list"], {
env: isolatedEnv.env,
});
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Profiles");
});
it("lists profiles in JSON format", async () => {
const result = await runCli(["--json", "profile", "list"], {
env: isolatedEnv.env,
});
expect(result.exitCode).toBe(0);
const json = JSON.parse(result.stdout);
expect(Array.isArray(json)).toBe(true);
});
});
// ── profile create ─────────────────────────────────────────────────
describe("profile create", () => {
it("creates a profile", async () => {
const result = await runCli(
["profile", "create", "test-profile"],
{ env: isolatedEnv.env }
);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Created profile");
expect(result.stdout).toContain("test-profile");
});
it("creates a profile with custom CDP port in JSON format", async () => {
const result = await runCli(
[
"--json",
"profile",
"create",
"test-profile-json",
"--cdp-port",
"9333",
],
{ env: isolatedEnv.env }
);
expect(result.exitCode).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);
expect(json.name).toBe("test-profile-json");
expect(json.cdp_port).toBe(9333);
});
it("auto-assigns CDP port when not specified", async () => {
const result = await runCli(
["--json", "profile", "create", "auto-port-profile"],
{ env: isolatedEnv.env }
);
expect(result.exitCode).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);
expect(typeof json.cdp_port).toBe("number");
expect(json.cdp_port).toBeGreaterThan(0);
});
});
// ── profile show ───────────────────────────────────────────────────
describe("profile show", () => {
it("shows profile details after creation", async () => {
// Create first
await runCli(
["profile", "create", "show-test", "--cdp-port", "9444"],
{ env: isolatedEnv.env }
);
const result = await runCli(["profile", "show", "show-test"], {
env: isolatedEnv.env,
});
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("show-test");
expect(result.stdout).toContain("9444");
});
it("shows profile details in JSON format", async () => {
// Create first
await runCli(
["profile", "create", "json-show", "--cdp-port", "9555"],
{ env: isolatedEnv.env }
);
const result = await runCli(
["--json", "profile", "show", "json-show"],
{ env: isolatedEnv.env }
);
expect(result.exitCode).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.name).toBe("json-show");
expect(json.cdp_port).toBe(9555);
expect(typeof json.headless).toBe("boolean");
});
it("fails for nonexistent profile", async () => {
const result = await runCli(
["profile", "show", "nonexistent-profile"],
{ env: isolatedEnv.env }
);
expect(result.exitCode).toBe(1);
expect(result.stderr).toMatch(/Profile.*not.*found|ProfileNotFound/i);
});
});
// ── profile delete ─────────────────────────────────────────────────
describe("profile delete", () => {
it("deletes a created profile", async () => {
// Create first
await runCli(
["profile", "create", "delete-me"],
{ env: isolatedEnv.env }
);
const result = await runCli(
["profile", "delete", "delete-me"],
{ env: isolatedEnv.env }
);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain("Deleted profile");
// Verify it's gone
const showResult = await runCli(
["profile", "show", "delete-me"],
{ env: isolatedEnv.env }
);
expect(showResult.exitCode).toBe(1);
});
});
// ── argument validation ────────────────────────────────────────────
describe("argument validation", () => {
it("profile without subcommand fails", async () => {
const result = await runCli(["profile"], { env: isolatedEnv.env });
expect(result.exitCode).toBe(2);
expect(result.stderr).toContain("subcommand");
});
it("profile create without name fails", async () => {
const result = await runCli(["profile", "create"], {
env: isolatedEnv.env,
});
expect(result.exitCode).toBe(2);
expect(result.stderr).toContain("NAME");
});
it("profile delete without name fails", async () => {
const result = await runCli(["profile", "delete"], {
env: isolatedEnv.env,
});
expect(result.exitCode).toBe(2);
expect(result.stderr).toContain("NAME");
});
});
});