gemiterm 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -9
- package/package.json +13 -4
- package/src/cli/command-registry.ts +66 -62
- package/src/cli/commands/continue-command.ts +75 -1
- package/src/cli/commands/install-skills-command.ts +35 -0
- package/src/cli/commands/new-command.ts +62 -0
- package/src/cli/index.ts +19 -44
- package/src/cli/utils/long-arg-guard.ts +28 -0
- package/src/cli/utils/prompt-file.ts +26 -0
- package/src/infrastructure/cli-parser.ts +77 -0
- package/src/infrastructure/io.ts +9 -0
- package/src/services/skills-cli-driver.ts +58 -0
- package/src/services/skills-service.ts +52 -0
- package/.env +0 -2
- package/.github/workflows/manual-release.yml +0 -34
- package/.github/workflows/release.yml +0 -198
- package/.github/workflows/test.yml +0 -29
- package/.opencode/commands/opsx-apply.md +0 -152
- package/.opencode/commands/opsx-archive.md +0 -157
- package/.opencode/commands/opsx-explore.md +0 -169
- package/.opencode/commands/opsx-propose.md +0 -104
- package/.opencode/commands/opsx-sync.md +0 -140
- package/.opencode/commands/search-intelligently.md +0 -9
- package/.opencode/opencode.json +0 -20
- package/.opencode/skills/intellisearch/.version +0 -1
- package/.opencode/skills/intellisearch/SKILL.md +0 -84
- package/.opencode/skills/intellisearch/references/brave-search.md +0 -67
- package/.opencode/skills/intellisearch/references/ddg-search.md +0 -79
- package/.opencode/skills/intellisearch/references/deepwiki-tools.md +0 -246
- package/.opencode/skills/intellisearch/references/examples.md +0 -97
- package/.opencode/skills/intellisearch/references/gh-cli.md +0 -143
- package/.opencode/skills/intellisearch/references/google-search.md +0 -40
- package/.opencode/skills/intellisearch/references/search-workflow.md +0 -143
- package/.opencode/skills/intellisearch/references/workflow.md +0 -115
- package/.opencode/skills/openspec-apply-change/SKILL.md +0 -159
- package/.opencode/skills/openspec-archive-change/SKILL.md +0 -117
- package/.opencode/skills/openspec-explore/SKILL.md +0 -287
- package/.opencode/skills/openspec-propose/SKILL.md +0 -111
- package/.opencode/skills/openspec-sync-specs/SKILL.md +0 -147
- package/.serena/project.yml +0 -133
- package/AGENTS.md +0 -136
- package/CHANGELOG.md +0 -3
- package/bun.lock +0 -105
- package/docs/INSTALL.md +0 -185
- package/docs/PLAYWRIGHT_CLI_API.md +0 -306
- package/docs/python-release-for-reference.md +0 -175
- package/examples/sample_json_export.json +0 -32
- package/examples/sample_markdown_export.md +0 -221
- package/examples/usage_examples.sh +0 -222
- package/install.ps1 +0 -264
- package/install.sh +0 -275
- package/openspec/MAESTRO_MIGRATION.md +0 -180
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/design.md +0 -194
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/proposal.md +0 -52
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/specs/multi-profile-conversations/spec.md +0 -133
- package/openspec/changes/archive/2026-06-08-command-spec-conformance/tasks.md +0 -95
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/design.md +0 -28
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/proposal.md +0 -22
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/specs/interactive-prompt-loop/spec.md +0 -35
- package/openspec/changes/archive/2026-06-09-refactor-interactive-prompt-loop/tasks.md +0 -28
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/design.md +0 -323
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/proposal.md +0 -122
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/specs/no-capability-changes/spec.md +0 -29
- package/openspec/changes/archive/2026-06-09-replace-gemini-api-placeholder-with-gemini-reverse/tasks.md +0 -172
- package/openspec/changes/archive/2026-06-09-v2-install-migration/.openspec.yaml +0 -2
- package/openspec/changes/archive/2026-06-09-v2-install-migration/design.md +0 -241
- package/openspec/changes/archive/2026-06-09-v2-install-migration/proposal.md +0 -41
- package/openspec/changes/archive/2026-06-09-v2-install-migration/specs/v2-installer/spec.md +0 -205
- package/openspec/changes/archive/2026-06-09-v2-install-migration/tasks.md +0 -97
- package/openspec/changes/commander-cli-parser/.openspec.yaml +0 -2
- package/openspec/changes/commander-cli-parser/design.md +0 -107
- package/openspec/changes/commander-cli-parser/proposal.md +0 -31
- package/openspec/changes/commander-cli-parser/specs/cli/spec.md +0 -48
- package/openspec/changes/commander-cli-parser/tasks.md +0 -33
- package/openspec/changes/cross-platform-build-and-ci/.openspec.yaml +0 -2
- package/openspec/changes/cross-platform-build-and-ci/design.md +0 -213
- package/openspec/changes/cross-platform-build-and-ci/proposal.md +0 -50
- package/openspec/changes/cross-platform-build-and-ci/specs/path-and-file-mediation/spec.md +0 -335
- package/openspec/changes/cross-platform-build-and-ci/specs/release-pipeline/spec.md +0 -95
- package/openspec/changes/cross-platform-build-and-ci/tasks.md +0 -164
- package/openspec/config.yaml +0 -20
- package/openspec/specs/auth/spec.md +0 -394
- package/openspec/specs/cli/spec.md +0 -108
- package/openspec/specs/commands/spec.md +0 -409
- package/openspec/specs/configuration/spec.md +0 -130
- package/openspec/specs/conversations/spec.md +0 -139
- package/openspec/specs/domain-model/spec.md +0 -128
- package/openspec/specs/formatters/spec.md +0 -158
- package/openspec/specs/install-browser/spec.md +0 -146
- package/openspec/specs/interactive-prompt-loop/spec.md +0 -41
- package/openspec/specs/logger/spec.md +0 -71
- package/openspec/specs/mediator/spec.md +0 -72
- package/openspec/specs/multi-profile-conversations/spec.md +0 -145
- package/openspec/specs/path-utils/spec.md +0 -101
- package/openspec/specs/profiles/spec.md +0 -94
- package/openspec/specs/storage/spec.md +0 -198
- package/openspec/specs/testing/spec.md +0 -237
- package/openspec/specs/v2-installer/spec.md +0 -205
- package/openspec/specs/validators/spec.md +0 -65
- package/scripts/build.ts +0 -88
- package/scripts/clean-build.ps1 +0 -4
- package/scripts/clean-build.sh +0 -5
- package/scripts/install-browser.ps1 +0 -5
- package/scripts/install-browser.sh +0 -6
- package/scripts/lint-path-mediation.ps1 +0 -65
- package/scripts/lint-path-mediation.sh +0 -40
- package/tests/cli/auth-command.test.ts +0 -220
- package/tests/cli/command-registry.test.ts +0 -67
- package/tests/cli/continue-command.test.ts +0 -100
- package/tests/cli/delete-command.test.ts +0 -128
- package/tests/cli/export-all-command.test.ts +0 -375
- package/tests/cli/install-browser-command.test.ts +0 -48
- package/tests/cli/list-command.test.ts +0 -179
- package/tests/cli/new-command.test.ts +0 -139
- package/tests/cli/status-command.test.ts +0 -106
- package/tests/core/query-handlers.test.ts +0 -187
- package/tests/fixtures/auth-fixtures.ts +0 -98
- package/tests/fixtures/chat-fixtures.ts +0 -87
- package/tests/infrastructure/cli-table.test.ts +0 -107
- package/tests/infrastructure/formatters.test.ts +0 -202
- package/tests/infrastructure/io.test.ts +0 -197
- package/tests/infrastructure/logger.test.ts +0 -74
- package/tests/infrastructure/storage.test.ts +0 -335
- package/tests/infrastructure/validators.test.ts +0 -67
- package/tests/integration/commands/auth.test.ts +0 -186
- package/tests/integration/commands/continue.test.ts +0 -130
- package/tests/integration/commands/delete.test.ts +0 -140
- package/tests/integration/commands/export.test.ts +0 -266
- package/tests/integration/commands/fetch.test.ts +0 -238
- package/tests/integration/commands/list.test.ts +0 -281
- package/tests/integration/commands/profile.test.ts +0 -258
- package/tests/integration/commands/status.test.ts +0 -223
- package/tests/integration/installer-script-shape.test.ts +0 -67
- package/tests/parity/compare-outputs.ts +0 -359
- package/tests/parity/test-commands-parity.ps1 +0 -82
- package/tests/parity/test-commands-parity.sh +0 -61
- package/tests/services/auth-service.test.ts +0 -315
- package/tests/services/cookie-monitor.test.ts +0 -229
- package/tests/services/cookie-storage-service.test.ts +0 -230
- package/tests/services/gemini-client-wrapper.test.ts +0 -688
- package/tests/services/install-browser-service.test.ts +0 -83
- package/tests/services/playwright-cli-driver.test.ts +0 -468
- package/tests/services/profile-auth-manager.test.ts +0 -334
- package/tests/services/profile-service.test.ts +0 -224
- package/tests/setup.ts +0 -95
- package/tests/smoke/smoke.test.ts +0 -52
- package/tests/unit/config.test.ts +0 -159
- package/tests/unit/errors.test.ts +0 -119
- package/tests/unit/formatters.test.ts +0 -316
- package/tests/unit/mediator.test.ts +0 -152
- package/tests/unit/path-utils.test.ts +0 -289
- package/tsconfig.json +0 -16
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, mock, spyOn } from "bun:test";
|
|
2
|
-
import { InstallBrowserService, InstallBrowserError } from "../../src/services/install-browser-service.ts";
|
|
3
|
-
|
|
4
|
-
describe("InstallBrowserService", () => {
|
|
5
|
-
let service: InstallBrowserService;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
service = new InstallBrowserService();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
describe("findSystemBrowser", () => {
|
|
12
|
-
test("returns BrowserCheckResult with found=true on Windows with Edge", () => {
|
|
13
|
-
const originalPlatform = process.platform;
|
|
14
|
-
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
|
|
15
|
-
|
|
16
|
-
const result = service.findSystemBrowser();
|
|
17
|
-
expect(typeof result.found).toBe("boolean");
|
|
18
|
-
expect(typeof result.browserName).toBe("string");
|
|
19
|
-
|
|
20
|
-
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("returns BrowserCheckResult on linux", () => {
|
|
24
|
-
const originalPlatform = process.platform;
|
|
25
|
-
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
|
|
26
|
-
|
|
27
|
-
const result = service.findSystemBrowser();
|
|
28
|
-
expect(typeof result.found).toBe("boolean");
|
|
29
|
-
expect(typeof result.browserName).toBe("string");
|
|
30
|
-
|
|
31
|
-
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("returns not found for unknown platform", () => {
|
|
35
|
-
const originalPlatform = process.platform;
|
|
36
|
-
Object.defineProperty(process, "platform", { value: "aix", configurable: true });
|
|
37
|
-
|
|
38
|
-
const result = service.findSystemBrowser();
|
|
39
|
-
expect(result.found).toBe(false);
|
|
40
|
-
expect(result.browserName).toBe("none");
|
|
41
|
-
|
|
42
|
-
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("install", () => {
|
|
47
|
-
test("throws InstallBrowserError when install fails", async () => {
|
|
48
|
-
const findSpy = spyOn(service, "findSystemBrowser").mockReturnValue({ found: false, browserName: "none" });
|
|
49
|
-
const runInstallSpy = spyOn(service as any, "runInstall").mockImplementation(() => {
|
|
50
|
-
throw new Error("spawn failed");
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await expect(service.install()).rejects.toBeInstanceOf(InstallBrowserError);
|
|
54
|
-
runInstallSpy.mockRestore();
|
|
55
|
-
findSpy.mockRestore();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("resolves successfully when install succeeds", async () => {
|
|
59
|
-
const runInstallSpy = spyOn(service as any, "runInstall").mockResolvedValue("Chromium downloaded");
|
|
60
|
-
const findSpy = spyOn(service, "findSystemBrowser").mockReturnValue({ found: false, browserName: "none" });
|
|
61
|
-
|
|
62
|
-
await expect(service.install()).resolves.toBeUndefined();
|
|
63
|
-
findSpy.mockRestore();
|
|
64
|
-
runInstallSpy.mockRestore();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("InstallBrowserError", () => {
|
|
70
|
-
test("has correct name and message", () => {
|
|
71
|
-
const error = new InstallBrowserError("test error");
|
|
72
|
-
expect(error.name).toBe("InstallBrowserError");
|
|
73
|
-
expect(error.message).toBe("test error");
|
|
74
|
-
expect(error.cause).toBeUndefined();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test("preserves cause", () => {
|
|
78
|
-
const cause = new Error("original");
|
|
79
|
-
const error = new InstallBrowserError("wrapper", cause);
|
|
80
|
-
expect(error.cause).toBe(cause);
|
|
81
|
-
expect(error.message).toBe("wrapper");
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, mock } from "bun:test";
|
|
2
|
-
import { writeFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
PlaywrightCliDriver,
|
|
5
|
-
PlaywrightCliError,
|
|
6
|
-
type PlaywrightRunner,
|
|
7
|
-
type PlaywrightRunnerResult,
|
|
8
|
-
type PlaywrightStrategy,
|
|
9
|
-
} from "../../src/services/playwright-cli-driver.ts";
|
|
10
|
-
|
|
11
|
-
function createMockRunner(strategy: PlaywrightStrategy = "direct"): PlaywrightRunner & {
|
|
12
|
-
_run: ReturnType<typeof mock>;
|
|
13
|
-
_spawnDetached: ReturnType<typeof mock>;
|
|
14
|
-
_results: PlaywrightRunnerResult[];
|
|
15
|
-
} {
|
|
16
|
-
const _run = mock(async (_args: string[]) => ({ exitCode: 0, stdout: "1.0.0", stderr: "" }));
|
|
17
|
-
const _spawnDetached = mock((_args: string[]) => {});
|
|
18
|
-
return {
|
|
19
|
-
strategy,
|
|
20
|
-
_run,
|
|
21
|
-
_spawnDetached,
|
|
22
|
-
_results: [],
|
|
23
|
-
async run(args) {
|
|
24
|
-
const result = await _run(args);
|
|
25
|
-
return result;
|
|
26
|
-
},
|
|
27
|
-
spawnDetached(args) {
|
|
28
|
-
_spawnDetached(args);
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe("PlaywrightCliDriver", () => {
|
|
34
|
-
let driver: PlaywrightCliDriver;
|
|
35
|
-
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
driver = new PlaywrightCliDriver({ runner: createMockRunner() });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe("withSession", () => {
|
|
41
|
-
test("prepends -s flag to args", () => {
|
|
42
|
-
const result = driver.withSession("my-session", ["eval", "() => 1"]);
|
|
43
|
-
expect(result).toEqual(["-s=my-session", "eval", "() => 1"]);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("works with empty args", () => {
|
|
47
|
-
const result = driver.withSession("s1", []);
|
|
48
|
-
expect(result).toEqual(["-s=s1"]);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
describe("runCli", () => {
|
|
53
|
-
test("resolves with stdout on successful exit", async () => {
|
|
54
|
-
const runner = createMockRunner();
|
|
55
|
-
runner._run.mockResolvedValueOnce({ exitCode: 0, stdout: "v1.2.3", stderr: "" });
|
|
56
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
57
|
-
|
|
58
|
-
const result = await d.runCli(["--version"]);
|
|
59
|
-
expect(result).toBe("v1.2.3");
|
|
60
|
-
expect(runner._run).toHaveBeenCalledWith(["--version"]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("rejects with PlaywrightCliError on non-zero exit", async () => {
|
|
64
|
-
const runner = createMockRunner();
|
|
65
|
-
runner._run.mockResolvedValueOnce({ exitCode: 1, stdout: "", stderr: "boom" });
|
|
66
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
67
|
-
|
|
68
|
-
await expect(d.runCli(["nonexistent-command"])).rejects.toBeInstanceOf(
|
|
69
|
-
PlaywrightCliError,
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("PlaywrightCliError has correct properties", async () => {
|
|
74
|
-
const runner = createMockRunner();
|
|
75
|
-
runner._run.mockResolvedValueOnce({ exitCode: 2, stdout: "", stderr: "nope" });
|
|
76
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
await d.runCli(["nonexistent-command"]);
|
|
80
|
-
expect.unreachable("Should have thrown");
|
|
81
|
-
} catch (err) {
|
|
82
|
-
expect(err).toBeInstanceOf(PlaywrightCliError);
|
|
83
|
-
const e = err as PlaywrightCliError;
|
|
84
|
-
expect(e.name).toBe("PlaywrightCliError");
|
|
85
|
-
expect(e.message).toContain("nonexistent-command");
|
|
86
|
-
expect(e.message).toContain("nope");
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe("openHeaded", () => {
|
|
92
|
-
test("constructs correct args without session", () => {
|
|
93
|
-
const d = new PlaywrightCliDriver({
|
|
94
|
-
runner: createMockRunner(),
|
|
95
|
-
profileDirResolver: (name) => `/abs/path/to/${name}`,
|
|
96
|
-
});
|
|
97
|
-
const args = d.buildOpenHeadedArgs("https://gemini.google.com/app", "my-profile");
|
|
98
|
-
expect(args).toContain("open");
|
|
99
|
-
expect(args).toContain("https://gemini.google.com/app");
|
|
100
|
-
expect(args).toContain("--browser=chromium");
|
|
101
|
-
expect(args).toContain("--headed");
|
|
102
|
-
expect(args).toContain("--persistent");
|
|
103
|
-
expect(args).toContain("--profile=/abs/path/to/my-profile");
|
|
104
|
-
expect(args).not.toContain(expect.stringContaining("-s="));
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("includes session flag when provided", () => {
|
|
108
|
-
const args = driver.buildOpenHeadedArgs(
|
|
109
|
-
"https://gemini.google.com/app",
|
|
110
|
-
"my-profile",
|
|
111
|
-
"my-session",
|
|
112
|
-
);
|
|
113
|
-
expect(args[0]).toBe("-s=my-session");
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("resolves profile to absolute path via injected resolver", () => {
|
|
117
|
-
const resolver = mock((name: string) => `/custom/root/${name}`);
|
|
118
|
-
const d = new PlaywrightCliDriver({ runner: createMockRunner(), profileDirResolver: resolver });
|
|
119
|
-
const args = d.buildOpenHeadedArgs("https://gemini.google.com/app", "p1");
|
|
120
|
-
expect(args).toContain("--profile=/custom/root/p1");
|
|
121
|
-
expect(resolver).toHaveBeenCalledWith("p1");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("openHeaded awaits runCli with URL as last arg", async () => {
|
|
125
|
-
const runner = createMockRunner();
|
|
126
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
127
|
-
await d.openHeaded("https://gemini.google.com/app", "p1", "s1");
|
|
128
|
-
expect(runner._run).toHaveBeenCalledTimes(1);
|
|
129
|
-
const args = runner._run.mock.calls[0]![0] as string[];
|
|
130
|
-
expect(args[0]).toBe("-s=s1");
|
|
131
|
-
expect(args).toContain("open");
|
|
132
|
-
expect(args[args.length - 1]).toBe("https://gemini.google.com/app");
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("openHeaded throws PlaywrightCliError when open command fails", async () => {
|
|
136
|
-
const runner = createMockRunner();
|
|
137
|
-
runner._run.mockResolvedValueOnce({ exitCode: 1, stdout: "", stderr: "open failed" });
|
|
138
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
139
|
-
await expect(d.openHeaded("https://gemini.google.com/app", "p1", "s1")).rejects.toBeInstanceOf(PlaywrightCliError);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
describe("evalJs", () => {
|
|
144
|
-
test("passes expression with session and --raw flag", async () => {
|
|
145
|
-
const runner = createMockRunner();
|
|
146
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
147
|
-
expect(args).toEqual(["-s=test-session", "eval", "() => document.title", "--raw"]);
|
|
148
|
-
return { exitCode: 0, stdout: '"Gemini"', stderr: "" };
|
|
149
|
-
});
|
|
150
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
151
|
-
|
|
152
|
-
const result = await d.evalJs("test-session", "() => document.title");
|
|
153
|
-
expect(result).toBe('"Gemini"');
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
describe("cookieList", () => {
|
|
158
|
-
test("parses valid JSON cookie output", async () => {
|
|
159
|
-
const cookieData = JSON.stringify([
|
|
160
|
-
{
|
|
161
|
-
name: "__Secure-1PSID",
|
|
162
|
-
value: "abc123",
|
|
163
|
-
domain: ".google.com",
|
|
164
|
-
path: "/",
|
|
165
|
-
expires: 1893456000,
|
|
166
|
-
httpOnly: true,
|
|
167
|
-
secure: true,
|
|
168
|
-
sameSite: "None",
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: "__Secure-1PSIDTS",
|
|
172
|
-
value: "xyz789",
|
|
173
|
-
domain: ".google.com",
|
|
174
|
-
path: "/",
|
|
175
|
-
expires: 1893456000,
|
|
176
|
-
httpOnly: true,
|
|
177
|
-
secure: true,
|
|
178
|
-
sameSite: "Lax",
|
|
179
|
-
},
|
|
180
|
-
]);
|
|
181
|
-
|
|
182
|
-
const runner = createMockRunner();
|
|
183
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
184
|
-
expect(args).toEqual(["-s=sess1", "cookie-list", "--json"]);
|
|
185
|
-
return { exitCode: 0, stdout: cookieData, stderr: "" };
|
|
186
|
-
});
|
|
187
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
188
|
-
|
|
189
|
-
const cookies = await d.cookieList("sess1");
|
|
190
|
-
expect(cookies).toHaveLength(2);
|
|
191
|
-
expect(cookies[0].name).toBe("__Secure-1PSID");
|
|
192
|
-
expect(cookies[0].value).toBe("abc123");
|
|
193
|
-
expect(cookies[0].secure).toBe(true);
|
|
194
|
-
expect(cookies[1].sameSite).toBe("Lax");
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("returns empty array for invalid JSON", async () => {
|
|
198
|
-
const runner = createMockRunner();
|
|
199
|
-
runner._run.mockResolvedValueOnce({ exitCode: 0, stdout: "not-json", stderr: "" });
|
|
200
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
201
|
-
|
|
202
|
-
const cookies = await d.cookieList("sess1");
|
|
203
|
-
expect(cookies).toEqual([]);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("returns empty array for non-array JSON", async () => {
|
|
207
|
-
const runner = createMockRunner();
|
|
208
|
-
runner._run.mockResolvedValueOnce({ exitCode: 0, stdout: '{"error": "no cookies"}', stderr: "" });
|
|
209
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
210
|
-
|
|
211
|
-
const cookies = await d.cookieList("sess1");
|
|
212
|
-
expect(cookies).toEqual([]);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("parses envelope { result: '<json-string>' } shape", async () => {
|
|
216
|
-
const innerArray = [
|
|
217
|
-
{
|
|
218
|
-
name: "__Secure-1PSID",
|
|
219
|
-
value: "abc123",
|
|
220
|
-
domain: ".google.com",
|
|
221
|
-
path: "/",
|
|
222
|
-
expires: 1893456000,
|
|
223
|
-
httpOnly: true,
|
|
224
|
-
secure: true,
|
|
225
|
-
sameSite: "None",
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
name: "__Secure-1PSIDTS",
|
|
229
|
-
value: "xyz789",
|
|
230
|
-
domain: ".google.com",
|
|
231
|
-
path: "/",
|
|
232
|
-
expires: 1893456000,
|
|
233
|
-
httpOnly: true,
|
|
234
|
-
secure: true,
|
|
235
|
-
sameSite: "Lax",
|
|
236
|
-
},
|
|
237
|
-
];
|
|
238
|
-
const envelope = JSON.stringify({ result: JSON.stringify(innerArray), raw: "{}" });
|
|
239
|
-
const runner = createMockRunner();
|
|
240
|
-
runner._run.mockResolvedValueOnce({ exitCode: 0, stdout: envelope, stderr: "" });
|
|
241
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
242
|
-
|
|
243
|
-
const cookies = await d.cookieList("sess1");
|
|
244
|
-
expect(cookies).toHaveLength(2);
|
|
245
|
-
expect(cookies[0].name).toBe("__Secure-1PSID");
|
|
246
|
-
expect(cookies[0].value).toBe("abc123");
|
|
247
|
-
expect(cookies[0].secure).toBe(true);
|
|
248
|
-
expect(cookies[1].sameSite).toBe("Lax");
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test("returns empty array for envelope with 'No cookies found'", async () => {
|
|
252
|
-
const envelope = JSON.stringify({ result: "No cookies found", raw: "No cookies found" });
|
|
253
|
-
const runner = createMockRunner();
|
|
254
|
-
runner._run.mockResolvedValueOnce({ exitCode: 0, stdout: envelope, stderr: "" });
|
|
255
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
256
|
-
|
|
257
|
-
const cookies = await d.cookieList("sess1");
|
|
258
|
-
expect(cookies).toEqual([]);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test("handles cookies missing fields with defaults", async () => {
|
|
262
|
-
const runner = createMockRunner();
|
|
263
|
-
runner._run.mockResolvedValueOnce({
|
|
264
|
-
exitCode: 0,
|
|
265
|
-
stdout: JSON.stringify([{ name: "test-cookie", value: "val" }]),
|
|
266
|
-
stderr: "",
|
|
267
|
-
});
|
|
268
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
269
|
-
|
|
270
|
-
const cookies = await d.cookieList("sess1");
|
|
271
|
-
expect(cookies).toHaveLength(1);
|
|
272
|
-
expect(cookies[0].name).toBe("test-cookie");
|
|
273
|
-
expect(cookies[0].domain).toBe("");
|
|
274
|
-
expect(cookies[0].path).toBe("/");
|
|
275
|
-
expect(cookies[0].expires).toBe(-1);
|
|
276
|
-
expect(cookies[0].httpOnly).toBe(false);
|
|
277
|
-
expect(cookies[0].secure).toBe(false);
|
|
278
|
-
expect(cookies[0].sameSite).toBe("None");
|
|
279
|
-
});
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
describe("stateSave", () => {
|
|
283
|
-
test("passes correct args", async () => {
|
|
284
|
-
const runner = createMockRunner();
|
|
285
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
286
|
-
expect(args).toEqual(["-s=sess1", "state-save", "/tmp/state.json"]);
|
|
287
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
288
|
-
});
|
|
289
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
290
|
-
|
|
291
|
-
await d.stateSave("sess1", "/tmp/state.json");
|
|
292
|
-
expect(runner._run).toHaveBeenCalledTimes(1);
|
|
293
|
-
});
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
describe("stateLoad", () => {
|
|
297
|
-
test("passes correct args", async () => {
|
|
298
|
-
const runner = createMockRunner();
|
|
299
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
300
|
-
expect(args).toEqual(["-s=sess1", "state-load", "/tmp/state.json"]);
|
|
301
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
302
|
-
});
|
|
303
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
304
|
-
|
|
305
|
-
await d.stateLoad("sess1", "/tmp/state.json");
|
|
306
|
-
expect(runner._run).toHaveBeenCalledTimes(1);
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
describe("cookieListFromState", () => {
|
|
311
|
-
test("invokes state-save with session, parses JSON file, and returns cookies with expires", async () => {
|
|
312
|
-
const storageState = {
|
|
313
|
-
cookies: [
|
|
314
|
-
{
|
|
315
|
-
name: "__Secure-1PSID",
|
|
316
|
-
value: "abc",
|
|
317
|
-
domain: ".google.com",
|
|
318
|
-
path: "/",
|
|
319
|
-
expires: 1893456000,
|
|
320
|
-
httpOnly: true,
|
|
321
|
-
secure: true,
|
|
322
|
-
sameSite: "None",
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
name: "__Secure-1PSIDTS",
|
|
326
|
-
value: "xyz",
|
|
327
|
-
domain: ".google.com",
|
|
328
|
-
path: "/",
|
|
329
|
-
expires: 1893456000.5,
|
|
330
|
-
httpOnly: true,
|
|
331
|
-
secure: true,
|
|
332
|
-
sameSite: "Lax",
|
|
333
|
-
},
|
|
334
|
-
],
|
|
335
|
-
origins: [],
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
let savedPath = "";
|
|
339
|
-
const runner = createMockRunner();
|
|
340
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
341
|
-
const stateIdx = args.indexOf("state-save");
|
|
342
|
-
savedPath = args[stateIdx + 1] ?? "";
|
|
343
|
-
writeFileSync(savedPath, JSON.stringify(storageState), "utf-8");
|
|
344
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
345
|
-
});
|
|
346
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
347
|
-
|
|
348
|
-
const cookies = await d.cookieListFromState("sess1");
|
|
349
|
-
|
|
350
|
-
expect(runner._run).toHaveBeenCalledTimes(1);
|
|
351
|
-
const calledArgs = runner._run.mock.calls[0]![0] as string[];
|
|
352
|
-
expect(calledArgs[0]).toBe("-s=sess1");
|
|
353
|
-
expect(calledArgs[1]).toBe("state-save");
|
|
354
|
-
expect(typeof savedPath).toBe("string");
|
|
355
|
-
expect(savedPath.length).toBeGreaterThan(0);
|
|
356
|
-
|
|
357
|
-
expect(cookies).toHaveLength(2);
|
|
358
|
-
expect(cookies[0]!.name).toBe("__Secure-1PSID");
|
|
359
|
-
expect(cookies[0]!.expires).toBe(1893456000);
|
|
360
|
-
expect(cookies[1]!.name).toBe("__Secure-1PSIDTS");
|
|
361
|
-
expect(cookies[1]!.expires).toBe(1893456000.5);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test("returns empty array when storage state has no cookies", async () => {
|
|
365
|
-
const runner = createMockRunner();
|
|
366
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
367
|
-
const stateIdx = args.indexOf("state-save");
|
|
368
|
-
const savedPath = args[stateIdx + 1] ?? "";
|
|
369
|
-
writeFileSync(savedPath, JSON.stringify({ cookies: [], origins: [] }), "utf-8");
|
|
370
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
371
|
-
});
|
|
372
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
373
|
-
|
|
374
|
-
const cookies = await d.cookieListFromState("sess1");
|
|
375
|
-
expect(cookies).toEqual([]);
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("cleans up the temp file even on read errors", async () => {
|
|
379
|
-
let savedPath = "";
|
|
380
|
-
const runner = createMockRunner();
|
|
381
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
382
|
-
savedPath = args[args.indexOf("state-save") + 1] ?? "";
|
|
383
|
-
writeFileSync(savedPath, "{not valid json", "utf-8");
|
|
384
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
385
|
-
});
|
|
386
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
387
|
-
|
|
388
|
-
await expect(d.cookieListFromState("sess1")).rejects.toThrow();
|
|
389
|
-
let stillExists = true;
|
|
390
|
-
try {
|
|
391
|
-
const { existsSync } = await import("node:fs");
|
|
392
|
-
stillExists = existsSync(savedPath);
|
|
393
|
-
} catch {
|
|
394
|
-
stillExists = false;
|
|
395
|
-
}
|
|
396
|
-
expect(stillExists).toBe(false);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
test("propagates state-save failures", async () => {
|
|
400
|
-
const runner = createMockRunner();
|
|
401
|
-
runner._run.mockResolvedValueOnce({ exitCode: 1, stdout: "", stderr: "save failed" });
|
|
402
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
403
|
-
|
|
404
|
-
await expect(d.cookieListFromState("sess1")).rejects.toBeInstanceOf(PlaywrightCliError);
|
|
405
|
-
});
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
describe("closeSession", () => {
|
|
409
|
-
test("passes correct args", async () => {
|
|
410
|
-
const runner = createMockRunner();
|
|
411
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
412
|
-
expect(args).toEqual(["-s=sess1", "close"]);
|
|
413
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
414
|
-
});
|
|
415
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
416
|
-
|
|
417
|
-
await d.closeSession("sess1");
|
|
418
|
-
expect(runner._run).toHaveBeenCalledTimes(1);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
test("swallows 'not found' errors (session already closed)", async () => {
|
|
422
|
-
const runner = createMockRunner();
|
|
423
|
-
runner._run.mockResolvedValueOnce({
|
|
424
|
-
exitCode: 1,
|
|
425
|
-
stdout: "",
|
|
426
|
-
stderr: "session not found",
|
|
427
|
-
});
|
|
428
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
429
|
-
|
|
430
|
-
await expect(d.closeSession("sess1")).resolves.toBeUndefined();
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
test("propagates other PlaywrightCliError failures", async () => {
|
|
434
|
-
const runner = createMockRunner();
|
|
435
|
-
runner._run.mockResolvedValueOnce({
|
|
436
|
-
exitCode: 1,
|
|
437
|
-
stdout: "",
|
|
438
|
-
stderr: "something else failed",
|
|
439
|
-
});
|
|
440
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
441
|
-
|
|
442
|
-
await expect(d.closeSession("sess1")).rejects.toBeInstanceOf(PlaywrightCliError);
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
describe("closeAll", () => {
|
|
447
|
-
test("passes correct args without session", async () => {
|
|
448
|
-
const runner = createMockRunner();
|
|
449
|
-
runner._run.mockImplementationOnce(async (args) => {
|
|
450
|
-
expect(args).toEqual(["close-all"]);
|
|
451
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
452
|
-
});
|
|
453
|
-
const d = new PlaywrightCliDriver({ runner });
|
|
454
|
-
|
|
455
|
-
await d.closeAll();
|
|
456
|
-
expect(runner._run).toHaveBeenCalledTimes(1);
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
describe("strategy", () => {
|
|
461
|
-
test("exposes the runner's strategy", () => {
|
|
462
|
-
const d1 = new PlaywrightCliDriver({ runner: createMockRunner("direct") });
|
|
463
|
-
expect(d1.strategy).toBe("direct");
|
|
464
|
-
const d2 = new PlaywrightCliDriver({ runner: createMockRunner("bunx") });
|
|
465
|
-
expect(d2.strategy).toBe("bunx");
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
});
|