@jackwener/opencli 1.7.16 → 1.7.18
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 +11 -9
- package/README.zh-CN.md +10 -8
- package/cli-manifest.json +377 -271
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +1 -1
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/image.js +38 -4
- package/clis/chatgpt/image.test.js +68 -1
- package/clis/chatgpt/new.js +1 -1
- package/clis/chatgpt/read.js +1 -1
- package/clis/chatgpt/send.js +1 -1
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +208 -16
- package/clis/chatgpt/utils.test.js +131 -2
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -1
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +1 -1
- package/clis/claude/read.js +1 -1
- package/clis/claude/send.js +1 -1
- package/clis/claude/status.js +1 -1
- package/clis/deepseek/ask.js +1 -1
- package/clis/deepseek/detail.js +1 -1
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +1 -1
- package/clis/deepseek/read.js +1 -1
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/doubao/ask.js +1 -1
- package/clis/doubao/detail.js +1 -1
- package/clis/doubao/history.js +1 -1
- package/clis/doubao/meeting-summary.js +1 -1
- package/clis/doubao/meeting-transcript.js +1 -1
- package/clis/doubao/new.js +1 -1
- package/clis/doubao/read.js +1 -1
- package/clis/doubao/send.js +1 -1
- package/clis/doubao/status.js +1 -1
- package/clis/doubao/utils.js +17 -0
- package/clis/doubao/utils.test.js +61 -0
- package/clis/gemini/ask.js +1 -1
- package/clis/gemini/deep-research-result.js +1 -1
- package/clis/gemini/deep-research.js +1 -1
- package/clis/gemini/image.js +1 -1
- package/clis/gemini/new.js +1 -1
- package/clis/grok/ask.js +1 -1
- package/clis/grok/detail.js +1 -1
- package/clis/grok/history.js +1 -1
- package/clis/grok/image.js +1 -1
- package/clis/grok/new.js +1 -1
- package/clis/grok/read.js +1 -1
- package/clis/grok/send.js +1 -1
- package/clis/grok/status.js +1 -1
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +2 -2
- package/clis/notebooklm/open.test.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/qwen/ask.js +1 -1
- package/clis/qwen/detail.js +1 -1
- package/clis/qwen/history.js +1 -1
- package/clis/qwen/image.js +1 -1
- package/clis/qwen/new.js +1 -1
- package/clis/qwen/read.js +1 -1
- package/clis/qwen/send.js +1 -1
- package/clis/qwen/status.js +1 -1
- package/clis/reddit/comment.js +1 -1
- package/clis/reddit/frontpage.js +1 -1
- package/clis/reddit/popular.js +1 -1
- package/clis/reddit/read.js +1 -1
- package/clis/reddit/read.test.js +2 -2
- package/clis/reddit/reply.js +182 -0
- package/clis/reddit/reply.test.js +89 -0
- package/clis/reddit/save.js +1 -1
- package/clis/reddit/saved.js +1 -1
- package/clis/reddit/search.js +1 -1
- package/clis/reddit/subreddit.js +1 -1
- package/clis/reddit/subscribe.js +1 -1
- package/clis/reddit/upvote.js +1 -1
- package/clis/reddit/upvoted.js +1 -1
- package/clis/reddit/user-comments.js +1 -1
- package/clis/reddit/user-posts.js +1 -1
- package/clis/reddit/user.js +1 -1
- package/clis/rednote/comments.js +76 -0
- package/clis/rednote/download.js +59 -0
- package/clis/rednote/feed.js +95 -0
- package/clis/rednote/navigation.test.js +26 -0
- package/clis/rednote/note.js +68 -0
- package/clis/rednote/notifications.js +139 -0
- package/clis/rednote/rednote.test.js +157 -0
- package/clis/rednote/search.js +97 -0
- package/clis/rednote/user.js +55 -0
- package/clis/twitter/article.js +1 -1
- package/clis/twitter/bookmark-folder.js +1 -1
- package/clis/twitter/bookmark-folders.js +1 -1
- package/clis/twitter/bookmarks.js +1 -1
- package/clis/twitter/download.js +1 -1
- package/clis/twitter/followers.js +1 -1
- package/clis/twitter/following.js +1 -1
- package/clis/twitter/likes.js +1 -1
- package/clis/twitter/list-tweets.js +1 -1
- package/clis/twitter/lists.js +1 -1
- package/clis/twitter/notifications.js +1 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/thread.js +1 -1
- package/clis/twitter/timeline.js +1 -1
- package/clis/twitter/trending.js +1 -1
- package/clis/twitter/tweets.js +1 -1
- package/clis/xiaohongshu/comments.js +34 -24
- package/clis/xiaohongshu/download.js +32 -23
- package/clis/xiaohongshu/feed.js +23 -15
- package/clis/xiaohongshu/note-helpers.js +16 -6
- package/clis/xiaohongshu/note.js +26 -20
- package/clis/xiaohongshu/notifications.js +26 -19
- package/clis/xiaohongshu/search.js +37 -28
- package/clis/xiaohongshu/user-helpers.js +13 -4
- package/clis/xiaohongshu/user-helpers.test.js +20 -0
- package/clis/xiaohongshu/user.js +9 -4
- package/clis/youtube/transcript.js +28 -3
- package/clis/youtube/transcript.test.js +90 -1
- package/clis/yuanbao/ask.js +1 -1
- package/clis/yuanbao/detail.js +1 -1
- package/clis/yuanbao/history.js +1 -1
- package/clis/yuanbao/new.js +1 -1
- package/clis/yuanbao/read.js +1 -1
- package/clis/yuanbao/send.js +1 -1
- package/clis/yuanbao/status.js +1 -1
- package/dist/src/browser/bridge.d.ts +3 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +3 -1
- package/dist/src/browser/daemon-client.d.ts +7 -14
- package/dist/src/browser/daemon-client.js +2 -6
- package/dist/src/browser/network-cache.d.ts +5 -5
- package/dist/src/browser/network-cache.js +8 -8
- package/dist/src/browser/network-cache.test.js +4 -4
- package/dist/src/browser/page.d.ts +8 -7
- package/dist/src/browser/page.js +23 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +60 -162
- package/dist/src/cli.test.js +184 -198
- package/dist/src/commanderAdapter.js +2 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +14 -73
- package/dist/src/doctor.test.js +28 -97
- package/dist/src/execution.d.ts +1 -0
- package/dist/src/execution.js +20 -21
- package/dist/src/execution.test.js +27 -31
- package/dist/src/help.js +7 -1
- package/dist/src/main.js +0 -19
- package/dist/src/manifest-types.d.ts +2 -4
- package/dist/src/observation/artifact.js +1 -1
- package/dist/src/observation/artifact.test.js +3 -3
- package/dist/src/observation/events.d.ts +1 -1
- package/dist/src/observation/manager.js +1 -1
- package/dist/src/observation/manager.test.js +3 -3
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +3 -12
- package/dist/src/registry.js +6 -10
- package/dist/src/runtime.d.ts +7 -2
- package/dist/src/runtime.js +3 -1
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/types.d.ts +0 -15
- package/package.json +1 -1
package/dist/src/cli.test.js
CHANGED
|
@@ -343,7 +343,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
343
343
|
const program = createProgram('', '');
|
|
344
344
|
const browser = program.commands.find(cmd => cmd.name() === 'browser');
|
|
345
345
|
expect(browser).toBeTruthy();
|
|
346
|
-
process.argv = ['node', 'opencli', 'browser', '--help', '-f', 'yaml'];
|
|
346
|
+
process.argv = ['node', 'opencli', 'browser', '--session', 'test', '--help', '-f', 'yaml'];
|
|
347
347
|
const data = yaml.load(browser.helpInformation());
|
|
348
348
|
expect(data.namespace).toBe('browser');
|
|
349
349
|
expect(data.command).toBe('opencli browser');
|
|
@@ -351,20 +351,17 @@ describe('createProgram root help descriptions', () => {
|
|
|
351
351
|
expect(data.command_count).toBeGreaterThan(20);
|
|
352
352
|
expect(data.namespace_options).toEqual(expect.arrayContaining([
|
|
353
353
|
expect.objectContaining({
|
|
354
|
-
name: '
|
|
355
|
-
flags: '--
|
|
354
|
+
name: 'session',
|
|
355
|
+
flags: '--session <name>',
|
|
356
356
|
takes_value: 'required',
|
|
357
|
+
required: true,
|
|
358
|
+
help: expect.stringContaining('required'),
|
|
357
359
|
}),
|
|
358
360
|
expect.objectContaining({
|
|
359
361
|
name: 'window',
|
|
360
362
|
flags: '--window <mode>',
|
|
361
363
|
takes_value: 'required',
|
|
362
364
|
}),
|
|
363
|
-
expect.objectContaining({
|
|
364
|
-
name: 'keepTab',
|
|
365
|
-
flags: '--keep-tab <bool>',
|
|
366
|
-
takes_value: 'required',
|
|
367
|
-
}),
|
|
368
365
|
]));
|
|
369
366
|
expect(data.global_options).toEqual(expect.arrayContaining([
|
|
370
367
|
expect.objectContaining({
|
|
@@ -411,7 +408,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
411
408
|
const browser = program.commands.find(cmd => cmd.name() === 'browser');
|
|
412
409
|
const tab = browser.commands.find(cmd => cmd.name() === 'tab');
|
|
413
410
|
expect(tab).toBeTruthy();
|
|
414
|
-
process.argv = ['node', 'opencli', 'browser', 'tab', '--help', '-f', 'yaml'];
|
|
411
|
+
process.argv = ['node', 'opencli', 'browser', '--session', 'test', 'tab', '--help', '-f', 'yaml'];
|
|
415
412
|
const data = yaml.load(tab.helpInformation());
|
|
416
413
|
expect(data).toMatchObject({
|
|
417
414
|
namespace: 'browser',
|
|
@@ -431,7 +428,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
431
428
|
usage: 'opencli browser tab close [targetId] [options]',
|
|
432
429
|
positionals: [{ name: 'targetId', help: 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"' }],
|
|
433
430
|
});
|
|
434
|
-
expect(data.namespace_options.map((option) => option.name)).toEqual(['
|
|
431
|
+
expect(data.namespace_options.map((option) => option.name)).toEqual(['session', 'window']);
|
|
435
432
|
expect(data.structured_help).toMatchObject({
|
|
436
433
|
usage: 'opencli browser tab --help -f yaml',
|
|
437
434
|
});
|
|
@@ -447,7 +444,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
447
444
|
const browser = program.commands.find(cmd => cmd.name() === 'browser');
|
|
448
445
|
const click = browser.commands.find(cmd => cmd.name() === 'click');
|
|
449
446
|
expect(click).toBeTruthy();
|
|
450
|
-
process.argv = ['node', 'opencli', 'browser', 'click', '--help', '-f', 'yaml'];
|
|
447
|
+
process.argv = ['node', 'opencli', 'browser', '--session', 'test', 'click', '--help', '-f', 'yaml'];
|
|
451
448
|
const data = yaml.load(click.helpInformation());
|
|
452
449
|
expect(data).toMatchObject({
|
|
453
450
|
namespace: 'browser',
|
|
@@ -460,7 +457,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
460
457
|
},
|
|
461
458
|
});
|
|
462
459
|
expect(data.command_options.map((option) => option.name)).toEqual(['role', 'name', 'label', 'text', 'testid', 'nth', 'tab']);
|
|
463
|
-
expect(data.namespace_options.map((option) => option.name)).toEqual(['
|
|
460
|
+
expect(data.namespace_options.map((option) => option.name)).toEqual(['session', 'window']);
|
|
464
461
|
expect(data.global_options.map((option) => option.name)).toContain('profile');
|
|
465
462
|
}
|
|
466
463
|
finally {
|
|
@@ -664,7 +661,7 @@ describe('browser verify', () => {
|
|
|
664
661
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
665
662
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
666
663
|
const program = createProgram('', '');
|
|
667
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture', '--trace', 'retain-on-failure']);
|
|
664
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--no-fixture', '--trace', 'retain-on-failure']);
|
|
668
665
|
expect(mockExecFileSync).toHaveBeenCalledTimes(1);
|
|
669
666
|
const [, execArgs] = mockExecFileSync.mock.calls[0];
|
|
670
667
|
expect(execArgs.slice(-6)).toEqual(['hn', 'top', '--trace', 'retain-on-failure', '--format', 'json']);
|
|
@@ -692,7 +689,7 @@ describe('browser verify', () => {
|
|
|
692
689
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
693
690
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
694
691
|
const program = createProgram('', '');
|
|
695
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture', '--seed-args', 'opencli-verify']);
|
|
692
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--no-fixture', '--seed-args', 'opencli-verify']);
|
|
696
693
|
expect(mockExecFileSync).toHaveBeenCalledTimes(1);
|
|
697
694
|
const [, execArgs] = mockExecFileSync.mock.calls[0];
|
|
698
695
|
expect(execArgs.slice(-5)).toEqual(['hn', 'top', 'opencli-verify', '--format', 'json']);
|
|
@@ -721,7 +718,7 @@ describe('browser verify', () => {
|
|
|
721
718
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
722
719
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
723
720
|
const program = createProgram('', '');
|
|
724
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--write-fixture', '--seed-args', 'opencli-verify']);
|
|
721
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--write-fixture', '--seed-args', 'opencli-verify']);
|
|
725
722
|
const fixtureFile = path.join(fakeHome, '.opencli', 'sites', 'hn', 'verify', 'top.json');
|
|
726
723
|
const fixture = JSON.parse(fs.readFileSync(fixtureFile, 'utf-8'));
|
|
727
724
|
expect(fixture.args).toEqual(['opencli-verify']);
|
|
@@ -753,7 +750,7 @@ describe('browser verify', () => {
|
|
|
753
750
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
754
751
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
755
752
|
const program = createProgram('', '');
|
|
756
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture']);
|
|
753
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--no-fixture']);
|
|
757
754
|
expect(process.exitCode).toBe(1);
|
|
758
755
|
const output = consoleLogSpy.mock.calls.map((args) => args.join(' ')).join('\n');
|
|
759
756
|
expect(output).toContain('Adapter output violates row shape conventions');
|
|
@@ -827,8 +824,8 @@ describe('profile list', () => {
|
|
|
827
824
|
describe('browser tab targeting commands', () => {
|
|
828
825
|
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
829
826
|
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
830
|
-
function getBrowserStateFile(cacheDir) {
|
|
831
|
-
return path.join(cacheDir, 'browser-state',
|
|
827
|
+
function getBrowserStateFile(cacheDir, session = 'test') {
|
|
828
|
+
return path.join(cacheDir, 'browser-state', `${session}.json`);
|
|
832
829
|
}
|
|
833
830
|
beforeEach(() => {
|
|
834
831
|
process.exitCode = undefined;
|
|
@@ -838,9 +835,8 @@ describe('browser tab targeting commands', () => {
|
|
|
838
835
|
mockBrowserConnect.mockClear();
|
|
839
836
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
840
837
|
delete process.env.OPENCLI_WINDOW;
|
|
841
|
-
delete process.env.OPENCLI_KEEP_TAB;
|
|
842
838
|
mockBindTab.mockReset().mockResolvedValue({
|
|
843
|
-
|
|
839
|
+
session: 'test',
|
|
844
840
|
page: 'tab-2',
|
|
845
841
|
url: 'https://user.example/inbox',
|
|
846
842
|
title: 'Inbox',
|
|
@@ -879,6 +875,7 @@ describe('browser tab targeting commands', () => {
|
|
|
879
875
|
state: 'complete',
|
|
880
876
|
elapsedMs: 10,
|
|
881
877
|
}),
|
|
878
|
+
session: 'test',
|
|
882
879
|
};
|
|
883
880
|
});
|
|
884
881
|
function lastJsonLog() {
|
|
@@ -890,55 +887,40 @@ describe('browser tab targeting commands', () => {
|
|
|
890
887
|
throw new Error(`Expected string arg to console.log, got ${typeof last}`);
|
|
891
888
|
return JSON.parse(last);
|
|
892
889
|
}
|
|
893
|
-
it('binds the current Chrome tab into a
|
|
890
|
+
it('binds the current Chrome tab into a browser session', async () => {
|
|
894
891
|
const program = createProgram('', '');
|
|
895
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
896
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
897
|
-
expect(mockBindTab).toHaveBeenCalledWith('
|
|
898
|
-
matchDomain: 'user.example',
|
|
899
|
-
matchPathPrefix: '/inbox',
|
|
900
|
-
});
|
|
892
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'bind']);
|
|
893
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser' });
|
|
894
|
+
expect(mockBindTab).toHaveBeenCalledWith('test', {});
|
|
901
895
|
const out = lastJsonLog();
|
|
902
|
-
expect(out.
|
|
896
|
+
expect(out.session).toBe('test');
|
|
903
897
|
expect(out.url).toBe('https://user.example/inbox');
|
|
904
898
|
});
|
|
905
|
-
it('
|
|
899
|
+
it('requires an explicit session for browser commands', async () => {
|
|
906
900
|
const program = createProgram('', '');
|
|
907
|
-
|
|
901
|
+
program.exitOverride((err) => { throw err; });
|
|
902
|
+
program.commands.find(cmd => cmd.name() === 'browser')?.exitOverride((err) => { throw err; });
|
|
903
|
+
await expect(program.parseAsync(['node', 'opencli', 'browser', 'state'])).rejects.toMatchObject({
|
|
904
|
+
code: 'commander.missingMandatoryOptionValue',
|
|
905
|
+
});
|
|
908
906
|
expect(mockBrowserConnect).not.toHaveBeenCalled();
|
|
909
|
-
expect(
|
|
910
|
-
const out = lastJsonLog();
|
|
911
|
-
expect(out.error.code).toBe('invalid_bind_workspace');
|
|
912
|
-
expect(process.exitCode).toBeDefined();
|
|
907
|
+
expect(stderrSpy.mock.calls.flat().join('')).toContain("required option '--session <name>' not specified");
|
|
913
908
|
});
|
|
914
|
-
it('runs browser commands against an explicit
|
|
909
|
+
it('runs browser commands against an explicit session', async () => {
|
|
915
910
|
const program = createProgram('', '');
|
|
916
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--
|
|
917
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
911
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state']);
|
|
912
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser', windowMode: 'foreground' });
|
|
918
913
|
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
919
914
|
});
|
|
920
915
|
it('passes browser --window through Commander options without relying on env pre-processing', async () => {
|
|
921
916
|
const program = createProgram('', '');
|
|
922
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--window', 'background', 'state']);
|
|
923
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
917
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', '--window', 'background', 'state']);
|
|
918
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser', windowMode: 'background' });
|
|
924
919
|
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
925
920
|
});
|
|
926
|
-
it('releases non-bound browser tab leases when --keep-tab=false', async () => {
|
|
927
|
-
const program = createProgram('', '');
|
|
928
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--keep-tab', 'false', 'state']);
|
|
929
|
-
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
930
|
-
expect(browserState.page?.closeWindow).toHaveBeenCalled();
|
|
931
|
-
});
|
|
932
|
-
it('does not auto-release explicit bound workspaces when --keep-tab=false', async () => {
|
|
933
|
-
const program = createProgram('', '');
|
|
934
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--workspace', 'bound:default', '--keep-tab', 'false', 'state']);
|
|
935
|
-
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
936
|
-
expect(browserState.page?.closeWindow).not.toHaveBeenCalled();
|
|
937
|
-
expect(stderrSpy.mock.calls.flat().join('')).toContain('--window/--keep-tab ignored for bound:* workspaces');
|
|
938
|
-
});
|
|
939
921
|
it('passes the opt-in AX source to browser state', async () => {
|
|
940
922
|
const program = createProgram('', '');
|
|
941
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--source', 'ax']);
|
|
923
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--source', 'ax']);
|
|
942
924
|
expect(browserState.page?.snapshot).toHaveBeenCalledWith({ viewportExpand: 2000, source: 'ax' });
|
|
943
925
|
});
|
|
944
926
|
it('prints DOM vs AX snapshot metrics without changing default state output', async () => {
|
|
@@ -952,7 +934,7 @@ describe('browser tab targeting commands', () => {
|
|
|
952
934
|
}),
|
|
953
935
|
};
|
|
954
936
|
const program = createProgram('', '');
|
|
955
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--compare-sources']);
|
|
937
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--compare-sources']);
|
|
956
938
|
expect(browserState.page?.snapshot).toHaveBeenCalledWith({ viewportExpand: 2000, source: 'dom' });
|
|
957
939
|
expect(browserState.page?.snapshot).toHaveBeenCalledWith({ viewportExpand: 2000, source: 'ax' });
|
|
958
940
|
const out = lastJsonLog();
|
|
@@ -970,7 +952,7 @@ describe('browser tab targeting commands', () => {
|
|
|
970
952
|
}),
|
|
971
953
|
};
|
|
972
954
|
const program = createProgram('', '');
|
|
973
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--compare-sources']);
|
|
955
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--compare-sources']);
|
|
974
956
|
const out = lastJsonLog();
|
|
975
957
|
expect(out.sources.dom).toMatchObject({ ok: true, refs: 1 });
|
|
976
958
|
expect(out.sources.ax).toMatchObject({
|
|
@@ -980,7 +962,7 @@ describe('browser tab targeting commands', () => {
|
|
|
980
962
|
});
|
|
981
963
|
it('rejects unknown browser state sources before touching the page', async () => {
|
|
982
964
|
const program = createProgram('', '');
|
|
983
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--source', 'magic']);
|
|
965
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--source', 'magic']);
|
|
984
966
|
expect(browserState.page?.snapshot).not.toHaveBeenCalled();
|
|
985
967
|
const out = lastJsonLog();
|
|
986
968
|
expect(out.error.code).toBe('invalid_source');
|
|
@@ -988,7 +970,7 @@ describe('browser tab targeting commands', () => {
|
|
|
988
970
|
});
|
|
989
971
|
it('captures annotated screenshots through the visual ref overlay path', async () => {
|
|
990
972
|
const program = createProgram('', '');
|
|
991
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'screenshot', '--annotate']);
|
|
973
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'screenshot', '--annotate']);
|
|
992
974
|
expect(browserState.page?.annotatedScreenshot).toHaveBeenCalledWith({
|
|
993
975
|
fullPage: false,
|
|
994
976
|
annotate: true,
|
|
@@ -999,38 +981,36 @@ describe('browser tab targeting commands', () => {
|
|
|
999
981
|
expect(browserState.page?.screenshot).not.toHaveBeenCalled();
|
|
1000
982
|
expect(consoleLogSpy).toHaveBeenLastCalledWith('annotated-base64-shot');
|
|
1001
983
|
});
|
|
1002
|
-
it('
|
|
984
|
+
it('allows history navigation in a bound session', async () => {
|
|
1003
985
|
browserState.page = {
|
|
1004
986
|
...browserState.page,
|
|
1005
|
-
workspace: 'bound:default',
|
|
1006
987
|
evaluate: vi.fn(),
|
|
1007
988
|
wait: vi.fn(),
|
|
989
|
+
session: 'test',
|
|
1008
990
|
};
|
|
1009
991
|
const program = createProgram('', '');
|
|
1010
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--
|
|
1011
|
-
expect(browserState.page?.evaluate).
|
|
1012
|
-
const out = lastJsonLog();
|
|
1013
|
-
expect(out.error.code).toBe('bound_navigation_blocked');
|
|
992
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'back']);
|
|
993
|
+
expect(browserState.page?.evaluate).toHaveBeenCalledWith('history.back()');
|
|
1014
994
|
});
|
|
1015
|
-
it('unbinds a
|
|
995
|
+
it('unbinds a session through the daemon close-window command', async () => {
|
|
1016
996
|
const program = createProgram('', '');
|
|
1017
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
1018
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
1019
|
-
expect(mockSendCommand).toHaveBeenCalledWith('close-window', {
|
|
997
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'unbind']);
|
|
998
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser' });
|
|
999
|
+
expect(mockSendCommand).toHaveBeenCalledWith('close-window', { session: 'test', surface: 'browser' });
|
|
1020
1000
|
const out = lastJsonLog();
|
|
1021
|
-
expect(out).toEqual({ unbound: true,
|
|
1001
|
+
expect(out).toEqual({ unbound: true, session: 'test' });
|
|
1022
1002
|
});
|
|
1023
1003
|
it('does not print false success when unbind fails', async () => {
|
|
1024
|
-
mockSendCommand.mockRejectedValueOnce(new BrowserCommandError('
|
|
1004
|
+
mockSendCommand.mockRejectedValueOnce(new BrowserCommandError('Session "test" is not attached to a tab.', 'bound_session_missing', 'Run bind again, then retry the browser command.'));
|
|
1025
1005
|
const program = createProgram('', '');
|
|
1026
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
1006
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'unbind']);
|
|
1027
1007
|
const out = lastJsonLog();
|
|
1028
1008
|
expect(out.error.code).toBe('bound_session_missing');
|
|
1029
1009
|
expect(process.exitCode).toBeDefined();
|
|
1030
1010
|
});
|
|
1031
1011
|
it('accepts JavaScript dialogs through the browser dialog command', async () => {
|
|
1032
1012
|
const program = createProgram('', '');
|
|
1033
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'dialog', 'accept', '--text', 'ok']);
|
|
1013
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'dialog', 'accept', '--text', 'ok']);
|
|
1034
1014
|
expect(browserState.page?.handleJavaScriptDialog).toHaveBeenCalledWith(true, 'ok');
|
|
1035
1015
|
const out = lastJsonLog();
|
|
1036
1016
|
expect(out).toEqual({ handled: true, action: 'accept', text: 'ok' });
|
|
@@ -1041,7 +1021,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1041
1021
|
evaluate: vi.fn().mockRejectedValue(new Error('JavaScript dialog showing')),
|
|
1042
1022
|
};
|
|
1043
1023
|
const program = createProgram('', '');
|
|
1044
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1024
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1045
1025
|
const out = lastJsonLog();
|
|
1046
1026
|
expect(out.error.code).toBe('javascript_dialog_open');
|
|
1047
1027
|
expect(out.error.hint).toContain('browser dialog accept');
|
|
@@ -1049,7 +1029,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1049
1029
|
});
|
|
1050
1030
|
it('binds browser commands to an explicit target tab via --tab', async () => {
|
|
1051
1031
|
const program = createProgram('', '');
|
|
1052
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--tab', 'tab-2', 'document.title']);
|
|
1032
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', '--tab', 'tab-2', 'document.title']);
|
|
1053
1033
|
expect(browserState.page?.setActivePage).toHaveBeenCalledWith('tab-2');
|
|
1054
1034
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
1055
1035
|
});
|
|
@@ -1061,7 +1041,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1061
1041
|
evaluate: vi.fn(),
|
|
1062
1042
|
};
|
|
1063
1043
|
const program = createProgram('', '');
|
|
1064
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--tab', 'tab-stale', 'document.title']);
|
|
1044
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', '--tab', 'tab-stale', 'document.title']);
|
|
1065
1045
|
expect(process.exitCode).toBeDefined();
|
|
1066
1046
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
1067
1047
|
expect(browserState.page?.evaluate).not.toHaveBeenCalled();
|
|
@@ -1069,50 +1049,50 @@ describe('browser tab targeting commands', () => {
|
|
|
1069
1049
|
});
|
|
1070
1050
|
it('lists tabs with target IDs via browser tab list', async () => {
|
|
1071
1051
|
const program = createProgram('', '');
|
|
1072
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'list']);
|
|
1052
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'list']);
|
|
1073
1053
|
expect(browserState.page?.tabs).toHaveBeenCalledTimes(1);
|
|
1074
1054
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-1"');
|
|
1075
1055
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-2"');
|
|
1076
1056
|
});
|
|
1077
1057
|
it('creates a new tab and prints its target ID', async () => {
|
|
1078
1058
|
const program = createProgram('', '');
|
|
1079
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'new', 'https://three.example']);
|
|
1059
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'new', 'https://three.example']);
|
|
1080
1060
|
expect(browserState.page?.newTab).toHaveBeenCalledWith('https://three.example');
|
|
1081
1061
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-3"');
|
|
1082
1062
|
});
|
|
1083
1063
|
it('prints the resolved target ID when browser open creates or navigates a tab', async () => {
|
|
1084
1064
|
const program = createProgram('', '');
|
|
1085
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'open', 'https://example.com']);
|
|
1065
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'open', 'https://example.com']);
|
|
1086
1066
|
expect(browserState.page?.goto).toHaveBeenCalledWith('https://example.com');
|
|
1087
1067
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"url": "https://one.example"');
|
|
1088
1068
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-1"');
|
|
1089
1069
|
});
|
|
1090
1070
|
it('lists cross-origin frames via browser frames', async () => {
|
|
1091
1071
|
const program = createProgram('', '');
|
|
1092
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'frames']);
|
|
1072
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'frames']);
|
|
1093
1073
|
expect(browserState.page?.frames).toHaveBeenCalledTimes(1);
|
|
1094
1074
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"frameId": "frame-1"');
|
|
1095
1075
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"url": "https://x.example/embed"');
|
|
1096
1076
|
});
|
|
1097
1077
|
it('routes browser eval --frame through frame-targeted evaluation', async () => {
|
|
1098
1078
|
const program = createProgram('', '');
|
|
1099
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--frame', '0', 'document.title']);
|
|
1079
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', '--frame', '0', 'document.title']);
|
|
1100
1080
|
expect(browserState.page?.evaluateInFrame).toHaveBeenCalledWith('document.title', 0);
|
|
1101
1081
|
expect(browserState.page?.evaluate).not.toHaveBeenCalled();
|
|
1102
1082
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('inside frame');
|
|
1103
1083
|
});
|
|
1104
1084
|
it('does not promote a newly created tab to the persisted default target', async () => {
|
|
1105
1085
|
const program = createProgram('', '');
|
|
1106
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'new', 'https://three.example']);
|
|
1107
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1086
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'new', 'https://three.example']);
|
|
1087
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1108
1088
|
expect(browserState.page?.newTab).toHaveBeenCalledWith('https://three.example');
|
|
1109
1089
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
1110
1090
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
1111
1091
|
});
|
|
1112
1092
|
it('persists an explicitly selected tab as the default target for later untargeted commands', async () => {
|
|
1113
1093
|
const program = createProgram('', '');
|
|
1114
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'select', 'tab-2']);
|
|
1115
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1094
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'select', 'tab-2']);
|
|
1095
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1116
1096
|
expect(browserState.page?.selectTab).toHaveBeenCalledWith('tab-2');
|
|
1117
1097
|
expect(browserState.page?.setActivePage).toHaveBeenCalledWith('tab-2');
|
|
1118
1098
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
@@ -1121,7 +1101,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1121
1101
|
it('clears a saved default target when it is no longer present in the current session', async () => {
|
|
1122
1102
|
const cacheDir = String(process.env.OPENCLI_CACHE_DIR);
|
|
1123
1103
|
const program = createProgram('', '');
|
|
1124
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'select', 'tab-2']);
|
|
1104
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'select', 'tab-2']);
|
|
1125
1105
|
expect(fs.existsSync(getBrowserStateFile(cacheDir))).toBe(true);
|
|
1126
1106
|
browserState.page = {
|
|
1127
1107
|
setActivePage: vi.fn(),
|
|
@@ -1130,35 +1110,36 @@ describe('browser tab targeting commands', () => {
|
|
|
1130
1110
|
evaluate: vi.fn().mockResolvedValue({ ok: true }),
|
|
1131
1111
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
1132
1112
|
};
|
|
1133
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1113
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1134
1114
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
1135
1115
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
1136
1116
|
expect(fs.existsSync(getBrowserStateFile(cacheDir))).toBe(false);
|
|
1137
1117
|
});
|
|
1138
1118
|
it('clears the persisted default target when that tab is closed', async () => {
|
|
1139
1119
|
const program = createProgram('', '');
|
|
1140
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'select', 'tab-2']);
|
|
1141
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'close', 'tab-2']);
|
|
1120
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'select', 'tab-2']);
|
|
1121
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'close', 'tab-2']);
|
|
1142
1122
|
vi.mocked(browserState.page?.setActivePage).mockClear();
|
|
1143
1123
|
vi.mocked(browserState.page?.evaluate).mockClear();
|
|
1144
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1124
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1145
1125
|
expect(browserState.page?.closeTab).toHaveBeenCalledWith('tab-2');
|
|
1146
1126
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
1147
1127
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
1148
1128
|
});
|
|
1149
1129
|
it('closes a tab by target ID', async () => {
|
|
1150
1130
|
const program = createProgram('', '');
|
|
1151
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'close', 'tab-2']);
|
|
1131
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'close', 'tab-2']);
|
|
1152
1132
|
expect(browserState.page?.closeTab).toHaveBeenCalledWith('tab-2');
|
|
1153
1133
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"closed": "tab-2"');
|
|
1154
1134
|
});
|
|
1155
1135
|
it('rejects closing a stale tab target ID that is no longer in the current session', async () => {
|
|
1156
1136
|
browserState.page = {
|
|
1137
|
+
session: 'test',
|
|
1157
1138
|
tabs: vi.fn().mockResolvedValue([]),
|
|
1158
1139
|
closeTab: vi.fn(),
|
|
1159
1140
|
};
|
|
1160
1141
|
const program = createProgram('', '');
|
|
1161
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'close', 'tab-stale']);
|
|
1142
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'close', 'tab-stale']);
|
|
1162
1143
|
expect(process.exitCode).toBeDefined();
|
|
1163
1144
|
expect(browserState.page?.closeTab).not.toHaveBeenCalled();
|
|
1164
1145
|
expect(stderrSpy.mock.calls.flat().join('\n')).toContain('Target tab tab-stale is not part of the current browser session');
|
|
@@ -1205,7 +1186,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1205
1186
|
]),
|
|
1206
1187
|
};
|
|
1207
1188
|
const program = createProgram('', '');
|
|
1208
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'analyze', 'https://target.example/']);
|
|
1189
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'analyze', 'https://target.example/']);
|
|
1209
1190
|
const out = lastJsonLog();
|
|
1210
1191
|
expect(browserState.page?.readNetworkCapture).toHaveBeenCalledTimes(2);
|
|
1211
1192
|
expect(out.anti_bot.vendor).toBe('cloudflare');
|
|
@@ -1266,7 +1247,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1266
1247
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
1267
1248
|
};
|
|
1268
1249
|
const program = createProgram('', '');
|
|
1269
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'analyze', 'https://target.example/']);
|
|
1250
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'analyze', 'https://target.example/']);
|
|
1270
1251
|
const out = lastJsonLog();
|
|
1271
1252
|
expect(browserState.page?.readNetworkCapture).toHaveBeenCalledTimes(2);
|
|
1272
1253
|
expect(bufferReads).toBe(2);
|
|
@@ -1305,7 +1286,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1305
1286
|
]),
|
|
1306
1287
|
};
|
|
1307
1288
|
const program = createProgram('', '');
|
|
1308
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1289
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1309
1290
|
const out = lastJsonLog();
|
|
1310
1291
|
expect(browserState.page?.startNetworkCapture).toHaveBeenCalledTimes(1);
|
|
1311
1292
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith(expect.stringContaining('window.__opencli_net'));
|
|
@@ -1353,7 +1334,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1353
1334
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
1354
1335
|
};
|
|
1355
1336
|
const program = createProgram('', '');
|
|
1356
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1337
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1357
1338
|
const out = lastJsonLog();
|
|
1358
1339
|
expect(browserState.page?.startNetworkCapture).toHaveBeenCalledTimes(1);
|
|
1359
1340
|
expect(browserState.page?.readNetworkCapture).toHaveBeenCalledTimes(2);
|
|
@@ -1377,7 +1358,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1377
1358
|
tabs: vi.fn().mockResolvedValue([{ index: 0, page: 'tab-1', url: 'https://target.example', title: 'Target', active: true }]),
|
|
1378
1359
|
};
|
|
1379
1360
|
const program = createProgram('', '');
|
|
1380
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1361
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1381
1362
|
expect(browserState.page?.waitForDownload).toHaveBeenCalledWith('receipt', 900);
|
|
1382
1363
|
expect(lastJsonLog()).toEqual({
|
|
1383
1364
|
downloaded: true,
|
|
@@ -1403,7 +1384,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1403
1384
|
tabs: vi.fn().mockResolvedValue([{ index: 0, page: 'tab-1', url: 'https://target.example', title: 'Target', active: true }]),
|
|
1404
1385
|
};
|
|
1405
1386
|
const program = createProgram('', '');
|
|
1406
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1387
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1407
1388
|
const out = lastJsonLog();
|
|
1408
1389
|
expect(out.error.code).toBe('download_not_seen');
|
|
1409
1390
|
expect(out.download.elapsedMs).toBe(900);
|
|
@@ -1413,10 +1394,10 @@ describe('browser tab targeting commands', () => {
|
|
|
1413
1394
|
describe('browser network command', () => {
|
|
1414
1395
|
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
1415
1396
|
function getNetworkCachePath(cacheDir) {
|
|
1416
|
-
return path.join(cacheDir, 'browser-network', '
|
|
1397
|
+
return path.join(cacheDir, 'browser-network', 'test.json');
|
|
1417
1398
|
}
|
|
1418
|
-
function
|
|
1419
|
-
return path.join(cacheDir, 'browser-network', '
|
|
1399
|
+
function getCustomNetworkCachePath(cacheDir) {
|
|
1400
|
+
return path.join(cacheDir, 'browser-network', 'custom.json');
|
|
1420
1401
|
}
|
|
1421
1402
|
function lastJsonLog() {
|
|
1422
1403
|
const calls = consoleLogSpy.mock.calls;
|
|
@@ -1434,6 +1415,7 @@ describe('browser network command', () => {
|
|
|
1434
1415
|
mockBrowserConnect.mockClear();
|
|
1435
1416
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
1436
1417
|
browserState.page = {
|
|
1418
|
+
session: 'test',
|
|
1437
1419
|
setActivePage: vi.fn(),
|
|
1438
1420
|
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
1439
1421
|
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
|
|
@@ -1460,7 +1442,7 @@ describe('browser network command', () => {
|
|
|
1460
1442
|
it('emits JSON with shape previews and persists the capture to disk', async () => {
|
|
1461
1443
|
const cacheDir = String(process.env.OPENCLI_CACHE_DIR);
|
|
1462
1444
|
const program = createProgram('', '');
|
|
1463
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1445
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1464
1446
|
const out = lastJsonLog();
|
|
1465
1447
|
expect(out.count).toBe(1);
|
|
1466
1448
|
expect(out.filtered_out).toBe(1);
|
|
@@ -1469,22 +1451,22 @@ describe('browser network command', () => {
|
|
|
1469
1451
|
expect(out.entries[0]).not.toHaveProperty('body');
|
|
1470
1452
|
expect(fs.existsSync(getNetworkCachePath(cacheDir))).toBe(true);
|
|
1471
1453
|
});
|
|
1472
|
-
it('uses the selected browser
|
|
1454
|
+
it('uses the selected browser session for network cache scope', async () => {
|
|
1473
1455
|
const cacheDir = String(process.env.OPENCLI_CACHE_DIR);
|
|
1474
1456
|
browserState.page = {
|
|
1475
1457
|
...browserState.page,
|
|
1476
|
-
|
|
1458
|
+
session: 'custom',
|
|
1477
1459
|
};
|
|
1478
1460
|
const program = createProgram('', '');
|
|
1479
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--
|
|
1461
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'custom', 'network']);
|
|
1480
1462
|
const out = lastJsonLog();
|
|
1481
|
-
expect(out.
|
|
1482
|
-
expect(fs.existsSync(
|
|
1463
|
+
expect(out.session).toBe('custom');
|
|
1464
|
+
expect(fs.existsSync(getCustomNetworkCachePath(cacheDir))).toBe(true);
|
|
1483
1465
|
expect(fs.existsSync(getNetworkCachePath(cacheDir))).toBe(false);
|
|
1484
1466
|
});
|
|
1485
1467
|
it('--all includes static resources that the default filter drops', async () => {
|
|
1486
1468
|
const program = createProgram('', '');
|
|
1487
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--all']);
|
|
1469
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--all']);
|
|
1488
1470
|
const out = lastJsonLog();
|
|
1489
1471
|
expect(out.count).toBe(2);
|
|
1490
1472
|
expect(out.entries.map((e) => e.key)).toContain('UserTweets');
|
|
@@ -1519,7 +1501,7 @@ describe('browser network command', () => {
|
|
|
1519
1501
|
},
|
|
1520
1502
|
]);
|
|
1521
1503
|
const program = createProgram('', '');
|
|
1522
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--since', '120s', '--failed']);
|
|
1504
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--since', '120s', '--failed']);
|
|
1523
1505
|
const out = lastJsonLog();
|
|
1524
1506
|
expect(out.count).toBe(1);
|
|
1525
1507
|
expect(out.entries[0].url).toBe('https://api.example.com/new-fail');
|
|
@@ -1543,7 +1525,7 @@ describe('browser network command', () => {
|
|
|
1543
1525
|
},
|
|
1544
1526
|
]);
|
|
1545
1527
|
const program = createProgram('', '');
|
|
1546
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1528
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1547
1529
|
const out = lastJsonLog();
|
|
1548
1530
|
expect(out.count).toBe(1);
|
|
1549
1531
|
expect(out.filtered_out).toBe(1);
|
|
@@ -1553,16 +1535,16 @@ describe('browser network command', () => {
|
|
|
1553
1535
|
});
|
|
1554
1536
|
it('--raw emits full bodies inline for every entry', async () => {
|
|
1555
1537
|
const program = createProgram('', '');
|
|
1556
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--raw']);
|
|
1538
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--raw']);
|
|
1557
1539
|
const out = lastJsonLog();
|
|
1558
1540
|
expect(out.entries[0].body).toEqual({ data: { user: { rest_id: '42' } } });
|
|
1559
1541
|
expect(out.entries[0].timestamp).toMatch(/T/);
|
|
1560
1542
|
});
|
|
1561
1543
|
it('--detail <key> returns the full body for the requested entry', async () => {
|
|
1562
1544
|
const program = createProgram('', '');
|
|
1563
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1545
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1564
1546
|
consoleLogSpy.mockClear();
|
|
1565
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'UserTweets']);
|
|
1547
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'UserTweets']);
|
|
1566
1548
|
const out = lastJsonLog();
|
|
1567
1549
|
expect(out.key).toBe('UserTweets');
|
|
1568
1550
|
expect(out.body).toEqual({ data: { user: { rest_id: '42' } } });
|
|
@@ -1571,9 +1553,9 @@ describe('browser network command', () => {
|
|
|
1571
1553
|
});
|
|
1572
1554
|
it('--detail reports key_not_found with the list of available keys', async () => {
|
|
1573
1555
|
const program = createProgram('', '');
|
|
1574
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1556
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1575
1557
|
consoleLogSpy.mockClear();
|
|
1576
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'NopeOp']);
|
|
1558
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'NopeOp']);
|
|
1577
1559
|
const out = lastJsonLog();
|
|
1578
1560
|
expect(out.error.code).toBe('key_not_found');
|
|
1579
1561
|
expect(out.error.available_keys).toContain('UserTweets');
|
|
@@ -1581,7 +1563,7 @@ describe('browser network command', () => {
|
|
|
1581
1563
|
});
|
|
1582
1564
|
it('--detail reports cache_missing when no capture has been persisted yet', async () => {
|
|
1583
1565
|
const program = createProgram('', '');
|
|
1584
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'UserTweets']);
|
|
1566
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'UserTweets']);
|
|
1585
1567
|
const out = lastJsonLog();
|
|
1586
1568
|
expect(out.error.code).toBe('cache_missing');
|
|
1587
1569
|
expect(process.exitCode).toBeDefined();
|
|
@@ -1589,7 +1571,7 @@ describe('browser network command', () => {
|
|
|
1589
1571
|
it('emits capture_failed when readNetworkCapture throws', async () => {
|
|
1590
1572
|
browserState.page.readNetworkCapture = vi.fn().mockRejectedValue(new Error('CDP disconnected'));
|
|
1591
1573
|
const program = createProgram('', '');
|
|
1592
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1574
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1593
1575
|
const out = lastJsonLog();
|
|
1594
1576
|
expect(out.error.code).toBe('capture_failed');
|
|
1595
1577
|
expect(out.error.message).toContain('CDP disconnected');
|
|
@@ -1602,7 +1584,7 @@ describe('browser network command', () => {
|
|
|
1602
1584
|
const clashDir = path.join(cacheDir, 'browser-network');
|
|
1603
1585
|
fs.writeFileSync(clashDir, 'not-a-directory');
|
|
1604
1586
|
const program = createProgram('', '');
|
|
1605
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1587
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1606
1588
|
const out = lastJsonLog();
|
|
1607
1589
|
expect(out.cache_warning).toMatch(/Could not persist capture cache/);
|
|
1608
1590
|
expect(out.count).toBe(1);
|
|
@@ -1627,7 +1609,7 @@ describe('browser network command', () => {
|
|
|
1627
1609
|
});
|
|
1628
1610
|
it('narrows entries to those whose shape has ALL named fields', async () => {
|
|
1629
1611
|
const program = createProgram('', '');
|
|
1630
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author,text,likes']);
|
|
1612
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author,text,likes']);
|
|
1631
1613
|
const out = lastJsonLog();
|
|
1632
1614
|
expect(out.count).toBe(1);
|
|
1633
1615
|
expect(out.filter).toEqual(['author', 'text', 'likes']);
|
|
@@ -1636,14 +1618,14 @@ describe('browser network command', () => {
|
|
|
1636
1618
|
});
|
|
1637
1619
|
it('matches container segments too, not just leaf names (any-segment rule)', async () => {
|
|
1638
1620
|
const program = createProgram('', '');
|
|
1639
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'data,items']);
|
|
1621
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'data,items']);
|
|
1640
1622
|
const out = lastJsonLog();
|
|
1641
1623
|
expect(out.count).toBe(1);
|
|
1642
1624
|
expect(out.entries[0].key).toBe('UserTweets');
|
|
1643
1625
|
});
|
|
1644
1626
|
it('drops entries that are missing any required field (AND semantics)', async () => {
|
|
1645
1627
|
const program = createProgram('', '');
|
|
1646
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author,followers']);
|
|
1628
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author,followers']);
|
|
1647
1629
|
const out = lastJsonLog();
|
|
1648
1630
|
expect(out.count).toBe(0);
|
|
1649
1631
|
expect(out.entries).toEqual([]);
|
|
@@ -1652,7 +1634,7 @@ describe('browser network command', () => {
|
|
|
1652
1634
|
});
|
|
1653
1635
|
it('returns empty entries (not an error) when nothing matches', async () => {
|
|
1654
1636
|
const program = createProgram('', '');
|
|
1655
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'nonexistent_field']);
|
|
1637
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'nonexistent_field']);
|
|
1656
1638
|
const out = lastJsonLog();
|
|
1657
1639
|
expect(out.count).toBe(0);
|
|
1658
1640
|
expect(out.entries).toEqual([]);
|
|
@@ -1661,43 +1643,43 @@ describe('browser network command', () => {
|
|
|
1661
1643
|
});
|
|
1662
1644
|
it('is case-sensitive so agents do not conflate `Id` with `id`', async () => {
|
|
1663
1645
|
const program = createProgram('', '');
|
|
1664
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'Data']);
|
|
1646
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'Data']);
|
|
1665
1647
|
const out = lastJsonLog();
|
|
1666
1648
|
expect(out.count).toBe(0);
|
|
1667
1649
|
});
|
|
1668
1650
|
it('persists the full (unfiltered) capture so --detail lookups still find filtered-out keys', async () => {
|
|
1669
1651
|
const program = createProgram('', '');
|
|
1670
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author,text,likes']);
|
|
1652
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author,text,likes']);
|
|
1671
1653
|
consoleLogSpy.mockClear();
|
|
1672
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'UserProfile']);
|
|
1654
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'UserProfile']);
|
|
1673
1655
|
const out = lastJsonLog();
|
|
1674
1656
|
expect(out.key).toBe('UserProfile');
|
|
1675
1657
|
expect(out.body).toEqual({ data: { user: { id: 'u1', followers: 10 } } });
|
|
1676
1658
|
});
|
|
1677
1659
|
it('composes with --raw: entries keep full bodies, filter still narrows', async () => {
|
|
1678
1660
|
const program = createProgram('', '');
|
|
1679
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author', '--raw']);
|
|
1661
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author', '--raw']);
|
|
1680
1662
|
const out = lastJsonLog();
|
|
1681
1663
|
expect(out.count).toBe(1);
|
|
1682
1664
|
expect(out.entries[0].body).toEqual({ data: { items: [{ author: 'a', text: 't', likes: 1 }] } });
|
|
1683
1665
|
});
|
|
1684
1666
|
it('reports invalid_filter for empty value', async () => {
|
|
1685
1667
|
const program = createProgram('', '');
|
|
1686
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', '']);
|
|
1668
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', '']);
|
|
1687
1669
|
const out = lastJsonLog();
|
|
1688
1670
|
expect(out.error.code).toBe('invalid_filter');
|
|
1689
1671
|
expect(process.exitCode).toBeDefined();
|
|
1690
1672
|
});
|
|
1691
1673
|
it('reports invalid_filter for commas-only value', async () => {
|
|
1692
1674
|
const program = createProgram('', '');
|
|
1693
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', ',,,']);
|
|
1675
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', ',,,']);
|
|
1694
1676
|
const out = lastJsonLog();
|
|
1695
1677
|
expect(out.error.code).toBe('invalid_filter');
|
|
1696
1678
|
expect(process.exitCode).toBeDefined();
|
|
1697
1679
|
});
|
|
1698
1680
|
it('rejects --filter combined with --detail as invalid_args', async () => {
|
|
1699
1681
|
const program = createProgram('', '');
|
|
1700
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author', '--detail', 'UserTweets']);
|
|
1682
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author', '--detail', 'UserTweets']);
|
|
1701
1683
|
const out = lastJsonLog();
|
|
1702
1684
|
expect(out.error.code).toBe('invalid_args');
|
|
1703
1685
|
expect(out.error.message).toContain('--filter');
|
|
@@ -1719,7 +1701,7 @@ describe('browser network command', () => {
|
|
|
1719
1701
|
},
|
|
1720
1702
|
]);
|
|
1721
1703
|
const program = createProgram('', '');
|
|
1722
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1704
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1723
1705
|
const out = lastJsonLog();
|
|
1724
1706
|
expect(out.body_truncated_count).toBe(1);
|
|
1725
1707
|
expect(out.entries[0].body_truncated).toBe(true);
|
|
@@ -1738,9 +1720,9 @@ describe('browser network command', () => {
|
|
|
1738
1720
|
},
|
|
1739
1721
|
]);
|
|
1740
1722
|
const program = createProgram('', '');
|
|
1741
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1723
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1742
1724
|
consoleLogSpy.mockClear();
|
|
1743
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'GET api.example.com/huge']);
|
|
1725
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'GET api.example.com/huge']);
|
|
1744
1726
|
const out = lastJsonLog();
|
|
1745
1727
|
expect(out.body_truncated).toBe(true);
|
|
1746
1728
|
expect(out.body_full_size).toBe(50_000_000);
|
|
@@ -1758,10 +1740,10 @@ describe('browser network command', () => {
|
|
|
1758
1740
|
},
|
|
1759
1741
|
]);
|
|
1760
1742
|
const program = createProgram('', '');
|
|
1761
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1743
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1762
1744
|
consoleLogSpy.mockClear();
|
|
1763
1745
|
await program.parseAsync([
|
|
1764
|
-
'node', 'opencli', 'browser', 'network',
|
|
1746
|
+
'node', 'opencli', 'browser', '--session', 'test', 'network',
|
|
1765
1747
|
'--detail', 'GET api.example.com/plain',
|
|
1766
1748
|
'--max-body', '100',
|
|
1767
1749
|
]);
|
|
@@ -1783,10 +1765,10 @@ describe('browser network command', () => {
|
|
|
1783
1765
|
},
|
|
1784
1766
|
]);
|
|
1785
1767
|
const program = createProgram('', '');
|
|
1786
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1768
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1787
1769
|
consoleLogSpy.mockClear();
|
|
1788
1770
|
await program.parseAsync([
|
|
1789
|
-
'node', 'opencli', 'browser', 'network',
|
|
1771
|
+
'node', 'opencli', 'browser', '--session', 'test', 'network',
|
|
1790
1772
|
'--detail', 'GET api.example.com/json',
|
|
1791
1773
|
'--max-body', '10',
|
|
1792
1774
|
]);
|
|
@@ -1807,10 +1789,10 @@ describe('browser network command', () => {
|
|
|
1807
1789
|
},
|
|
1808
1790
|
]);
|
|
1809
1791
|
const program = createProgram('', '');
|
|
1810
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1792
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1811
1793
|
consoleLogSpy.mockClear();
|
|
1812
1794
|
await program.parseAsync([
|
|
1813
|
-
'node', 'opencli', 'browser', 'network',
|
|
1795
|
+
'node', 'opencli', 'browser', '--session', 'test', 'network',
|
|
1814
1796
|
'--detail', 'GET api.example.com/x',
|
|
1815
1797
|
'--max-body', 'abc',
|
|
1816
1798
|
]);
|
|
@@ -1830,7 +1812,7 @@ describe('browser network command', () => {
|
|
|
1830
1812
|
},
|
|
1831
1813
|
]);
|
|
1832
1814
|
const program = createProgram('', '');
|
|
1833
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--raw']);
|
|
1815
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--raw']);
|
|
1834
1816
|
const out = lastJsonLog();
|
|
1835
1817
|
expect(out.entries).toHaveLength(1);
|
|
1836
1818
|
const entry = out.entries[0];
|
|
@@ -1851,6 +1833,7 @@ describe('browser console command', () => {
|
|
|
1851
1833
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
1852
1834
|
const now = Date.now();
|
|
1853
1835
|
browserState.page = {
|
|
1836
|
+
session: 'test',
|
|
1854
1837
|
setActivePage: vi.fn(),
|
|
1855
1838
|
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
1856
1839
|
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
|
|
@@ -1872,7 +1855,7 @@ describe('browser console command', () => {
|
|
|
1872
1855
|
}
|
|
1873
1856
|
it('filters console messages by level and time window', async () => {
|
|
1874
1857
|
const program = createProgram('', '');
|
|
1875
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'console', '--level', 'error', '--since', '120s']);
|
|
1858
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'console', '--level', 'error', '--since', '120s']);
|
|
1876
1859
|
const out = lastJsonLog();
|
|
1877
1860
|
expect(out.count).toBe(1);
|
|
1878
1861
|
expect(out.messages[0]).toMatchObject({ type: 'error', text: 'boom' });
|
|
@@ -1909,14 +1892,14 @@ describe('browser get html command', () => {
|
|
|
1909
1892
|
const big = '<div>' + 'x'.repeat(100_000) + '</div>';
|
|
1910
1893
|
browserState.page.evaluate.mockResolvedValueOnce({ kind: 'ok', html: big });
|
|
1911
1894
|
const program = createProgram('', '');
|
|
1912
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html']);
|
|
1895
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html']);
|
|
1913
1896
|
expect(lastLogArg()).toBe(big);
|
|
1914
1897
|
});
|
|
1915
1898
|
it('caps output with --max and prepends a visible truncation marker', async () => {
|
|
1916
1899
|
const big = '<div>' + 'x'.repeat(500) + '</div>';
|
|
1917
1900
|
browserState.page.evaluate.mockResolvedValueOnce({ kind: 'ok', html: big });
|
|
1918
1901
|
const program = createProgram('', '');
|
|
1919
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '100']);
|
|
1902
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '100']);
|
|
1920
1903
|
const out = String(lastLogArg());
|
|
1921
1904
|
expect(out.startsWith('<!-- opencli: truncated 100 of')).toBe(true);
|
|
1922
1905
|
expect(out.length).toBeGreaterThan(100);
|
|
@@ -1924,21 +1907,21 @@ describe('browser get html command', () => {
|
|
|
1924
1907
|
});
|
|
1925
1908
|
it('rejects negative --max with invalid_max error', async () => {
|
|
1926
1909
|
const program = createProgram('', '');
|
|
1927
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '-1']);
|
|
1910
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '-1']);
|
|
1928
1911
|
expect(lastJsonLog().error.code).toBe('invalid_max');
|
|
1929
1912
|
expect(process.exitCode).toBeDefined();
|
|
1930
1913
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
1931
1914
|
});
|
|
1932
1915
|
it('rejects fractional --max with invalid_max error', async () => {
|
|
1933
1916
|
const program = createProgram('', '');
|
|
1934
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '1.5']);
|
|
1917
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '1.5']);
|
|
1935
1918
|
expect(lastJsonLog().error.code).toBe('invalid_max');
|
|
1936
1919
|
expect(process.exitCode).toBeDefined();
|
|
1937
1920
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
1938
1921
|
});
|
|
1939
1922
|
it('rejects non-numeric --max (e.g. "10abc") with invalid_max error', async () => {
|
|
1940
1923
|
const program = createProgram('', '');
|
|
1941
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '10abc']);
|
|
1924
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '10abc']);
|
|
1942
1925
|
expect(lastJsonLog().error.code).toBe('invalid_max');
|
|
1943
1926
|
expect(process.exitCode).toBeDefined();
|
|
1944
1927
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
@@ -1950,7 +1933,7 @@ describe('browser get html command', () => {
|
|
|
1950
1933
|
tree: { tag: 'div', attrs: { class: 'hero' }, text: 'Hi', children: [] },
|
|
1951
1934
|
});
|
|
1952
1935
|
const program = createProgram('', '');
|
|
1953
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '.hero', '--as', 'json']);
|
|
1936
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '.hero', '--as', 'json']);
|
|
1954
1937
|
const out = lastJsonLog();
|
|
1955
1938
|
expect(out.matched).toBe(1);
|
|
1956
1939
|
expect(out.tree.tag).toBe('div');
|
|
@@ -1959,14 +1942,14 @@ describe('browser get html command', () => {
|
|
|
1959
1942
|
it('--as json emits selector_not_found when matched is 0', async () => {
|
|
1960
1943
|
browserState.page.evaluate.mockResolvedValueOnce({ selector: '.missing', matched: 0, tree: null });
|
|
1961
1944
|
const program = createProgram('', '');
|
|
1962
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '.missing', '--as', 'json']);
|
|
1945
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '.missing', '--as', 'json']);
|
|
1963
1946
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
1964
1947
|
expect(process.exitCode).toBeDefined();
|
|
1965
1948
|
});
|
|
1966
1949
|
it('raw mode emits selector_not_found when the selector matches nothing', async () => {
|
|
1967
1950
|
browserState.page.evaluate.mockResolvedValueOnce({ kind: 'ok', html: null });
|
|
1968
1951
|
const program = createProgram('', '');
|
|
1969
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '.missing']);
|
|
1952
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '.missing']);
|
|
1970
1953
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
1971
1954
|
expect(process.exitCode).toBeDefined();
|
|
1972
1955
|
});
|
|
@@ -1976,7 +1959,7 @@ describe('browser get html command', () => {
|
|
|
1976
1959
|
reason: "'##$@@' is not a valid selector",
|
|
1977
1960
|
});
|
|
1978
1961
|
const program = createProgram('', '');
|
|
1979
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '##$@@']);
|
|
1962
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '##$@@']);
|
|
1980
1963
|
const err = lastJsonLog().error;
|
|
1981
1964
|
expect(err.code).toBe('invalid_selector');
|
|
1982
1965
|
expect(err.message).toContain('##$@@');
|
|
@@ -1990,7 +1973,7 @@ describe('browser get html command', () => {
|
|
|
1990
1973
|
reason: "'##$@@' is not a valid selector",
|
|
1991
1974
|
});
|
|
1992
1975
|
const program = createProgram('', '');
|
|
1993
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '##$@@', '--as', 'json']);
|
|
1976
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '##$@@', '--as', 'json']);
|
|
1994
1977
|
const err = lastJsonLog().error;
|
|
1995
1978
|
expect(err.code).toBe('invalid_selector');
|
|
1996
1979
|
expect(err.message).toContain('##$@@');
|
|
@@ -1998,7 +1981,7 @@ describe('browser get html command', () => {
|
|
|
1998
1981
|
});
|
|
1999
1982
|
it('rejects unknown --as format with invalid_format error', async () => {
|
|
2000
1983
|
const program = createProgram('', '');
|
|
2001
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--as', 'yaml']);
|
|
1984
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--as', 'yaml']);
|
|
2002
1985
|
expect(lastJsonLog().error.code).toBe('invalid_format');
|
|
2003
1986
|
expect(process.exitCode).toBeDefined();
|
|
2004
1987
|
});
|
|
@@ -2031,6 +2014,7 @@ function installSelectorFirstTestHarness(label, pageOverrides) {
|
|
|
2031
2014
|
setActivePage: vi.fn(),
|
|
2032
2015
|
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
2033
2016
|
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
|
|
2017
|
+
session: 'test',
|
|
2034
2018
|
...pageOverrides(),
|
|
2035
2019
|
};
|
|
2036
2020
|
});
|
|
@@ -2052,7 +2036,7 @@ describe('browser find command', () => {
|
|
|
2052
2036
|
],
|
|
2053
2037
|
});
|
|
2054
2038
|
const program = createProgram('', '');
|
|
2055
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.btn']);
|
|
2039
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.btn']);
|
|
2056
2040
|
const out = lastJsonLog();
|
|
2057
2041
|
expect(out.matches_n).toBe(2);
|
|
2058
2042
|
expect(out.entries).toHaveLength(2);
|
|
@@ -2068,7 +2052,7 @@ describe('browser find command', () => {
|
|
|
2068
2052
|
],
|
|
2069
2053
|
});
|
|
2070
2054
|
const program = createProgram('', '');
|
|
2071
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--role', 'button', '--name', 'Save']);
|
|
2055
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--role', 'button', '--name', 'Save']);
|
|
2072
2056
|
const js = browserState.page.evaluate.mock.calls[0][0];
|
|
2073
2057
|
expect(js).toContain('CRITERIA');
|
|
2074
2058
|
expect(js).toContain('function accessibleName');
|
|
@@ -2082,7 +2066,7 @@ describe('browser find command', () => {
|
|
|
2082
2066
|
it('forwards --limit / --text-max into the generated JS', async () => {
|
|
2083
2067
|
browserState.page.evaluate.mockResolvedValueOnce({ matches_n: 0, entries: [] });
|
|
2084
2068
|
const program = createProgram('', '');
|
|
2085
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.btn', '--limit', '3', '--text-max', '20']);
|
|
2069
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.btn', '--limit', '3', '--text-max', '20']);
|
|
2086
2070
|
const js = browserState.page.evaluate.mock.calls[0][0];
|
|
2087
2071
|
expect(js).toContain('LIMIT = 3');
|
|
2088
2072
|
expect(js).toContain('TEXT_MAX = 20');
|
|
@@ -2092,7 +2076,7 @@ describe('browser find command', () => {
|
|
|
2092
2076
|
error: { code: 'invalid_selector', message: 'Invalid CSS selector: ">>>"', hint: 'Check the selector syntax.' },
|
|
2093
2077
|
});
|
|
2094
2078
|
const program = createProgram('', '');
|
|
2095
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '>>>']);
|
|
2079
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '>>>']);
|
|
2096
2080
|
expect(lastJsonLog().error.code).toBe('invalid_selector');
|
|
2097
2081
|
expect(process.exitCode).toBeDefined();
|
|
2098
2082
|
});
|
|
@@ -2101,20 +2085,20 @@ describe('browser find command', () => {
|
|
|
2101
2085
|
error: { code: 'selector_not_found', message: 'CSS selector ".missing" matched 0 elements', hint: 'Use browser state to inspect the page.' },
|
|
2102
2086
|
});
|
|
2103
2087
|
const program = createProgram('', '');
|
|
2104
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.missing']);
|
|
2088
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.missing']);
|
|
2105
2089
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
2106
2090
|
expect(process.exitCode).toBeDefined();
|
|
2107
2091
|
});
|
|
2108
2092
|
it('rejects missing --css with usage_error (no evaluate call)', async () => {
|
|
2109
2093
|
const program = createProgram('', '');
|
|
2110
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find']);
|
|
2094
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find']);
|
|
2111
2095
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2112
2096
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
2113
2097
|
expect(process.exitCode).toBeDefined();
|
|
2114
2098
|
});
|
|
2115
2099
|
it('rejects malformed --limit with usage_error (no evaluate call)', async () => {
|
|
2116
2100
|
const program = createProgram('', '');
|
|
2117
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.btn', '--limit', 'abc']);
|
|
2101
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.btn', '--limit', 'abc']);
|
|
2118
2102
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2119
2103
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
2120
2104
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2131,7 +2115,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2131
2115
|
// 2nd call: getTextResolvedJs -> the element's text
|
|
2132
2116
|
evalMock.mockResolvedValueOnce('Hello world');
|
|
2133
2117
|
const program = createProgram('', '');
|
|
2134
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '7']);
|
|
2118
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '7']);
|
|
2135
2119
|
expect(lastJsonLog()).toEqual({ value: 'Hello world', matches_n: 1, match_level: 'exact' });
|
|
2136
2120
|
});
|
|
2137
2121
|
it('resolves a semantic locator to a ref before get text', async () => {
|
|
@@ -2145,7 +2129,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2145
2129
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2146
2130
|
evalMock.mockResolvedValueOnce('Save');
|
|
2147
2131
|
const program = createProgram('', '');
|
|
2148
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2132
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2149
2133
|
expect(evalMock.mock.calls[0][0]).toContain('function accessibleName');
|
|
2150
2134
|
expect(evalMock.mock.calls[1][0]).toContain('const ref = "12"');
|
|
2151
2135
|
expect(lastJsonLog()).toEqual({ value: 'Save', matches_n: 1, match_level: 'exact' });
|
|
@@ -2163,7 +2147,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2163
2147
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2164
2148
|
evalMock.mockResolvedValueOnce('Save');
|
|
2165
2149
|
const program = createProgram('', '');
|
|
2166
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2150
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2167
2151
|
expect(evalMock.mock.calls[0][0]).toContain('const LIMIT = 6');
|
|
2168
2152
|
expect(evalMock.mock.calls[1][0]).toContain('const ref = "12"');
|
|
2169
2153
|
expect(lastJsonLog()).toEqual({ value: 'Save', matches_n: 1, match_level: 'exact', total_matches: 3 });
|
|
@@ -2173,7 +2157,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2173
2157
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 3, match_level: 'exact' });
|
|
2174
2158
|
evalMock.mockResolvedValueOnce('first');
|
|
2175
2159
|
const program = createProgram('', '');
|
|
2176
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '.btn']);
|
|
2160
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '.btn']);
|
|
2177
2161
|
expect(lastJsonLog()).toEqual({ value: 'first', matches_n: 3, match_level: 'exact' });
|
|
2178
2162
|
});
|
|
2179
2163
|
it('parses the attributes payload back into a real object', async () => {
|
|
@@ -2182,7 +2166,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2182
2166
|
// getAttributesResolvedJs returns a JSON-encoded string — the CLI must parse it
|
|
2183
2167
|
evalMock.mockResolvedValueOnce(JSON.stringify({ id: 'nav', class: 'hero' }));
|
|
2184
2168
|
const program = createProgram('', '');
|
|
2185
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'attributes', '#nav']);
|
|
2169
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'attributes', '#nav']);
|
|
2186
2170
|
const out = lastJsonLog();
|
|
2187
2171
|
expect(out.matches_n).toBe(1);
|
|
2188
2172
|
expect(out.match_level).toBe('exact');
|
|
@@ -2196,7 +2180,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2196
2180
|
hint: 'Try a less specific selector.',
|
|
2197
2181
|
});
|
|
2198
2182
|
const program = createProgram('', '');
|
|
2199
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '.missing']);
|
|
2183
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '.missing']);
|
|
2200
2184
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
2201
2185
|
expect(process.exitCode).toBeDefined();
|
|
2202
2186
|
});
|
|
@@ -2205,7 +2189,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2205
2189
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 4, match_level: 'exact' });
|
|
2206
2190
|
evalMock.mockResolvedValueOnce('second');
|
|
2207
2191
|
const program = createProgram('', '');
|
|
2208
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'value', '.btn', '--nth', '1']);
|
|
2192
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'value', '.btn', '--nth', '1']);
|
|
2209
2193
|
const resolveJs = evalMock.mock.calls[0][0];
|
|
2210
2194
|
// resolveTargetJs embeds nth as a raw number literal; look for the binding
|
|
2211
2195
|
expect(resolveJs).toContain('const nth = 1');
|
|
@@ -2213,7 +2197,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2213
2197
|
});
|
|
2214
2198
|
it('rejects malformed --nth with usage_error before touching the page', async () => {
|
|
2215
2199
|
const program = createProgram('', '');
|
|
2216
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '.btn', '--nth', 'abc']);
|
|
2200
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '.btn', '--nth', 'abc']);
|
|
2217
2201
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2218
2202
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
2219
2203
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2261,7 +2245,7 @@ describe('browser click/type commands', () => {
|
|
|
2261
2245
|
it('emits {clicked, target, matches_n, match_level} on success', async () => {
|
|
2262
2246
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2263
2247
|
const program = createProgram('', '');
|
|
2264
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '#save']);
|
|
2248
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '#save']);
|
|
2265
2249
|
expect(browserState.page.click).toHaveBeenCalledWith('#save', {});
|
|
2266
2250
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '#save', matches_n: 1, match_level: 'exact' });
|
|
2267
2251
|
});
|
|
@@ -2274,7 +2258,7 @@ describe('browser click/type commands', () => {
|
|
|
2274
2258
|
});
|
|
2275
2259
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2276
2260
|
const program = createProgram('', '');
|
|
2277
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '--role', 'button', '--name', 'Submit']);
|
|
2261
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '--role', 'button', '--name', 'Submit']);
|
|
2278
2262
|
expect(browserState.page.click).toHaveBeenCalledWith('23', {});
|
|
2279
2263
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '23', matches_n: 1, match_level: 'exact' });
|
|
2280
2264
|
});
|
|
@@ -2287,7 +2271,7 @@ describe('browser click/type commands', () => {
|
|
|
2287
2271
|
],
|
|
2288
2272
|
});
|
|
2289
2273
|
const program = createProgram('', '');
|
|
2290
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '--role', 'button', '--name', 'Save']);
|
|
2274
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '--role', 'button', '--name', 'Save']);
|
|
2291
2275
|
const err = lastJsonLog().error;
|
|
2292
2276
|
expect(err.code).toBe('semantic_ambiguous');
|
|
2293
2277
|
expect(err.matches_n).toBe(2);
|
|
@@ -2302,7 +2286,7 @@ describe('browser click/type commands', () => {
|
|
|
2302
2286
|
],
|
|
2303
2287
|
});
|
|
2304
2288
|
const program = createProgram('', '');
|
|
2305
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'hover', '--role', 'button', '--name', 'Settings']);
|
|
2289
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'hover', '--role', 'button', '--name', 'Settings']);
|
|
2306
2290
|
expect(browserState.page.hover).toHaveBeenCalledWith('31', {});
|
|
2307
2291
|
expect(lastJsonLog()).toEqual({ hovered: true, target: '31', matches_n: 1, match_level: 'exact' });
|
|
2308
2292
|
});
|
|
@@ -2315,7 +2299,7 @@ describe('browser click/type commands', () => {
|
|
|
2315
2299
|
});
|
|
2316
2300
|
browserState.page.setChecked.mockResolvedValueOnce({ checked: true, changed: false, matches_n: 1, match_level: 'exact', kind: 'checkbox' });
|
|
2317
2301
|
const program = createProgram('', '');
|
|
2318
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'check', '--role', 'checkbox', '--name', 'Accept']);
|
|
2302
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'check', '--role', 'checkbox', '--name', 'Accept']);
|
|
2319
2303
|
expect(browserState.page.setChecked).toHaveBeenCalledWith('32', true, {});
|
|
2320
2304
|
expect(lastJsonLog()).toEqual({ checked: true, changed: false, target: '32', matches_n: 1, match_level: 'exact', kind: 'checkbox' });
|
|
2321
2305
|
});
|
|
@@ -2339,7 +2323,7 @@ describe('browser click/type commands', () => {
|
|
|
2339
2323
|
multiple: false,
|
|
2340
2324
|
});
|
|
2341
2325
|
const program = createProgram('', '');
|
|
2342
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'upload', '--role', 'button', '--name', 'Upload receipt', file]);
|
|
2326
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'upload', '--role', 'button', '--name', 'Upload receipt', file]);
|
|
2343
2327
|
expect(browserState.page.uploadFiles).toHaveBeenCalledWith('33', [file], {});
|
|
2344
2328
|
expect(lastJsonLog()).toMatchObject({ uploaded: true, target: '33', files: 1 });
|
|
2345
2329
|
});
|
|
@@ -2355,7 +2339,7 @@ describe('browser click/type commands', () => {
|
|
|
2355
2339
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2356
2340
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2357
2341
|
const program = createProgram('', '');
|
|
2358
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '--label', 'Email', 'me@example.com']);
|
|
2342
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '--label', 'Email', 'me@example.com']);
|
|
2359
2343
|
expect(browserState.page.click).toHaveBeenCalledWith('34', {});
|
|
2360
2344
|
expect(browserState.page.typeText).toHaveBeenCalledWith('34', 'me@example.com', {});
|
|
2361
2345
|
expect(lastJsonLog()).toMatchObject({ typed: true, target: '34', text: 'me@example.com' });
|
|
@@ -2377,7 +2361,7 @@ describe('browser click/type commands', () => {
|
|
|
2377
2361
|
match_level: 'exact',
|
|
2378
2362
|
});
|
|
2379
2363
|
const program = createProgram('', '');
|
|
2380
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '--label', 'Email', 'me@example.com']);
|
|
2364
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '--label', 'Email', 'me@example.com']);
|
|
2381
2365
|
expect(browserState.page.fillText).toHaveBeenCalledWith('35', 'me@example.com', {});
|
|
2382
2366
|
expect(lastJsonLog()).toMatchObject({ filled: true, verified: true, target: '35', text: 'me@example.com' });
|
|
2383
2367
|
});
|
|
@@ -2409,6 +2393,8 @@ describe('browser click/type commands', () => {
|
|
|
2409
2393
|
'node',
|
|
2410
2394
|
'opencli',
|
|
2411
2395
|
'browser',
|
|
2396
|
+
'--session',
|
|
2397
|
+
'test',
|
|
2412
2398
|
'drag',
|
|
2413
2399
|
'--from-role',
|
|
2414
2400
|
'button',
|
|
@@ -2425,13 +2411,13 @@ describe('browser click/type commands', () => {
|
|
|
2425
2411
|
it('surfaces match_level=stable when resolver falls back to fingerprint match', async () => {
|
|
2426
2412
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'stable' });
|
|
2427
2413
|
const program = createProgram('', '');
|
|
2428
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '7']);
|
|
2414
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '7']);
|
|
2429
2415
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '7', matches_n: 1, match_level: 'stable' });
|
|
2430
2416
|
});
|
|
2431
2417
|
it('forwards --nth as ResolveOptions.nth to page.click', async () => {
|
|
2432
2418
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 3, match_level: 'exact' });
|
|
2433
2419
|
const program = createProgram('', '');
|
|
2434
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn', '--nth', '2']);
|
|
2420
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn', '--nth', '2']);
|
|
2435
2421
|
expect(browserState.page.click).toHaveBeenCalledWith('.btn', { nth: 2 });
|
|
2436
2422
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '.btn', matches_n: 3, match_level: 'exact' });
|
|
2437
2423
|
});
|
|
@@ -2443,7 +2429,7 @@ describe('browser click/type commands', () => {
|
|
|
2443
2429
|
matches_n: 3,
|
|
2444
2430
|
}));
|
|
2445
2431
|
const program = createProgram('', '');
|
|
2446
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn']);
|
|
2432
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn']);
|
|
2447
2433
|
const err = lastJsonLog().error;
|
|
2448
2434
|
expect(err.code).toBe('selector_ambiguous');
|
|
2449
2435
|
expect(err.matches_n).toBe(3);
|
|
@@ -2457,13 +2443,13 @@ describe('browser click/type commands', () => {
|
|
|
2457
2443
|
matches_n: 3,
|
|
2458
2444
|
}));
|
|
2459
2445
|
const program = createProgram('', '');
|
|
2460
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn', '--nth', '99']);
|
|
2446
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn', '--nth', '99']);
|
|
2461
2447
|
expect(lastJsonLog().error.code).toBe('selector_nth_out_of_range');
|
|
2462
2448
|
expect(process.exitCode).toBeDefined();
|
|
2463
2449
|
});
|
|
2464
2450
|
it('rejects malformed --nth on click with usage_error before touching the page', async () => {
|
|
2465
2451
|
const program = createProgram('', '');
|
|
2466
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn', '--nth', 'abc']);
|
|
2452
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn', '--nth', 'abc']);
|
|
2467
2453
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2468
2454
|
expect(browserState.page.click).not.toHaveBeenCalled();
|
|
2469
2455
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2471,21 +2457,21 @@ describe('browser click/type commands', () => {
|
|
|
2471
2457
|
it('hover: delegates to page.hover and emits a structured envelope', async () => {
|
|
2472
2458
|
browserState.page.hover.mockResolvedValueOnce({ matches_n: 2, match_level: 'exact' });
|
|
2473
2459
|
const program = createProgram('', '');
|
|
2474
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'hover', '.menu', '--nth', '1']);
|
|
2460
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'hover', '.menu', '--nth', '1']);
|
|
2475
2461
|
expect(browserState.page.hover).toHaveBeenCalledWith('.menu', { nth: 1 });
|
|
2476
2462
|
expect(lastJsonLog()).toEqual({ hovered: true, target: '.menu', matches_n: 2, match_level: 'exact' });
|
|
2477
2463
|
});
|
|
2478
2464
|
it('focus: delegates to page.focus and reports whether the element took focus', async () => {
|
|
2479
2465
|
browserState.page.focus.mockResolvedValueOnce({ focused: true, matches_n: 1, match_level: 'stable' });
|
|
2480
2466
|
const program = createProgram('', '');
|
|
2481
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'focus', '7']);
|
|
2467
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'focus', '7']);
|
|
2482
2468
|
expect(browserState.page.focus).toHaveBeenCalledWith('7', {});
|
|
2483
2469
|
expect(lastJsonLog()).toEqual({ focused: true, target: '7', matches_n: 1, match_level: 'stable' });
|
|
2484
2470
|
});
|
|
2485
2471
|
it('dblclick: delegates to page.dblClick and emits a structured envelope', async () => {
|
|
2486
2472
|
browserState.page.dblClick.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2487
2473
|
const program = createProgram('', '');
|
|
2488
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'dblclick', '#row']);
|
|
2474
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'dblclick', '#row']);
|
|
2489
2475
|
expect(browserState.page.dblClick).toHaveBeenCalledWith('#row', {});
|
|
2490
2476
|
expect(lastJsonLog()).toEqual({ dblclicked: true, target: '#row', matches_n: 1, match_level: 'exact' });
|
|
2491
2477
|
});
|
|
@@ -2498,7 +2484,7 @@ describe('browser click/type commands', () => {
|
|
|
2498
2484
|
kind: 'checkbox',
|
|
2499
2485
|
});
|
|
2500
2486
|
const program = createProgram('', '');
|
|
2501
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'check', '.todo', '--nth', '1']);
|
|
2487
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'check', '.todo', '--nth', '1']);
|
|
2502
2488
|
expect(browserState.page.setChecked).toHaveBeenCalledWith('.todo', true, { nth: 1 });
|
|
2503
2489
|
expect(lastJsonLog()).toEqual({ checked: true, changed: true, target: '.todo', matches_n: 2, match_level: 'exact', kind: 'checkbox' });
|
|
2504
2490
|
});
|
|
@@ -2511,7 +2497,7 @@ describe('browser click/type commands', () => {
|
|
|
2511
2497
|
kind: 'checkbox',
|
|
2512
2498
|
});
|
|
2513
2499
|
const program = createProgram('', '');
|
|
2514
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'uncheck', '#subscribe']);
|
|
2500
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'uncheck', '#subscribe']);
|
|
2515
2501
|
expect(browserState.page.setChecked).toHaveBeenCalledWith('#subscribe', false, {});
|
|
2516
2502
|
expect(lastJsonLog()).toEqual({ checked: false, changed: false, target: '#subscribe', matches_n: 1, match_level: 'stable', kind: 'checkbox' });
|
|
2517
2503
|
});
|
|
@@ -2529,7 +2515,7 @@ describe('browser click/type commands', () => {
|
|
|
2529
2515
|
multiple: false,
|
|
2530
2516
|
});
|
|
2531
2517
|
const program = createProgram('', '');
|
|
2532
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'upload', '#file', file]);
|
|
2518
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'upload', '#file', file]);
|
|
2533
2519
|
expect(browserState.page.uploadFiles).toHaveBeenCalledWith('#file', [file], {});
|
|
2534
2520
|
expect(lastJsonLog()).toEqual({
|
|
2535
2521
|
uploaded: true,
|
|
@@ -2543,7 +2529,7 @@ describe('browser click/type commands', () => {
|
|
|
2543
2529
|
});
|
|
2544
2530
|
it('upload: rejects missing files before touching the page', async () => {
|
|
2545
2531
|
const program = createProgram('', '');
|
|
2546
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'upload', '#file', '/tmp/opencli-missing-file']);
|
|
2532
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'upload', '#file', '/tmp/opencli-missing-file']);
|
|
2547
2533
|
expect(lastJsonLog().error.code).toBe('file_not_found');
|
|
2548
2534
|
expect(browserState.page.uploadFiles).not.toHaveBeenCalled();
|
|
2549
2535
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2559,7 +2545,7 @@ describe('browser click/type commands', () => {
|
|
|
2559
2545
|
target_match_level: 'stable',
|
|
2560
2546
|
});
|
|
2561
2547
|
const program = createProgram('', '');
|
|
2562
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'drag', '.card', '.lane', '--from-nth', '2', '--to-nth', '1']);
|
|
2548
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'drag', '.card', '.lane', '--from-nth', '2', '--to-nth', '1']);
|
|
2563
2549
|
expect(browserState.page.drag).toHaveBeenCalledWith('.card', '.lane', { from: { nth: 2 }, to: { nth: 1 } });
|
|
2564
2550
|
expect(lastJsonLog()).toEqual({
|
|
2565
2551
|
dragged: true,
|
|
@@ -2573,7 +2559,7 @@ describe('browser click/type commands', () => {
|
|
|
2573
2559
|
});
|
|
2574
2560
|
it('drag: rejects malformed --from-nth before touching the page', async () => {
|
|
2575
2561
|
const program = createProgram('', '');
|
|
2576
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'drag', '.card', '.lane', '--from-nth', 'abc']);
|
|
2562
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'drag', '.card', '.lane', '--from-nth', 'abc']);
|
|
2577
2563
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2578
2564
|
expect(browserState.page.drag).not.toHaveBeenCalled();
|
|
2579
2565
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2583,7 +2569,7 @@ describe('browser click/type commands', () => {
|
|
|
2583
2569
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2584
2570
|
browserState.page.evaluate.mockResolvedValueOnce(false); // isAutocomplete
|
|
2585
2571
|
const program = createProgram('', '');
|
|
2586
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '#q', 'hello']);
|
|
2572
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '#q', 'hello']);
|
|
2587
2573
|
expect(browserState.page.click).toHaveBeenCalledWith('#q', {});
|
|
2588
2574
|
expect(browserState.page.wait).toHaveBeenCalledWith(0.3);
|
|
2589
2575
|
expect(browserState.page.typeText).toHaveBeenCalledWith('#q', 'hello', {});
|
|
@@ -2596,7 +2582,7 @@ describe('browser click/type commands', () => {
|
|
|
2596
2582
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2597
2583
|
browserState.page.evaluate.mockResolvedValueOnce(true);
|
|
2598
2584
|
const program = createProgram('', '');
|
|
2599
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '#q', 'hi']);
|
|
2585
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '#q', 'hi']);
|
|
2600
2586
|
const waitCalls = browserState.page.wait.mock.calls;
|
|
2601
2587
|
expect(waitCalls).toContainEqual([0.3]);
|
|
2602
2588
|
expect(waitCalls).toContainEqual([0.4]);
|
|
@@ -2608,7 +2594,7 @@ describe('browser click/type commands', () => {
|
|
|
2608
2594
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'reidentified' });
|
|
2609
2595
|
browserState.page.evaluate.mockResolvedValueOnce(false);
|
|
2610
2596
|
const program = createProgram('', '');
|
|
2611
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '9', 'hi']);
|
|
2597
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '9', 'hi']);
|
|
2612
2598
|
// The typeText call is the authoritative match_level source for the `type` envelope.
|
|
2613
2599
|
expect(lastJsonLog().match_level).toBe('reidentified');
|
|
2614
2600
|
});
|
|
@@ -2616,7 +2602,7 @@ describe('browser click/type commands', () => {
|
|
|
2616
2602
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 5, match_level: 'exact' });
|
|
2617
2603
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 5, match_level: 'exact' });
|
|
2618
2604
|
const program = createProgram('', '');
|
|
2619
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '.field', 'x', '--nth', '3']);
|
|
2605
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '.field', 'x', '--nth', '3']);
|
|
2620
2606
|
expect(browserState.page.click).toHaveBeenCalledWith('.field', { nth: 3 });
|
|
2621
2607
|
expect(browserState.page.typeText).toHaveBeenCalledWith('.field', 'x', { nth: 3 });
|
|
2622
2608
|
});
|
|
@@ -2632,7 +2618,7 @@ describe('browser click/type commands', () => {
|
|
|
2632
2618
|
mode: 'textarea',
|
|
2633
2619
|
});
|
|
2634
2620
|
const program = createProgram('', '');
|
|
2635
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'line1\\n/ / raw']);
|
|
2621
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '#msg', 'line1\\n/ / raw']);
|
|
2636
2622
|
expect(browserState.page.fillText).toHaveBeenCalledWith('#msg', 'line1\\n/ / raw', {});
|
|
2637
2623
|
expect(lastJsonLog()).toEqual({
|
|
2638
2624
|
filled: true,
|
|
@@ -2658,7 +2644,7 @@ describe('browser click/type commands', () => {
|
|
|
2658
2644
|
match_level: 'exact',
|
|
2659
2645
|
});
|
|
2660
2646
|
const program = createProgram('', '');
|
|
2661
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'expected']);
|
|
2647
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '#msg', 'expected']);
|
|
2662
2648
|
expect(lastJsonLog()).toEqual({
|
|
2663
2649
|
filled: true,
|
|
2664
2650
|
verified: false,
|
|
@@ -2673,7 +2659,7 @@ describe('browser click/type commands', () => {
|
|
|
2673
2659
|
});
|
|
2674
2660
|
it('fill: forwards --nth to page.fillText', async () => {
|
|
2675
2661
|
const program = createProgram('', '');
|
|
2676
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '.field', 'x', '--nth', '2']);
|
|
2662
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '.field', 'x', '--nth', '2']);
|
|
2677
2663
|
expect(browserState.page.fillText).toHaveBeenCalledWith('.field', 'x', { nth: 2 });
|
|
2678
2664
|
});
|
|
2679
2665
|
});
|
|
@@ -2686,7 +2672,7 @@ describe('browser select command', () => {
|
|
|
2686
2672
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2687
2673
|
evalMock.mockResolvedValueOnce({ selected: 'US' });
|
|
2688
2674
|
const program = createProgram('', '');
|
|
2689
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '#country', 'US']);
|
|
2675
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '#country', 'US']);
|
|
2690
2676
|
expect(lastJsonLog()).toEqual({ selected: 'US', target: '#country', matches_n: 1, match_level: 'exact' });
|
|
2691
2677
|
});
|
|
2692
2678
|
it('maps "Not a <select>" to a not_a_select error envelope', async () => {
|
|
@@ -2694,7 +2680,7 @@ describe('browser select command', () => {
|
|
|
2694
2680
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2695
2681
|
evalMock.mockResolvedValueOnce({ error: 'Not a <select>' });
|
|
2696
2682
|
const program = createProgram('', '');
|
|
2697
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '#not-select', 'US']);
|
|
2683
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '#not-select', 'US']);
|
|
2698
2684
|
const err = lastJsonLog().error;
|
|
2699
2685
|
expect(err.code).toBe('not_a_select');
|
|
2700
2686
|
expect(err.matches_n).toBe(1);
|
|
@@ -2705,7 +2691,7 @@ describe('browser select command', () => {
|
|
|
2705
2691
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2706
2692
|
evalMock.mockResolvedValueOnce({ error: 'Option "XX" not found', available: ['US', 'CA'] });
|
|
2707
2693
|
const program = createProgram('', '');
|
|
2708
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '#country', 'XX']);
|
|
2694
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '#country', 'XX']);
|
|
2709
2695
|
const err = lastJsonLog().error;
|
|
2710
2696
|
expect(err.code).toBe('option_not_found');
|
|
2711
2697
|
expect(err.available).toEqual(['US', 'CA']);
|
|
@@ -2723,7 +2709,7 @@ describe('browser select command', () => {
|
|
|
2723
2709
|
.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' })
|
|
2724
2710
|
.mockResolvedValueOnce({ selected: 'Uruguay' });
|
|
2725
2711
|
const program = createProgram('', '');
|
|
2726
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '--label', 'Country', 'Uruguay']);
|
|
2712
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '--label', 'Country', 'Uruguay']);
|
|
2727
2713
|
expect(lastJsonLog()).toEqual({ selected: 'Uruguay', target: '36', matches_n: 1, match_level: 'exact' });
|
|
2728
2714
|
});
|
|
2729
2715
|
it('surfaces selector_ambiguous from the resolver before calling selectResolvedJs', async () => {
|
|
@@ -2735,7 +2721,7 @@ describe('browser select command', () => {
|
|
|
2735
2721
|
matches_n: 2,
|
|
2736
2722
|
});
|
|
2737
2723
|
const program = createProgram('', '');
|
|
2738
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '.dropdown', 'US']);
|
|
2724
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '.dropdown', 'US']);
|
|
2739
2725
|
expect(lastJsonLog().error.code).toBe('selector_ambiguous');
|
|
2740
2726
|
// The select payload JS must not fire when resolution fails
|
|
2741
2727
|
expect(browserState.page.evaluate.mock.calls).toHaveLength(1);
|