btcp-browser-agent 0.1.0 → 0.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/package.json +8 -9
- package/packages/core/dist/actions.d.ts +97 -0
- package/packages/core/dist/actions.js +940 -0
- package/packages/core/dist/errors.d.ts +138 -0
- package/packages/core/dist/errors.js +157 -0
- package/packages/core/dist/index.d.ts +120 -0
- package/packages/core/dist/index.js +134 -0
- package/packages/core/dist/ref-map.d.ts +16 -0
- package/packages/core/dist/ref-map.js +91 -0
- package/packages/core/dist/snapshot.d.ts +37 -0
- package/packages/core/dist/snapshot.js +751 -0
- package/packages/core/dist/types.d.ts +396 -0
- package/packages/core/dist/types.js +7 -0
- package/packages/extension/dist/background.d.ts +227 -0
- package/packages/extension/dist/background.js +737 -0
- package/packages/extension/dist/content.d.ts +18 -0
- package/packages/extension/dist/content.js +149 -0
- package/packages/extension/dist/index.d.ts +228 -0
- package/packages/extension/dist/index.js +350 -0
- package/packages/extension/dist/session-manager.d.ts +87 -0
- package/packages/extension/dist/session-manager.js +322 -0
- package/packages/extension/{src/session-types.ts → dist/session-types.d.ts} +113 -144
- package/packages/extension/dist/session-types.js +5 -0
- package/packages/extension/dist/types.d.ts +88 -0
- package/packages/extension/dist/types.js +7 -0
- package/CLAUDE.md +0 -230
- package/SKILL.md +0 -143
- package/SNAPSHOT_IMPROVEMENTS.md +0 -302
- package/USAGE.md +0 -146
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/docs/browser-cli-design.md +0 -500
- package/examples/chrome-extension/CHANGELOG.md +0 -210
- package/examples/chrome-extension/DEBUG.md +0 -231
- package/examples/chrome-extension/ERROR_FIXED.md +0 -147
- package/examples/chrome-extension/QUICK_TEST.md +0 -189
- package/examples/chrome-extension/README.md +0 -149
- package/examples/chrome-extension/SESSION_ONLY_MODE.md +0 -305
- package/examples/chrome-extension/TEST_WITH_YOUR_TABS.md +0 -97
- package/examples/chrome-extension/build.js +0 -43
- package/examples/chrome-extension/manifest.json +0 -37
- package/examples/chrome-extension/package-lock.json +0 -1063
- package/examples/chrome-extension/package.json +0 -21
- package/examples/chrome-extension/popup.html +0 -195
- package/examples/chrome-extension/src/background.ts +0 -12
- package/examples/chrome-extension/src/content.ts +0 -7
- package/examples/chrome-extension/src/popup.ts +0 -303
- package/examples/chrome-extension/src/scenario-google-github.ts +0 -389
- package/examples/chrome-extension/test-page.html +0 -127
- package/examples/chrome-extension/tests/README.md +0 -206
- package/examples/chrome-extension/tests/scenario-google-to-github-star.ts +0 -380
- package/examples/chrome-extension/tsconfig.json +0 -14
- package/examples/snapshots/README.md +0 -207
- package/examples/snapshots/amazon-com-detail.html +0 -9528
- package/examples/snapshots/amazon-com-detail.snapshot.txt +0 -997
- package/examples/snapshots/convert-snapshots.ts +0 -97
- package/examples/snapshots/edition-cnn-com.html +0 -13292
- package/examples/snapshots/edition-cnn-com.snapshot.txt +0 -562
- package/examples/snapshots/github-com-microsoft-vscode.html +0 -2916
- package/examples/snapshots/github-com-microsoft-vscode.snapshot.txt +0 -455
- package/examples/snapshots/google-search.html +0 -20012
- package/examples/snapshots/google-search.snapshot.txt +0 -195
- package/examples/snapshots/metadata.json +0 -86
- package/examples/snapshots/npr-org-templates.html +0 -2031
- package/examples/snapshots/npr-org-templates.snapshot.txt +0 -224
- package/examples/snapshots/stackoverflow-com.html +0 -5216
- package/examples/snapshots/stackoverflow-com.snapshot.txt +0 -2404
- package/examples/snapshots/test-all-mode.html +0 -46
- package/examples/snapshots/test-all-mode.snapshot.txt +0 -5
- package/examples/snapshots/validate.test.ts +0 -296
- package/packages/cli/package.json +0 -42
- package/packages/cli/src/__tests__/cli.test.ts +0 -434
- package/packages/cli/src/__tests__/errors.test.ts +0 -226
- package/packages/cli/src/__tests__/executor.test.ts +0 -275
- package/packages/cli/src/__tests__/formatter.test.ts +0 -260
- package/packages/cli/src/__tests__/parser.test.ts +0 -288
- package/packages/cli/src/__tests__/suggestions.test.ts +0 -255
- package/packages/cli/src/commands/back.ts +0 -22
- package/packages/cli/src/commands/check.ts +0 -33
- package/packages/cli/src/commands/clear.ts +0 -33
- package/packages/cli/src/commands/click.ts +0 -32
- package/packages/cli/src/commands/closetab.ts +0 -31
- package/packages/cli/src/commands/eval.ts +0 -41
- package/packages/cli/src/commands/fill.ts +0 -30
- package/packages/cli/src/commands/focus.ts +0 -33
- package/packages/cli/src/commands/forward.ts +0 -22
- package/packages/cli/src/commands/goto.ts +0 -34
- package/packages/cli/src/commands/help.ts +0 -162
- package/packages/cli/src/commands/hover.ts +0 -34
- package/packages/cli/src/commands/index.ts +0 -129
- package/packages/cli/src/commands/newtab.ts +0 -35
- package/packages/cli/src/commands/press.ts +0 -40
- package/packages/cli/src/commands/reload.ts +0 -25
- package/packages/cli/src/commands/screenshot.ts +0 -27
- package/packages/cli/src/commands/scroll.ts +0 -64
- package/packages/cli/src/commands/select.ts +0 -35
- package/packages/cli/src/commands/snapshot.ts +0 -21
- package/packages/cli/src/commands/tab.ts +0 -32
- package/packages/cli/src/commands/tabs.ts +0 -26
- package/packages/cli/src/commands/text.ts +0 -27
- package/packages/cli/src/commands/title.ts +0 -17
- package/packages/cli/src/commands/type.ts +0 -38
- package/packages/cli/src/commands/uncheck.ts +0 -33
- package/packages/cli/src/commands/url.ts +0 -17
- package/packages/cli/src/commands/wait.ts +0 -54
- package/packages/cli/src/errors.ts +0 -164
- package/packages/cli/src/executor.ts +0 -68
- package/packages/cli/src/formatter.ts +0 -215
- package/packages/cli/src/index.ts +0 -257
- package/packages/cli/src/parser.ts +0 -195
- package/packages/cli/src/suggestions.ts +0 -207
- package/packages/cli/src/terminal/Terminal.ts +0 -365
- package/packages/cli/src/terminal/index.ts +0 -5
- package/packages/cli/src/types.ts +0 -155
- package/packages/cli/tsconfig.json +0 -20
- package/packages/core/package.json +0 -35
- package/packages/core/src/actions.ts +0 -1210
- package/packages/core/src/errors.ts +0 -296
- package/packages/core/src/index.test.ts +0 -638
- package/packages/core/src/index.ts +0 -220
- package/packages/core/src/ref-map.ts +0 -107
- package/packages/core/src/snapshot.ts +0 -873
- package/packages/core/src/types.ts +0 -536
- package/packages/core/tsconfig.json +0 -23
- package/packages/extension/README.md +0 -129
- package/packages/extension/package.json +0 -43
- package/packages/extension/src/background.ts +0 -888
- package/packages/extension/src/content.ts +0 -172
- package/packages/extension/src/index.ts +0 -579
- package/packages/extension/src/session-manager.ts +0 -385
- package/packages/extension/src/types.ts +0 -162
- package/packages/extension/tsconfig.json +0 -28
- package/src/index.ts +0 -64
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -26
- package/vitest.config.ts +0 -13
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Executor tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import { executeCommand, executeCommandString } from '../executor.js';
|
|
7
|
-
import { parseCommand } from '../parser.js';
|
|
8
|
-
import { CommandNotFoundError, ExecutionError, CLIError } from '../errors.js';
|
|
9
|
-
import type { CommandClient, ParsedCommand } from '../types.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Create a mock client for testing
|
|
13
|
-
*/
|
|
14
|
-
function createMockClient(overrides: Partial<CommandClient> = {}): CommandClient {
|
|
15
|
-
return {
|
|
16
|
-
execute: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
17
|
-
navigate: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
18
|
-
back: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
19
|
-
forward: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
20
|
-
reload: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
21
|
-
getUrl: vi.fn().mockResolvedValue('https://example.com'),
|
|
22
|
-
getTitle: vi.fn().mockResolvedValue('Example Page'),
|
|
23
|
-
snapshot: vi.fn().mockResolvedValue('- button "Submit" [@ref:1]'),
|
|
24
|
-
click: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
25
|
-
type: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
26
|
-
fill: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
27
|
-
getText: vi.fn().mockResolvedValue('Hello'),
|
|
28
|
-
isVisible: vi.fn().mockResolvedValue(true),
|
|
29
|
-
screenshot: vi.fn().mockResolvedValue('data:image/png;base64,ABC123'),
|
|
30
|
-
tabNew: vi.fn().mockResolvedValue({ tabId: 123, url: 'about:blank' }),
|
|
31
|
-
tabClose: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
32
|
-
tabSwitch: vi.fn().mockResolvedValue({ id: '1', success: true }),
|
|
33
|
-
tabList: vi.fn().mockResolvedValue([
|
|
34
|
-
{ id: 1, url: 'https://example.com', title: 'Example', active: true, index: 0 },
|
|
35
|
-
]),
|
|
36
|
-
...overrides,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
describe('executeCommand', () => {
|
|
41
|
-
let client: CommandClient;
|
|
42
|
-
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
client = createMockClient();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('command lookup', () => {
|
|
48
|
-
it('executes valid command', async () => {
|
|
49
|
-
const command: ParsedCommand = {
|
|
50
|
-
name: 'snapshot',
|
|
51
|
-
args: [],
|
|
52
|
-
flags: {},
|
|
53
|
-
raw: 'snapshot',
|
|
54
|
-
};
|
|
55
|
-
const result = await executeCommand(client, command);
|
|
56
|
-
expect(result.success).toBe(true);
|
|
57
|
-
expect(client.snapshot).toHaveBeenCalled();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('throws CommandNotFoundError for unknown command', async () => {
|
|
61
|
-
const command: ParsedCommand = {
|
|
62
|
-
name: 'unknowncommand',
|
|
63
|
-
args: [],
|
|
64
|
-
flags: {},
|
|
65
|
-
raw: 'unknowncommand',
|
|
66
|
-
};
|
|
67
|
-
await expect(executeCommand(client, command)).rejects.toThrow(CommandNotFoundError);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('includes similar commands in CommandNotFoundError', async () => {
|
|
71
|
-
const command: ParsedCommand = {
|
|
72
|
-
name: 'clck',
|
|
73
|
-
args: [],
|
|
74
|
-
flags: {},
|
|
75
|
-
raw: 'clck',
|
|
76
|
-
};
|
|
77
|
-
try {
|
|
78
|
-
await executeCommand(client, command);
|
|
79
|
-
expect.fail('Should have thrown');
|
|
80
|
-
} catch (error) {
|
|
81
|
-
expect(error).toBeInstanceOf(CommandNotFoundError);
|
|
82
|
-
const cmdError = error as CommandNotFoundError;
|
|
83
|
-
expect(cmdError.suggestions).toBeDefined();
|
|
84
|
-
expect(cmdError.suggestions?.some((s) => s.includes('click'))).toBe(true);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('command execution', () => {
|
|
90
|
-
it('passes args to command handler', async () => {
|
|
91
|
-
const command: ParsedCommand = {
|
|
92
|
-
name: 'goto',
|
|
93
|
-
args: ['https://example.com'],
|
|
94
|
-
flags: {},
|
|
95
|
-
raw: 'goto https://example.com',
|
|
96
|
-
};
|
|
97
|
-
await executeCommand(client, command);
|
|
98
|
-
expect(client.navigate).toHaveBeenCalledWith('https://example.com');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('passes flags to command handler', async () => {
|
|
102
|
-
const command: ParsedCommand = {
|
|
103
|
-
name: 'reload',
|
|
104
|
-
args: [],
|
|
105
|
-
flags: { hard: true },
|
|
106
|
-
raw: 'reload --hard',
|
|
107
|
-
};
|
|
108
|
-
await executeCommand(client, command);
|
|
109
|
-
expect(client.reload).toHaveBeenCalledWith({ bypassCache: true });
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('returns command result', async () => {
|
|
113
|
-
const command: ParsedCommand = {
|
|
114
|
-
name: 'url',
|
|
115
|
-
args: [],
|
|
116
|
-
flags: {},
|
|
117
|
-
raw: 'url',
|
|
118
|
-
};
|
|
119
|
-
const result = await executeCommand(client, command);
|
|
120
|
-
expect(result.success).toBe(true);
|
|
121
|
-
expect(result.data).toBe('https://example.com');
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
describe('error handling', () => {
|
|
126
|
-
it('adds contextual suggestion on error result', async () => {
|
|
127
|
-
client = createMockClient({
|
|
128
|
-
click: vi.fn().mockResolvedValue({
|
|
129
|
-
id: '1',
|
|
130
|
-
success: false,
|
|
131
|
-
error: 'Element not found',
|
|
132
|
-
}),
|
|
133
|
-
});
|
|
134
|
-
const command: ParsedCommand = {
|
|
135
|
-
name: 'click',
|
|
136
|
-
args: ['@ref:5'],
|
|
137
|
-
flags: {},
|
|
138
|
-
raw: 'click @ref:5',
|
|
139
|
-
};
|
|
140
|
-
const result = await executeCommand(client, command);
|
|
141
|
-
expect(result.success).toBe(false);
|
|
142
|
-
expect(result.error).toContain('Element not found');
|
|
143
|
-
// Should include contextual suggestion
|
|
144
|
-
expect(result.error).toContain('snapshot');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('re-throws CLIError as-is', async () => {
|
|
148
|
-
const cliError = new CLIError('Custom CLI error');
|
|
149
|
-
client = createMockClient({
|
|
150
|
-
click: vi.fn().mockRejectedValue(cliError),
|
|
151
|
-
});
|
|
152
|
-
const command: ParsedCommand = {
|
|
153
|
-
name: 'click',
|
|
154
|
-
args: ['@ref:1'],
|
|
155
|
-
flags: {},
|
|
156
|
-
raw: 'click @ref:1',
|
|
157
|
-
};
|
|
158
|
-
await expect(executeCommand(client, command)).rejects.toBe(cliError);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('wraps unexpected errors in ExecutionError', async () => {
|
|
162
|
-
const genericError = new Error('Network failure');
|
|
163
|
-
client = createMockClient({
|
|
164
|
-
navigate: vi.fn().mockRejectedValue(genericError),
|
|
165
|
-
});
|
|
166
|
-
const command: ParsedCommand = {
|
|
167
|
-
name: 'goto',
|
|
168
|
-
args: ['https://example.com'],
|
|
169
|
-
flags: {},
|
|
170
|
-
raw: 'goto https://example.com',
|
|
171
|
-
};
|
|
172
|
-
try {
|
|
173
|
-
await executeCommand(client, command);
|
|
174
|
-
expect.fail('Should have thrown');
|
|
175
|
-
} catch (error) {
|
|
176
|
-
expect(error).toBeInstanceOf(ExecutionError);
|
|
177
|
-
const execError = error as ExecutionError;
|
|
178
|
-
expect(execError.message).toContain('Network failure');
|
|
179
|
-
expect(execError.command).toBe('goto');
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('includes contextual suggestion in wrapped error', async () => {
|
|
184
|
-
const genericError = new Error('Failed to navigate');
|
|
185
|
-
client = createMockClient({
|
|
186
|
-
navigate: vi.fn().mockRejectedValue(genericError),
|
|
187
|
-
});
|
|
188
|
-
const command: ParsedCommand = {
|
|
189
|
-
name: 'goto',
|
|
190
|
-
args: ['example.com'],
|
|
191
|
-
flags: {},
|
|
192
|
-
raw: 'goto example.com',
|
|
193
|
-
};
|
|
194
|
-
try {
|
|
195
|
-
await executeCommand(client, command);
|
|
196
|
-
expect.fail('Should have thrown');
|
|
197
|
-
} catch (error) {
|
|
198
|
-
expect(error).toBeInstanceOf(ExecutionError);
|
|
199
|
-
const execError = error as ExecutionError;
|
|
200
|
-
// Should include suggestion about URL format
|
|
201
|
-
expect(execError.suggestions).toBeDefined();
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe('result passthrough', () => {
|
|
207
|
-
it('returns success result unchanged when no error', async () => {
|
|
208
|
-
const command: ParsedCommand = {
|
|
209
|
-
name: 'snapshot',
|
|
210
|
-
args: [],
|
|
211
|
-
flags: {},
|
|
212
|
-
raw: 'snapshot',
|
|
213
|
-
};
|
|
214
|
-
const result = await executeCommand(client, command);
|
|
215
|
-
expect(result.success).toBe(true);
|
|
216
|
-
expect(result.data).toContain('button');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('returns error result from command handler', async () => {
|
|
220
|
-
client = createMockClient({
|
|
221
|
-
click: vi.fn().mockResolvedValue({
|
|
222
|
-
id: '1',
|
|
223
|
-
success: false,
|
|
224
|
-
error: 'Failed',
|
|
225
|
-
}),
|
|
226
|
-
});
|
|
227
|
-
const command: ParsedCommand = {
|
|
228
|
-
name: 'click',
|
|
229
|
-
args: ['@ref:1'],
|
|
230
|
-
flags: {},
|
|
231
|
-
raw: 'click @ref:1',
|
|
232
|
-
};
|
|
233
|
-
const result = await executeCommand(client, command);
|
|
234
|
-
// Command handler transforms result, doesn't preserve raw data
|
|
235
|
-
expect(result.success).toBe(false);
|
|
236
|
-
expect(result.error).toContain('Failed');
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
describe('executeCommandString', () => {
|
|
242
|
-
let client: CommandClient;
|
|
243
|
-
|
|
244
|
-
beforeEach(() => {
|
|
245
|
-
client = createMockClient();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('parses and executes command string', async () => {
|
|
249
|
-
const result = await executeCommandString(
|
|
250
|
-
client,
|
|
251
|
-
'goto https://example.com',
|
|
252
|
-
parseCommand
|
|
253
|
-
);
|
|
254
|
-
expect(result.success).toBe(true);
|
|
255
|
-
expect(client.navigate).toHaveBeenCalledWith('https://example.com');
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('handles parse errors', async () => {
|
|
259
|
-
await expect(
|
|
260
|
-
executeCommandString(client, '', parseCommand)
|
|
261
|
-
).rejects.toThrow('Empty command');
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
it('uses custom parser', async () => {
|
|
265
|
-
const customParser = vi.fn().mockReturnValue({
|
|
266
|
-
name: 'snapshot',
|
|
267
|
-
args: [],
|
|
268
|
-
flags: {},
|
|
269
|
-
raw: 'custom',
|
|
270
|
-
});
|
|
271
|
-
await executeCommandString(client, 'custom', customParser);
|
|
272
|
-
expect(customParser).toHaveBeenCalledWith('custom');
|
|
273
|
-
expect(client.snapshot).toHaveBeenCalled();
|
|
274
|
-
});
|
|
275
|
-
});
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Formatter tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest';
|
|
6
|
-
import {
|
|
7
|
-
formatResult,
|
|
8
|
-
formatData,
|
|
9
|
-
formatHelp,
|
|
10
|
-
formatCommandHelp,
|
|
11
|
-
formatScreenshot,
|
|
12
|
-
formatErrorWithSuggestions,
|
|
13
|
-
formatSuccessWithNextSteps,
|
|
14
|
-
} from '../formatter.js';
|
|
15
|
-
|
|
16
|
-
describe('formatResult', () => {
|
|
17
|
-
describe('success results', () => {
|
|
18
|
-
it('formats success with message', () => {
|
|
19
|
-
const result = formatResult({ success: true, message: 'Navigated to example.com' });
|
|
20
|
-
expect(result.type).toBe('success');
|
|
21
|
-
expect(result.content).toBe('✓ Navigated to example.com');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('formats success with data', () => {
|
|
25
|
-
const result = formatResult({ success: true, data: 'Some data' });
|
|
26
|
-
expect(result.type).toBe('data');
|
|
27
|
-
expect(result.content).toBe('Some data');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('formats success without message or data', () => {
|
|
31
|
-
const result = formatResult({ success: true });
|
|
32
|
-
expect(result.type).toBe('success');
|
|
33
|
-
expect(result.content).toBe('✓ Done');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('prefers message over data', () => {
|
|
37
|
-
const result = formatResult({ success: true, message: 'Done!', data: 'ignored' });
|
|
38
|
-
expect(result.content).toBe('✓ Done!');
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('error results', () => {
|
|
43
|
-
it('formats error with message', () => {
|
|
44
|
-
const result = formatResult({ success: false, error: 'Something went wrong' });
|
|
45
|
-
expect(result.type).toBe('error');
|
|
46
|
-
expect(result.content).toBe('✗ Error: Something went wrong');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('formats error without message', () => {
|
|
50
|
-
const result = formatResult({ success: false });
|
|
51
|
-
expect(result.type).toBe('error');
|
|
52
|
-
expect(result.content).toBe('✗ Error: Unknown error');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('formatData', () => {
|
|
58
|
-
describe('primitive types', () => {
|
|
59
|
-
it('formats null', () => {
|
|
60
|
-
expect(formatData(null)).toBe('');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('formats undefined', () => {
|
|
64
|
-
expect(formatData(undefined)).toBe('');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('formats string', () => {
|
|
68
|
-
expect(formatData('hello')).toBe('hello');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('formats number', () => {
|
|
72
|
-
expect(formatData(42)).toBe('42');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('formats boolean', () => {
|
|
76
|
-
expect(formatData(true)).toBe('true');
|
|
77
|
-
expect(formatData(false)).toBe('false');
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('arrays', () => {
|
|
82
|
-
it('formats empty array', () => {
|
|
83
|
-
expect(formatData([])).toBe('(empty)');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('formats array of primitives', () => {
|
|
87
|
-
expect(formatData(['a', 'b', 'c'])).toBe('a\nb\nc');
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('formats array of numbers', () => {
|
|
91
|
-
expect(formatData([1, 2, 3])).toBe('1\n2\n3');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('formats array of objects with indices', () => {
|
|
95
|
-
const result = formatData([{ name: 'a' }, { name: 'b' }]);
|
|
96
|
-
expect(result).toContain('[0]');
|
|
97
|
-
expect(result).toContain('[1]');
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
describe('objects', () => {
|
|
102
|
-
it('formats empty object', () => {
|
|
103
|
-
expect(formatData({})).toBe('{}');
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it('formats simple object', () => {
|
|
107
|
-
const result = formatData({ name: 'test', value: 42 });
|
|
108
|
-
expect(result).toContain('name: test');
|
|
109
|
-
expect(result).toContain('value: 42');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// Removed: snapshot now returns string directly, not object with tree field
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
describe('formatHelp', () => {
|
|
117
|
-
it('formats command list', () => {
|
|
118
|
-
const commands = [
|
|
119
|
-
{ name: 'goto', description: 'Navigate to URL', usage: 'goto <url>' },
|
|
120
|
-
{ name: 'click', description: 'Click element', usage: 'click <selector>' },
|
|
121
|
-
];
|
|
122
|
-
const result = formatHelp(commands);
|
|
123
|
-
expect(result).toContain('goto');
|
|
124
|
-
expect(result).toContain('Navigate to URL');
|
|
125
|
-
expect(result).toContain('click');
|
|
126
|
-
expect(result).toContain('Click element');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('aligns descriptions', () => {
|
|
130
|
-
const commands = [
|
|
131
|
-
{ name: 'a', description: 'Short', usage: 'a' },
|
|
132
|
-
{ name: 'longer', description: 'Longer name', usage: 'longer' },
|
|
133
|
-
];
|
|
134
|
-
const result = formatHelp(commands);
|
|
135
|
-
// Both descriptions should be aligned
|
|
136
|
-
const lines = result.split('\n');
|
|
137
|
-
expect(lines.length).toBe(2);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('formatCommandHelp', () => {
|
|
142
|
-
it('formats command with all fields', () => {
|
|
143
|
-
const command = {
|
|
144
|
-
name: 'click',
|
|
145
|
-
description: 'Click an element',
|
|
146
|
-
usage: 'click <selector>',
|
|
147
|
-
examples: ['click @ref:5', 'click #submit'],
|
|
148
|
-
};
|
|
149
|
-
const result = formatCommandHelp(command);
|
|
150
|
-
expect(result).toContain('click');
|
|
151
|
-
expect(result).toContain('Click an element');
|
|
152
|
-
expect(result).toContain('click <selector>');
|
|
153
|
-
expect(result).toContain('click @ref:5');
|
|
154
|
-
expect(result).toContain('click #submit');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('formats command without examples', () => {
|
|
158
|
-
const command = {
|
|
159
|
-
name: 'back',
|
|
160
|
-
description: 'Go back in history',
|
|
161
|
-
usage: 'back',
|
|
162
|
-
};
|
|
163
|
-
const result = formatCommandHelp(command);
|
|
164
|
-
expect(result).toContain('back');
|
|
165
|
-
expect(result).toContain('Go back in history');
|
|
166
|
-
expect(result).not.toContain('Examples');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('includes selector help for interaction commands', () => {
|
|
170
|
-
const command = {
|
|
171
|
-
name: 'click',
|
|
172
|
-
description: 'Click an element',
|
|
173
|
-
usage: 'click <selector>',
|
|
174
|
-
};
|
|
175
|
-
const result = formatCommandHelp(command);
|
|
176
|
-
expect(result).toContain('Selectors');
|
|
177
|
-
expect(result).toContain('@ref:N');
|
|
178
|
-
expect(result).toContain('#id');
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it('excludes selector help for non-interaction commands', () => {
|
|
182
|
-
const command = {
|
|
183
|
-
name: 'goto',
|
|
184
|
-
description: 'Navigate to URL',
|
|
185
|
-
usage: 'goto <url>',
|
|
186
|
-
};
|
|
187
|
-
const result = formatCommandHelp(command);
|
|
188
|
-
expect(result).not.toContain('Selectors');
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('formatScreenshot', () => {
|
|
193
|
-
it('extracts format and size from PNG data URL', () => {
|
|
194
|
-
// Create a small base64 string (~100 bytes)
|
|
195
|
-
const base64 = 'A'.repeat(100);
|
|
196
|
-
const dataUrl = `data:image/png;base64,${base64}`;
|
|
197
|
-
const result = formatScreenshot(dataUrl);
|
|
198
|
-
expect(result).toContain('png');
|
|
199
|
-
expect(result).toContain('KB');
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('extracts format and size from JPEG data URL', () => {
|
|
203
|
-
const base64 = 'A'.repeat(1000);
|
|
204
|
-
const dataUrl = `data:image/jpeg;base64,${base64}`;
|
|
205
|
-
const result = formatScreenshot(dataUrl);
|
|
206
|
-
expect(result).toContain('jpeg');
|
|
207
|
-
expect(result).toContain('KB');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('handles invalid data URL', () => {
|
|
211
|
-
const result = formatScreenshot('not-a-data-url');
|
|
212
|
-
expect(result).toBe('Screenshot captured');
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe('formatErrorWithSuggestions', () => {
|
|
217
|
-
it('formats error without suggestions', () => {
|
|
218
|
-
const result = formatErrorWithSuggestions('Something failed');
|
|
219
|
-
expect(result).toBe('✗ Error: Something failed');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('formats error with suggestions', () => {
|
|
223
|
-
const result = formatErrorWithSuggestions('Element not found', [
|
|
224
|
-
'Run snapshot first',
|
|
225
|
-
'Check selector syntax',
|
|
226
|
-
]);
|
|
227
|
-
expect(result).toContain('✗ Error: Element not found');
|
|
228
|
-
expect(result).toContain('Suggestions:');
|
|
229
|
-
expect(result).toContain('Run snapshot first');
|
|
230
|
-
expect(result).toContain('Check selector syntax');
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('formats error with empty suggestions array', () => {
|
|
234
|
-
const result = formatErrorWithSuggestions('Error', []);
|
|
235
|
-
expect(result).toBe('✗ Error: Error');
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe('formatSuccessWithNextSteps', () => {
|
|
240
|
-
it('formats success without next steps', () => {
|
|
241
|
-
const result = formatSuccessWithNextSteps('Operation completed');
|
|
242
|
-
expect(result).toBe('✓ Operation completed');
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('formats success with next steps', () => {
|
|
246
|
-
const result = formatSuccessWithNextSteps('Navigated successfully', [
|
|
247
|
-
'snapshot # See page elements',
|
|
248
|
-
'click @ref:N # Interact',
|
|
249
|
-
]);
|
|
250
|
-
expect(result).toContain('✓ Navigated successfully');
|
|
251
|
-
expect(result).toContain('Next steps:');
|
|
252
|
-
expect(result).toContain('snapshot');
|
|
253
|
-
expect(result).toContain('click');
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it('formats success with empty next steps array', () => {
|
|
257
|
-
const result = formatSuccessWithNextSteps('Done', []);
|
|
258
|
-
expect(result).toBe('✓ Done');
|
|
259
|
-
});
|
|
260
|
-
});
|