browser-debugger-cli 0.11.0 → 0.13.0
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/.claude/skills/bdg/SKILL.md +4 -4
- package/README.md +143 -79
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +62 -12
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +42 -11
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +169 -49
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +12 -7
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +22 -12
- package/dist/commands/optionBehaviors.js +53 -16
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +9 -2
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +20 -5
- package/dist/commands/start.js +84 -23
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +36 -14
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +19 -53
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +9234 -7222
- package/dist/errors/messages.d.ts +88 -15
- package/dist/errors/messages.js +177 -27
- package/dist/index.js +19322 -13961
- package/dist/ipc/client.d.ts +22 -2
- package/dist/ipc/client.js +34 -5
- package/dist/ipc/protocol/auditTypes.d.ts +135 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +35 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +16 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +9 -2
- package/dist/runtime/dom/actionEffects.js +30 -14
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +37 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +220 -0
- package/dist/runtime/dom/auditScripts.d.ts +113 -0
- package/dist/runtime/dom/auditScripts.js +148 -0
- package/dist/runtime/dom/elementGeometry.d.ts +16 -3
- package/dist/runtime/dom/elementGeometry.js +49 -10
- package/dist/runtime/dom/elementInfo.d.ts +74 -17
- package/dist/runtime/dom/elementInfo.js +187 -34
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +92 -28
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +90 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +5 -1
- package/dist/runtime/dom/inspectModel.js +37 -10
- package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
- package/dist/runtime/dom/inspectPaintModel.js +182 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +112 -12
- package/dist/runtime/dom/inspectScripts.js +357 -32
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +40 -16
- package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
- package/dist/runtime/dom/reactEventHelpers.js +90 -36
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +115 -0
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +2 -2
- package/dist/ui/formatters/dom.js +10 -8
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +50 -17
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +168 -11
- package/dist/ui/messages/commands.js +245 -18
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +12 -0
- package/dist/ui/messages/preview.js +18 -2
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
package/dist/commands/console.js
CHANGED
|
@@ -6,11 +6,12 @@ import { runCommand } from './shared/CommandRunner.js';
|
|
|
6
6
|
import { jsonOption } from './shared/commonOptions.js';
|
|
7
7
|
import { noteFollowConnected } from './shared/daemonErrorHandler.js';
|
|
8
8
|
import { fetchConsoleMessages, createErrorResult } from './shared/dataFetcher.js';
|
|
9
|
-
import { followFetchFailure, setupFollowMode, } from './shared/followMode.js';
|
|
9
|
+
import { followFetchFailure, newPageCrashes, setupFollowMode, } from './shared/followMode.js';
|
|
10
10
|
import { handleValidationError } from './shared/handleValidationError.js';
|
|
11
11
|
import { consoleLevelOption, positiveIntRule } from './shared/validation.js';
|
|
12
12
|
import { buildSuccessResponse } from '../ui/OutputBuilder.js';
|
|
13
13
|
import { buildConsoleJsonOutput, formatConsole, formatConsoleFollowLines, LEVEL_MAP, lastMessages, } from '../ui/formatters/console.js';
|
|
14
|
+
import { pageCrashedNote } from '../ui/messages/commands.js';
|
|
14
15
|
import { followingConsoleMessage, stoppedFollowingConsoleMessage, } from '../ui/messages/consoleMessages.js';
|
|
15
16
|
const MIN_LAST = 0;
|
|
16
17
|
const MAX_LAST = 10000;
|
|
@@ -37,9 +38,40 @@ export function listsMessages(options) {
|
|
|
37
38
|
export function filterByCurrentNavigation(messages, currentNavigationId) {
|
|
38
39
|
if (messages.length === 0)
|
|
39
40
|
return messages;
|
|
40
|
-
const navId =
|
|
41
|
+
const navId = shownNavigationId(messages, currentNavigationId);
|
|
41
42
|
return messages.filter((m) => (m.navigationId ?? 0) === navId);
|
|
42
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Navigation id of the page whose messages are shown without `--history`.
|
|
46
|
+
*
|
|
47
|
+
* @param messages - All captured messages
|
|
48
|
+
* @param currentNavigationId - Navigation id of the current page, if known
|
|
49
|
+
* @returns That id, else the newest navigation id among the messages
|
|
50
|
+
*/
|
|
51
|
+
function shownNavigationId(messages, currentNavigationId) {
|
|
52
|
+
return currentNavigationId ?? Math.max(...messages.map((m) => m.navigationId ?? 0));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Dropped messages that could have been in the view: all of them with
|
|
56
|
+
* `--history`; for the current page only while the oldest kept message is
|
|
57
|
+
* that page's (else every dropped one came from an earlier page).
|
|
58
|
+
*
|
|
59
|
+
* @param messages - All kept messages, oldest first
|
|
60
|
+
* @param dropped - Oldest messages the session dropped at its limit
|
|
61
|
+
* @param options - `--history`
|
|
62
|
+
* @param currentNavigationId - Navigation id of the current page, if known
|
|
63
|
+
* @returns Dropped count to warn about (0: none of the view's)
|
|
64
|
+
*/
|
|
65
|
+
export function droppedInView(messages, dropped, options, currentNavigationId) {
|
|
66
|
+
if (dropped === 0 || options.history)
|
|
67
|
+
return dropped;
|
|
68
|
+
const oldest = messages[0];
|
|
69
|
+
if (!oldest)
|
|
70
|
+
return dropped;
|
|
71
|
+
return (oldest.navigationId ?? 0) === shownNavigationId(messages, currentNavigationId)
|
|
72
|
+
? dropped
|
|
73
|
+
: 0;
|
|
74
|
+
}
|
|
43
75
|
export function filterByLevel(messages, level) {
|
|
44
76
|
return messages.filter((m) => LEVEL_MAP[m.type] === level);
|
|
45
77
|
}
|
|
@@ -84,10 +116,15 @@ export function skippedMessages(all, listed) {
|
|
|
84
116
|
* @param options - Command options
|
|
85
117
|
* @param lastN - `--last` value
|
|
86
118
|
* @param skipped - Messages the filters left out between the listed ones
|
|
119
|
+
* @param dropped - Oldest messages the session dropped at its limit
|
|
120
|
+
* @param pageCrashedAt - When the page crashed, while it is not loaded again
|
|
87
121
|
* @returns Formatting options
|
|
88
122
|
*/
|
|
89
|
-
function buildFormatOptions(options, lastN, skipped) {
|
|
123
|
+
function buildFormatOptions(options, lastN, skipped, dropped, pageCrashedAt) {
|
|
90
124
|
return {
|
|
125
|
+
...(options.last !== undefined && { groupLimit: lastN }),
|
|
126
|
+
...(dropped && { dropped }),
|
|
127
|
+
...(pageCrashedAt !== undefined && { pageCrashedAt }),
|
|
91
128
|
json: options.json,
|
|
92
129
|
list: listsMessages(options),
|
|
93
130
|
follow: options.follow,
|
|
@@ -99,13 +136,15 @@ function buildFormatOptions(options, lastN, skipped) {
|
|
|
99
136
|
}
|
|
100
137
|
/**
|
|
101
138
|
* Stream console messages: the last `lastN` at start, then each new message
|
|
102
|
-
* once (like `tail -f`), with a separator when the page navigates
|
|
139
|
+
* once (like `tail -f`), with a separator when the page navigates and a
|
|
140
|
+
* warning (JSON `pageCrashedAt`) once when the page crashes.
|
|
103
141
|
*
|
|
104
142
|
* @param options - Command options
|
|
105
143
|
* @param lastN - Messages to show at start (0 = all)
|
|
106
144
|
*/
|
|
107
145
|
async function runFollowMode(options, lastN) {
|
|
108
146
|
const shown = new Set();
|
|
147
|
+
const newCrash = newPageCrashes();
|
|
109
148
|
let navigationId;
|
|
110
149
|
let started = false;
|
|
111
150
|
const showConsole = async () => {
|
|
@@ -115,6 +154,7 @@ async function runFollowMode(options, lastN) {
|
|
|
115
154
|
}
|
|
116
155
|
noteFollowConnected();
|
|
117
156
|
const { messages, currentNavigationId } = result.data;
|
|
157
|
+
const crashedAt = newCrash(result.data.pageCrashedAt);
|
|
118
158
|
const matching = applyFilters(messages, options, currentNavigationId);
|
|
119
159
|
const keys = messageKeys(matching);
|
|
120
160
|
const fresh = matching.filter((_message, i) => !shown.has(keys[i]));
|
|
@@ -124,9 +164,13 @@ async function runFollowMode(options, lastN) {
|
|
|
124
164
|
const navigated = started && navigationId !== currentNavigationId;
|
|
125
165
|
navigationId = currentNavigationId;
|
|
126
166
|
if (options.json) {
|
|
127
|
-
if (!started || backlog.length > 0) {
|
|
128
|
-
const data = buildConsoleJsonOutput(backlog, {
|
|
129
|
-
|
|
167
|
+
if (!started || backlog.length > 0 || crashedAt !== undefined) {
|
|
168
|
+
const data = buildConsoleJsonOutput(backlog, {
|
|
169
|
+
list: true,
|
|
170
|
+
last: 0,
|
|
171
|
+
pageCrashedAt: crashedAt,
|
|
172
|
+
});
|
|
173
|
+
console.log(JSON.stringify(buildSuccessResponse(data)));
|
|
130
174
|
}
|
|
131
175
|
}
|
|
132
176
|
else {
|
|
@@ -137,6 +181,8 @@ async function runFollowMode(options, lastN) {
|
|
|
137
181
|
});
|
|
138
182
|
if (text)
|
|
139
183
|
console.log(text);
|
|
184
|
+
if (crashedAt !== undefined)
|
|
185
|
+
console.log(pageCrashedNote(crashedAt));
|
|
140
186
|
}
|
|
141
187
|
started = true;
|
|
142
188
|
return undefined;
|
|
@@ -196,19 +242,23 @@ export function registerConsoleCommand(program) {
|
|
|
196
242
|
if (!result.success) {
|
|
197
243
|
return createErrorResult(result.error, result.exitCode, result.suggestion);
|
|
198
244
|
}
|
|
199
|
-
const { messages, currentNavigationId } = result.data;
|
|
245
|
+
const { messages, currentNavigationId, dropped, pageCrashedAt } = result.data;
|
|
200
246
|
const filtered = applyFilters(messages, options, currentNavigationId);
|
|
201
247
|
if (options.json) {
|
|
202
248
|
return {
|
|
203
249
|
success: true,
|
|
204
|
-
data: buildConsoleJsonOutput(filtered, buildFormatOptions(options, lastN)),
|
|
250
|
+
data: buildConsoleJsonOutput(filtered, buildFormatOptions(options, lastN, undefined, dropped, pageCrashedAt)),
|
|
205
251
|
};
|
|
206
252
|
}
|
|
207
|
-
|
|
253
|
+
const droppedShown = droppedInView(messages, dropped, options, currentNavigationId);
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
data: { messages, filtered, dropped: droppedShown, pageCrashedAt },
|
|
257
|
+
};
|
|
208
258
|
}, options, (data) => {
|
|
209
|
-
const { messages, filtered } = data;
|
|
259
|
+
const { messages, filtered, dropped, pageCrashedAt } = data;
|
|
210
260
|
const skipped = skippedMessages(messages, lastMessages(filtered, lastN));
|
|
211
|
-
return formatConsole(filtered, buildFormatOptions(options, lastN, skipped));
|
|
261
|
+
return formatConsole(filtered, buildFormatOptions(options, lastN, skipped, dropped, pageCrashedAt));
|
|
212
262
|
});
|
|
213
263
|
});
|
|
214
264
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg css search <text>` - find text in the page's stylesheets, including
|
|
3
|
+
* cross-origin ones that page scripts cannot read: where a token is set, which
|
|
4
|
+
* rules use `oklch(`, a class or a custom property.
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Register the `css` command group.
|
|
9
|
+
*
|
|
10
|
+
* @param program - Root command
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerCssCommands(program: Command): void;
|
|
13
|
+
//# sourceMappingURL=css.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg css search <text>` - find text in the page's stylesheets, including
|
|
3
|
+
* cross-origin ones that page scripts cannot read: where a token is set, which
|
|
4
|
+
* rules use `oklch(`, a class or a custom property.
|
|
5
|
+
*/
|
|
6
|
+
import { runCommand } from './shared/CommandRunner.js';
|
|
7
|
+
import { jsonOption } from './shared/commonOptions.js';
|
|
8
|
+
import { integerOption } from './shared/validation.js';
|
|
9
|
+
import { cssSearch } from '../ipc/client.js';
|
|
10
|
+
import { formatCssSearch } from '../ui/formatters/audit.js';
|
|
11
|
+
import { CSS_SEARCH_HELP_EXAMPLES } from '../ui/messages/commands.js';
|
|
12
|
+
import { EXIT_CODES } from '../utils/exitCodes.js';
|
|
13
|
+
/**
|
|
14
|
+
* Register the `css` command group.
|
|
15
|
+
*
|
|
16
|
+
* @param program - Root command
|
|
17
|
+
*/
|
|
18
|
+
export function registerCssCommands(program) {
|
|
19
|
+
const css = program.command('css').description("The page's stylesheets: search <text>");
|
|
20
|
+
css
|
|
21
|
+
.command('search')
|
|
22
|
+
.description('Find text in every stylesheet of the page (cross-origin ones too), with the rule and file:line')
|
|
23
|
+
.argument('<text>', 'Text to find (case-insensitive), e.g. oklch( or .btn-primary; put -- before one that starts with - (-- --brand)')
|
|
24
|
+
.option('--limit <n>', 'Matches listed (default: 20)', integerOption(1, 500))
|
|
25
|
+
.addOption(jsonOption())
|
|
26
|
+
.addHelpText('after', CSS_SEARCH_HELP_EXAMPLES)
|
|
27
|
+
.action(async (text, options) => {
|
|
28
|
+
await runCommand(() => search(text, options), options, formatCssSearch);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ask the daemon to search the stylesheets.
|
|
33
|
+
*
|
|
34
|
+
* @param text - Text to find
|
|
35
|
+
* @param options - Limit
|
|
36
|
+
* @returns Command result
|
|
37
|
+
*/
|
|
38
|
+
async function search(text, options) {
|
|
39
|
+
const response = await cssSearch({
|
|
40
|
+
query: text,
|
|
41
|
+
...(options.limit !== undefined && { limit: options.limit }),
|
|
42
|
+
});
|
|
43
|
+
if (response.status === 'error' || !response.data) {
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
error: response.error ?? 'Failed to search the stylesheets',
|
|
47
|
+
exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
|
|
48
|
+
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { success: true, data: response.data };
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=css.js.map
|
|
@@ -109,7 +109,9 @@ export declare class DomElementResolver {
|
|
|
109
109
|
*
|
|
110
110
|
* @param index - Zero-based index
|
|
111
111
|
* @returns Cached node and the query's selector
|
|
112
|
-
* @throws CommandError (83) without a session, (81) without a usable cache,
|
|
112
|
+
* @throws CommandError (83) without a session, (81) without a usable cache,
|
|
113
|
+
* (87) for an index outside the cached results or of a page document
|
|
114
|
+
* that has since been replaced (another page's elements reuse its node ids)
|
|
113
115
|
*/
|
|
114
116
|
private lookup;
|
|
115
117
|
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* // { success: true, selector: 'button.submit' }
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
|
+
import { pageDocumentId } from './helpers/query.js';
|
|
21
22
|
import { noActiveSessionError } from '../shared/CommandRunner.js';
|
|
22
23
|
import { CommandError } from '../../errors/index.js';
|
|
23
24
|
import { cachedIndexOutOfRangeError, indexWithIndexOptionError, staleNodeError, } from '../../errors/messages.js';
|
|
@@ -136,7 +137,9 @@ export class DomElementResolver {
|
|
|
136
137
|
*
|
|
137
138
|
* @param index - Zero-based index
|
|
138
139
|
* @returns Cached node and the query's selector
|
|
139
|
-
* @throws CommandError (83) without a session, (81) without a usable cache,
|
|
140
|
+
* @throws CommandError (83) without a session, (81) without a usable cache,
|
|
141
|
+
* (87) for an index outside the cached results or of a page document
|
|
142
|
+
* that has since been replaced (another page's elements reuse its node ids)
|
|
140
143
|
*/
|
|
141
144
|
async lookup(index) {
|
|
142
145
|
const validation = await this.cacheManager.validate();
|
|
@@ -145,11 +148,15 @@ export class DomElementResolver {
|
|
|
145
148
|
throw noActiveSessionError();
|
|
146
149
|
throw new CommandError(validation.error ?? 'No cached query results found', validation.suggestion ? { suggestion: validation.suggestion } : {}, EXIT_CODES.INVALID_ARGUMENTS);
|
|
147
150
|
}
|
|
148
|
-
const { nodes, selector } = validation.cache;
|
|
151
|
+
const { nodes, selector, count, document } = validation.cache;
|
|
149
152
|
const source = indexSourceOf(index, selector);
|
|
153
|
+
if (document !== undefined && (await pageDocumentId()) !== document) {
|
|
154
|
+
const err = staleNodeError(index, source);
|
|
155
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
156
|
+
}
|
|
150
157
|
const node = nodes.find((n) => n.index === index);
|
|
151
158
|
if (!node) {
|
|
152
|
-
const err = cachedIndexOutOfRangeError(source, nodes.length);
|
|
159
|
+
const err = cachedIndexOutOfRangeError(source, nodes.length, count);
|
|
153
160
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
154
161
|
}
|
|
155
162
|
return { node, selector, source };
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Uses IPC/callCDP pattern for consistency with other DOM commands.
|
|
10
10
|
*/
|
|
11
11
|
import { DomElementResolver } from './DomElementResolver.js';
|
|
12
|
-
import { getDomContext, resolveBackendNodeIds } from './helpers/index.js';
|
|
12
|
+
import { getDomContext, pageDocumentId, resolveBackendNodeIds, } from './helpers/index.js';
|
|
13
13
|
import { withSecretMasked } from './semanticUtils.js';
|
|
14
14
|
import { runCommand, runJsonCommand } from '../shared/CommandRunner.js';
|
|
15
15
|
import { jsonOption } from '../shared/commonOptions.js';
|
|
@@ -85,6 +85,7 @@ async function handleA11yQuery(pattern, options) {
|
|
|
85
85
|
const err = invalidQueryPatternError(pattern);
|
|
86
86
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
87
87
|
}
|
|
88
|
+
const document = await pageDocumentId();
|
|
88
89
|
const tree = await collectA11yTree();
|
|
89
90
|
const result = queryA11yTree(tree, queryPattern);
|
|
90
91
|
if (result.count === 0) {
|
|
@@ -101,7 +102,7 @@ async function handleA11yQuery(pattern, options) {
|
|
|
101
102
|
tag: node.role,
|
|
102
103
|
...(node.name && { preview: node.name }),
|
|
103
104
|
})),
|
|
104
|
-
});
|
|
105
|
+
}, document);
|
|
105
106
|
return {
|
|
106
107
|
success: true,
|
|
107
108
|
data: limitMatches(indexed, options.limit ?? (options.json ? 0 : A11Y_QUERY_LIMIT)),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom audit [check...]` - page-wide checks without a screenshot or
|
|
3
|
+
* `dom eval`: text below a WCAG contrast level, what makes the page scroll
|
|
4
|
+
* sideways and cut-off text and scaled images, fixed and sticky layers, and
|
|
5
|
+
* running animations.
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from 'commander';
|
|
8
|
+
/**
|
|
9
|
+
* Register `bdg dom audit`.
|
|
10
|
+
*
|
|
11
|
+
* @param dom - The `dom` command group
|
|
12
|
+
*/
|
|
13
|
+
export declare function registerAuditCommand(dom: Command): void;
|
|
14
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bdg dom audit [check...]` - page-wide checks without a screenshot or
|
|
3
|
+
* `dom eval`: text below a WCAG contrast level, what makes the page scroll
|
|
4
|
+
* sideways and cut-off text and scaled images, fixed and sticky layers, and
|
|
5
|
+
* running animations.
|
|
6
|
+
*/
|
|
7
|
+
import { InvalidArgumentError } from 'commander';
|
|
8
|
+
import { runCommand } from '../shared/CommandRunner.js';
|
|
9
|
+
import { jsonOption } from '../shared/commonOptions.js';
|
|
10
|
+
import { integerOption } from '../shared/validation.js';
|
|
11
|
+
import { unknownAuditCheckMessage } from '../../errors/messages.js';
|
|
12
|
+
import { domAudit } from '../../ipc/client.js';
|
|
13
|
+
import { AUDIT_CHECKS } from '../../ipc/protocol/auditTypes.js';
|
|
14
|
+
import { formatAudit } from '../../ui/formatters/audit.js';
|
|
15
|
+
import { AUDIT_HELP_EXAMPLES } from '../../ui/messages/commands.js';
|
|
16
|
+
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
17
|
+
import { findSimilar } from '../../utils/suggestions.js';
|
|
18
|
+
/**
|
|
19
|
+
* Register `bdg dom audit`.
|
|
20
|
+
*
|
|
21
|
+
* @param dom - The `dom` command group
|
|
22
|
+
*/
|
|
23
|
+
export function registerAuditCommand(dom) {
|
|
24
|
+
dom
|
|
25
|
+
.command('audit')
|
|
26
|
+
.description('Page-wide checks: text below WCAG contrast, sideways scroll and cut-off text, scaled images, fixed/sticky layers, animations')
|
|
27
|
+
.argument('[checks...]', `Checks to run: ${AUDIT_CHECKS.join(', ')} (default: all)`, checkList)
|
|
28
|
+
.option('--level <level>', 'WCAG level text must reach: AA or AAA (default: AA)', levelOption)
|
|
29
|
+
.option('--limit <n>', 'Findings listed per check (default: 20)', integerOption(1, 500))
|
|
30
|
+
.addOption(jsonOption())
|
|
31
|
+
.addHelpText('after', AUDIT_HELP_EXAMPLES)
|
|
32
|
+
.action(async (checks, options) => {
|
|
33
|
+
await runCommand(() => audit(checks, options), options, formatAudit);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse the checks, rejecting unknown names with the closest one.
|
|
38
|
+
*
|
|
39
|
+
* @param value - One check name
|
|
40
|
+
* @param previous - Checks so far
|
|
41
|
+
* @returns Checks
|
|
42
|
+
* @throws InvalidArgumentError for an unknown name
|
|
43
|
+
*/
|
|
44
|
+
function checkList(value, previous = []) {
|
|
45
|
+
const check = AUDIT_CHECKS.find((name) => name === value.trim().toLowerCase());
|
|
46
|
+
if (!check) {
|
|
47
|
+
throw new InvalidArgumentError(unknownAuditCheckMessage(value, findSimilar(value, [...AUDIT_CHECKS]), AUDIT_CHECKS));
|
|
48
|
+
}
|
|
49
|
+
return previous.includes(check) ? previous : [...previous, check];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse `--level`.
|
|
53
|
+
*
|
|
54
|
+
* @param value - Level
|
|
55
|
+
* @returns AA or AAA
|
|
56
|
+
* @throws InvalidArgumentError for another value
|
|
57
|
+
*/
|
|
58
|
+
function levelOption(value) {
|
|
59
|
+
const level = value.trim().toUpperCase();
|
|
60
|
+
if (level === 'AA' || level === 'AAA')
|
|
61
|
+
return level;
|
|
62
|
+
throw new InvalidArgumentError('Use AA or AAA');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ask the daemon to audit the page.
|
|
66
|
+
*
|
|
67
|
+
* @param checks - Checks given (all when none)
|
|
68
|
+
* @param options - Level and limit
|
|
69
|
+
* @returns Command result
|
|
70
|
+
*/
|
|
71
|
+
async function audit(checks, options) {
|
|
72
|
+
const response = await domAudit({
|
|
73
|
+
checks: checks && checks.length > 0 ? checks : [...AUDIT_CHECKS],
|
|
74
|
+
...(options.level && { level: options.level }),
|
|
75
|
+
...(options.limit !== undefined && { limit: options.limit }),
|
|
76
|
+
});
|
|
77
|
+
if (response.status === 'error' || !response.data) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: response.error ?? 'Failed to audit the page',
|
|
81
|
+
exitCode: response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE,
|
|
82
|
+
...(response.suggestion && { errorContext: { suggestion: response.suggestion } }),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { success: true, data: response.data };
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
import type { DomEvalCommandOptions } from '../shared/optionTypes.js';
|
|
9
9
|
/**
|
|
10
10
|
* Handle `bdg dom eval <script> [--frame <frame>]`. With `--frame`, the
|
|
11
|
-
* frame the script ran in is a `Frame:` line on stderr (JSON: `frame`),
|
|
12
|
-
*
|
|
11
|
+
* frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), and
|
|
12
|
+
* a warning about how the result was copied goes there too (JSON:
|
|
13
|
+
* `warning`), so stdout stays the bare value for pipes.
|
|
13
14
|
*/
|
|
14
15
|
export declare function handleDomEval(script: string, options: DomEvalCommandOptions): Promise<void>;
|
|
15
16
|
//# sourceMappingURL=eval.d.ts.map
|
|
@@ -10,12 +10,13 @@ import { runCommand } from '../shared/CommandRunner.js';
|
|
|
10
10
|
import { emptyScriptError, withLoadingHint } from '../../errors/messages.js';
|
|
11
11
|
import { domEval } from '../../ipc/client.js';
|
|
12
12
|
import { formatDomEval } from '../../ui/formatters/dom.js';
|
|
13
|
-
import { evalFrameLine } from '../../ui/messages/commands.js';
|
|
13
|
+
import { evalFrameLine, warningMessage } from '../../ui/messages/commands.js';
|
|
14
14
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
15
15
|
/**
|
|
16
16
|
* Handle `bdg dom eval <script> [--frame <frame>]`. With `--frame`, the
|
|
17
|
-
* frame the script ran in is a `Frame:` line on stderr (JSON: `frame`),
|
|
18
|
-
*
|
|
17
|
+
* frame the script ran in is a `Frame:` line on stderr (JSON: `frame`), and
|
|
18
|
+
* a warning about how the result was copied goes there too (JSON:
|
|
19
|
+
* `warning`), so stdout stays the bare value for pipes.
|
|
19
20
|
*/
|
|
20
21
|
export async function handleDomEval(script, options) {
|
|
21
22
|
await runCommand(async () => {
|
|
@@ -38,7 +39,11 @@ export async function handleDomEval(script, options) {
|
|
|
38
39
|
...(suggestion && { errorContext: { suggestion } }),
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
const { value, type, subtype, frame } = response.data;
|
|
42
|
+
const { value, type, subtype, frame, warning } = response.data;
|
|
43
|
+
const hint = [
|
|
44
|
+
...(frame !== undefined ? [evalFrameLine(frame)] : []),
|
|
45
|
+
...(warning ? [warningMessage(warning)] : []),
|
|
46
|
+
].join('\n');
|
|
42
47
|
return {
|
|
43
48
|
success: true,
|
|
44
49
|
data: {
|
|
@@ -46,8 +51,9 @@ export async function handleDomEval(script, options) {
|
|
|
46
51
|
type,
|
|
47
52
|
...(subtype && { subtype }),
|
|
48
53
|
...(frame !== undefined && { frame }),
|
|
54
|
+
...(warning && { warning }),
|
|
49
55
|
},
|
|
50
|
-
...(
|
|
56
|
+
...(hint && !options.json && { hint }),
|
|
51
57
|
};
|
|
52
58
|
}, options, formatDomEval);
|
|
53
59
|
}
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* validation state, and suggested commands for agent consumption.
|
|
6
6
|
*/
|
|
7
7
|
import { calculateSummary, orderForms, primaryButtonIndex } from './formSummary.js';
|
|
8
|
-
import { resolveBackendNodeIds } from './helpers/index.js';
|
|
8
|
+
import { pageDocumentId, resolveBackendNodeIds } from './helpers/index.js';
|
|
9
9
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
10
10
|
import { jsonOption } from '../shared/commonOptions.js';
|
|
11
11
|
import { noFormsFoundError, formInIframeError } from '../../errors/messages.js';
|
|
12
12
|
import { domFormDiscover } from '../../ipc/client.js';
|
|
13
|
+
import { MASKED_VALUE } from '../../runtime/dom/elementInfo.js';
|
|
13
14
|
import { FORM_DISCOVERY_CACHE_SELECTOR, QueryCacheManager } from '../../session/QueryCacheManager.js';
|
|
14
15
|
import { formatFormDiscovery } from '../../ui/formatters/form.js';
|
|
15
16
|
import { createLogger } from '../../ui/logging/index.js';
|
|
@@ -84,16 +85,14 @@ function buildFieldState(raw) {
|
|
|
84
85
|
return 'empty';
|
|
85
86
|
}
|
|
86
87
|
/**
|
|
87
|
-
*
|
|
88
|
+
* The masked value of a sensitive field (the page script never sends its
|
|
89
|
+
* real value).
|
|
88
90
|
*
|
|
89
91
|
* @param raw - Raw field data
|
|
90
92
|
* @returns Masked value string
|
|
91
93
|
*/
|
|
92
94
|
function buildMaskedValue(raw) {
|
|
93
|
-
|
|
94
|
-
return '•'.repeat(Math.min(raw.value.length, 8));
|
|
95
|
-
}
|
|
96
|
-
return undefined;
|
|
95
|
+
return raw.value === MASKED_VALUE ? MASKED_VALUE : undefined;
|
|
97
96
|
}
|
|
98
97
|
/**
|
|
99
98
|
* Build interaction warning for non-native fields.
|
|
@@ -244,8 +243,9 @@ function transformForm(raw) {
|
|
|
244
243
|
* the selector later.
|
|
245
244
|
*
|
|
246
245
|
* @param forms - Discovered forms
|
|
246
|
+
* @param document - Identity of the page document they were found in, read before discovery
|
|
247
247
|
*/
|
|
248
|
-
async function cacheFormElements(forms) {
|
|
248
|
+
async function cacheFormElements(forms, document) {
|
|
249
249
|
const elements = forms.flatMap((form) => [...form.fields, ...form.buttons]);
|
|
250
250
|
const backendNodeIds = await resolveBackendNodeIds(elements.map((el) => el.selector)).catch((error) => {
|
|
251
251
|
log.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
|
|
@@ -259,7 +259,7 @@ async function cacheFormElements(forms) {
|
|
|
259
259
|
nodeId: backendNodeIds[i] ?? 0,
|
|
260
260
|
selector: el.selector,
|
|
261
261
|
})),
|
|
262
|
-
});
|
|
262
|
+
}, document);
|
|
263
263
|
log.debug(`Cached ${elements.length} form elements`);
|
|
264
264
|
}
|
|
265
265
|
/**
|
|
@@ -269,6 +269,7 @@ async function cacheFormElements(forms) {
|
|
|
269
269
|
*/
|
|
270
270
|
async function handleFormCommand(options) {
|
|
271
271
|
await runCommand(async () => {
|
|
272
|
+
const document = await pageDocumentId();
|
|
272
273
|
const response = await domFormDiscover();
|
|
273
274
|
if (response.status === 'error' || !response.data) {
|
|
274
275
|
return {
|
|
@@ -311,7 +312,7 @@ async function handleFormCommand(options) {
|
|
|
311
312
|
const allForms = orderForms(rawData.forms).map(transformForm);
|
|
312
313
|
const forms = options.all ? allForms : [allForms[0]];
|
|
313
314
|
// Cache ALL forms so global indices work with bdg dom fill/click
|
|
314
|
-
await cacheFormElements(allForms);
|
|
315
|
+
await cacheFormElements(allForms, document);
|
|
315
316
|
const result = {
|
|
316
317
|
formCount: rawData.forms.length,
|
|
317
318
|
selectedForm: 0,
|
|
@@ -10,14 +10,15 @@ import { InvalidArgumentError } from 'commander';
|
|
|
10
10
|
import { runElementCommand } from './helpers/runElementCommand.js';
|
|
11
11
|
import { runCommand } from '../shared/CommandRunner.js';
|
|
12
12
|
import { jsonOption, SELECTOR_OR_INDEX_ARGUMENT } from '../shared/commonOptions.js';
|
|
13
|
+
import { createErrorResult } from '../shared/dataFetcher.js';
|
|
13
14
|
import { integerOption } from '../shared/validation.js';
|
|
14
15
|
import { CommandError } from '../../errors/index.js';
|
|
15
|
-
import { VIA_LABEL_SUFFIX,
|
|
16
|
-
import { domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
|
|
16
|
+
import { VIA_LABEL_SUFFIX, conflictingOptionsError, hoverOffWithTargetError, missingArgumentError, indexSourceText, internalError, scrollOptionsError, } from '../../errors/messages.js';
|
|
17
|
+
import { callCDP, domClick, domFill, domPressKey, domScroll, domSubmit } from '../../ipc/client.js';
|
|
17
18
|
import { findUnknownModifiers } from '../../runtime/dom/keyMapping.js';
|
|
18
19
|
import { formatTriggeredRequestLines, formatTriggeredRequestsTitle, } from '../../ui/formatters/triggeredRequests.js';
|
|
19
20
|
import { OutputFormatter } from '../../ui/formatting.js';
|
|
20
|
-
import { CLICK_RESULT_WAIT_HELP, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, actionStatusLine, dialogConsoleText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
|
|
21
|
+
import { CLICK_RESULT_WAIT_HELP, HOVER_OFF_DONE, HOVER_USAGE, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, pointerScrollText, actionStatusLine, dialogConsoleText, moreMessagesText, newMessageText, pageNavigationText, shownElementText, stillChangingNote, } from '../../ui/messages/commands.js';
|
|
21
22
|
import { sessionCommand } from '../../ui/messages/sessionCommand.js';
|
|
22
23
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
23
24
|
/** Help of `--strict` on click and hover */
|
|
@@ -96,13 +97,18 @@ export function registerFormInteractionCommands(program) {
|
|
|
96
97
|
});
|
|
97
98
|
domCommand
|
|
98
99
|
.command('hover')
|
|
99
|
-
.description('Move the mouse over an element (shows hover menus and tooltips)')
|
|
100
|
-
.argument('
|
|
100
|
+
.description('Move the mouse over an element (shows hover menus and tooltips); --off moves it off the page')
|
|
101
|
+
.argument('[selectorOrIndex]', SELECTOR_OR_INDEX_ARGUMENT)
|
|
101
102
|
.option('--index <n>', 'Element index if selector matches multiple (0-based)', integerOption(0))
|
|
103
|
+
.option('--off', 'Move the mouse off the page instead (closes menus that open on hover)')
|
|
102
104
|
.option('--strict', STRICT_OPTION_HELP)
|
|
103
105
|
.option('--no-wait', 'Skip waiting for network stability after hovering')
|
|
104
106
|
.addOption(jsonOption())
|
|
105
107
|
.action(async (selectorOrIndex, options) => {
|
|
108
|
+
if (options.off || selectorOrIndex === undefined) {
|
|
109
|
+
await hoverOff(selectorOrIndex, options);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
106
112
|
await runPointerCommand(selectorOrIndex, options, 'hover');
|
|
107
113
|
});
|
|
108
114
|
domCommand
|
|
@@ -260,13 +266,32 @@ function scrollOptionsProblem(selector, options) {
|
|
|
260
266
|
* @param options - Command options
|
|
261
267
|
* @param action - Pointer action
|
|
262
268
|
*/
|
|
269
|
+
/**
|
|
270
|
+
* `bdg dom hover --off`: move the mouse off the page, so `mouseleave` and
|
|
271
|
+
* `:hover` end and menus that open on hover close.
|
|
272
|
+
*
|
|
273
|
+
* @param selectorOrIndex - Must be absent with --off
|
|
274
|
+
* @param options - Command options
|
|
275
|
+
*/
|
|
276
|
+
async function hoverOff(selectorOrIndex, options) {
|
|
277
|
+
await runCommand(async () => {
|
|
278
|
+
if (!options.off || selectorOrIndex !== undefined || options.index !== undefined) {
|
|
279
|
+
const err = options.off ? hoverOffWithTargetError() : missingArgumentError(HOVER_USAGE);
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
error: err.message,
|
|
283
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
284
|
+
errorContext: { suggestion: err.suggestion },
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
await callCDP('Input.dispatchMouseEvent', { type: 'mouseMoved', x: -1, y: -1 });
|
|
288
|
+
return { success: true, data: { pointer: 'off' } };
|
|
289
|
+
}, options, () => HOVER_OFF_DONE);
|
|
290
|
+
}
|
|
263
291
|
async function runPointerCommand(selectorOrIndex, options, action) {
|
|
292
|
+
const conflict = conflictingOptionsError('--double', '--right');
|
|
264
293
|
await runCommand(() => options.double && options.right
|
|
265
|
-
? Promise.resolve(
|
|
266
|
-
success: false,
|
|
267
|
-
error: conflictingOptionsMessage('--double', '--right'),
|
|
268
|
-
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
269
|
-
})
|
|
294
|
+
? Promise.resolve(createErrorResult(conflict.message, EXIT_CODES.INVALID_ARGUMENTS, conflict.suggestion))
|
|
270
295
|
: runElementCommand({
|
|
271
296
|
selectorOrIndex,
|
|
272
297
|
index: options.index,
|
|
@@ -316,7 +341,10 @@ function formatActionOutput(done, details, result, options = {}) {
|
|
|
316
341
|
fmt.keyValueList(details, keyWidth);
|
|
317
342
|
if (result.navigation)
|
|
318
343
|
fmt.keyValue('Page', pageNavigationText(result.navigation), keyWidth);
|
|
319
|
-
listRows(fmt, 'New text',
|
|
344
|
+
listRows(fmt, 'New text', [
|
|
345
|
+
...(result.messages ?? []).map(newMessageText),
|
|
346
|
+
...(result.moreMessages ? [moreMessagesText(result.moreMessages)] : []),
|
|
347
|
+
], keyWidth);
|
|
320
348
|
listRows(fmt, 'Shown', (result.shown ?? []).map(shownElementText), keyWidth);
|
|
321
349
|
const omitted = result.triggeredRequestsOmitted;
|
|
322
350
|
const requests = formatTriggeredRequestLines(result.triggeredRequests ?? [], omitted);
|
|
@@ -400,6 +428,9 @@ function formatClickOutput(result) {
|
|
|
400
428
|
...selectorRows(result),
|
|
401
429
|
elementRow(result),
|
|
402
430
|
['Method', result.method === 'dom' ? 'DOM events' : 'mouse events'],
|
|
431
|
+
...(result.scrolledBy
|
|
432
|
+
? [['Scrolled', pointerScrollText(result.scrolledBy)]]
|
|
433
|
+
: []),
|
|
403
434
|
], result, { action: POINTER_ACTION_NOUN[result.action ?? 'click'] }).build();
|
|
404
435
|
}
|
|
405
436
|
/**
|