@jackwener/opencli 1.7.15 → 1.7.17
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 +15 -13
- package/README.zh-CN.md +15 -12
- package/cli-manifest.json +165 -209
- package/clis/chatgpt/ask.js +3 -2
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +7 -2
- 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 +3 -2
- package/clis/chatgpt/send.js +3 -2
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +259 -25
- package/clis/chatgpt/utils.test.js +166 -2
- package/clis/claude/ask.js +23 -8
- package/clis/claude/detail.js +10 -3
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +9 -3
- package/clis/claude/read.js +3 -2
- package/clis/claude/send.js +9 -4
- package/clis/claude/status.js +1 -1
- package/clis/claude/utils.js +27 -4
- package/clis/deepseek/ask.js +22 -9
- package/clis/deepseek/detail.js +10 -2
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +14 -3
- package/clis/deepseek/read.js +3 -2
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/deepseek/utils.js +8 -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/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/linkedin/search.js +8 -11
- package/clis/maimai/search-talents.js +10 -6
- 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/openreview/author.js +58 -0
- package/clis/openreview/openreview.test.js +83 -1
- package/clis/openreview/utils.js +14 -0
- 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 -0
- package/clis/reddit/frontpage.js +1 -0
- package/clis/reddit/popular.js +1 -0
- package/clis/reddit/read.js +2 -0
- package/clis/reddit/read.test.js +4 -0
- package/clis/reddit/save.js +1 -0
- package/clis/reddit/saved.js +1 -0
- package/clis/reddit/search.js +1 -0
- package/clis/reddit/subreddit.js +1 -0
- package/clis/reddit/subscribe.js +1 -0
- package/clis/reddit/upvote.js +1 -0
- package/clis/reddit/upvoted.js +1 -0
- package/clis/reddit/user-comments.js +1 -0
- package/clis/reddit/user-posts.js +1 -0
- package/clis/reddit/user.js +1 -0
- package/clis/twitter/article.js +7 -4
- package/clis/twitter/bookmark-folder.js +3 -5
- package/clis/twitter/bookmark-folder.test.js +5 -2
- package/clis/twitter/bookmark-folders.js +3 -5
- package/clis/twitter/bookmark-folders.test.js +3 -1
- package/clis/twitter/bookmarks.js +3 -5
- package/clis/twitter/download.js +1 -0
- package/clis/twitter/followers.js +1 -0
- package/clis/twitter/following.js +3 -6
- package/clis/twitter/following.test.js +2 -1
- package/clis/twitter/likes.js +3 -5
- package/clis/twitter/list-add.js +4 -3
- package/clis/twitter/list-add.test.js +23 -1
- package/clis/twitter/list-remove.js +4 -3
- package/clis/twitter/list-remove.test.js +23 -1
- package/clis/twitter/list-tweets.js +3 -5
- package/clis/twitter/lists.js +3 -5
- package/clis/twitter/notifications.js +1 -0
- package/clis/twitter/profile.js +7 -4
- package/clis/twitter/search.js +1 -0
- package/clis/twitter/thread.js +5 -7
- package/clis/twitter/timeline.js +5 -7
- package/clis/twitter/trending.js +4 -4
- package/clis/twitter/tweets.js +3 -6
- package/clis/youtube/like.js +6 -2
- package/clis/youtube/subscribe.js +6 -2
- package/clis/youtube/unlike.js +6 -2
- package/clis/youtube/unsubscribe.js +6 -2
- package/clis/youtube/utils.js +19 -13
- package/clis/youtube/utils.test.js +17 -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 +4 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +4 -1
- package/dist/src/browser/daemon-client.d.ts +9 -16
- package/dist/src/browser/daemon-client.js +8 -9
- package/dist/src/browser/daemon-client.test.js +10 -0
- 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 +9 -7
- package/dist/src/browser/page.js +27 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +91 -125
- package/dist/src/cli.test.js +293 -180
- package/dist/src/commanderAdapter.js +9 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +8 -72
- package/dist/src/doctor.test.js +26 -97
- package/dist/src/execution.d.ts +3 -0
- package/dist/src/execution.js +47 -23
- package/dist/src/execution.test.js +68 -45
- package/dist/src/external-clis.yaml +24 -0
- package/dist/src/help.d.ts +1 -0
- package/dist/src/help.js +36 -1
- package/dist/src/main.js +0 -29
- 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 +10 -2
- package/dist/src/runtime.js +4 -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,19 +343,24 @@ 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');
|
|
350
350
|
expect(data.description).toBe('Browser control — navigate, click, type, extract, wait (no LLM needed)');
|
|
351
351
|
expect(data.command_count).toBeGreaterThan(20);
|
|
352
|
-
expect(data.namespace_options).
|
|
353
|
-
{
|
|
354
|
-
name: '
|
|
355
|
-
flags: '--
|
|
352
|
+
expect(data.namespace_options).toEqual(expect.arrayContaining([
|
|
353
|
+
expect.objectContaining({
|
|
354
|
+
name: 'session',
|
|
355
|
+
flags: '--session <name>',
|
|
356
356
|
takes_value: 'required',
|
|
357
|
-
},
|
|
358
|
-
|
|
357
|
+
}),
|
|
358
|
+
expect.objectContaining({
|
|
359
|
+
name: 'window',
|
|
360
|
+
flags: '--window <mode>',
|
|
361
|
+
takes_value: 'required',
|
|
362
|
+
}),
|
|
363
|
+
]));
|
|
359
364
|
expect(data.global_options).toEqual(expect.arrayContaining([
|
|
360
365
|
expect.objectContaining({
|
|
361
366
|
name: 'version',
|
|
@@ -401,7 +406,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
401
406
|
const browser = program.commands.find(cmd => cmd.name() === 'browser');
|
|
402
407
|
const tab = browser.commands.find(cmd => cmd.name() === 'tab');
|
|
403
408
|
expect(tab).toBeTruthy();
|
|
404
|
-
process.argv = ['node', 'opencli', 'browser', 'tab', '--help', '-f', 'yaml'];
|
|
409
|
+
process.argv = ['node', 'opencli', 'browser', '--session', 'test', 'tab', '--help', '-f', 'yaml'];
|
|
405
410
|
const data = yaml.load(tab.helpInformation());
|
|
406
411
|
expect(data).toMatchObject({
|
|
407
412
|
namespace: 'browser',
|
|
@@ -421,7 +426,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
421
426
|
usage: 'opencli browser tab close [targetId] [options]',
|
|
422
427
|
positionals: [{ name: 'targetId', help: 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"' }],
|
|
423
428
|
});
|
|
424
|
-
expect(data.namespace_options.map((option) => option.name)).toEqual(['
|
|
429
|
+
expect(data.namespace_options.map((option) => option.name)).toEqual(['session', 'window']);
|
|
425
430
|
expect(data.structured_help).toMatchObject({
|
|
426
431
|
usage: 'opencli browser tab --help -f yaml',
|
|
427
432
|
});
|
|
@@ -437,7 +442,7 @@ describe('createProgram root help descriptions', () => {
|
|
|
437
442
|
const browser = program.commands.find(cmd => cmd.name() === 'browser');
|
|
438
443
|
const click = browser.commands.find(cmd => cmd.name() === 'click');
|
|
439
444
|
expect(click).toBeTruthy();
|
|
440
|
-
process.argv = ['node', 'opencli', 'browser', 'click', '--help', '-f', 'yaml'];
|
|
445
|
+
process.argv = ['node', 'opencli', 'browser', '--session', 'test', 'click', '--help', '-f', 'yaml'];
|
|
441
446
|
const data = yaml.load(click.helpInformation());
|
|
442
447
|
expect(data).toMatchObject({
|
|
443
448
|
namespace: 'browser',
|
|
@@ -450,13 +455,113 @@ describe('createProgram root help descriptions', () => {
|
|
|
450
455
|
},
|
|
451
456
|
});
|
|
452
457
|
expect(data.command_options.map((option) => option.name)).toEqual(['role', 'name', 'label', 'text', 'testid', 'nth', 'tab']);
|
|
453
|
-
expect(data.namespace_options.map((option) => option.name)).toEqual(['
|
|
458
|
+
expect(data.namespace_options.map((option) => option.name)).toEqual(['session', 'window']);
|
|
454
459
|
expect(data.global_options.map((option) => option.name)).toContain('profile');
|
|
455
460
|
}
|
|
456
461
|
finally {
|
|
457
462
|
process.argv = argv;
|
|
458
463
|
}
|
|
459
464
|
});
|
|
465
|
+
it('renders daemon namespace structured help with leaves and global options', () => {
|
|
466
|
+
const argv = process.argv;
|
|
467
|
+
try {
|
|
468
|
+
const program = createProgram('', '');
|
|
469
|
+
const daemon = program.commands.find(cmd => cmd.name() === 'daemon');
|
|
470
|
+
expect(daemon).toBeTruthy();
|
|
471
|
+
process.argv = ['node', 'opencli', 'daemon', '--help', '-f', 'yaml'];
|
|
472
|
+
const data = yaml.load(daemon.helpInformation());
|
|
473
|
+
expect(data).toMatchObject({
|
|
474
|
+
namespace: 'daemon',
|
|
475
|
+
command: 'opencli daemon',
|
|
476
|
+
usage: 'opencli daemon <command> [args] [options]',
|
|
477
|
+
description: 'Manage the opencli daemon',
|
|
478
|
+
command_count: 3,
|
|
479
|
+
namespace_options: [],
|
|
480
|
+
structured_help: { usage: 'opencli daemon --help -f yaml' },
|
|
481
|
+
});
|
|
482
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['restart', 'status', 'stop']);
|
|
483
|
+
expect(data.global_options.map((option) => option.name)).toEqual(expect.arrayContaining(['version', 'profile']));
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
process.argv = argv;
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
it('renders plugin namespace structured help with positional + option leaves', () => {
|
|
490
|
+
const argv = process.argv;
|
|
491
|
+
try {
|
|
492
|
+
const program = createProgram('', '');
|
|
493
|
+
const plugin = program.commands.find(cmd => cmd.name() === 'plugin');
|
|
494
|
+
expect(plugin).toBeTruthy();
|
|
495
|
+
process.argv = ['node', 'opencli', 'plugin', '--help', '-f', 'yaml'];
|
|
496
|
+
const data = yaml.load(plugin.helpInformation());
|
|
497
|
+
expect(data).toMatchObject({
|
|
498
|
+
namespace: 'plugin',
|
|
499
|
+
command: 'opencli plugin',
|
|
500
|
+
description: 'Manage opencli plugins',
|
|
501
|
+
namespace_options: [],
|
|
502
|
+
});
|
|
503
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['create', 'install', 'list', 'uninstall', 'update']);
|
|
504
|
+
const update = data.commands.find((cmd) => cmd.name === 'update');
|
|
505
|
+
expect(update).toMatchObject({
|
|
506
|
+
usage: 'opencli plugin update [name] [options]',
|
|
507
|
+
positionals: [{ name: 'name' }],
|
|
508
|
+
});
|
|
509
|
+
expect(update.command_options.map((option) => option.name)).toEqual(['all']);
|
|
510
|
+
}
|
|
511
|
+
finally {
|
|
512
|
+
process.argv = argv;
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
it('renders adapter namespace structured help preserving original description after applyRootSubcommandSummaries', () => {
|
|
516
|
+
const argv = process.argv;
|
|
517
|
+
try {
|
|
518
|
+
const program = createProgram('', '');
|
|
519
|
+
const adapter = program.commands.find(cmd => cmd.name() === 'adapter');
|
|
520
|
+
expect(adapter).toBeTruthy();
|
|
521
|
+
process.argv = ['node', 'opencli', 'adapter', '--help', '-f', 'yaml'];
|
|
522
|
+
const data = yaml.load(adapter.helpInformation());
|
|
523
|
+
// applyRootSubcommandSummaries() rewrites .description() to a child-name listing;
|
|
524
|
+
// structured help must surface the original product description via the snapshot.
|
|
525
|
+
expect(data.description).toBe('Manage CLI adapters');
|
|
526
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['eject', 'reset', 'status']);
|
|
527
|
+
const reset = data.commands.find((cmd) => cmd.name === 'reset');
|
|
528
|
+
expect(reset).toMatchObject({
|
|
529
|
+
usage: 'opencli adapter reset [site] [options]',
|
|
530
|
+
positionals: [{ name: 'site' }],
|
|
531
|
+
});
|
|
532
|
+
expect(reset.command_options.map((option) => option.name)).toEqual(['all']);
|
|
533
|
+
}
|
|
534
|
+
finally {
|
|
535
|
+
process.argv = argv;
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
it('renders profile namespace structured help including required positionals', () => {
|
|
539
|
+
const argv = process.argv;
|
|
540
|
+
try {
|
|
541
|
+
const program = createProgram('', '');
|
|
542
|
+
const profile = program.commands.find(cmd => cmd.name() === 'profile');
|
|
543
|
+
expect(profile).toBeTruthy();
|
|
544
|
+
process.argv = ['node', 'opencli', 'profile', '--help', '-f', 'yaml'];
|
|
545
|
+
const data = yaml.load(profile.helpInformation());
|
|
546
|
+
expect(data).toMatchObject({
|
|
547
|
+
namespace: 'profile',
|
|
548
|
+
description: 'Manage Browser Bridge Chrome profiles',
|
|
549
|
+
command_count: 3,
|
|
550
|
+
});
|
|
551
|
+
expect(data.commands.map((cmd) => cmd.name)).toEqual(['list', 'rename', 'use']);
|
|
552
|
+
const rename = data.commands.find((cmd) => cmd.name === 'rename');
|
|
553
|
+
expect(rename).toMatchObject({
|
|
554
|
+
usage: 'opencli profile rename <contextId> <alias> [options]',
|
|
555
|
+
positionals: [
|
|
556
|
+
{ name: 'contextId', required: true },
|
|
557
|
+
{ name: 'alias', required: true },
|
|
558
|
+
],
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
finally {
|
|
562
|
+
process.argv = argv;
|
|
563
|
+
}
|
|
564
|
+
});
|
|
460
565
|
});
|
|
461
566
|
describe('resolveBrowserVerifyInvocation', () => {
|
|
462
567
|
it('prefers the built entry declared in package metadata', () => {
|
|
@@ -554,7 +659,7 @@ describe('browser verify', () => {
|
|
|
554
659
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
555
660
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
556
661
|
const program = createProgram('', '');
|
|
557
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture', '--trace', 'retain-on-failure']);
|
|
662
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--no-fixture', '--trace', 'retain-on-failure']);
|
|
558
663
|
expect(mockExecFileSync).toHaveBeenCalledTimes(1);
|
|
559
664
|
const [, execArgs] = mockExecFileSync.mock.calls[0];
|
|
560
665
|
expect(execArgs.slice(-6)).toEqual(['hn', 'top', '--trace', 'retain-on-failure', '--format', 'json']);
|
|
@@ -582,7 +687,7 @@ describe('browser verify', () => {
|
|
|
582
687
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
583
688
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
584
689
|
const program = createProgram('', '');
|
|
585
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture', '--seed-args', 'opencli-verify']);
|
|
690
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--no-fixture', '--seed-args', 'opencli-verify']);
|
|
586
691
|
expect(mockExecFileSync).toHaveBeenCalledTimes(1);
|
|
587
692
|
const [, execArgs] = mockExecFileSync.mock.calls[0];
|
|
588
693
|
expect(execArgs.slice(-5)).toEqual(['hn', 'top', 'opencli-verify', '--format', 'json']);
|
|
@@ -611,7 +716,7 @@ describe('browser verify', () => {
|
|
|
611
716
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
612
717
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
613
718
|
const program = createProgram('', '');
|
|
614
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--write-fixture', '--seed-args', 'opencli-verify']);
|
|
719
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--write-fixture', '--seed-args', 'opencli-verify']);
|
|
615
720
|
const fixtureFile = path.join(fakeHome, '.opencli', 'sites', 'hn', 'verify', 'top.json');
|
|
616
721
|
const fixture = JSON.parse(fs.readFileSync(fixtureFile, 'utf-8'));
|
|
617
722
|
expect(fixture.args).toEqual(['opencli-verify']);
|
|
@@ -643,7 +748,7 @@ describe('browser verify', () => {
|
|
|
643
748
|
fs.mkdirSync(adapterDir, { recursive: true });
|
|
644
749
|
fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
|
|
645
750
|
const program = createProgram('', '');
|
|
646
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture']);
|
|
751
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'verify', 'hn/top', '--no-fixture']);
|
|
647
752
|
expect(process.exitCode).toBe(1);
|
|
648
753
|
const output = consoleLogSpy.mock.calls.map((args) => args.join(' ')).join('\n');
|
|
649
754
|
expect(output).toContain('Adapter output violates row shape conventions');
|
|
@@ -717,8 +822,8 @@ describe('profile list', () => {
|
|
|
717
822
|
describe('browser tab targeting commands', () => {
|
|
718
823
|
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
719
824
|
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
720
|
-
function getBrowserStateFile(cacheDir) {
|
|
721
|
-
return path.join(cacheDir, 'browser-state',
|
|
825
|
+
function getBrowserStateFile(cacheDir, session = 'test') {
|
|
826
|
+
return path.join(cacheDir, 'browser-state', `${session}.json`);
|
|
722
827
|
}
|
|
723
828
|
beforeEach(() => {
|
|
724
829
|
process.exitCode = undefined;
|
|
@@ -727,8 +832,9 @@ describe('browser tab targeting commands', () => {
|
|
|
727
832
|
stderrSpy.mockClear();
|
|
728
833
|
mockBrowserConnect.mockClear();
|
|
729
834
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
835
|
+
delete process.env.OPENCLI_WINDOW;
|
|
730
836
|
mockBindTab.mockReset().mockResolvedValue({
|
|
731
|
-
|
|
837
|
+
session: 'test',
|
|
732
838
|
page: 'tab-2',
|
|
733
839
|
url: 'https://user.example/inbox',
|
|
734
840
|
title: 'Inbox',
|
|
@@ -759,6 +865,7 @@ describe('browser tab targeting commands', () => {
|
|
|
759
865
|
screenshot: vi.fn().mockResolvedValue('base64-shot'),
|
|
760
866
|
annotatedScreenshot: vi.fn().mockResolvedValue('annotated-base64-shot'),
|
|
761
867
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
868
|
+
closeWindow: vi.fn().mockResolvedValue(undefined),
|
|
762
869
|
waitForDownload: vi.fn().mockResolvedValue({
|
|
763
870
|
downloaded: true,
|
|
764
871
|
filename: '/tmp/receipt.pdf',
|
|
@@ -766,6 +873,7 @@ describe('browser tab targeting commands', () => {
|
|
|
766
873
|
state: 'complete',
|
|
767
874
|
elapsedMs: 10,
|
|
768
875
|
}),
|
|
876
|
+
session: 'test',
|
|
769
877
|
};
|
|
770
878
|
});
|
|
771
879
|
function lastJsonLog() {
|
|
@@ -777,36 +885,37 @@ describe('browser tab targeting commands', () => {
|
|
|
777
885
|
throw new Error(`Expected string arg to console.log, got ${typeof last}`);
|
|
778
886
|
return JSON.parse(last);
|
|
779
887
|
}
|
|
780
|
-
it('binds the current Chrome tab into a
|
|
888
|
+
it('binds the current Chrome tab into a browser session', async () => {
|
|
781
889
|
const program = createProgram('', '');
|
|
782
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
783
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
784
|
-
expect(mockBindTab).toHaveBeenCalledWith('
|
|
785
|
-
matchDomain: 'user.example',
|
|
786
|
-
matchPathPrefix: '/inbox',
|
|
787
|
-
});
|
|
890
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'bind']);
|
|
891
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser' });
|
|
892
|
+
expect(mockBindTab).toHaveBeenCalledWith('test', {});
|
|
788
893
|
const out = lastJsonLog();
|
|
789
|
-
expect(out.
|
|
894
|
+
expect(out.session).toBe('test');
|
|
790
895
|
expect(out.url).toBe('https://user.example/inbox');
|
|
791
896
|
});
|
|
792
|
-
it('
|
|
897
|
+
it('requires an explicit session for browser commands', async () => {
|
|
793
898
|
const program = createProgram('', '');
|
|
794
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
899
|
+
await program.parseAsync(['node', 'opencli', 'browser', 'state']);
|
|
795
900
|
expect(mockBrowserConnect).not.toHaveBeenCalled();
|
|
796
|
-
expect(
|
|
797
|
-
const out = lastJsonLog();
|
|
798
|
-
expect(out.error.code).toBe('invalid_bind_workspace');
|
|
901
|
+
expect(stderrSpy.mock.calls.flat().join('')).toContain('--session <name> is required');
|
|
799
902
|
expect(process.exitCode).toBeDefined();
|
|
800
903
|
});
|
|
801
|
-
it('runs browser commands against an explicit
|
|
904
|
+
it('runs browser commands against an explicit session', async () => {
|
|
802
905
|
const program = createProgram('', '');
|
|
803
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--
|
|
804
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
906
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state']);
|
|
907
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser', windowMode: 'foreground' });
|
|
908
|
+
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
909
|
+
});
|
|
910
|
+
it('passes browser --window through Commander options without relying on env pre-processing', async () => {
|
|
911
|
+
const program = createProgram('', '');
|
|
912
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', '--window', 'background', 'state']);
|
|
913
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser', windowMode: 'background' });
|
|
805
914
|
expect(browserState.page?.snapshot).toHaveBeenCalled();
|
|
806
915
|
});
|
|
807
916
|
it('passes the opt-in AX source to browser state', async () => {
|
|
808
917
|
const program = createProgram('', '');
|
|
809
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--source', 'ax']);
|
|
918
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--source', 'ax']);
|
|
810
919
|
expect(browserState.page?.snapshot).toHaveBeenCalledWith({ viewportExpand: 2000, source: 'ax' });
|
|
811
920
|
});
|
|
812
921
|
it('prints DOM vs AX snapshot metrics without changing default state output', async () => {
|
|
@@ -820,7 +929,7 @@ describe('browser tab targeting commands', () => {
|
|
|
820
929
|
}),
|
|
821
930
|
};
|
|
822
931
|
const program = createProgram('', '');
|
|
823
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--compare-sources']);
|
|
932
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--compare-sources']);
|
|
824
933
|
expect(browserState.page?.snapshot).toHaveBeenCalledWith({ viewportExpand: 2000, source: 'dom' });
|
|
825
934
|
expect(browserState.page?.snapshot).toHaveBeenCalledWith({ viewportExpand: 2000, source: 'ax' });
|
|
826
935
|
const out = lastJsonLog();
|
|
@@ -838,7 +947,7 @@ describe('browser tab targeting commands', () => {
|
|
|
838
947
|
}),
|
|
839
948
|
};
|
|
840
949
|
const program = createProgram('', '');
|
|
841
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--compare-sources']);
|
|
950
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--compare-sources']);
|
|
842
951
|
const out = lastJsonLog();
|
|
843
952
|
expect(out.sources.dom).toMatchObject({ ok: true, refs: 1 });
|
|
844
953
|
expect(out.sources.ax).toMatchObject({
|
|
@@ -848,7 +957,7 @@ describe('browser tab targeting commands', () => {
|
|
|
848
957
|
});
|
|
849
958
|
it('rejects unknown browser state sources before touching the page', async () => {
|
|
850
959
|
const program = createProgram('', '');
|
|
851
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'state', '--source', 'magic']);
|
|
960
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'state', '--source', 'magic']);
|
|
852
961
|
expect(browserState.page?.snapshot).not.toHaveBeenCalled();
|
|
853
962
|
const out = lastJsonLog();
|
|
854
963
|
expect(out.error.code).toBe('invalid_source');
|
|
@@ -856,7 +965,7 @@ describe('browser tab targeting commands', () => {
|
|
|
856
965
|
});
|
|
857
966
|
it('captures annotated screenshots through the visual ref overlay path', async () => {
|
|
858
967
|
const program = createProgram('', '');
|
|
859
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'screenshot', '--annotate']);
|
|
968
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'screenshot', '--annotate']);
|
|
860
969
|
expect(browserState.page?.annotatedScreenshot).toHaveBeenCalledWith({
|
|
861
970
|
fullPage: false,
|
|
862
971
|
annotate: true,
|
|
@@ -867,38 +976,36 @@ describe('browser tab targeting commands', () => {
|
|
|
867
976
|
expect(browserState.page?.screenshot).not.toHaveBeenCalled();
|
|
868
977
|
expect(consoleLogSpy).toHaveBeenLastCalledWith('annotated-base64-shot');
|
|
869
978
|
});
|
|
870
|
-
it('
|
|
979
|
+
it('allows history navigation in a bound session', async () => {
|
|
871
980
|
browserState.page = {
|
|
872
981
|
...browserState.page,
|
|
873
|
-
workspace: 'bound:default',
|
|
874
982
|
evaluate: vi.fn(),
|
|
875
983
|
wait: vi.fn(),
|
|
984
|
+
session: 'test',
|
|
876
985
|
};
|
|
877
986
|
const program = createProgram('', '');
|
|
878
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--
|
|
879
|
-
expect(browserState.page?.evaluate).
|
|
880
|
-
const out = lastJsonLog();
|
|
881
|
-
expect(out.error.code).toBe('bound_navigation_blocked');
|
|
987
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'back']);
|
|
988
|
+
expect(browserState.page?.evaluate).toHaveBeenCalledWith('history.back()');
|
|
882
989
|
});
|
|
883
|
-
it('unbinds a
|
|
990
|
+
it('unbinds a session through the daemon close-window command', async () => {
|
|
884
991
|
const program = createProgram('', '');
|
|
885
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
886
|
-
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30,
|
|
887
|
-
expect(mockSendCommand).toHaveBeenCalledWith('close-window', {
|
|
992
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'unbind']);
|
|
993
|
+
expect(mockBrowserConnect).toHaveBeenCalledWith({ timeout: 30, session: 'test', surface: 'browser' });
|
|
994
|
+
expect(mockSendCommand).toHaveBeenCalledWith('close-window', { session: 'test', surface: 'browser' });
|
|
888
995
|
const out = lastJsonLog();
|
|
889
|
-
expect(out).toEqual({ unbound: true,
|
|
996
|
+
expect(out).toEqual({ unbound: true, session: 'test' });
|
|
890
997
|
});
|
|
891
998
|
it('does not print false success when unbind fails', async () => {
|
|
892
|
-
mockSendCommand.mockRejectedValueOnce(new BrowserCommandError('
|
|
999
|
+
mockSendCommand.mockRejectedValueOnce(new BrowserCommandError('Session "test" is not attached to a tab.', 'bound_session_missing', 'Run bind again, then retry the browser command.'));
|
|
893
1000
|
const program = createProgram('', '');
|
|
894
|
-
await program.parseAsync(['node', 'opencli', 'browser', '
|
|
1001
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'unbind']);
|
|
895
1002
|
const out = lastJsonLog();
|
|
896
1003
|
expect(out.error.code).toBe('bound_session_missing');
|
|
897
1004
|
expect(process.exitCode).toBeDefined();
|
|
898
1005
|
});
|
|
899
1006
|
it('accepts JavaScript dialogs through the browser dialog command', async () => {
|
|
900
1007
|
const program = createProgram('', '');
|
|
901
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'dialog', 'accept', '--text', 'ok']);
|
|
1008
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'dialog', 'accept', '--text', 'ok']);
|
|
902
1009
|
expect(browserState.page?.handleJavaScriptDialog).toHaveBeenCalledWith(true, 'ok');
|
|
903
1010
|
const out = lastJsonLog();
|
|
904
1011
|
expect(out).toEqual({ handled: true, action: 'accept', text: 'ok' });
|
|
@@ -909,7 +1016,7 @@ describe('browser tab targeting commands', () => {
|
|
|
909
1016
|
evaluate: vi.fn().mockRejectedValue(new Error('JavaScript dialog showing')),
|
|
910
1017
|
};
|
|
911
1018
|
const program = createProgram('', '');
|
|
912
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1019
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
913
1020
|
const out = lastJsonLog();
|
|
914
1021
|
expect(out.error.code).toBe('javascript_dialog_open');
|
|
915
1022
|
expect(out.error.hint).toContain('browser dialog accept');
|
|
@@ -917,7 +1024,7 @@ describe('browser tab targeting commands', () => {
|
|
|
917
1024
|
});
|
|
918
1025
|
it('binds browser commands to an explicit target tab via --tab', async () => {
|
|
919
1026
|
const program = createProgram('', '');
|
|
920
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--tab', 'tab-2', 'document.title']);
|
|
1027
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', '--tab', 'tab-2', 'document.title']);
|
|
921
1028
|
expect(browserState.page?.setActivePage).toHaveBeenCalledWith('tab-2');
|
|
922
1029
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
923
1030
|
});
|
|
@@ -929,7 +1036,7 @@ describe('browser tab targeting commands', () => {
|
|
|
929
1036
|
evaluate: vi.fn(),
|
|
930
1037
|
};
|
|
931
1038
|
const program = createProgram('', '');
|
|
932
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--tab', 'tab-stale', 'document.title']);
|
|
1039
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', '--tab', 'tab-stale', 'document.title']);
|
|
933
1040
|
expect(process.exitCode).toBeDefined();
|
|
934
1041
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
935
1042
|
expect(browserState.page?.evaluate).not.toHaveBeenCalled();
|
|
@@ -937,50 +1044,50 @@ describe('browser tab targeting commands', () => {
|
|
|
937
1044
|
});
|
|
938
1045
|
it('lists tabs with target IDs via browser tab list', async () => {
|
|
939
1046
|
const program = createProgram('', '');
|
|
940
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'list']);
|
|
1047
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'list']);
|
|
941
1048
|
expect(browserState.page?.tabs).toHaveBeenCalledTimes(1);
|
|
942
1049
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-1"');
|
|
943
1050
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-2"');
|
|
944
1051
|
});
|
|
945
1052
|
it('creates a new tab and prints its target ID', async () => {
|
|
946
1053
|
const program = createProgram('', '');
|
|
947
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'new', 'https://three.example']);
|
|
1054
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'new', 'https://three.example']);
|
|
948
1055
|
expect(browserState.page?.newTab).toHaveBeenCalledWith('https://three.example');
|
|
949
1056
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-3"');
|
|
950
1057
|
});
|
|
951
1058
|
it('prints the resolved target ID when browser open creates or navigates a tab', async () => {
|
|
952
1059
|
const program = createProgram('', '');
|
|
953
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'open', 'https://example.com']);
|
|
1060
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'open', 'https://example.com']);
|
|
954
1061
|
expect(browserState.page?.goto).toHaveBeenCalledWith('https://example.com');
|
|
955
1062
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"url": "https://one.example"');
|
|
956
1063
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"page": "tab-1"');
|
|
957
1064
|
});
|
|
958
1065
|
it('lists cross-origin frames via browser frames', async () => {
|
|
959
1066
|
const program = createProgram('', '');
|
|
960
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'frames']);
|
|
1067
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'frames']);
|
|
961
1068
|
expect(browserState.page?.frames).toHaveBeenCalledTimes(1);
|
|
962
1069
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"frameId": "frame-1"');
|
|
963
1070
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"url": "https://x.example/embed"');
|
|
964
1071
|
});
|
|
965
1072
|
it('routes browser eval --frame through frame-targeted evaluation', async () => {
|
|
966
1073
|
const program = createProgram('', '');
|
|
967
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--frame', '0', 'document.title']);
|
|
1074
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', '--frame', '0', 'document.title']);
|
|
968
1075
|
expect(browserState.page?.evaluateInFrame).toHaveBeenCalledWith('document.title', 0);
|
|
969
1076
|
expect(browserState.page?.evaluate).not.toHaveBeenCalled();
|
|
970
1077
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('inside frame');
|
|
971
1078
|
});
|
|
972
1079
|
it('does not promote a newly created tab to the persisted default target', async () => {
|
|
973
1080
|
const program = createProgram('', '');
|
|
974
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'new', 'https://three.example']);
|
|
975
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1081
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'new', 'https://three.example']);
|
|
1082
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
976
1083
|
expect(browserState.page?.newTab).toHaveBeenCalledWith('https://three.example');
|
|
977
1084
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
978
1085
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
979
1086
|
});
|
|
980
1087
|
it('persists an explicitly selected tab as the default target for later untargeted commands', async () => {
|
|
981
1088
|
const program = createProgram('', '');
|
|
982
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'select', 'tab-2']);
|
|
983
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1089
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'select', 'tab-2']);
|
|
1090
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
984
1091
|
expect(browserState.page?.selectTab).toHaveBeenCalledWith('tab-2');
|
|
985
1092
|
expect(browserState.page?.setActivePage).toHaveBeenCalledWith('tab-2');
|
|
986
1093
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
@@ -989,7 +1096,7 @@ describe('browser tab targeting commands', () => {
|
|
|
989
1096
|
it('clears a saved default target when it is no longer present in the current session', async () => {
|
|
990
1097
|
const cacheDir = String(process.env.OPENCLI_CACHE_DIR);
|
|
991
1098
|
const program = createProgram('', '');
|
|
992
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'select', 'tab-2']);
|
|
1099
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'select', 'tab-2']);
|
|
993
1100
|
expect(fs.existsSync(getBrowserStateFile(cacheDir))).toBe(true);
|
|
994
1101
|
browserState.page = {
|
|
995
1102
|
setActivePage: vi.fn(),
|
|
@@ -998,35 +1105,36 @@ describe('browser tab targeting commands', () => {
|
|
|
998
1105
|
evaluate: vi.fn().mockResolvedValue({ ok: true }),
|
|
999
1106
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
1000
1107
|
};
|
|
1001
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1108
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1002
1109
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
1003
1110
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
1004
1111
|
expect(fs.existsSync(getBrowserStateFile(cacheDir))).toBe(false);
|
|
1005
1112
|
});
|
|
1006
1113
|
it('clears the persisted default target when that tab is closed', async () => {
|
|
1007
1114
|
const program = createProgram('', '');
|
|
1008
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'select', 'tab-2']);
|
|
1009
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'close', 'tab-2']);
|
|
1115
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'select', 'tab-2']);
|
|
1116
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'close', 'tab-2']);
|
|
1010
1117
|
vi.mocked(browserState.page?.setActivePage).mockClear();
|
|
1011
1118
|
vi.mocked(browserState.page?.evaluate).mockClear();
|
|
1012
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
|
|
1119
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'eval', 'document.title']);
|
|
1013
1120
|
expect(browserState.page?.closeTab).toHaveBeenCalledWith('tab-2');
|
|
1014
1121
|
expect(browserState.page?.setActivePage).not.toHaveBeenCalled();
|
|
1015
1122
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith('document.title');
|
|
1016
1123
|
});
|
|
1017
1124
|
it('closes a tab by target ID', async () => {
|
|
1018
1125
|
const program = createProgram('', '');
|
|
1019
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'close', 'tab-2']);
|
|
1126
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'close', 'tab-2']);
|
|
1020
1127
|
expect(browserState.page?.closeTab).toHaveBeenCalledWith('tab-2');
|
|
1021
1128
|
expect(consoleLogSpy.mock.calls.flat().join('\n')).toContain('"closed": "tab-2"');
|
|
1022
1129
|
});
|
|
1023
1130
|
it('rejects closing a stale tab target ID that is no longer in the current session', async () => {
|
|
1024
1131
|
browserState.page = {
|
|
1132
|
+
session: 'test',
|
|
1025
1133
|
tabs: vi.fn().mockResolvedValue([]),
|
|
1026
1134
|
closeTab: vi.fn(),
|
|
1027
1135
|
};
|
|
1028
1136
|
const program = createProgram('', '');
|
|
1029
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'tab', 'close', 'tab-stale']);
|
|
1137
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'tab', 'close', 'tab-stale']);
|
|
1030
1138
|
expect(process.exitCode).toBeDefined();
|
|
1031
1139
|
expect(browserState.page?.closeTab).not.toHaveBeenCalled();
|
|
1032
1140
|
expect(stderrSpy.mock.calls.flat().join('\n')).toContain('Target tab tab-stale is not part of the current browser session');
|
|
@@ -1073,7 +1181,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1073
1181
|
]),
|
|
1074
1182
|
};
|
|
1075
1183
|
const program = createProgram('', '');
|
|
1076
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'analyze', 'https://target.example/']);
|
|
1184
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'analyze', 'https://target.example/']);
|
|
1077
1185
|
const out = lastJsonLog();
|
|
1078
1186
|
expect(browserState.page?.readNetworkCapture).toHaveBeenCalledTimes(2);
|
|
1079
1187
|
expect(out.anti_bot.vendor).toBe('cloudflare');
|
|
@@ -1134,7 +1242,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1134
1242
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
1135
1243
|
};
|
|
1136
1244
|
const program = createProgram('', '');
|
|
1137
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'analyze', 'https://target.example/']);
|
|
1245
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'analyze', 'https://target.example/']);
|
|
1138
1246
|
const out = lastJsonLog();
|
|
1139
1247
|
expect(browserState.page?.readNetworkCapture).toHaveBeenCalledTimes(2);
|
|
1140
1248
|
expect(bufferReads).toBe(2);
|
|
@@ -1173,7 +1281,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1173
1281
|
]),
|
|
1174
1282
|
};
|
|
1175
1283
|
const program = createProgram('', '');
|
|
1176
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1284
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1177
1285
|
const out = lastJsonLog();
|
|
1178
1286
|
expect(browserState.page?.startNetworkCapture).toHaveBeenCalledTimes(1);
|
|
1179
1287
|
expect(browserState.page?.evaluate).toHaveBeenCalledWith(expect.stringContaining('window.__opencli_net'));
|
|
@@ -1221,7 +1329,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1221
1329
|
readNetworkCapture: vi.fn().mockResolvedValue([]),
|
|
1222
1330
|
};
|
|
1223
1331
|
const program = createProgram('', '');
|
|
1224
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1332
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'xhr', '/api/target', '--timeout', '900']);
|
|
1225
1333
|
const out = lastJsonLog();
|
|
1226
1334
|
expect(browserState.page?.startNetworkCapture).toHaveBeenCalledTimes(1);
|
|
1227
1335
|
expect(browserState.page?.readNetworkCapture).toHaveBeenCalledTimes(2);
|
|
@@ -1245,7 +1353,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1245
1353
|
tabs: vi.fn().mockResolvedValue([{ index: 0, page: 'tab-1', url: 'https://target.example', title: 'Target', active: true }]),
|
|
1246
1354
|
};
|
|
1247
1355
|
const program = createProgram('', '');
|
|
1248
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1356
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1249
1357
|
expect(browserState.page?.waitForDownload).toHaveBeenCalledWith('receipt', 900);
|
|
1250
1358
|
expect(lastJsonLog()).toEqual({
|
|
1251
1359
|
downloaded: true,
|
|
@@ -1271,7 +1379,7 @@ describe('browser tab targeting commands', () => {
|
|
|
1271
1379
|
tabs: vi.fn().mockResolvedValue([{ index: 0, page: 'tab-1', url: 'https://target.example', title: 'Target', active: true }]),
|
|
1272
1380
|
};
|
|
1273
1381
|
const program = createProgram('', '');
|
|
1274
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1382
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'wait', 'download', 'receipt', '--timeout', '900']);
|
|
1275
1383
|
const out = lastJsonLog();
|
|
1276
1384
|
expect(out.error.code).toBe('download_not_seen');
|
|
1277
1385
|
expect(out.download.elapsedMs).toBe(900);
|
|
@@ -1281,10 +1389,10 @@ describe('browser tab targeting commands', () => {
|
|
|
1281
1389
|
describe('browser network command', () => {
|
|
1282
1390
|
const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
1283
1391
|
function getNetworkCachePath(cacheDir) {
|
|
1284
|
-
return path.join(cacheDir, 'browser-network', '
|
|
1392
|
+
return path.join(cacheDir, 'browser-network', 'test.json');
|
|
1285
1393
|
}
|
|
1286
|
-
function
|
|
1287
|
-
return path.join(cacheDir, 'browser-network', '
|
|
1394
|
+
function getCustomNetworkCachePath(cacheDir) {
|
|
1395
|
+
return path.join(cacheDir, 'browser-network', 'custom.json');
|
|
1288
1396
|
}
|
|
1289
1397
|
function lastJsonLog() {
|
|
1290
1398
|
const calls = consoleLogSpy.mock.calls;
|
|
@@ -1302,6 +1410,7 @@ describe('browser network command', () => {
|
|
|
1302
1410
|
mockBrowserConnect.mockClear();
|
|
1303
1411
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
1304
1412
|
browserState.page = {
|
|
1413
|
+
session: 'test',
|
|
1305
1414
|
setActivePage: vi.fn(),
|
|
1306
1415
|
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
1307
1416
|
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
|
|
@@ -1328,7 +1437,7 @@ describe('browser network command', () => {
|
|
|
1328
1437
|
it('emits JSON with shape previews and persists the capture to disk', async () => {
|
|
1329
1438
|
const cacheDir = String(process.env.OPENCLI_CACHE_DIR);
|
|
1330
1439
|
const program = createProgram('', '');
|
|
1331
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1440
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1332
1441
|
const out = lastJsonLog();
|
|
1333
1442
|
expect(out.count).toBe(1);
|
|
1334
1443
|
expect(out.filtered_out).toBe(1);
|
|
@@ -1337,22 +1446,22 @@ describe('browser network command', () => {
|
|
|
1337
1446
|
expect(out.entries[0]).not.toHaveProperty('body');
|
|
1338
1447
|
expect(fs.existsSync(getNetworkCachePath(cacheDir))).toBe(true);
|
|
1339
1448
|
});
|
|
1340
|
-
it('uses the selected browser
|
|
1449
|
+
it('uses the selected browser session for network cache scope', async () => {
|
|
1341
1450
|
const cacheDir = String(process.env.OPENCLI_CACHE_DIR);
|
|
1342
1451
|
browserState.page = {
|
|
1343
1452
|
...browserState.page,
|
|
1344
|
-
|
|
1453
|
+
session: 'custom',
|
|
1345
1454
|
};
|
|
1346
1455
|
const program = createProgram('', '');
|
|
1347
|
-
await program.parseAsync(['node', 'opencli', 'browser', '--
|
|
1456
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'custom', 'network']);
|
|
1348
1457
|
const out = lastJsonLog();
|
|
1349
|
-
expect(out.
|
|
1350
|
-
expect(fs.existsSync(
|
|
1458
|
+
expect(out.session).toBe('custom');
|
|
1459
|
+
expect(fs.existsSync(getCustomNetworkCachePath(cacheDir))).toBe(true);
|
|
1351
1460
|
expect(fs.existsSync(getNetworkCachePath(cacheDir))).toBe(false);
|
|
1352
1461
|
});
|
|
1353
1462
|
it('--all includes static resources that the default filter drops', async () => {
|
|
1354
1463
|
const program = createProgram('', '');
|
|
1355
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--all']);
|
|
1464
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--all']);
|
|
1356
1465
|
const out = lastJsonLog();
|
|
1357
1466
|
expect(out.count).toBe(2);
|
|
1358
1467
|
expect(out.entries.map((e) => e.key)).toContain('UserTweets');
|
|
@@ -1387,7 +1496,7 @@ describe('browser network command', () => {
|
|
|
1387
1496
|
},
|
|
1388
1497
|
]);
|
|
1389
1498
|
const program = createProgram('', '');
|
|
1390
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--since', '120s', '--failed']);
|
|
1499
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--since', '120s', '--failed']);
|
|
1391
1500
|
const out = lastJsonLog();
|
|
1392
1501
|
expect(out.count).toBe(1);
|
|
1393
1502
|
expect(out.entries[0].url).toBe('https://api.example.com/new-fail');
|
|
@@ -1411,7 +1520,7 @@ describe('browser network command', () => {
|
|
|
1411
1520
|
},
|
|
1412
1521
|
]);
|
|
1413
1522
|
const program = createProgram('', '');
|
|
1414
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1523
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1415
1524
|
const out = lastJsonLog();
|
|
1416
1525
|
expect(out.count).toBe(1);
|
|
1417
1526
|
expect(out.filtered_out).toBe(1);
|
|
@@ -1421,16 +1530,16 @@ describe('browser network command', () => {
|
|
|
1421
1530
|
});
|
|
1422
1531
|
it('--raw emits full bodies inline for every entry', async () => {
|
|
1423
1532
|
const program = createProgram('', '');
|
|
1424
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--raw']);
|
|
1533
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--raw']);
|
|
1425
1534
|
const out = lastJsonLog();
|
|
1426
1535
|
expect(out.entries[0].body).toEqual({ data: { user: { rest_id: '42' } } });
|
|
1427
1536
|
expect(out.entries[0].timestamp).toMatch(/T/);
|
|
1428
1537
|
});
|
|
1429
1538
|
it('--detail <key> returns the full body for the requested entry', async () => {
|
|
1430
1539
|
const program = createProgram('', '');
|
|
1431
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1540
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1432
1541
|
consoleLogSpy.mockClear();
|
|
1433
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'UserTweets']);
|
|
1542
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'UserTweets']);
|
|
1434
1543
|
const out = lastJsonLog();
|
|
1435
1544
|
expect(out.key).toBe('UserTweets');
|
|
1436
1545
|
expect(out.body).toEqual({ data: { user: { rest_id: '42' } } });
|
|
@@ -1439,9 +1548,9 @@ describe('browser network command', () => {
|
|
|
1439
1548
|
});
|
|
1440
1549
|
it('--detail reports key_not_found with the list of available keys', async () => {
|
|
1441
1550
|
const program = createProgram('', '');
|
|
1442
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1551
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1443
1552
|
consoleLogSpy.mockClear();
|
|
1444
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'NopeOp']);
|
|
1553
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'NopeOp']);
|
|
1445
1554
|
const out = lastJsonLog();
|
|
1446
1555
|
expect(out.error.code).toBe('key_not_found');
|
|
1447
1556
|
expect(out.error.available_keys).toContain('UserTweets');
|
|
@@ -1449,7 +1558,7 @@ describe('browser network command', () => {
|
|
|
1449
1558
|
});
|
|
1450
1559
|
it('--detail reports cache_missing when no capture has been persisted yet', async () => {
|
|
1451
1560
|
const program = createProgram('', '');
|
|
1452
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'UserTweets']);
|
|
1561
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'UserTweets']);
|
|
1453
1562
|
const out = lastJsonLog();
|
|
1454
1563
|
expect(out.error.code).toBe('cache_missing');
|
|
1455
1564
|
expect(process.exitCode).toBeDefined();
|
|
@@ -1457,7 +1566,7 @@ describe('browser network command', () => {
|
|
|
1457
1566
|
it('emits capture_failed when readNetworkCapture throws', async () => {
|
|
1458
1567
|
browserState.page.readNetworkCapture = vi.fn().mockRejectedValue(new Error('CDP disconnected'));
|
|
1459
1568
|
const program = createProgram('', '');
|
|
1460
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1569
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1461
1570
|
const out = lastJsonLog();
|
|
1462
1571
|
expect(out.error.code).toBe('capture_failed');
|
|
1463
1572
|
expect(out.error.message).toContain('CDP disconnected');
|
|
@@ -1470,7 +1579,7 @@ describe('browser network command', () => {
|
|
|
1470
1579
|
const clashDir = path.join(cacheDir, 'browser-network');
|
|
1471
1580
|
fs.writeFileSync(clashDir, 'not-a-directory');
|
|
1472
1581
|
const program = createProgram('', '');
|
|
1473
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1582
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1474
1583
|
const out = lastJsonLog();
|
|
1475
1584
|
expect(out.cache_warning).toMatch(/Could not persist capture cache/);
|
|
1476
1585
|
expect(out.count).toBe(1);
|
|
@@ -1495,7 +1604,7 @@ describe('browser network command', () => {
|
|
|
1495
1604
|
});
|
|
1496
1605
|
it('narrows entries to those whose shape has ALL named fields', async () => {
|
|
1497
1606
|
const program = createProgram('', '');
|
|
1498
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author,text,likes']);
|
|
1607
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author,text,likes']);
|
|
1499
1608
|
const out = lastJsonLog();
|
|
1500
1609
|
expect(out.count).toBe(1);
|
|
1501
1610
|
expect(out.filter).toEqual(['author', 'text', 'likes']);
|
|
@@ -1504,14 +1613,14 @@ describe('browser network command', () => {
|
|
|
1504
1613
|
});
|
|
1505
1614
|
it('matches container segments too, not just leaf names (any-segment rule)', async () => {
|
|
1506
1615
|
const program = createProgram('', '');
|
|
1507
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'data,items']);
|
|
1616
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'data,items']);
|
|
1508
1617
|
const out = lastJsonLog();
|
|
1509
1618
|
expect(out.count).toBe(1);
|
|
1510
1619
|
expect(out.entries[0].key).toBe('UserTweets');
|
|
1511
1620
|
});
|
|
1512
1621
|
it('drops entries that are missing any required field (AND semantics)', async () => {
|
|
1513
1622
|
const program = createProgram('', '');
|
|
1514
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author,followers']);
|
|
1623
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author,followers']);
|
|
1515
1624
|
const out = lastJsonLog();
|
|
1516
1625
|
expect(out.count).toBe(0);
|
|
1517
1626
|
expect(out.entries).toEqual([]);
|
|
@@ -1520,7 +1629,7 @@ describe('browser network command', () => {
|
|
|
1520
1629
|
});
|
|
1521
1630
|
it('returns empty entries (not an error) when nothing matches', async () => {
|
|
1522
1631
|
const program = createProgram('', '');
|
|
1523
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'nonexistent_field']);
|
|
1632
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'nonexistent_field']);
|
|
1524
1633
|
const out = lastJsonLog();
|
|
1525
1634
|
expect(out.count).toBe(0);
|
|
1526
1635
|
expect(out.entries).toEqual([]);
|
|
@@ -1529,43 +1638,43 @@ describe('browser network command', () => {
|
|
|
1529
1638
|
});
|
|
1530
1639
|
it('is case-sensitive so agents do not conflate `Id` with `id`', async () => {
|
|
1531
1640
|
const program = createProgram('', '');
|
|
1532
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'Data']);
|
|
1641
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'Data']);
|
|
1533
1642
|
const out = lastJsonLog();
|
|
1534
1643
|
expect(out.count).toBe(0);
|
|
1535
1644
|
});
|
|
1536
1645
|
it('persists the full (unfiltered) capture so --detail lookups still find filtered-out keys', async () => {
|
|
1537
1646
|
const program = createProgram('', '');
|
|
1538
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author,text,likes']);
|
|
1647
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author,text,likes']);
|
|
1539
1648
|
consoleLogSpy.mockClear();
|
|
1540
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'UserProfile']);
|
|
1649
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'UserProfile']);
|
|
1541
1650
|
const out = lastJsonLog();
|
|
1542
1651
|
expect(out.key).toBe('UserProfile');
|
|
1543
1652
|
expect(out.body).toEqual({ data: { user: { id: 'u1', followers: 10 } } });
|
|
1544
1653
|
});
|
|
1545
1654
|
it('composes with --raw: entries keep full bodies, filter still narrows', async () => {
|
|
1546
1655
|
const program = createProgram('', '');
|
|
1547
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author', '--raw']);
|
|
1656
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author', '--raw']);
|
|
1548
1657
|
const out = lastJsonLog();
|
|
1549
1658
|
expect(out.count).toBe(1);
|
|
1550
1659
|
expect(out.entries[0].body).toEqual({ data: { items: [{ author: 'a', text: 't', likes: 1 }] } });
|
|
1551
1660
|
});
|
|
1552
1661
|
it('reports invalid_filter for empty value', async () => {
|
|
1553
1662
|
const program = createProgram('', '');
|
|
1554
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', '']);
|
|
1663
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', '']);
|
|
1555
1664
|
const out = lastJsonLog();
|
|
1556
1665
|
expect(out.error.code).toBe('invalid_filter');
|
|
1557
1666
|
expect(process.exitCode).toBeDefined();
|
|
1558
1667
|
});
|
|
1559
1668
|
it('reports invalid_filter for commas-only value', async () => {
|
|
1560
1669
|
const program = createProgram('', '');
|
|
1561
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', ',,,']);
|
|
1670
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', ',,,']);
|
|
1562
1671
|
const out = lastJsonLog();
|
|
1563
1672
|
expect(out.error.code).toBe('invalid_filter');
|
|
1564
1673
|
expect(process.exitCode).toBeDefined();
|
|
1565
1674
|
});
|
|
1566
1675
|
it('rejects --filter combined with --detail as invalid_args', async () => {
|
|
1567
1676
|
const program = createProgram('', '');
|
|
1568
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--filter', 'author', '--detail', 'UserTweets']);
|
|
1677
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--filter', 'author', '--detail', 'UserTweets']);
|
|
1569
1678
|
const out = lastJsonLog();
|
|
1570
1679
|
expect(out.error.code).toBe('invalid_args');
|
|
1571
1680
|
expect(out.error.message).toContain('--filter');
|
|
@@ -1587,7 +1696,7 @@ describe('browser network command', () => {
|
|
|
1587
1696
|
},
|
|
1588
1697
|
]);
|
|
1589
1698
|
const program = createProgram('', '');
|
|
1590
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1699
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1591
1700
|
const out = lastJsonLog();
|
|
1592
1701
|
expect(out.body_truncated_count).toBe(1);
|
|
1593
1702
|
expect(out.entries[0].body_truncated).toBe(true);
|
|
@@ -1606,9 +1715,9 @@ describe('browser network command', () => {
|
|
|
1606
1715
|
},
|
|
1607
1716
|
]);
|
|
1608
1717
|
const program = createProgram('', '');
|
|
1609
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1718
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1610
1719
|
consoleLogSpy.mockClear();
|
|
1611
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--detail', 'GET api.example.com/huge']);
|
|
1720
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--detail', 'GET api.example.com/huge']);
|
|
1612
1721
|
const out = lastJsonLog();
|
|
1613
1722
|
expect(out.body_truncated).toBe(true);
|
|
1614
1723
|
expect(out.body_full_size).toBe(50_000_000);
|
|
@@ -1626,10 +1735,10 @@ describe('browser network command', () => {
|
|
|
1626
1735
|
},
|
|
1627
1736
|
]);
|
|
1628
1737
|
const program = createProgram('', '');
|
|
1629
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1738
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1630
1739
|
consoleLogSpy.mockClear();
|
|
1631
1740
|
await program.parseAsync([
|
|
1632
|
-
'node', 'opencli', 'browser', 'network',
|
|
1741
|
+
'node', 'opencli', 'browser', '--session', 'test', 'network',
|
|
1633
1742
|
'--detail', 'GET api.example.com/plain',
|
|
1634
1743
|
'--max-body', '100',
|
|
1635
1744
|
]);
|
|
@@ -1651,10 +1760,10 @@ describe('browser network command', () => {
|
|
|
1651
1760
|
},
|
|
1652
1761
|
]);
|
|
1653
1762
|
const program = createProgram('', '');
|
|
1654
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1763
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1655
1764
|
consoleLogSpy.mockClear();
|
|
1656
1765
|
await program.parseAsync([
|
|
1657
|
-
'node', 'opencli', 'browser', 'network',
|
|
1766
|
+
'node', 'opencli', 'browser', '--session', 'test', 'network',
|
|
1658
1767
|
'--detail', 'GET api.example.com/json',
|
|
1659
1768
|
'--max-body', '10',
|
|
1660
1769
|
]);
|
|
@@ -1675,10 +1784,10 @@ describe('browser network command', () => {
|
|
|
1675
1784
|
},
|
|
1676
1785
|
]);
|
|
1677
1786
|
const program = createProgram('', '');
|
|
1678
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network']);
|
|
1787
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network']);
|
|
1679
1788
|
consoleLogSpy.mockClear();
|
|
1680
1789
|
await program.parseAsync([
|
|
1681
|
-
'node', 'opencli', 'browser', 'network',
|
|
1790
|
+
'node', 'opencli', 'browser', '--session', 'test', 'network',
|
|
1682
1791
|
'--detail', 'GET api.example.com/x',
|
|
1683
1792
|
'--max-body', 'abc',
|
|
1684
1793
|
]);
|
|
@@ -1698,7 +1807,7 @@ describe('browser network command', () => {
|
|
|
1698
1807
|
},
|
|
1699
1808
|
]);
|
|
1700
1809
|
const program = createProgram('', '');
|
|
1701
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'network', '--raw']);
|
|
1810
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'network', '--raw']);
|
|
1702
1811
|
const out = lastJsonLog();
|
|
1703
1812
|
expect(out.entries).toHaveLength(1);
|
|
1704
1813
|
const entry = out.entries[0];
|
|
@@ -1719,6 +1828,7 @@ describe('browser console command', () => {
|
|
|
1719
1828
|
mockBrowserClose.mockReset().mockResolvedValue(undefined);
|
|
1720
1829
|
const now = Date.now();
|
|
1721
1830
|
browserState.page = {
|
|
1831
|
+
session: 'test',
|
|
1722
1832
|
setActivePage: vi.fn(),
|
|
1723
1833
|
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
1724
1834
|
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
|
|
@@ -1740,7 +1850,7 @@ describe('browser console command', () => {
|
|
|
1740
1850
|
}
|
|
1741
1851
|
it('filters console messages by level and time window', async () => {
|
|
1742
1852
|
const program = createProgram('', '');
|
|
1743
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'console', '--level', 'error', '--since', '120s']);
|
|
1853
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'console', '--level', 'error', '--since', '120s']);
|
|
1744
1854
|
const out = lastJsonLog();
|
|
1745
1855
|
expect(out.count).toBe(1);
|
|
1746
1856
|
expect(out.messages[0]).toMatchObject({ type: 'error', text: 'boom' });
|
|
@@ -1777,14 +1887,14 @@ describe('browser get html command', () => {
|
|
|
1777
1887
|
const big = '<div>' + 'x'.repeat(100_000) + '</div>';
|
|
1778
1888
|
browserState.page.evaluate.mockResolvedValueOnce({ kind: 'ok', html: big });
|
|
1779
1889
|
const program = createProgram('', '');
|
|
1780
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html']);
|
|
1890
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html']);
|
|
1781
1891
|
expect(lastLogArg()).toBe(big);
|
|
1782
1892
|
});
|
|
1783
1893
|
it('caps output with --max and prepends a visible truncation marker', async () => {
|
|
1784
1894
|
const big = '<div>' + 'x'.repeat(500) + '</div>';
|
|
1785
1895
|
browserState.page.evaluate.mockResolvedValueOnce({ kind: 'ok', html: big });
|
|
1786
1896
|
const program = createProgram('', '');
|
|
1787
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '100']);
|
|
1897
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '100']);
|
|
1788
1898
|
const out = String(lastLogArg());
|
|
1789
1899
|
expect(out.startsWith('<!-- opencli: truncated 100 of')).toBe(true);
|
|
1790
1900
|
expect(out.length).toBeGreaterThan(100);
|
|
@@ -1792,21 +1902,21 @@ describe('browser get html command', () => {
|
|
|
1792
1902
|
});
|
|
1793
1903
|
it('rejects negative --max with invalid_max error', async () => {
|
|
1794
1904
|
const program = createProgram('', '');
|
|
1795
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '-1']);
|
|
1905
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '-1']);
|
|
1796
1906
|
expect(lastJsonLog().error.code).toBe('invalid_max');
|
|
1797
1907
|
expect(process.exitCode).toBeDefined();
|
|
1798
1908
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
1799
1909
|
});
|
|
1800
1910
|
it('rejects fractional --max with invalid_max error', async () => {
|
|
1801
1911
|
const program = createProgram('', '');
|
|
1802
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '1.5']);
|
|
1912
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '1.5']);
|
|
1803
1913
|
expect(lastJsonLog().error.code).toBe('invalid_max');
|
|
1804
1914
|
expect(process.exitCode).toBeDefined();
|
|
1805
1915
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
1806
1916
|
});
|
|
1807
1917
|
it('rejects non-numeric --max (e.g. "10abc") with invalid_max error', async () => {
|
|
1808
1918
|
const program = createProgram('', '');
|
|
1809
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--max', '10abc']);
|
|
1919
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--max', '10abc']);
|
|
1810
1920
|
expect(lastJsonLog().error.code).toBe('invalid_max');
|
|
1811
1921
|
expect(process.exitCode).toBeDefined();
|
|
1812
1922
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
@@ -1818,7 +1928,7 @@ describe('browser get html command', () => {
|
|
|
1818
1928
|
tree: { tag: 'div', attrs: { class: 'hero' }, text: 'Hi', children: [] },
|
|
1819
1929
|
});
|
|
1820
1930
|
const program = createProgram('', '');
|
|
1821
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '.hero', '--as', 'json']);
|
|
1931
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '.hero', '--as', 'json']);
|
|
1822
1932
|
const out = lastJsonLog();
|
|
1823
1933
|
expect(out.matched).toBe(1);
|
|
1824
1934
|
expect(out.tree.tag).toBe('div');
|
|
@@ -1827,14 +1937,14 @@ describe('browser get html command', () => {
|
|
|
1827
1937
|
it('--as json emits selector_not_found when matched is 0', async () => {
|
|
1828
1938
|
browserState.page.evaluate.mockResolvedValueOnce({ selector: '.missing', matched: 0, tree: null });
|
|
1829
1939
|
const program = createProgram('', '');
|
|
1830
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '.missing', '--as', 'json']);
|
|
1940
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '.missing', '--as', 'json']);
|
|
1831
1941
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
1832
1942
|
expect(process.exitCode).toBeDefined();
|
|
1833
1943
|
});
|
|
1834
1944
|
it('raw mode emits selector_not_found when the selector matches nothing', async () => {
|
|
1835
1945
|
browserState.page.evaluate.mockResolvedValueOnce({ kind: 'ok', html: null });
|
|
1836
1946
|
const program = createProgram('', '');
|
|
1837
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '.missing']);
|
|
1947
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '.missing']);
|
|
1838
1948
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
1839
1949
|
expect(process.exitCode).toBeDefined();
|
|
1840
1950
|
});
|
|
@@ -1844,7 +1954,7 @@ describe('browser get html command', () => {
|
|
|
1844
1954
|
reason: "'##$@@' is not a valid selector",
|
|
1845
1955
|
});
|
|
1846
1956
|
const program = createProgram('', '');
|
|
1847
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '##$@@']);
|
|
1957
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '##$@@']);
|
|
1848
1958
|
const err = lastJsonLog().error;
|
|
1849
1959
|
expect(err.code).toBe('invalid_selector');
|
|
1850
1960
|
expect(err.message).toContain('##$@@');
|
|
@@ -1858,7 +1968,7 @@ describe('browser get html command', () => {
|
|
|
1858
1968
|
reason: "'##$@@' is not a valid selector",
|
|
1859
1969
|
});
|
|
1860
1970
|
const program = createProgram('', '');
|
|
1861
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--selector', '##$@@', '--as', 'json']);
|
|
1971
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--selector', '##$@@', '--as', 'json']);
|
|
1862
1972
|
const err = lastJsonLog().error;
|
|
1863
1973
|
expect(err.code).toBe('invalid_selector');
|
|
1864
1974
|
expect(err.message).toContain('##$@@');
|
|
@@ -1866,7 +1976,7 @@ describe('browser get html command', () => {
|
|
|
1866
1976
|
});
|
|
1867
1977
|
it('rejects unknown --as format with invalid_format error', async () => {
|
|
1868
1978
|
const program = createProgram('', '');
|
|
1869
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'html', '--as', 'yaml']);
|
|
1979
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'html', '--as', 'yaml']);
|
|
1870
1980
|
expect(lastJsonLog().error.code).toBe('invalid_format');
|
|
1871
1981
|
expect(process.exitCode).toBeDefined();
|
|
1872
1982
|
});
|
|
@@ -1899,6 +2009,7 @@ function installSelectorFirstTestHarness(label, pageOverrides) {
|
|
|
1899
2009
|
setActivePage: vi.fn(),
|
|
1900
2010
|
getActivePage: vi.fn().mockReturnValue('tab-1'),
|
|
1901
2011
|
tabs: vi.fn().mockResolvedValue([{ page: 'tab-1', active: true }]),
|
|
2012
|
+
session: 'test',
|
|
1902
2013
|
...pageOverrides(),
|
|
1903
2014
|
};
|
|
1904
2015
|
});
|
|
@@ -1920,7 +2031,7 @@ describe('browser find command', () => {
|
|
|
1920
2031
|
],
|
|
1921
2032
|
});
|
|
1922
2033
|
const program = createProgram('', '');
|
|
1923
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.btn']);
|
|
2034
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.btn']);
|
|
1924
2035
|
const out = lastJsonLog();
|
|
1925
2036
|
expect(out.matches_n).toBe(2);
|
|
1926
2037
|
expect(out.entries).toHaveLength(2);
|
|
@@ -1936,7 +2047,7 @@ describe('browser find command', () => {
|
|
|
1936
2047
|
],
|
|
1937
2048
|
});
|
|
1938
2049
|
const program = createProgram('', '');
|
|
1939
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--role', 'button', '--name', 'Save']);
|
|
2050
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--role', 'button', '--name', 'Save']);
|
|
1940
2051
|
const js = browserState.page.evaluate.mock.calls[0][0];
|
|
1941
2052
|
expect(js).toContain('CRITERIA');
|
|
1942
2053
|
expect(js).toContain('function accessibleName');
|
|
@@ -1950,7 +2061,7 @@ describe('browser find command', () => {
|
|
|
1950
2061
|
it('forwards --limit / --text-max into the generated JS', async () => {
|
|
1951
2062
|
browserState.page.evaluate.mockResolvedValueOnce({ matches_n: 0, entries: [] });
|
|
1952
2063
|
const program = createProgram('', '');
|
|
1953
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.btn', '--limit', '3', '--text-max', '20']);
|
|
2064
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.btn', '--limit', '3', '--text-max', '20']);
|
|
1954
2065
|
const js = browserState.page.evaluate.mock.calls[0][0];
|
|
1955
2066
|
expect(js).toContain('LIMIT = 3');
|
|
1956
2067
|
expect(js).toContain('TEXT_MAX = 20');
|
|
@@ -1960,7 +2071,7 @@ describe('browser find command', () => {
|
|
|
1960
2071
|
error: { code: 'invalid_selector', message: 'Invalid CSS selector: ">>>"', hint: 'Check the selector syntax.' },
|
|
1961
2072
|
});
|
|
1962
2073
|
const program = createProgram('', '');
|
|
1963
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '>>>']);
|
|
2074
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '>>>']);
|
|
1964
2075
|
expect(lastJsonLog().error.code).toBe('invalid_selector');
|
|
1965
2076
|
expect(process.exitCode).toBeDefined();
|
|
1966
2077
|
});
|
|
@@ -1969,20 +2080,20 @@ describe('browser find command', () => {
|
|
|
1969
2080
|
error: { code: 'selector_not_found', message: 'CSS selector ".missing" matched 0 elements', hint: 'Use browser state to inspect the page.' },
|
|
1970
2081
|
});
|
|
1971
2082
|
const program = createProgram('', '');
|
|
1972
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.missing']);
|
|
2083
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.missing']);
|
|
1973
2084
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
1974
2085
|
expect(process.exitCode).toBeDefined();
|
|
1975
2086
|
});
|
|
1976
2087
|
it('rejects missing --css with usage_error (no evaluate call)', async () => {
|
|
1977
2088
|
const program = createProgram('', '');
|
|
1978
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find']);
|
|
2089
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find']);
|
|
1979
2090
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
1980
2091
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
1981
2092
|
expect(process.exitCode).toBeDefined();
|
|
1982
2093
|
});
|
|
1983
2094
|
it('rejects malformed --limit with usage_error (no evaluate call)', async () => {
|
|
1984
2095
|
const program = createProgram('', '');
|
|
1985
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'find', '--css', '.btn', '--limit', 'abc']);
|
|
2096
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'find', '--css', '.btn', '--limit', 'abc']);
|
|
1986
2097
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
1987
2098
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
1988
2099
|
expect(process.exitCode).toBeDefined();
|
|
@@ -1999,7 +2110,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
1999
2110
|
// 2nd call: getTextResolvedJs -> the element's text
|
|
2000
2111
|
evalMock.mockResolvedValueOnce('Hello world');
|
|
2001
2112
|
const program = createProgram('', '');
|
|
2002
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '7']);
|
|
2113
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '7']);
|
|
2003
2114
|
expect(lastJsonLog()).toEqual({ value: 'Hello world', matches_n: 1, match_level: 'exact' });
|
|
2004
2115
|
});
|
|
2005
2116
|
it('resolves a semantic locator to a ref before get text', async () => {
|
|
@@ -2013,7 +2124,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2013
2124
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2014
2125
|
evalMock.mockResolvedValueOnce('Save');
|
|
2015
2126
|
const program = createProgram('', '');
|
|
2016
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2127
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2017
2128
|
expect(evalMock.mock.calls[0][0]).toContain('function accessibleName');
|
|
2018
2129
|
expect(evalMock.mock.calls[1][0]).toContain('const ref = "12"');
|
|
2019
2130
|
expect(lastJsonLog()).toEqual({ value: 'Save', matches_n: 1, match_level: 'exact' });
|
|
@@ -2031,7 +2142,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2031
2142
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2032
2143
|
evalMock.mockResolvedValueOnce('Save');
|
|
2033
2144
|
const program = createProgram('', '');
|
|
2034
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2145
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '--role', 'button', '--name', 'Save']);
|
|
2035
2146
|
expect(evalMock.mock.calls[0][0]).toContain('const LIMIT = 6');
|
|
2036
2147
|
expect(evalMock.mock.calls[1][0]).toContain('const ref = "12"');
|
|
2037
2148
|
expect(lastJsonLog()).toEqual({ value: 'Save', matches_n: 1, match_level: 'exact', total_matches: 3 });
|
|
@@ -2041,7 +2152,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2041
2152
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 3, match_level: 'exact' });
|
|
2042
2153
|
evalMock.mockResolvedValueOnce('first');
|
|
2043
2154
|
const program = createProgram('', '');
|
|
2044
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '.btn']);
|
|
2155
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '.btn']);
|
|
2045
2156
|
expect(lastJsonLog()).toEqual({ value: 'first', matches_n: 3, match_level: 'exact' });
|
|
2046
2157
|
});
|
|
2047
2158
|
it('parses the attributes payload back into a real object', async () => {
|
|
@@ -2050,7 +2161,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2050
2161
|
// getAttributesResolvedJs returns a JSON-encoded string — the CLI must parse it
|
|
2051
2162
|
evalMock.mockResolvedValueOnce(JSON.stringify({ id: 'nav', class: 'hero' }));
|
|
2052
2163
|
const program = createProgram('', '');
|
|
2053
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'attributes', '#nav']);
|
|
2164
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'attributes', '#nav']);
|
|
2054
2165
|
const out = lastJsonLog();
|
|
2055
2166
|
expect(out.matches_n).toBe(1);
|
|
2056
2167
|
expect(out.match_level).toBe('exact');
|
|
@@ -2064,7 +2175,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2064
2175
|
hint: 'Try a less specific selector.',
|
|
2065
2176
|
});
|
|
2066
2177
|
const program = createProgram('', '');
|
|
2067
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '.missing']);
|
|
2178
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '.missing']);
|
|
2068
2179
|
expect(lastJsonLog().error.code).toBe('selector_not_found');
|
|
2069
2180
|
expect(process.exitCode).toBeDefined();
|
|
2070
2181
|
});
|
|
@@ -2073,7 +2184,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2073
2184
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 4, match_level: 'exact' });
|
|
2074
2185
|
evalMock.mockResolvedValueOnce('second');
|
|
2075
2186
|
const program = createProgram('', '');
|
|
2076
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'value', '.btn', '--nth', '1']);
|
|
2187
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'value', '.btn', '--nth', '1']);
|
|
2077
2188
|
const resolveJs = evalMock.mock.calls[0][0];
|
|
2078
2189
|
// resolveTargetJs embeds nth as a raw number literal; look for the binding
|
|
2079
2190
|
expect(resolveJs).toContain('const nth = 1');
|
|
@@ -2081,7 +2192,7 @@ describe('browser get text/value/attributes commands', () => {
|
|
|
2081
2192
|
});
|
|
2082
2193
|
it('rejects malformed --nth with usage_error before touching the page', async () => {
|
|
2083
2194
|
const program = createProgram('', '');
|
|
2084
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'get', 'text', '.btn', '--nth', 'abc']);
|
|
2195
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'get', 'text', '.btn', '--nth', 'abc']);
|
|
2085
2196
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2086
2197
|
expect(browserState.page.evaluate).not.toHaveBeenCalled();
|
|
2087
2198
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2129,7 +2240,7 @@ describe('browser click/type commands', () => {
|
|
|
2129
2240
|
it('emits {clicked, target, matches_n, match_level} on success', async () => {
|
|
2130
2241
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2131
2242
|
const program = createProgram('', '');
|
|
2132
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '#save']);
|
|
2243
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '#save']);
|
|
2133
2244
|
expect(browserState.page.click).toHaveBeenCalledWith('#save', {});
|
|
2134
2245
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '#save', matches_n: 1, match_level: 'exact' });
|
|
2135
2246
|
});
|
|
@@ -2142,7 +2253,7 @@ describe('browser click/type commands', () => {
|
|
|
2142
2253
|
});
|
|
2143
2254
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2144
2255
|
const program = createProgram('', '');
|
|
2145
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '--role', 'button', '--name', 'Submit']);
|
|
2256
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '--role', 'button', '--name', 'Submit']);
|
|
2146
2257
|
expect(browserState.page.click).toHaveBeenCalledWith('23', {});
|
|
2147
2258
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '23', matches_n: 1, match_level: 'exact' });
|
|
2148
2259
|
});
|
|
@@ -2155,7 +2266,7 @@ describe('browser click/type commands', () => {
|
|
|
2155
2266
|
],
|
|
2156
2267
|
});
|
|
2157
2268
|
const program = createProgram('', '');
|
|
2158
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '--role', 'button', '--name', 'Save']);
|
|
2269
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '--role', 'button', '--name', 'Save']);
|
|
2159
2270
|
const err = lastJsonLog().error;
|
|
2160
2271
|
expect(err.code).toBe('semantic_ambiguous');
|
|
2161
2272
|
expect(err.matches_n).toBe(2);
|
|
@@ -2170,7 +2281,7 @@ describe('browser click/type commands', () => {
|
|
|
2170
2281
|
],
|
|
2171
2282
|
});
|
|
2172
2283
|
const program = createProgram('', '');
|
|
2173
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'hover', '--role', 'button', '--name', 'Settings']);
|
|
2284
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'hover', '--role', 'button', '--name', 'Settings']);
|
|
2174
2285
|
expect(browserState.page.hover).toHaveBeenCalledWith('31', {});
|
|
2175
2286
|
expect(lastJsonLog()).toEqual({ hovered: true, target: '31', matches_n: 1, match_level: 'exact' });
|
|
2176
2287
|
});
|
|
@@ -2183,7 +2294,7 @@ describe('browser click/type commands', () => {
|
|
|
2183
2294
|
});
|
|
2184
2295
|
browserState.page.setChecked.mockResolvedValueOnce({ checked: true, changed: false, matches_n: 1, match_level: 'exact', kind: 'checkbox' });
|
|
2185
2296
|
const program = createProgram('', '');
|
|
2186
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'check', '--role', 'checkbox', '--name', 'Accept']);
|
|
2297
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'check', '--role', 'checkbox', '--name', 'Accept']);
|
|
2187
2298
|
expect(browserState.page.setChecked).toHaveBeenCalledWith('32', true, {});
|
|
2188
2299
|
expect(lastJsonLog()).toEqual({ checked: true, changed: false, target: '32', matches_n: 1, match_level: 'exact', kind: 'checkbox' });
|
|
2189
2300
|
});
|
|
@@ -2207,7 +2318,7 @@ describe('browser click/type commands', () => {
|
|
|
2207
2318
|
multiple: false,
|
|
2208
2319
|
});
|
|
2209
2320
|
const program = createProgram('', '');
|
|
2210
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'upload', '--role', 'button', '--name', 'Upload receipt', file]);
|
|
2321
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'upload', '--role', 'button', '--name', 'Upload receipt', file]);
|
|
2211
2322
|
expect(browserState.page.uploadFiles).toHaveBeenCalledWith('33', [file], {});
|
|
2212
2323
|
expect(lastJsonLog()).toMatchObject({ uploaded: true, target: '33', files: 1 });
|
|
2213
2324
|
});
|
|
@@ -2223,7 +2334,7 @@ describe('browser click/type commands', () => {
|
|
|
2223
2334
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2224
2335
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2225
2336
|
const program = createProgram('', '');
|
|
2226
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '--label', 'Email', 'me@example.com']);
|
|
2337
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '--label', 'Email', 'me@example.com']);
|
|
2227
2338
|
expect(browserState.page.click).toHaveBeenCalledWith('34', {});
|
|
2228
2339
|
expect(browserState.page.typeText).toHaveBeenCalledWith('34', 'me@example.com', {});
|
|
2229
2340
|
expect(lastJsonLog()).toMatchObject({ typed: true, target: '34', text: 'me@example.com' });
|
|
@@ -2245,7 +2356,7 @@ describe('browser click/type commands', () => {
|
|
|
2245
2356
|
match_level: 'exact',
|
|
2246
2357
|
});
|
|
2247
2358
|
const program = createProgram('', '');
|
|
2248
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '--label', 'Email', 'me@example.com']);
|
|
2359
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '--label', 'Email', 'me@example.com']);
|
|
2249
2360
|
expect(browserState.page.fillText).toHaveBeenCalledWith('35', 'me@example.com', {});
|
|
2250
2361
|
expect(lastJsonLog()).toMatchObject({ filled: true, verified: true, target: '35', text: 'me@example.com' });
|
|
2251
2362
|
});
|
|
@@ -2277,6 +2388,8 @@ describe('browser click/type commands', () => {
|
|
|
2277
2388
|
'node',
|
|
2278
2389
|
'opencli',
|
|
2279
2390
|
'browser',
|
|
2391
|
+
'--session',
|
|
2392
|
+
'test',
|
|
2280
2393
|
'drag',
|
|
2281
2394
|
'--from-role',
|
|
2282
2395
|
'button',
|
|
@@ -2293,13 +2406,13 @@ describe('browser click/type commands', () => {
|
|
|
2293
2406
|
it('surfaces match_level=stable when resolver falls back to fingerprint match', async () => {
|
|
2294
2407
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 1, match_level: 'stable' });
|
|
2295
2408
|
const program = createProgram('', '');
|
|
2296
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '7']);
|
|
2409
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '7']);
|
|
2297
2410
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '7', matches_n: 1, match_level: 'stable' });
|
|
2298
2411
|
});
|
|
2299
2412
|
it('forwards --nth as ResolveOptions.nth to page.click', async () => {
|
|
2300
2413
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 3, match_level: 'exact' });
|
|
2301
2414
|
const program = createProgram('', '');
|
|
2302
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn', '--nth', '2']);
|
|
2415
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn', '--nth', '2']);
|
|
2303
2416
|
expect(browserState.page.click).toHaveBeenCalledWith('.btn', { nth: 2 });
|
|
2304
2417
|
expect(lastJsonLog()).toEqual({ clicked: true, target: '.btn', matches_n: 3, match_level: 'exact' });
|
|
2305
2418
|
});
|
|
@@ -2311,7 +2424,7 @@ describe('browser click/type commands', () => {
|
|
|
2311
2424
|
matches_n: 3,
|
|
2312
2425
|
}));
|
|
2313
2426
|
const program = createProgram('', '');
|
|
2314
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn']);
|
|
2427
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn']);
|
|
2315
2428
|
const err = lastJsonLog().error;
|
|
2316
2429
|
expect(err.code).toBe('selector_ambiguous');
|
|
2317
2430
|
expect(err.matches_n).toBe(3);
|
|
@@ -2325,13 +2438,13 @@ describe('browser click/type commands', () => {
|
|
|
2325
2438
|
matches_n: 3,
|
|
2326
2439
|
}));
|
|
2327
2440
|
const program = createProgram('', '');
|
|
2328
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn', '--nth', '99']);
|
|
2441
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn', '--nth', '99']);
|
|
2329
2442
|
expect(lastJsonLog().error.code).toBe('selector_nth_out_of_range');
|
|
2330
2443
|
expect(process.exitCode).toBeDefined();
|
|
2331
2444
|
});
|
|
2332
2445
|
it('rejects malformed --nth on click with usage_error before touching the page', async () => {
|
|
2333
2446
|
const program = createProgram('', '');
|
|
2334
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'click', '.btn', '--nth', 'abc']);
|
|
2447
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'click', '.btn', '--nth', 'abc']);
|
|
2335
2448
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2336
2449
|
expect(browserState.page.click).not.toHaveBeenCalled();
|
|
2337
2450
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2339,21 +2452,21 @@ describe('browser click/type commands', () => {
|
|
|
2339
2452
|
it('hover: delegates to page.hover and emits a structured envelope', async () => {
|
|
2340
2453
|
browserState.page.hover.mockResolvedValueOnce({ matches_n: 2, match_level: 'exact' });
|
|
2341
2454
|
const program = createProgram('', '');
|
|
2342
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'hover', '.menu', '--nth', '1']);
|
|
2455
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'hover', '.menu', '--nth', '1']);
|
|
2343
2456
|
expect(browserState.page.hover).toHaveBeenCalledWith('.menu', { nth: 1 });
|
|
2344
2457
|
expect(lastJsonLog()).toEqual({ hovered: true, target: '.menu', matches_n: 2, match_level: 'exact' });
|
|
2345
2458
|
});
|
|
2346
2459
|
it('focus: delegates to page.focus and reports whether the element took focus', async () => {
|
|
2347
2460
|
browserState.page.focus.mockResolvedValueOnce({ focused: true, matches_n: 1, match_level: 'stable' });
|
|
2348
2461
|
const program = createProgram('', '');
|
|
2349
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'focus', '7']);
|
|
2462
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'focus', '7']);
|
|
2350
2463
|
expect(browserState.page.focus).toHaveBeenCalledWith('7', {});
|
|
2351
2464
|
expect(lastJsonLog()).toEqual({ focused: true, target: '7', matches_n: 1, match_level: 'stable' });
|
|
2352
2465
|
});
|
|
2353
2466
|
it('dblclick: delegates to page.dblClick and emits a structured envelope', async () => {
|
|
2354
2467
|
browserState.page.dblClick.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2355
2468
|
const program = createProgram('', '');
|
|
2356
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'dblclick', '#row']);
|
|
2469
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'dblclick', '#row']);
|
|
2357
2470
|
expect(browserState.page.dblClick).toHaveBeenCalledWith('#row', {});
|
|
2358
2471
|
expect(lastJsonLog()).toEqual({ dblclicked: true, target: '#row', matches_n: 1, match_level: 'exact' });
|
|
2359
2472
|
});
|
|
@@ -2366,7 +2479,7 @@ describe('browser click/type commands', () => {
|
|
|
2366
2479
|
kind: 'checkbox',
|
|
2367
2480
|
});
|
|
2368
2481
|
const program = createProgram('', '');
|
|
2369
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'check', '.todo', '--nth', '1']);
|
|
2482
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'check', '.todo', '--nth', '1']);
|
|
2370
2483
|
expect(browserState.page.setChecked).toHaveBeenCalledWith('.todo', true, { nth: 1 });
|
|
2371
2484
|
expect(lastJsonLog()).toEqual({ checked: true, changed: true, target: '.todo', matches_n: 2, match_level: 'exact', kind: 'checkbox' });
|
|
2372
2485
|
});
|
|
@@ -2379,7 +2492,7 @@ describe('browser click/type commands', () => {
|
|
|
2379
2492
|
kind: 'checkbox',
|
|
2380
2493
|
});
|
|
2381
2494
|
const program = createProgram('', '');
|
|
2382
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'uncheck', '#subscribe']);
|
|
2495
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'uncheck', '#subscribe']);
|
|
2383
2496
|
expect(browserState.page.setChecked).toHaveBeenCalledWith('#subscribe', false, {});
|
|
2384
2497
|
expect(lastJsonLog()).toEqual({ checked: false, changed: false, target: '#subscribe', matches_n: 1, match_level: 'stable', kind: 'checkbox' });
|
|
2385
2498
|
});
|
|
@@ -2397,7 +2510,7 @@ describe('browser click/type commands', () => {
|
|
|
2397
2510
|
multiple: false,
|
|
2398
2511
|
});
|
|
2399
2512
|
const program = createProgram('', '');
|
|
2400
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'upload', '#file', file]);
|
|
2513
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'upload', '#file', file]);
|
|
2401
2514
|
expect(browserState.page.uploadFiles).toHaveBeenCalledWith('#file', [file], {});
|
|
2402
2515
|
expect(lastJsonLog()).toEqual({
|
|
2403
2516
|
uploaded: true,
|
|
@@ -2411,7 +2524,7 @@ describe('browser click/type commands', () => {
|
|
|
2411
2524
|
});
|
|
2412
2525
|
it('upload: rejects missing files before touching the page', async () => {
|
|
2413
2526
|
const program = createProgram('', '');
|
|
2414
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'upload', '#file', '/tmp/opencli-missing-file']);
|
|
2527
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'upload', '#file', '/tmp/opencli-missing-file']);
|
|
2415
2528
|
expect(lastJsonLog().error.code).toBe('file_not_found');
|
|
2416
2529
|
expect(browserState.page.uploadFiles).not.toHaveBeenCalled();
|
|
2417
2530
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2427,7 +2540,7 @@ describe('browser click/type commands', () => {
|
|
|
2427
2540
|
target_match_level: 'stable',
|
|
2428
2541
|
});
|
|
2429
2542
|
const program = createProgram('', '');
|
|
2430
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'drag', '.card', '.lane', '--from-nth', '2', '--to-nth', '1']);
|
|
2543
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'drag', '.card', '.lane', '--from-nth', '2', '--to-nth', '1']);
|
|
2431
2544
|
expect(browserState.page.drag).toHaveBeenCalledWith('.card', '.lane', { from: { nth: 2 }, to: { nth: 1 } });
|
|
2432
2545
|
expect(lastJsonLog()).toEqual({
|
|
2433
2546
|
dragged: true,
|
|
@@ -2441,7 +2554,7 @@ describe('browser click/type commands', () => {
|
|
|
2441
2554
|
});
|
|
2442
2555
|
it('drag: rejects malformed --from-nth before touching the page', async () => {
|
|
2443
2556
|
const program = createProgram('', '');
|
|
2444
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'drag', '.card', '.lane', '--from-nth', 'abc']);
|
|
2557
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'drag', '.card', '.lane', '--from-nth', 'abc']);
|
|
2445
2558
|
expect(lastJsonLog().error.code).toBe('usage_error');
|
|
2446
2559
|
expect(browserState.page.drag).not.toHaveBeenCalled();
|
|
2447
2560
|
expect(process.exitCode).toBeDefined();
|
|
@@ -2451,7 +2564,7 @@ describe('browser click/type commands', () => {
|
|
|
2451
2564
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2452
2565
|
browserState.page.evaluate.mockResolvedValueOnce(false); // isAutocomplete
|
|
2453
2566
|
const program = createProgram('', '');
|
|
2454
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '#q', 'hello']);
|
|
2567
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '#q', 'hello']);
|
|
2455
2568
|
expect(browserState.page.click).toHaveBeenCalledWith('#q', {});
|
|
2456
2569
|
expect(browserState.page.wait).toHaveBeenCalledWith(0.3);
|
|
2457
2570
|
expect(browserState.page.typeText).toHaveBeenCalledWith('#q', 'hello', {});
|
|
@@ -2464,7 +2577,7 @@ describe('browser click/type commands', () => {
|
|
|
2464
2577
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'exact' });
|
|
2465
2578
|
browserState.page.evaluate.mockResolvedValueOnce(true);
|
|
2466
2579
|
const program = createProgram('', '');
|
|
2467
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '#q', 'hi']);
|
|
2580
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '#q', 'hi']);
|
|
2468
2581
|
const waitCalls = browserState.page.wait.mock.calls;
|
|
2469
2582
|
expect(waitCalls).toContainEqual([0.3]);
|
|
2470
2583
|
expect(waitCalls).toContainEqual([0.4]);
|
|
@@ -2476,7 +2589,7 @@ describe('browser click/type commands', () => {
|
|
|
2476
2589
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 1, match_level: 'reidentified' });
|
|
2477
2590
|
browserState.page.evaluate.mockResolvedValueOnce(false);
|
|
2478
2591
|
const program = createProgram('', '');
|
|
2479
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '9', 'hi']);
|
|
2592
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '9', 'hi']);
|
|
2480
2593
|
// The typeText call is the authoritative match_level source for the `type` envelope.
|
|
2481
2594
|
expect(lastJsonLog().match_level).toBe('reidentified');
|
|
2482
2595
|
});
|
|
@@ -2484,7 +2597,7 @@ describe('browser click/type commands', () => {
|
|
|
2484
2597
|
browserState.page.click.mockResolvedValueOnce({ matches_n: 5, match_level: 'exact' });
|
|
2485
2598
|
browserState.page.typeText.mockResolvedValueOnce({ matches_n: 5, match_level: 'exact' });
|
|
2486
2599
|
const program = createProgram('', '');
|
|
2487
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'type', '.field', 'x', '--nth', '3']);
|
|
2600
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'type', '.field', 'x', '--nth', '3']);
|
|
2488
2601
|
expect(browserState.page.click).toHaveBeenCalledWith('.field', { nth: 3 });
|
|
2489
2602
|
expect(browserState.page.typeText).toHaveBeenCalledWith('.field', 'x', { nth: 3 });
|
|
2490
2603
|
});
|
|
@@ -2500,7 +2613,7 @@ describe('browser click/type commands', () => {
|
|
|
2500
2613
|
mode: 'textarea',
|
|
2501
2614
|
});
|
|
2502
2615
|
const program = createProgram('', '');
|
|
2503
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'line1\\n/ / raw']);
|
|
2616
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '#msg', 'line1\\n/ / raw']);
|
|
2504
2617
|
expect(browserState.page.fillText).toHaveBeenCalledWith('#msg', 'line1\\n/ / raw', {});
|
|
2505
2618
|
expect(lastJsonLog()).toEqual({
|
|
2506
2619
|
filled: true,
|
|
@@ -2526,7 +2639,7 @@ describe('browser click/type commands', () => {
|
|
|
2526
2639
|
match_level: 'exact',
|
|
2527
2640
|
});
|
|
2528
2641
|
const program = createProgram('', '');
|
|
2529
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'expected']);
|
|
2642
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '#msg', 'expected']);
|
|
2530
2643
|
expect(lastJsonLog()).toEqual({
|
|
2531
2644
|
filled: true,
|
|
2532
2645
|
verified: false,
|
|
@@ -2541,7 +2654,7 @@ describe('browser click/type commands', () => {
|
|
|
2541
2654
|
});
|
|
2542
2655
|
it('fill: forwards --nth to page.fillText', async () => {
|
|
2543
2656
|
const program = createProgram('', '');
|
|
2544
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'fill', '.field', 'x', '--nth', '2']);
|
|
2657
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'fill', '.field', 'x', '--nth', '2']);
|
|
2545
2658
|
expect(browserState.page.fillText).toHaveBeenCalledWith('.field', 'x', { nth: 2 });
|
|
2546
2659
|
});
|
|
2547
2660
|
});
|
|
@@ -2554,7 +2667,7 @@ describe('browser select command', () => {
|
|
|
2554
2667
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2555
2668
|
evalMock.mockResolvedValueOnce({ selected: 'US' });
|
|
2556
2669
|
const program = createProgram('', '');
|
|
2557
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '#country', 'US']);
|
|
2670
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '#country', 'US']);
|
|
2558
2671
|
expect(lastJsonLog()).toEqual({ selected: 'US', target: '#country', matches_n: 1, match_level: 'exact' });
|
|
2559
2672
|
});
|
|
2560
2673
|
it('maps "Not a <select>" to a not_a_select error envelope', async () => {
|
|
@@ -2562,7 +2675,7 @@ describe('browser select command', () => {
|
|
|
2562
2675
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2563
2676
|
evalMock.mockResolvedValueOnce({ error: 'Not a <select>' });
|
|
2564
2677
|
const program = createProgram('', '');
|
|
2565
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '#not-select', 'US']);
|
|
2678
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '#not-select', 'US']);
|
|
2566
2679
|
const err = lastJsonLog().error;
|
|
2567
2680
|
expect(err.code).toBe('not_a_select');
|
|
2568
2681
|
expect(err.matches_n).toBe(1);
|
|
@@ -2573,7 +2686,7 @@ describe('browser select command', () => {
|
|
|
2573
2686
|
evalMock.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' });
|
|
2574
2687
|
evalMock.mockResolvedValueOnce({ error: 'Option "XX" not found', available: ['US', 'CA'] });
|
|
2575
2688
|
const program = createProgram('', '');
|
|
2576
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '#country', 'XX']);
|
|
2689
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '#country', 'XX']);
|
|
2577
2690
|
const err = lastJsonLog().error;
|
|
2578
2691
|
expect(err.code).toBe('option_not_found');
|
|
2579
2692
|
expect(err.available).toEqual(['US', 'CA']);
|
|
@@ -2591,7 +2704,7 @@ describe('browser select command', () => {
|
|
|
2591
2704
|
.mockResolvedValueOnce({ ok: true, matches_n: 1, match_level: 'exact' })
|
|
2592
2705
|
.mockResolvedValueOnce({ selected: 'Uruguay' });
|
|
2593
2706
|
const program = createProgram('', '');
|
|
2594
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '--label', 'Country', 'Uruguay']);
|
|
2707
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '--label', 'Country', 'Uruguay']);
|
|
2595
2708
|
expect(lastJsonLog()).toEqual({ selected: 'Uruguay', target: '36', matches_n: 1, match_level: 'exact' });
|
|
2596
2709
|
});
|
|
2597
2710
|
it('surfaces selector_ambiguous from the resolver before calling selectResolvedJs', async () => {
|
|
@@ -2603,7 +2716,7 @@ describe('browser select command', () => {
|
|
|
2603
2716
|
matches_n: 2,
|
|
2604
2717
|
});
|
|
2605
2718
|
const program = createProgram('', '');
|
|
2606
|
-
await program.parseAsync(['node', 'opencli', 'browser', 'select', '.dropdown', 'US']);
|
|
2719
|
+
await program.parseAsync(['node', 'opencli', 'browser', '--session', 'test', 'select', '.dropdown', 'US']);
|
|
2607
2720
|
expect(lastJsonLog().error.code).toBe('selector_ambiguous');
|
|
2608
2721
|
// The select payload JS must not fire when resolution fails
|
|
2609
2722
|
expect(browserState.page.evaluate.mock.calls).toHaveLength(1);
|