@jackwener/opencli 1.7.16 → 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 +8 -9
- package/README.zh-CN.md +8 -8
- package/cli-manifest.json +97 -271
- package/clis/chatgpt/ask.js +1 -1
- package/clis/chatgpt/commands.test.js +2 -2
- package/clis/chatgpt/detail.js +1 -1
- package/clis/chatgpt/history.js +1 -1
- package/clis/chatgpt/image.js +38 -4
- package/clis/chatgpt/image.test.js +68 -1
- package/clis/chatgpt/new.js +1 -1
- package/clis/chatgpt/read.js +1 -1
- package/clis/chatgpt/send.js +1 -1
- package/clis/chatgpt/status.js +1 -1
- package/clis/chatgpt/utils.js +208 -16
- package/clis/chatgpt/utils.test.js +131 -2
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -1
- package/clis/claude/history.js +1 -1
- package/clis/claude/new.js +1 -1
- package/clis/claude/read.js +1 -1
- package/clis/claude/send.js +1 -1
- package/clis/claude/status.js +1 -1
- package/clis/deepseek/ask.js +1 -1
- package/clis/deepseek/detail.js +1 -1
- package/clis/deepseek/history.js +1 -1
- package/clis/deepseek/new.js +1 -1
- package/clis/deepseek/read.js +1 -1
- package/clis/deepseek/send.js +1 -1
- package/clis/deepseek/status.js +1 -1
- package/clis/doubao/ask.js +1 -1
- package/clis/doubao/detail.js +1 -1
- package/clis/doubao/history.js +1 -1
- package/clis/doubao/meeting-summary.js +1 -1
- package/clis/doubao/meeting-transcript.js +1 -1
- package/clis/doubao/new.js +1 -1
- package/clis/doubao/read.js +1 -1
- package/clis/doubao/send.js +1 -1
- package/clis/doubao/status.js +1 -1
- package/clis/gemini/ask.js +1 -1
- package/clis/gemini/deep-research-result.js +1 -1
- package/clis/gemini/deep-research.js +1 -1
- package/clis/gemini/image.js +1 -1
- package/clis/gemini/new.js +1 -1
- package/clis/grok/ask.js +1 -1
- package/clis/grok/detail.js +1 -1
- package/clis/grok/history.js +1 -1
- package/clis/grok/image.js +1 -1
- package/clis/grok/new.js +1 -1
- package/clis/grok/read.js +1 -1
- package/clis/grok/send.js +1 -1
- package/clis/grok/status.js +1 -1
- package/clis/notebooklm/current.js +1 -1
- package/clis/notebooklm/get.js +1 -1
- package/clis/notebooklm/history.js +1 -1
- package/clis/notebooklm/note-list.js +1 -1
- package/clis/notebooklm/notes-get.js +1 -1
- package/clis/notebooklm/open.js +2 -2
- package/clis/notebooklm/open.test.js +1 -1
- package/clis/notebooklm/source-fulltext.js +1 -1
- package/clis/notebooklm/source-get.js +1 -1
- package/clis/notebooklm/source-guide.js +1 -1
- package/clis/notebooklm/source-list.js +1 -1
- package/clis/notebooklm/summary.js +1 -1
- package/clis/qwen/ask.js +1 -1
- package/clis/qwen/detail.js +1 -1
- package/clis/qwen/history.js +1 -1
- package/clis/qwen/image.js +1 -1
- package/clis/qwen/new.js +1 -1
- package/clis/qwen/read.js +1 -1
- package/clis/qwen/send.js +1 -1
- package/clis/qwen/status.js +1 -1
- package/clis/reddit/comment.js +1 -1
- package/clis/reddit/frontpage.js +1 -1
- package/clis/reddit/popular.js +1 -1
- package/clis/reddit/read.js +1 -1
- package/clis/reddit/read.test.js +2 -2
- package/clis/reddit/save.js +1 -1
- package/clis/reddit/saved.js +1 -1
- package/clis/reddit/search.js +1 -1
- package/clis/reddit/subreddit.js +1 -1
- package/clis/reddit/subscribe.js +1 -1
- package/clis/reddit/upvote.js +1 -1
- package/clis/reddit/upvoted.js +1 -1
- package/clis/reddit/user-comments.js +1 -1
- package/clis/reddit/user-posts.js +1 -1
- package/clis/reddit/user.js +1 -1
- package/clis/twitter/article.js +1 -1
- package/clis/twitter/bookmark-folder.js +1 -1
- package/clis/twitter/bookmark-folders.js +1 -1
- package/clis/twitter/bookmarks.js +1 -1
- package/clis/twitter/download.js +1 -1
- package/clis/twitter/followers.js +1 -1
- package/clis/twitter/following.js +1 -1
- package/clis/twitter/likes.js +1 -1
- package/clis/twitter/list-tweets.js +1 -1
- package/clis/twitter/lists.js +1 -1
- package/clis/twitter/notifications.js +1 -1
- package/clis/twitter/profile.js +1 -1
- package/clis/twitter/search.js +1 -1
- package/clis/twitter/thread.js +1 -1
- package/clis/twitter/timeline.js +1 -1
- package/clis/twitter/trending.js +1 -1
- package/clis/twitter/tweets.js +1 -1
- package/clis/yuanbao/ask.js +1 -1
- package/clis/yuanbao/detail.js +1 -1
- package/clis/yuanbao/history.js +1 -1
- package/clis/yuanbao/new.js +1 -1
- package/clis/yuanbao/read.js +1 -1
- package/clis/yuanbao/send.js +1 -1
- package/clis/yuanbao/status.js +1 -1
- package/dist/src/browser/bridge.d.ts +3 -1
- package/dist/src/browser/bridge.js +3 -1
- package/dist/src/browser/cdp.d.ts +3 -1
- package/dist/src/browser/daemon-client.d.ts +7 -14
- package/dist/src/browser/daemon-client.js +2 -6
- package/dist/src/browser/network-cache.d.ts +5 -5
- package/dist/src/browser/network-cache.js +8 -8
- package/dist/src/browser/network-cache.test.js +4 -4
- package/dist/src/browser/page.d.ts +8 -7
- package/dist/src/browser/page.js +23 -16
- package/dist/src/browser/page.test.js +60 -30
- package/dist/src/build-manifest.js +1 -1
- package/dist/src/cli.js +60 -162
- package/dist/src/cli.test.js +178 -197
- package/dist/src/commanderAdapter.js +2 -0
- package/dist/src/discovery.js +1 -1
- package/dist/src/doctor.d.ts +0 -4
- package/dist/src/doctor.js +8 -72
- package/dist/src/doctor.test.js +26 -97
- package/dist/src/execution.d.ts +1 -0
- package/dist/src/execution.js +20 -21
- package/dist/src/execution.test.js +27 -31
- package/dist/src/help.js +7 -1
- package/dist/src/main.js +0 -19
- package/dist/src/manifest-types.d.ts +2 -4
- package/dist/src/observation/artifact.js +1 -1
- package/dist/src/observation/artifact.test.js +3 -3
- package/dist/src/observation/events.d.ts +1 -1
- package/dist/src/observation/manager.js +1 -1
- package/dist/src/observation/manager.test.js +3 -3
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +3 -12
- package/dist/src/registry.js +6 -10
- package/dist/src/runtime.d.ts +7 -2
- package/dist/src/runtime.js +3 -1
- package/dist/src/serialization.d.ts +1 -1
- package/dist/src/serialization.js +1 -1
- package/dist/src/types.d.ts +0 -15
- package/package.json +1 -1
|
@@ -51,6 +51,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
51
51
|
if (cmd.browser) {
|
|
52
52
|
subCmd
|
|
53
53
|
.option('--window <mode>', 'Browser window mode: foreground or background')
|
|
54
|
+
.option('--site-session <mode>', 'Adapter site session lifecycle: ephemeral or persistent')
|
|
54
55
|
.option('--keep-tab <bool>', 'Keep the browser tab lease after the command finishes');
|
|
55
56
|
}
|
|
56
57
|
const originalHelpInformation = subCmd.helpInformation.bind(subCmd);
|
|
@@ -108,6 +109,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
|
|
|
108
109
|
...(typeof globals.profile === 'string' && globals.profile.trim() ? { profile: globals.profile.trim() } : {}),
|
|
109
110
|
...(typeof optionsRecord.trace === 'string' && optionsRecord.trace !== 'off' ? { trace: optionsRecord.trace } : {}),
|
|
110
111
|
...(cmd.browser && typeof optionsRecord.window === 'string' ? { windowMode: optionsRecord.window } : {}),
|
|
112
|
+
...(cmd.browser && typeof optionsRecord.siteSession === 'string' ? { siteSession: optionsRecord.siteSession } : {}),
|
|
111
113
|
...(cmd.browser && typeof optionsRecord.keepTab === 'string' ? { keepTab: optionsRecord.keepTab } : {}),
|
|
112
114
|
});
|
|
113
115
|
if (result === null || result === undefined) {
|
package/dist/src/discovery.js
CHANGED
|
@@ -135,7 +135,7 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
135
135
|
pipeline: entry.pipeline,
|
|
136
136
|
source: entry.sourceFile ? path.resolve(clisDir, entry.sourceFile) : modulePath,
|
|
137
137
|
navigateBefore: entry.navigateBefore,
|
|
138
|
-
|
|
138
|
+
siteSession: entry.siteSession,
|
|
139
139
|
_lazy: true,
|
|
140
140
|
_modulePath: modulePath,
|
|
141
141
|
};
|
package/dist/src/doctor.d.ts
CHANGED
|
@@ -3,13 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Simplified for the daemon-based architecture.
|
|
5
5
|
*/
|
|
6
|
-
import type { BrowserSessionInfo } from './types.js';
|
|
7
6
|
import type { BrowserProfileStatus } from './browser/daemon-client.js';
|
|
8
7
|
import { type AdapterShadow } from './adapter-shadow.js';
|
|
9
8
|
export type DoctorOptions = {
|
|
10
9
|
yes?: boolean;
|
|
11
|
-
live?: boolean;
|
|
12
|
-
sessions?: boolean;
|
|
13
10
|
cliVersion?: string;
|
|
14
11
|
};
|
|
15
12
|
export type ConnectivityResult = {
|
|
@@ -28,7 +25,6 @@ export type DoctorReport = {
|
|
|
28
25
|
extensionVersion?: string;
|
|
29
26
|
latestExtensionVersion?: string;
|
|
30
27
|
connectivity?: ConnectivityResult;
|
|
31
|
-
sessions?: BrowserSessionInfo[];
|
|
32
28
|
profiles?: BrowserProfileStatus[];
|
|
33
29
|
adapterShadows?: AdapterShadow[];
|
|
34
30
|
issues: string[];
|
package/dist/src/doctor.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { styleText } from 'node:util';
|
|
7
7
|
import { DEFAULT_DAEMON_PORT } from './constants.js';
|
|
8
8
|
import { BrowserBridge } from './browser/index.js';
|
|
9
|
-
import { getDaemonHealth
|
|
9
|
+
import { getDaemonHealth } from './browser/daemon-client.js';
|
|
10
10
|
import { getErrorMessage } from './errors.js';
|
|
11
11
|
import { getRuntimeLabel } from './runtime-detect.js';
|
|
12
12
|
import { getCachedLatestExtensionVersion } from './update-check.js';
|
|
@@ -66,47 +66,17 @@ export async function checkConnectivity(opts) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
export async function runBrowserDoctor(opts = {}) {
|
|
69
|
-
// Live connectivity check doubles as auto-start
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// No live probe — daemon may have idle-exited. Do a minimal auto-start
|
|
76
|
-
// so we don't misreport a lazy-lifecycle stop as a real failure.
|
|
77
|
-
const initialHealth = await getDaemonHealth();
|
|
78
|
-
if (initialHealth.state === 'stopped') {
|
|
79
|
-
try {
|
|
80
|
-
const bridge = new BrowserBridge();
|
|
81
|
-
await bridge.connect({ timeout: 5 });
|
|
82
|
-
await bridge.close();
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Auto-start failed; we'll report it below.
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Single status read *after* all side-effects (live check / auto-start) settle.
|
|
69
|
+
// Live connectivity check is the core of doctor — it doubles as auto-start
|
|
70
|
+
// (bridge.connect spawns daemon) and validates end-to-end browser bridge health.
|
|
71
|
+
const connectivity = await checkConnectivity();
|
|
72
|
+
// Single status read *after* connectivity side-effects settle.
|
|
90
73
|
const health = await getDaemonHealth();
|
|
91
74
|
const daemonRunning = health.state !== 'stopped';
|
|
92
75
|
const extensionConnected = health.state === 'ready';
|
|
93
|
-
const daemonFlaky =
|
|
94
|
-
const extensionFlaky =
|
|
76
|
+
const daemonFlaky = connectivity.ok && !daemonRunning;
|
|
77
|
+
const extensionFlaky = connectivity.ok && daemonRunning && !extensionConnected;
|
|
95
78
|
const daemonStale = isDaemonStale(health.status, opts.cliVersion);
|
|
96
79
|
const profiles = health.status?.profiles;
|
|
97
|
-
let sessions;
|
|
98
|
-
if (opts.sessions) {
|
|
99
|
-
if (profiles && profiles.length > 0) {
|
|
100
|
-
const grouped = await Promise.all(profiles.map(async (profile) => {
|
|
101
|
-
const rows = await listSessions({ contextId: profile.contextId }).catch(() => []);
|
|
102
|
-
return rows.map((row) => ({ ...row, contextId: row.contextId ?? profile.contextId }));
|
|
103
|
-
}));
|
|
104
|
-
sessions = grouped.flat();
|
|
105
|
-
}
|
|
106
|
-
else if (health.state === 'ready') {
|
|
107
|
-
sessions = await listSessions();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
80
|
const extensionVersion = health.status?.extensionVersion;
|
|
111
81
|
const adapterShadows = findShadowedUserAdapters();
|
|
112
82
|
const issues = [];
|
|
@@ -147,7 +117,7 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
147
117
|
' This usually means an outdated Browser Bridge extension.\n' +
|
|
148
118
|
' Reload or reinstall the extension from: https://github.com/jackwener/opencli/releases');
|
|
149
119
|
}
|
|
150
|
-
if (
|
|
120
|
+
if (!connectivity.ok) {
|
|
151
121
|
issues.push(`Browser connectivity test failed: ${connectivity.error ?? 'unknown'}`);
|
|
152
122
|
}
|
|
153
123
|
const extensionCompatRange = health.status?.extensionCompatRange;
|
|
@@ -187,7 +157,6 @@ export async function runBrowserDoctor(opts = {}) {
|
|
|
187
157
|
extensionVersion,
|
|
188
158
|
latestExtensionVersion,
|
|
189
159
|
connectivity,
|
|
190
|
-
sessions,
|
|
191
160
|
profiles,
|
|
192
161
|
adapterShadows,
|
|
193
162
|
issues,
|
|
@@ -244,39 +213,6 @@ export function renderBrowserDoctorReport(report) {
|
|
|
244
213
|
: `failed (${report.connectivity.error ?? 'unknown'})`;
|
|
245
214
|
lines.push(`${connIcon} Connectivity: ${detail}`);
|
|
246
215
|
}
|
|
247
|
-
else {
|
|
248
|
-
lines.push(`${styleText('dim', '[SKIP]')} Connectivity: skipped (--no-live)`);
|
|
249
|
-
}
|
|
250
|
-
if (report.sessions) {
|
|
251
|
-
lines.push('', styleText('bold', 'Sessions:'));
|
|
252
|
-
if (report.sessions.length === 0) {
|
|
253
|
-
lines.push(styleText('dim', ' • no active automation sessions'));
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
const byContext = new Map();
|
|
257
|
-
for (const session of report.sessions) {
|
|
258
|
-
const contextId = typeof session.contextId === 'string' && session.contextId ? session.contextId : 'default';
|
|
259
|
-
const rows = byContext.get(contextId) ?? [];
|
|
260
|
-
rows.push(session);
|
|
261
|
-
byContext.set(contextId, rows);
|
|
262
|
-
}
|
|
263
|
-
for (const [contextId, rows] of byContext) {
|
|
264
|
-
if (byContext.size > 1)
|
|
265
|
-
lines.push(styleText('dim', ` [profile: ${contextId}]`));
|
|
266
|
-
for (const session of rows) {
|
|
267
|
-
const idle = session.idleMsRemaining == null
|
|
268
|
-
? 'none'
|
|
269
|
-
: `${Math.ceil(session.idleMsRemaining / 1000)}s`;
|
|
270
|
-
const target = session.preferredTabId != null
|
|
271
|
-
? `tab ${session.preferredTabId}`
|
|
272
|
-
: `window ${session.windowId ?? 'unknown'}`;
|
|
273
|
-
const mode = session.ownership ?? (session.owned === false ? 'borrowed' : 'owned');
|
|
274
|
-
const windowRole = session.windowRole ? `, window=${session.windowRole}` : '';
|
|
275
|
-
lines.push(styleText('dim', ` • ${session.workspace ?? 'default'} → ${target}, mode=${mode}${windowRole}, tabs=${session.tabCount ?? 0}, idle=${idle}`));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
216
|
if (report.issues.length) {
|
|
281
217
|
lines.push('', styleText('yellow', 'Issues:'));
|
|
282
218
|
for (const issue of report.issues) {
|
package/dist/src/doctor.test.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
const { mockGetDaemonHealth,
|
|
2
|
+
const { mockGetDaemonHealth, mockConnect, mockClose, mockFindShadowedUserAdapters } = vi.hoisted(() => ({
|
|
3
3
|
mockGetDaemonHealth: vi.fn(),
|
|
4
|
-
mockListSessions: vi.fn(),
|
|
5
4
|
mockConnect: vi.fn(),
|
|
6
5
|
mockClose: vi.fn(),
|
|
7
6
|
mockFindShadowedUserAdapters: vi.fn(),
|
|
8
7
|
}));
|
|
9
8
|
vi.mock('./browser/daemon-client.js', () => ({
|
|
10
9
|
getDaemonHealth: mockGetDaemonHealth,
|
|
11
|
-
listSessions: mockListSessions,
|
|
12
10
|
}));
|
|
13
11
|
vi.mock('./browser/index.js', () => ({
|
|
14
12
|
BrowserBridge: class {
|
|
@@ -29,6 +27,12 @@ describe('doctor report rendering', () => {
|
|
|
29
27
|
beforeEach(() => {
|
|
30
28
|
vi.clearAllMocks();
|
|
31
29
|
mockFindShadowedUserAdapters.mockReturnValue([]);
|
|
30
|
+
// Doctor always runs live connectivity. Tests that want connect to fail override.
|
|
31
|
+
mockConnect.mockResolvedValue({
|
|
32
|
+
evaluate: vi.fn().mockResolvedValue(2),
|
|
33
|
+
closeWindow: vi.fn().mockResolvedValue(undefined),
|
|
34
|
+
});
|
|
35
|
+
mockClose.mockResolvedValue(undefined);
|
|
32
36
|
});
|
|
33
37
|
it('renders OK-style report when daemon and extension connected', () => {
|
|
34
38
|
const text = strip(renderBrowserDoctorReport({
|
|
@@ -97,34 +101,7 @@ describe('doctor report rendering', () => {
|
|
|
97
101
|
}));
|
|
98
102
|
expect(text).toContain('[OK] Connectivity: connected in 1.2s');
|
|
99
103
|
});
|
|
100
|
-
it('renders
|
|
101
|
-
const text = strip(renderBrowserDoctorReport({
|
|
102
|
-
daemonRunning: true,
|
|
103
|
-
extensionConnected: true,
|
|
104
|
-
issues: [],
|
|
105
|
-
}));
|
|
106
|
-
expect(text).toContain('[SKIP] Connectivity: skipped (--no-live)');
|
|
107
|
-
});
|
|
108
|
-
it('renders sessions with tab leases and no idle timer', () => {
|
|
109
|
-
const text = strip(renderBrowserDoctorReport({
|
|
110
|
-
daemonRunning: true,
|
|
111
|
-
extensionConnected: true,
|
|
112
|
-
issues: [],
|
|
113
|
-
sessions: [
|
|
114
|
-
{
|
|
115
|
-
workspace: 'bound:default',
|
|
116
|
-
windowId: 2,
|
|
117
|
-
preferredTabId: 42,
|
|
118
|
-
ownership: 'borrowed',
|
|
119
|
-
windowRole: 'borrowed-user',
|
|
120
|
-
tabCount: 1,
|
|
121
|
-
idleMsRemaining: null,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
}));
|
|
125
|
-
expect(text).toContain('bound:default → tab 42, mode=borrowed, window=borrowed-user, tabs=1, idle=none');
|
|
126
|
-
});
|
|
127
|
-
it('renders connected profiles and groups sessions by profile', () => {
|
|
104
|
+
it('renders connected profiles when multiple are present', () => {
|
|
128
105
|
const text = strip(renderBrowserDoctorReport({
|
|
129
106
|
daemonRunning: true,
|
|
130
107
|
extensionConnected: false,
|
|
@@ -133,35 +110,10 @@ describe('doctor report rendering', () => {
|
|
|
133
110
|
{ contextId: 'personal', extensionConnected: true, extensionVersion: '1.2.3', pending: 0 },
|
|
134
111
|
],
|
|
135
112
|
issues: [],
|
|
136
|
-
sessions: [
|
|
137
|
-
{
|
|
138
|
-
contextId: 'work',
|
|
139
|
-
workspace: 'bound:default',
|
|
140
|
-
windowId: 2,
|
|
141
|
-
preferredTabId: 42,
|
|
142
|
-
ownership: 'borrowed',
|
|
143
|
-
windowRole: 'borrowed-user',
|
|
144
|
-
tabCount: 1,
|
|
145
|
-
idleMsRemaining: null,
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
contextId: 'personal',
|
|
149
|
-
workspace: 'site:foo',
|
|
150
|
-
windowId: 1,
|
|
151
|
-
preferredTabId: 10,
|
|
152
|
-
ownership: 'owned',
|
|
153
|
-
windowRole: 'automation',
|
|
154
|
-
tabCount: 1,
|
|
155
|
-
idleMsRemaining: 1000,
|
|
156
|
-
},
|
|
157
|
-
],
|
|
158
113
|
}));
|
|
159
114
|
expect(text).toContain('Profiles:');
|
|
160
115
|
expect(text).toContain('work: connected v1.2.3');
|
|
161
|
-
expect(text).toContain('
|
|
162
|
-
expect(text).toContain('[profile: personal]');
|
|
163
|
-
expect(text).toContain('bound:default → tab 42');
|
|
164
|
-
expect(text).toContain('site:foo → tab 10');
|
|
116
|
+
expect(text).toContain('personal: connected v1.2.3');
|
|
165
117
|
});
|
|
166
118
|
it('renders unstable extension state when live connectivity and status disagree', () => {
|
|
167
119
|
const text = strip(renderBrowserDoctorReport({
|
|
@@ -185,25 +137,20 @@ describe('doctor report rendering', () => {
|
|
|
185
137
|
expect(text).toContain('[WARN] Daemon: unstable');
|
|
186
138
|
expect(text).toContain('Daemon connectivity is unstable.');
|
|
187
139
|
});
|
|
188
|
-
it('reports daemon not running when
|
|
189
|
-
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'stopped', status: null });
|
|
140
|
+
it('reports daemon not running when connectivity fails and daemon stays stopped', async () => {
|
|
190
141
|
mockConnect.mockRejectedValueOnce(new Error('Could not start daemon'));
|
|
191
142
|
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'stopped', status: null });
|
|
192
|
-
const report = await runBrowserDoctor(
|
|
143
|
+
const report = await runBrowserDoctor();
|
|
193
144
|
expect(report.daemonRunning).toBe(false);
|
|
194
145
|
expect(report.extensionConnected).toBe(false);
|
|
195
|
-
expect(
|
|
146
|
+
expect(report.connectivity?.ok).toBe(false);
|
|
196
147
|
expect(report.issues).toEqual(expect.arrayContaining([
|
|
197
148
|
expect.stringContaining('Daemon is not running'),
|
|
198
149
|
]));
|
|
199
150
|
});
|
|
200
151
|
it('reports flapping when live check succeeds but final status shows extension disconnected', async () => {
|
|
201
|
-
mockConnect.mockResolvedValueOnce({
|
|
202
|
-
evaluate: vi.fn().mockResolvedValue(2),
|
|
203
|
-
});
|
|
204
|
-
mockClose.mockResolvedValueOnce(undefined);
|
|
205
152
|
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'no-extension', status: { extensionConnected: false } });
|
|
206
|
-
const report = await runBrowserDoctor(
|
|
153
|
+
const report = await runBrowserDoctor();
|
|
207
154
|
expect(report.daemonRunning).toBe(true);
|
|
208
155
|
expect(report.extensionConnected).toBe(false);
|
|
209
156
|
expect(report.extensionFlaky).toBe(true);
|
|
@@ -212,12 +159,8 @@ describe('doctor report rendering', () => {
|
|
|
212
159
|
]));
|
|
213
160
|
});
|
|
214
161
|
it('reports daemon flapping when live check succeeds but daemon disappears afterward', async () => {
|
|
215
|
-
mockConnect.mockResolvedValueOnce({
|
|
216
|
-
evaluate: vi.fn().mockResolvedValue(2),
|
|
217
|
-
});
|
|
218
|
-
mockClose.mockResolvedValueOnce(undefined);
|
|
219
162
|
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'stopped', status: null });
|
|
220
|
-
const report = await runBrowserDoctor(
|
|
163
|
+
const report = await runBrowserDoctor();
|
|
221
164
|
expect(report.daemonRunning).toBe(false);
|
|
222
165
|
expect(report.daemonFlaky).toBe(true);
|
|
223
166
|
expect(report.extensionConnected).toBe(false);
|
|
@@ -235,20 +178,11 @@ describe('doctor report rendering', () => {
|
|
|
235
178
|
closeWindow,
|
|
236
179
|
};
|
|
237
180
|
});
|
|
238
|
-
mockClose.mockResolvedValueOnce(undefined);
|
|
239
181
|
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'ready', status: { extensionConnected: true } });
|
|
240
|
-
await runBrowserDoctor(
|
|
182
|
+
await runBrowserDoctor();
|
|
241
183
|
expect(timeoutSeen).toBe(8);
|
|
242
184
|
expect(closeWindow).toHaveBeenCalledTimes(1);
|
|
243
185
|
});
|
|
244
|
-
it('skips auto-start in no-live mode when daemon is already running', async () => {
|
|
245
|
-
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'no-extension', status: { extensionConnected: false } });
|
|
246
|
-
mockGetDaemonHealth.mockResolvedValueOnce({ state: 'no-extension', status: { extensionConnected: false } });
|
|
247
|
-
const report = await runBrowserDoctor({ live: false });
|
|
248
|
-
expect(mockConnect).not.toHaveBeenCalled();
|
|
249
|
-
expect(report.daemonRunning).toBe(true);
|
|
250
|
-
expect(report.extensionConnected).toBe(false);
|
|
251
|
-
});
|
|
252
186
|
it('reports an issue when the extension is connected but does not report a version', async () => {
|
|
253
187
|
const status = {
|
|
254
188
|
state: 'ready',
|
|
@@ -257,10 +191,8 @@ describe('doctor report rendering', () => {
|
|
|
257
191
|
extensionVersion: undefined,
|
|
258
192
|
},
|
|
259
193
|
};
|
|
260
|
-
mockGetDaemonHealth
|
|
261
|
-
|
|
262
|
-
.mockResolvedValueOnce(status);
|
|
263
|
-
const report = await runBrowserDoctor({ live: false });
|
|
194
|
+
mockGetDaemonHealth.mockResolvedValue(status);
|
|
195
|
+
const report = await runBrowserDoctor();
|
|
264
196
|
expect(report.issues).toEqual(expect.arrayContaining([
|
|
265
197
|
expect.stringContaining('did not report a version'),
|
|
266
198
|
]));
|
|
@@ -274,10 +206,8 @@ describe('doctor report rendering', () => {
|
|
|
274
206
|
extensionVersion: '1.0.3',
|
|
275
207
|
},
|
|
276
208
|
};
|
|
277
|
-
mockGetDaemonHealth
|
|
278
|
-
|
|
279
|
-
.mockResolvedValueOnce(status);
|
|
280
|
-
const report = await runBrowserDoctor({ live: false, cliVersion: '1.7.9' });
|
|
209
|
+
mockGetDaemonHealth.mockResolvedValue(status);
|
|
210
|
+
const report = await runBrowserDoctor({ cliVersion: '1.7.9' });
|
|
281
211
|
expect(report.daemonStale).toBe(true);
|
|
282
212
|
expect(report.issues).toEqual(expect.arrayContaining([
|
|
283
213
|
expect.stringContaining('Stale daemon detected: daemon v1.7.6 != CLI v1.7.9'),
|
|
@@ -292,9 +222,7 @@ describe('doctor report rendering', () => {
|
|
|
292
222
|
extensionVersion: '1.0.3',
|
|
293
223
|
},
|
|
294
224
|
};
|
|
295
|
-
mockGetDaemonHealth
|
|
296
|
-
.mockResolvedValueOnce(status)
|
|
297
|
-
.mockResolvedValueOnce(status);
|
|
225
|
+
mockGetDaemonHealth.mockResolvedValue(status);
|
|
298
226
|
mockFindShadowedUserAdapters.mockReturnValueOnce([
|
|
299
227
|
{
|
|
300
228
|
name: 'instagram/saved',
|
|
@@ -302,7 +230,7 @@ describe('doctor report rendering', () => {
|
|
|
302
230
|
builtinPath: '/pkg/clis/instagram/saved.js',
|
|
303
231
|
},
|
|
304
232
|
]);
|
|
305
|
-
const report = await runBrowserDoctor({
|
|
233
|
+
const report = await runBrowserDoctor({ cliVersion: '1.7.9' });
|
|
306
234
|
expect(report.adapterShadows).toHaveLength(1);
|
|
307
235
|
expect(report.issues).toEqual(expect.arrayContaining([
|
|
308
236
|
expect.stringContaining('Local adapter overrides shadow packaged adapters'),
|
|
@@ -320,10 +248,11 @@ describe('doctor report rendering', () => {
|
|
|
320
248
|
],
|
|
321
249
|
},
|
|
322
250
|
};
|
|
323
|
-
mockGetDaemonHealth
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
251
|
+
mockGetDaemonHealth.mockResolvedValue(status);
|
|
252
|
+
// Real connectivity would fail in profile-required state; force it here so
|
|
253
|
+
// the test exercises the profile-required issue path, not the flaky path.
|
|
254
|
+
mockConnect.mockRejectedValueOnce(new Error('profile required'));
|
|
255
|
+
const report = await runBrowserDoctor();
|
|
327
256
|
expect(report.profiles).toHaveLength(2);
|
|
328
257
|
expect(report.issues).toEqual(expect.arrayContaining([
|
|
329
258
|
expect.stringContaining('Multiple Chrome profiles are connected'),
|
package/dist/src/execution.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs,
|
|
|
18
18
|
trace?: string;
|
|
19
19
|
keepTab?: string;
|
|
20
20
|
windowMode?: string;
|
|
21
|
+
siteSession?: string;
|
|
21
22
|
onTraceExport?: (trace: ObservationExportResult) => void;
|
|
22
23
|
}): Promise<unknown>;
|
|
23
24
|
export declare function prepareCommandArgs(cmd: CliCommand, rawKwargs: CommandArgs): CommandArgs;
|
package/dist/src/execution.js
CHANGED
|
@@ -29,7 +29,6 @@ const _loadedModules = new Map();
|
|
|
29
29
|
/** Track mtime of loaded user adapter files for hot-reload in daemon mode. */
|
|
30
30
|
const _moduleMtimes = new Map();
|
|
31
31
|
const _userClisDir = `${os.homedir()}/.opencli/clis/`;
|
|
32
|
-
const INTERACTIVE_BROWSER_IDLE_TIMEOUT_SECONDS = 600;
|
|
33
32
|
function normalizeTraceMode(raw) {
|
|
34
33
|
if (raw === undefined || raw === null || raw === '' || raw === 'off')
|
|
35
34
|
return 'off';
|
|
@@ -164,8 +163,8 @@ function isDomainRootPreNav(preNavUrl, domain) {
|
|
|
164
163
|
return false;
|
|
165
164
|
}
|
|
166
165
|
}
|
|
167
|
-
async function shouldRunPreNav(cmd, page,
|
|
168
|
-
if (
|
|
166
|
+
async function shouldRunPreNav(cmd, page, siteSession, preNavUrl) {
|
|
167
|
+
if (siteSession !== 'persistent' || !cmd.domain)
|
|
169
168
|
return true;
|
|
170
169
|
if (!isDomainRootPreNav(preNavUrl, cmd.domain))
|
|
171
170
|
return true;
|
|
@@ -212,10 +211,9 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
212
211
|
const BrowserFactory = getBrowserFactory(cmd.site);
|
|
213
212
|
const contextId = resolveProfileContextId(opts.profile);
|
|
214
213
|
const internal = cmd;
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
const keepTab = resolveKeepTab(browserReuse, opts.keepTab);
|
|
214
|
+
const siteSession = resolveSiteSession(cmd, opts.siteSession);
|
|
215
|
+
const session = resolveAdapterBrowserSession(cmd, siteSession);
|
|
216
|
+
const keepTab = resolveKeepTab(siteSession, opts.keepTab);
|
|
219
217
|
const windowMode = resolveBrowserWindowMode('background', opts.windowMode);
|
|
220
218
|
result = await browserSession(BrowserFactory, async (page) => {
|
|
221
219
|
const observation = traceMode === 'off'
|
|
@@ -223,7 +221,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
223
221
|
: new ObservationSession({
|
|
224
222
|
scope: {
|
|
225
223
|
contextId,
|
|
226
|
-
|
|
224
|
+
session,
|
|
227
225
|
target: page.getActivePage?.(),
|
|
228
226
|
site: cmd.site,
|
|
229
227
|
command: fullName(cmd),
|
|
@@ -240,7 +238,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
240
238
|
await page.startNetworkCapture?.().catch(() => false);
|
|
241
239
|
}
|
|
242
240
|
const preNavUrl = resolvePreNav(cmd);
|
|
243
|
-
if (preNavUrl && await shouldRunPreNav(cmd, page,
|
|
241
|
+
if (preNavUrl && await shouldRunPreNav(cmd, page, siteSession, preNavUrl)) {
|
|
244
242
|
observation?.record({
|
|
245
243
|
stream: 'action',
|
|
246
244
|
name: 'pre_navigate',
|
|
@@ -332,7 +330,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
|
|
|
332
330
|
await page.closeWindow?.().catch(() => { });
|
|
333
331
|
throw err;
|
|
334
332
|
}
|
|
335
|
-
}, {
|
|
333
|
+
}, { session, cdpEndpoint, contextId, windowMode, surface: 'adapter', siteSession });
|
|
336
334
|
}
|
|
337
335
|
else {
|
|
338
336
|
// Non-browser commands: enforce a timeout only when the command exposes
|
|
@@ -442,19 +440,18 @@ export function prepareCommandArgs(cmd, rawKwargs) {
|
|
|
442
440
|
* the runtime kills the Promise.
|
|
443
441
|
*/
|
|
444
442
|
const RUNTIME_TIMEOUT_PADDING_SECONDS = 30;
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
if (raw === undefined || raw === '')
|
|
443
|
+
function normalizeSiteSession(raw) {
|
|
444
|
+
if (raw === undefined || raw === null || raw === '')
|
|
448
445
|
return null;
|
|
449
|
-
if (raw === '
|
|
446
|
+
if (raw === 'ephemeral' || raw === 'persistent')
|
|
450
447
|
return raw;
|
|
451
|
-
throw new ArgumentError(`--
|
|
448
|
+
throw new ArgumentError(`--site-session must be one of: ephemeral, persistent. Received: "${String(raw)}"`);
|
|
452
449
|
}
|
|
453
|
-
function
|
|
454
|
-
return
|
|
450
|
+
function resolveSiteSession(cmd, rawOption) {
|
|
451
|
+
return normalizeSiteSession(rawOption) ?? cmd.siteSession ?? 'ephemeral';
|
|
455
452
|
}
|
|
456
|
-
function
|
|
457
|
-
if (
|
|
453
|
+
function resolveAdapterBrowserSession(cmd, siteSession) {
|
|
454
|
+
if (siteSession === 'persistent')
|
|
458
455
|
return `site:${cmd.site}`;
|
|
459
456
|
return `site:${cmd.site}:${crypto.randomUUID()}`;
|
|
460
457
|
}
|
|
@@ -467,10 +464,12 @@ function normalizeBooleanOption(name, raw) {
|
|
|
467
464
|
return false;
|
|
468
465
|
throw new ArgumentError(`${name} must be one of: true, false. Received: "${String(raw)}"`);
|
|
469
466
|
}
|
|
470
|
-
function resolveKeepTab(
|
|
467
|
+
function resolveKeepTab(siteSession, rawOption) {
|
|
468
|
+
if (siteSession === 'persistent')
|
|
469
|
+
return true;
|
|
471
470
|
return normalizeBooleanOption('--keep-tab', rawOption)
|
|
472
471
|
?? normalizeBooleanOption('OPENCLI_KEEP_TAB', process.env.OPENCLI_KEEP_TAB)
|
|
473
|
-
??
|
|
472
|
+
?? false;
|
|
474
473
|
}
|
|
475
474
|
function normalizeWindowMode(name, raw) {
|
|
476
475
|
if (raw === undefined || raw === '')
|
|
@@ -136,7 +136,7 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
136
136
|
});
|
|
137
137
|
vi.restoreAllMocks();
|
|
138
138
|
});
|
|
139
|
-
it('reuses a site
|
|
139
|
+
it('reuses a persistent site browser session and keeps the tab lease open', async () => {
|
|
140
140
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
141
141
|
const mockPage = { closeWindow };
|
|
142
142
|
const sessionOpts = [];
|
|
@@ -147,22 +147,24 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
147
147
|
});
|
|
148
148
|
const cmd = cli({
|
|
149
149
|
site: 'test-execution',
|
|
150
|
-
name: '
|
|
151
|
-
description: 'test site
|
|
150
|
+
name: 'site-session-persistent', access: 'read',
|
|
151
|
+
description: 'test persistent site session',
|
|
152
152
|
browser: true,
|
|
153
153
|
strategy: Strategy.PUBLIC,
|
|
154
|
-
|
|
154
|
+
siteSession: 'persistent',
|
|
155
155
|
func: async () => [{ ok: true }],
|
|
156
156
|
});
|
|
157
157
|
await executeCommand(cmd, {});
|
|
158
|
-
await executeCommand(cmd, {});
|
|
158
|
+
await executeCommand(cmd, {}, false, { keepTab: 'false' });
|
|
159
159
|
expect(sessionOpts).toHaveLength(2);
|
|
160
|
-
expect(sessionOpts[0]).toMatchObject({
|
|
161
|
-
expect(sessionOpts[1]).toMatchObject({
|
|
160
|
+
expect(sessionOpts[0]).toMatchObject({ session: 'site:test-execution', windowMode: 'background', siteSession: 'persistent' });
|
|
161
|
+
expect(sessionOpts[1]).toMatchObject({ session: 'site:test-execution', windowMode: 'background', siteSession: 'persistent' });
|
|
162
|
+
expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
|
|
163
|
+
expect(sessionOpts[1]?.idleTimeout).toBeUndefined();
|
|
162
164
|
expect(closeWindow).not.toHaveBeenCalled();
|
|
163
165
|
vi.restoreAllMocks();
|
|
164
166
|
});
|
|
165
|
-
it('keeps default browser commands on one-shot
|
|
167
|
+
it('keeps default browser commands on one-shot adapter sessions', async () => {
|
|
166
168
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
167
169
|
const mockPage = { closeWindow };
|
|
168
170
|
const sessionOpts = [];
|
|
@@ -173,8 +175,8 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
173
175
|
});
|
|
174
176
|
const cmd = cli({
|
|
175
177
|
site: 'test-execution',
|
|
176
|
-
name: '
|
|
177
|
-
description: 'test default one-shot browser
|
|
178
|
+
name: 'site-session-default', access: 'read',
|
|
179
|
+
description: 'test default one-shot browser session',
|
|
178
180
|
browser: true,
|
|
179
181
|
strategy: Strategy.PUBLIC,
|
|
180
182
|
func: async () => [{ ok: true }],
|
|
@@ -182,9 +184,9 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
182
184
|
await executeCommand(cmd, {});
|
|
183
185
|
await executeCommand(cmd, {});
|
|
184
186
|
expect(sessionOpts).toHaveLength(2);
|
|
185
|
-
expect(sessionOpts[0]?.
|
|
186
|
-
expect(sessionOpts[1]?.
|
|
187
|
-
expect(sessionOpts[0]?.
|
|
187
|
+
expect(sessionOpts[0]?.session).toMatch(/^site:test-execution:/);
|
|
188
|
+
expect(sessionOpts[1]?.session).toMatch(/^site:test-execution:/);
|
|
189
|
+
expect(sessionOpts[0]?.session).not.toBe(sessionOpts[1]?.session);
|
|
188
190
|
expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
|
|
189
191
|
expect(sessionOpts[1]?.idleTimeout).toBeUndefined();
|
|
190
192
|
expect(sessionOpts[0]?.windowMode).toBe('background');
|
|
@@ -192,7 +194,7 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
192
194
|
expect(closeWindow).toHaveBeenCalledTimes(2);
|
|
193
195
|
vi.restoreAllMocks();
|
|
194
196
|
});
|
|
195
|
-
it('lets user --
|
|
197
|
+
it('lets user --site-session ephemeral override adapter persistent metadata', async () => {
|
|
196
198
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
197
199
|
const mockPage = { closeWindow };
|
|
198
200
|
const sessionOpts = [];
|
|
@@ -201,33 +203,27 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
201
203
|
sessionOpts.push(opts ?? {});
|
|
202
204
|
return fn(mockPage);
|
|
203
205
|
});
|
|
204
|
-
const prev = process.env.OPENCLI_BROWSER_REUSE;
|
|
205
|
-
process.env.OPENCLI_BROWSER_REUSE = 'none';
|
|
206
206
|
try {
|
|
207
207
|
const cmd = cli({
|
|
208
208
|
site: 'test-execution',
|
|
209
|
-
name: '
|
|
210
|
-
description: 'test user
|
|
209
|
+
name: 'site-session-override-ephemeral', access: 'read',
|
|
210
|
+
description: 'test user site-session override',
|
|
211
211
|
browser: true,
|
|
212
212
|
strategy: Strategy.PUBLIC,
|
|
213
|
-
|
|
213
|
+
siteSession: 'persistent',
|
|
214
214
|
func: async () => [{ ok: true }],
|
|
215
215
|
});
|
|
216
|
-
await executeCommand(cmd, {});
|
|
216
|
+
await executeCommand(cmd, {}, false, { siteSession: 'ephemeral' });
|
|
217
217
|
expect(sessionOpts).toHaveLength(1);
|
|
218
|
-
expect(sessionOpts[0]?.
|
|
218
|
+
expect(sessionOpts[0]?.session).toMatch(/^site:test-execution:/);
|
|
219
219
|
expect(sessionOpts[0]?.idleTimeout).toBeUndefined();
|
|
220
220
|
expect(closeWindow).toHaveBeenCalledTimes(1);
|
|
221
221
|
}
|
|
222
222
|
finally {
|
|
223
|
-
if (prev === undefined)
|
|
224
|
-
delete process.env.OPENCLI_BROWSER_REUSE;
|
|
225
|
-
else
|
|
226
|
-
process.env.OPENCLI_BROWSER_REUSE = prev;
|
|
227
223
|
vi.restoreAllMocks();
|
|
228
224
|
}
|
|
229
225
|
});
|
|
230
|
-
it('skips repeated domain pre-navigation for site
|
|
226
|
+
it('skips repeated domain pre-navigation for persistent site sessions', async () => {
|
|
231
227
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
232
228
|
const goto = vi.fn().mockResolvedValue(undefined);
|
|
233
229
|
const mockPage = {
|
|
@@ -239,12 +235,12 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
239
235
|
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
240
236
|
const cmd = cli({
|
|
241
237
|
site: 'test-execution',
|
|
242
|
-
name: '
|
|
238
|
+
name: 'site-session-skip-prenav', access: 'read',
|
|
243
239
|
description: 'test reused same-domain tabs do not reset conversation state',
|
|
244
240
|
browser: true,
|
|
245
241
|
strategy: Strategy.COOKIE,
|
|
246
242
|
domain: 'grok.com',
|
|
247
|
-
|
|
243
|
+
siteSession: 'persistent',
|
|
248
244
|
func: async () => [{ ok: true }],
|
|
249
245
|
});
|
|
250
246
|
await executeCommand(cmd, {});
|
|
@@ -252,7 +248,7 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
252
248
|
expect(closeWindow).not.toHaveBeenCalled();
|
|
253
249
|
vi.restoreAllMocks();
|
|
254
250
|
});
|
|
255
|
-
it('keeps explicit path pre-navigation for site
|
|
251
|
+
it('keeps explicit path pre-navigation for persistent site sessions', async () => {
|
|
256
252
|
const closeWindow = vi.fn().mockResolvedValue(undefined);
|
|
257
253
|
const goto = vi.fn().mockResolvedValue(undefined);
|
|
258
254
|
const mockPage = {
|
|
@@ -264,13 +260,13 @@ describe('executeCommand — non-browser timeout', () => {
|
|
|
264
260
|
vi.spyOn(runtime, 'browserSession').mockImplementation(async (_Factory, fn) => fn(mockPage));
|
|
265
261
|
const cmd = cli({
|
|
266
262
|
site: 'test-execution',
|
|
267
|
-
name: '
|
|
263
|
+
name: 'site-session-path-prenav', access: 'read',
|
|
268
264
|
description: 'test explicit path pre-navigation still runs',
|
|
269
265
|
browser: true,
|
|
270
266
|
strategy: Strategy.COOKIE,
|
|
271
267
|
domain: 'example.com',
|
|
272
268
|
navigateBefore: 'https://example.com/dashboard',
|
|
273
|
-
|
|
269
|
+
siteSession: 'persistent',
|
|
274
270
|
func: async () => [{ ok: true }],
|
|
275
271
|
});
|
|
276
272
|
await executeCommand(cmd, {});
|