browser-debugger-cli 0.9.0 → 0.10.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/README.md +4 -1
- package/dist/commands/dom/a11y.js +2 -1
- package/dist/commands/dom/formInteraction.js +56 -25
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +1 -1
- package/dist/commands/dom/helpers/query.js +66 -19
- package/dist/commands/dom/helpers/runElementCommand.js +4 -3
- package/dist/commands/dom/helpers/screenshot.js +85 -12
- package/dist/commands/dom/index.d.ts +1 -0
- package/dist/commands/dom/index.js +8 -3
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.js +2 -2
- package/dist/commands/dom/listeners.js +2 -2
- package/dist/commands/dom/semanticUtils.d.ts +14 -1
- package/dist/commands/dom/semanticUtils.js +44 -3
- package/dist/commands/network/list.js +13 -2
- package/dist/commands/optionBehaviors.js +48 -6
- package/dist/commands/page.d.ts +1 -1
- package/dist/commands/page.js +62 -3
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/optionTypes.d.ts +21 -0
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +91 -10
- package/dist/commands/shared/validation.d.ts +11 -0
- package/dist/commands/shared/validation.js +16 -0
- package/dist/daemon/launcher.d.ts +8 -1
- package/dist/daemon/launcher.js +3 -1
- package/dist/daemon/session/Session.d.ts +7 -0
- package/dist/daemon/session/Session.js +23 -1
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +65 -9
- package/dist/daemon/session/interactions.d.ts +18 -5
- package/dist/daemon/session/interactions.js +22 -12
- package/dist/daemon.js +3565 -329
- package/dist/errors/messages.d.ts +69 -0
- package/dist/errors/messages.js +102 -1
- package/dist/index.js +2416 -1320
- package/dist/ipc/client.d.ts +9 -0
- package/dist/ipc/client.js +13 -0
- package/dist/ipc/protocol/commands.d.ts +56 -1
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +35 -2
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/runtime/dom/actionEffects.d.ts +94 -15
- package/dist/runtime/dom/actionEffects.js +173 -27
- package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
- package/dist/runtime/dom/actionEffectsScripts.js +224 -32
- package/dist/runtime/dom/elementInfo.d.ts +26 -0
- package/dist/runtime/dom/elementInfo.js +65 -0
- package/dist/runtime/dom/eventListeners.js +14 -4
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
- package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
- package/dist/runtime/dom/frameSelection.d.ts +11 -0
- package/dist/runtime/dom/frameSelection.js +20 -1
- package/dist/runtime/dom/frames.d.ts +38 -5
- package/dist/runtime/dom/frames.js +136 -21
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +5 -1
- package/dist/runtime/dom/layout.js +10 -3
- package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
- package/dist/runtime/dom/listenerPageScripts.js +95 -9
- package/dist/runtime/dom/listenerSummary.d.ts +4 -0
- package/dist/runtime/dom/listenerSummary.js +26 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
- package/dist/runtime/dom/reactEventHelpers.js +12 -4
- package/dist/runtime/page/emulation.d.ts +20 -0
- package/dist/runtime/page/emulation.js +37 -0
- package/dist/telemetry/a11y.d.ts +10 -0
- package/dist/telemetry/a11y.js +78 -1
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/types.d.ts +32 -0
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +59 -3
- package/dist/ui/formatters/dom.d.ts +2 -1
- package/dist/ui/formatters/dom.js +25 -9
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.js +2 -2
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +23 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/formatting.d.ts +9 -0
- package/dist/ui/formatting.js +6 -3
- package/dist/ui/messages/commands.d.ts +123 -7
- package/dist/ui/messages/commands.js +181 -10
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/session.d.ts +14 -0
- package/dist/ui/messages/session.js +20 -0
- package/dist/utils/async.d.ts +9 -0
- package/dist/utils/async.js +17 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/selectorFilters.d.ts +12 -0
- package/dist/utils/selectorFilters.js +29 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Page scripts behind the "what changed" part of DOM action results: one
|
|
3
3
|
* snapshot before the action ({@link EFFECTS_START_SCRIPT}) and one read after
|
|
4
|
-
* it ({@link EFFECTS_READ_SCRIPT})
|
|
4
|
+
* it ({@link EFFECTS_READ_SCRIPT}), plus the snapshot a hover takes of what
|
|
5
|
+
* is hidden around its target ({@link REVEAL_SNAPSHOT_JS}).
|
|
5
6
|
*/
|
|
6
7
|
import { ELEMENT_DESCRIPTION_JS } from './elementInfo.js';
|
|
7
8
|
/** Class or id words that mark an element as a message (flash, toast, field error) */
|
|
@@ -23,14 +24,65 @@ const MESSAGE_CANDIDATES = [
|
|
|
23
24
|
'output',
|
|
24
25
|
...MESSAGE_WORDS.flatMap((word) => [`[class*="${word}" i]`, `[id*="${word}" i]`]),
|
|
25
26
|
].join(', ');
|
|
27
|
+
/** Class or id words that mark an element as a loading indicator */
|
|
28
|
+
const LOADER_WORDS = ['loading', 'loader', 'spinner'];
|
|
29
|
+
/** Loading indicators by state or role, plus a cheap prefilter for the class and id words */
|
|
30
|
+
const LOADER_CANDIDATES = [
|
|
31
|
+
'[aria-busy="true"]',
|
|
32
|
+
'[role="progressbar"]',
|
|
33
|
+
...['load', 'spinner'].flatMap((word) => [`[class*="${word}" i]`, `[id*="${word}" i]`]),
|
|
34
|
+
].join(', ');
|
|
35
|
+
/** Elements anywhere on the page a hover may show (tooltips, menus, popovers) */
|
|
36
|
+
const REVEAL_GLOBAL_CANDIDATES = '[role="tooltip"], [role="menu"], [role="listbox"], [role="dialog"], [popover]';
|
|
37
|
+
/** Elements a hover snapshot looks at, at most */
|
|
38
|
+
const MAX_REVEAL_CANDIDATES = 1500;
|
|
39
|
+
/** Time a hover snapshot may spend before it stops looking (ms) */
|
|
40
|
+
const REVEAL_BUDGET_MS = 8;
|
|
41
|
+
/**
|
|
42
|
+
* Containers that hold a target and the results of its key presses (a form,
|
|
43
|
+
* a search box, a dialog); without one, the target's grandparent is used
|
|
44
|
+
*/
|
|
45
|
+
const NEAR_CONTAINERS = 'form, [role="form"], [role="search"], dialog, [role="dialog"], [role="combobox"]';
|
|
46
|
+
/** Added elements reported wherever they are: popups and messages */
|
|
47
|
+
const POPUP_ROLES = /^(tooltip|menu|listbox|dialog|alert|alertdialog|status)$/;
|
|
26
48
|
/** Messages kept per snapshot (after filtering) */
|
|
27
49
|
const MAX_MESSAGES = 50;
|
|
28
50
|
/** Time a message snapshot may spend before it stops looking (ms) */
|
|
29
51
|
const MESSAGES_BUDGET_MS = 5;
|
|
52
|
+
/** Time the list of shown elements may spend before it stops looking (ms) */
|
|
53
|
+
const SHOWN_BUDGET_MS = 10;
|
|
54
|
+
/** Shown elements a read returns at most */
|
|
55
|
+
const MAX_SHOWN = 10;
|
|
56
|
+
/** Added and removed elements a watch keeps */
|
|
57
|
+
const MAX_TRACKED_NODES = 200;
|
|
30
58
|
/** Longer texts are containers (a page, a form), not messages */
|
|
31
59
|
const MAX_MESSAGE_TEXT = 300;
|
|
32
60
|
/** How long a page keeps watching when no read or stop arrives (ms) */
|
|
33
61
|
const MAX_WATCH_MS = 30000;
|
|
62
|
+
/** Bursts of DOM changes a watch keeps (their times) */
|
|
63
|
+
const MAX_BURSTS = 20;
|
|
64
|
+
/**
|
|
65
|
+
* Page-side test whether an element is shown: rendered, not aria-hidden,
|
|
66
|
+
* not `visibility: hidden` and not fully transparent.
|
|
67
|
+
*/
|
|
68
|
+
const SHOWN_JS = `(el) => !el.closest('[aria-hidden="true"]') && el.getClientRects().length > 0 &&
|
|
69
|
+
(!el.checkVisibility || el.checkVisibility({ visibilityProperty: true, opacityProperty: true }))`;
|
|
70
|
+
/**
|
|
71
|
+
* Page-side visible text of an element, whitespace collapsed: text in
|
|
72
|
+
* hidden elements, scripts and styles left out, and parts for which `skip`
|
|
73
|
+
* says so. Stops a little over {@link MAX_MESSAGE_TEXT} characters.
|
|
74
|
+
*/
|
|
75
|
+
const VISIBLE_TEXT_JS = `(el, skip) => {
|
|
76
|
+
let text = '';
|
|
77
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
78
|
+
while (text.length <= ${MAX_MESSAGE_TEXT} && walker.nextNode()) {
|
|
79
|
+
const parent = walker.currentNode.parentElement;
|
|
80
|
+
if (!parent || /^(script|style|template|noscript)$/.test(parent.localName) || skip(parent)) continue;
|
|
81
|
+
if (parent.checkVisibility && !parent.checkVisibility({ visibilityProperty: true, opacityProperty: true })) continue;
|
|
82
|
+
text += ' ' + walker.currentNode.data;
|
|
83
|
+
}
|
|
84
|
+
return text.replace(/\\s+/g, ' ').trim();
|
|
85
|
+
}`;
|
|
34
86
|
/**
|
|
35
87
|
* Page-side test whether an element is part of a message's chrome rather
|
|
36
88
|
* than its text: aria-hidden parts, buttons, and elements whose class or
|
|
@@ -62,6 +114,8 @@ const MESSAGES_JS = `(ids) => {
|
|
|
62
114
|
const deadline = performance.now() + ${MESSAGES_BUDGET_MS};
|
|
63
115
|
const description = ${ELEMENT_DESCRIPTION_JS};
|
|
64
116
|
const chrome = ${MESSAGE_CHROME_JS};
|
|
117
|
+
const shown = ${SHOWN_JS};
|
|
118
|
+
const visibleText = ${VISIBLE_TEXT_JS};
|
|
65
119
|
const describe = (el) => {
|
|
66
120
|
const text = description(el);
|
|
67
121
|
const role = el.getAttribute('role');
|
|
@@ -71,28 +125,15 @@ const MESSAGES_JS = `(ids) => {
|
|
|
71
125
|
const named = (el) => !/^(timer|marquee|progressbar)$/.test(el.getAttribute('role') || '') &&
|
|
72
126
|
(el.matches('[role="alert"], [role="status"], [aria-live]:not([aria-live="off"]), output') ||
|
|
73
127
|
word.test(el.getAttribute('class') || '') || word.test(el.id || ''));
|
|
74
|
-
const shown = (el) => !el.closest('[aria-hidden="true"]') && el.getClientRects().length > 0 &&
|
|
75
|
-
(!el.checkVisibility || el.checkVisibility({ visibilityProperty: true, opacityProperty: true }));
|
|
76
128
|
const inChrome = (node, el) => {
|
|
77
129
|
for (let n = node; n && n !== el; n = n.parentElement) if (chrome(n)) return true;
|
|
78
130
|
return false;
|
|
79
131
|
};
|
|
80
|
-
const textOf = (el) => {
|
|
81
|
-
let text = '';
|
|
82
|
-
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
83
|
-
while (text.length <= ${MAX_MESSAGE_TEXT} && walker.nextNode()) {
|
|
84
|
-
const parent = walker.currentNode.parentElement;
|
|
85
|
-
if (!parent || inChrome(parent, el)) continue;
|
|
86
|
-
if (parent.checkVisibility && !parent.checkVisibility({ visibilityProperty: true, opacityProperty: true })) continue;
|
|
87
|
-
text += ' ' + walker.currentNode.data;
|
|
88
|
-
}
|
|
89
|
-
return text.replace(/\\s+/g, ' ').trim();
|
|
90
|
-
};
|
|
91
132
|
const found = [];
|
|
92
133
|
for (const el of document.querySelectorAll(${JSON.stringify(MESSAGE_CANDIDATES)})) {
|
|
93
134
|
if (found.length >= ${MAX_MESSAGES} || performance.now() > deadline) break;
|
|
94
135
|
if (!named(el) || !shown(el)) continue;
|
|
95
|
-
const text =
|
|
136
|
+
const text = visibleText(el, (node) => inChrome(node, el));
|
|
96
137
|
if (text !== '' && text.length <= ${MAX_MESSAGE_TEXT}) found.push({ el: el, text: text });
|
|
97
138
|
}
|
|
98
139
|
return found
|
|
@@ -106,6 +147,102 @@ const MESSAGES_JS = `(ids) => {
|
|
|
106
147
|
return { id: id, text: m.text, element: describe(m.el) };
|
|
107
148
|
});
|
|
108
149
|
}`;
|
|
150
|
+
/**
|
|
151
|
+
* Page-side list of the loading indicators shown: elements with
|
|
152
|
+
* `aria-busy="true"`, a progressbar role, or a class or id word from
|
|
153
|
+
* {@link LOADER_WORDS} (`is-loading`, `spinner`; not `lazyloading`).
|
|
154
|
+
*/
|
|
155
|
+
const LOADERS_JS = `() => {
|
|
156
|
+
const shown = ${SHOWN_JS};
|
|
157
|
+
const word = /\\b(${LOADER_WORDS.join('|')})\\b/i;
|
|
158
|
+
const loader = (el) => el.getAttribute('aria-busy') === 'true' || el.getAttribute('role') === 'progressbar' ||
|
|
159
|
+
word.test(el.getAttribute('class') || '') || word.test(el.id || '');
|
|
160
|
+
return Array.from(document.querySelectorAll(${JSON.stringify(LOADER_CANDIDATES)})).filter((el) => loader(el) && shown(el));
|
|
161
|
+
}`;
|
|
162
|
+
/**
|
|
163
|
+
* Page-side snapshot a hover takes right before the mouse moves (called by
|
|
164
|
+
* the click script with the hovered element): the elements around it (its
|
|
165
|
+
* parent and everything in it) and the tooltips, menus, listboxes, dialogs
|
|
166
|
+
* and popovers anywhere on the page that are hidden, at most
|
|
167
|
+
* {@link MAX_REVEAL_CANDIDATES} looked at within {@link REVEAL_BUDGET_MS}.
|
|
168
|
+
* They are kept by identity in the action's watch, so its read can tell
|
|
169
|
+
* which of them the hover revealed, also through CSS `:hover` rules that
|
|
170
|
+
* change no DOM, and elements moving in the page can't pass for revealed
|
|
171
|
+
* ones. Does nothing without a running watch (another frame).
|
|
172
|
+
*/
|
|
173
|
+
export const REVEAL_SNAPSHOT_JS = `(el) => {
|
|
174
|
+
const state = window.__bdgEffects;
|
|
175
|
+
if (!state || state.stopped) return;
|
|
176
|
+
const deadline = performance.now() + ${REVEAL_BUDGET_MS};
|
|
177
|
+
const shown = ${SHOWN_JS};
|
|
178
|
+
const parent = el.parentElement;
|
|
179
|
+
const scope = parent && !/^(body|html)$/.test(parent.localName) ? parent : el;
|
|
180
|
+
const hidden = [];
|
|
181
|
+
let looked = 0;
|
|
182
|
+
const room = () => looked < ${MAX_REVEAL_CANDIDATES} && performance.now() <= deadline;
|
|
183
|
+
const look = (candidate) => {
|
|
184
|
+
looked++;
|
|
185
|
+
if (!shown(candidate)) hidden.push(candidate);
|
|
186
|
+
};
|
|
187
|
+
for (const candidate of document.querySelectorAll(${JSON.stringify(REVEAL_GLOBAL_CANDIDATES)})) {
|
|
188
|
+
if (!room()) break;
|
|
189
|
+
look(candidate);
|
|
190
|
+
}
|
|
191
|
+
const walker = document.createTreeWalker(scope, NodeFilter.SHOW_ELEMENT);
|
|
192
|
+
for (let node = scope; node && room(); node = walker.nextNode()) look(node);
|
|
193
|
+
state.reveal = { target: el, hidden: hidden };
|
|
194
|
+
}`;
|
|
195
|
+
/**
|
|
196
|
+
* Page-side container whose added elements count as an action's result:
|
|
197
|
+
* the target's form, search box, dialog or combobox, else its grandparent
|
|
198
|
+
* (its parent when the grandparent is the body).
|
|
199
|
+
*/
|
|
200
|
+
const NEAR_SCOPE_JS = `(target) => {
|
|
201
|
+
const container = target.closest(${JSON.stringify(NEAR_CONTAINERS)});
|
|
202
|
+
if (container) return container;
|
|
203
|
+
const parent = target.parentElement || target;
|
|
204
|
+
const grandparent = parent.parentElement;
|
|
205
|
+
return grandparent && !/^(body|html)$/.test(grandparent.localName) ? grandparent : parent;
|
|
206
|
+
}`;
|
|
207
|
+
/**
|
|
208
|
+
* Page-side list of the elements an action showed: elements added during
|
|
209
|
+
* the watch inside the target's container ({@link NEAR_SCOPE_JS}) or, anywhere,
|
|
210
|
+
* popups and messages (tooltip, menu, listbox, dialog, alert and status
|
|
211
|
+
* roles, `aria-live`, message-like classes), plus, after a hover, the
|
|
212
|
+
* elements hidden before it that are shown now ({@link REVEAL_SNAPSHOT_JS}).
|
|
213
|
+
* Background widgets elsewhere on the page do not count. Only shown ones
|
|
214
|
+
* with visible text count, the outermost of nested ones, and not those
|
|
215
|
+
* whose text a removed element had (a re-render). At most
|
|
216
|
+
* {@link MAX_SHOWN}, within {@link SHOWN_BUDGET_MS}.
|
|
217
|
+
*/
|
|
218
|
+
export const SHOWN_ELEMENTS_JS = `(state) => {
|
|
219
|
+
const deadline = performance.now() + ${SHOWN_BUDGET_MS};
|
|
220
|
+
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
221
|
+
const shown = ${SHOWN_JS};
|
|
222
|
+
const visibleText = ${VISIBLE_TEXT_JS};
|
|
223
|
+
const word = /\\b(${MESSAGE_WORDS.join('|')})\\b/i;
|
|
224
|
+
const target = state.reveal ? state.reveal.target : state.keyTarget;
|
|
225
|
+
const scope = target && target.isConnected ? (${NEAR_SCOPE_JS})(target) : null;
|
|
226
|
+
const popup = (el) => ${POPUP_ROLES}.test(el.getAttribute('role') || '') || el.hasAttribute('popover') ||
|
|
227
|
+
(el.hasAttribute('aria-live') && el.getAttribute('aria-live') !== 'off') ||
|
|
228
|
+
word.test(el.getAttribute('class') || '') || word.test(el.id || '');
|
|
229
|
+
const near = (el) => (scope !== null && scope.contains(el)) || popup(el);
|
|
230
|
+
const contentOf = (node) => (node.textContent || '').replace(/\\s+/g, ' ').trim();
|
|
231
|
+
const removed = new Set(state.removed.map(contentOf));
|
|
232
|
+
const candidates = new Set(state.added.filter((node) => node.isConnected && near(node)));
|
|
233
|
+
if (state.reveal) for (const el of state.reveal.hidden) if (el.isConnected) candidates.add(el);
|
|
234
|
+
const found = [];
|
|
235
|
+
for (const el of candidates) {
|
|
236
|
+
if (performance.now() > deadline) break;
|
|
237
|
+
if (!shown(el) || removed.has(contentOf(el))) continue;
|
|
238
|
+
const text = visibleText(el, () => false);
|
|
239
|
+
if (text !== '') found.push({ el: el, text: text });
|
|
240
|
+
}
|
|
241
|
+
return found
|
|
242
|
+
.filter((m) => !found.some((other) => other !== m && other.el.contains(m.el)))
|
|
243
|
+
.slice(0, ${MAX_SHOWN})
|
|
244
|
+
.map((m) => ({ text: m.text, element: describe(m.el) }));
|
|
245
|
+
}`;
|
|
109
246
|
/**
|
|
110
247
|
* Page-side test whether a mutation is only focus/hover churn: a class
|
|
111
248
|
* change on an element the action's events hit (`targets`) that only adds
|
|
@@ -119,6 +256,18 @@ export const CHURN_ONLY_JS = `(record, targets) => {
|
|
|
119
256
|
const changed = [...before].filter((c) => !after.has(c)).concat([...after].filter((c) => !before.has(c)));
|
|
120
257
|
return changed.every((c) => /focus|hover/i.test(c));
|
|
121
258
|
}`;
|
|
259
|
+
/**
|
|
260
|
+
* Page-side test whether a mutation changes the page's structure or state
|
|
261
|
+
* rather than only animating it: elements added or removed, or an attribute
|
|
262
|
+
* other than `style` changed. Text-only changes (clocks, counters) and style
|
|
263
|
+
* changes (script-driven animations) do not count.
|
|
264
|
+
*/
|
|
265
|
+
export const STRUCTURAL_CHANGE_JS = `(record) => {
|
|
266
|
+
if (record.type === 'attributes') return record.attributeName !== 'style';
|
|
267
|
+
if (record.type !== 'childList') return false;
|
|
268
|
+
const element = (node) => node.nodeType === 1;
|
|
269
|
+
return Array.from(record.addedNodes).some(element) || Array.from(record.removedNodes).some(element);
|
|
270
|
+
}`;
|
|
122
271
|
/**
|
|
123
272
|
* Page-side reason why "no effect" can't be claimed even without DOM
|
|
124
273
|
* changes, or undefined: `clipboard` (a copy or cut happened), `no-event`
|
|
@@ -148,24 +297,61 @@ export const UNCERTAIN_JS = `(state, active) => {
|
|
|
148
297
|
if (active && active !== state.focus && !plain(active)) return 'focus';
|
|
149
298
|
return undefined;
|
|
150
299
|
}`;
|
|
300
|
+
/**
|
|
301
|
+
* Page-side signs, at a read, that the page is still working on the
|
|
302
|
+
* action's result: how long ago each recent burst of DOM changes was (ms,
|
|
303
|
+
* newest last), and a loading indicator shown since the start (described).
|
|
304
|
+
*/
|
|
305
|
+
const SETTLE_JS = `(state) => {
|
|
306
|
+
const now = performance.now();
|
|
307
|
+
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
308
|
+
const loader = (${LOADERS_JS})().find((el) => !state.loaders.has(el));
|
|
309
|
+
return {
|
|
310
|
+
burstAges: state.bursts.map((time) => Math.round(now - time)),
|
|
311
|
+
loading: loader ? describe(loader) : null
|
|
312
|
+
};
|
|
313
|
+
}`;
|
|
151
314
|
/**
|
|
152
315
|
* Snapshot before an action, left in `window.__bdgEffects`: the messages
|
|
153
|
-
* shown, and a MutationObserver (on the document,
|
|
154
|
-
* any shadow root attached while watching, which
|
|
155
|
-
* counting changes other than
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
316
|
+
* and loading indicators shown, and a MutationObserver (on the document,
|
|
317
|
+
* its open shadow roots and any shadow root attached while watching, which
|
|
318
|
+
* also counts as a change) counting changes other than
|
|
319
|
+
* {@link CHURN_ONLY_JS}, keeping the elements added and removed and the
|
|
320
|
+
* times of {@link STRUCTURAL_CHANGE_JS} bursts. Capture listeners record
|
|
321
|
+
* which elements the action's events reached, the first key press's target,
|
|
322
|
+
* copy/cut events and the scroll position at the first press (a click
|
|
323
|
+
* scrolls its target into view first). The watch stops itself after
|
|
324
|
+
* {@link MAX_WATCH_MS}, so a snapshot that ran late (after a navigation,
|
|
325
|
+
* with nobody reading it) leaves nothing behind. Evaluates to the URL and
|
|
326
|
+
* the messages.
|
|
161
327
|
*/
|
|
162
328
|
export const EFFECTS_START_SCRIPT = `(() => {
|
|
163
329
|
if (window.__bdgEffects) window.__bdgEffects.stop();
|
|
164
330
|
const churnOnly = ${CHURN_ONLY_JS};
|
|
331
|
+
const structural = ${STRUCTURAL_CHANGE_JS};
|
|
165
332
|
const ids = { map: new WeakMap(), next: 1 };
|
|
166
|
-
const state = {
|
|
333
|
+
const state = {
|
|
334
|
+
ids: ids, changes: 0, targets: new Set(), path: new Set(), focus: document.activeElement,
|
|
335
|
+
pressScroll: null, copied: false, stopped: false, added: [], removed: [], bursts: [],
|
|
336
|
+
keyTarget: null, loaders: new Set((${LOADERS_JS})())
|
|
337
|
+
};
|
|
338
|
+
const keep = (list, nodes) => {
|
|
339
|
+
for (const node of nodes) if (node.nodeType === 1 && list.length < ${MAX_TRACKED_NODES}) list.push(node);
|
|
340
|
+
};
|
|
167
341
|
const count = (records) => {
|
|
168
|
-
|
|
342
|
+
let burst = false;
|
|
343
|
+
for (const record of records) {
|
|
344
|
+
if (churnOnly(record, state.targets)) continue;
|
|
345
|
+
state.changes++;
|
|
346
|
+
burst = burst || structural(record);
|
|
347
|
+
if (record.type === 'childList') {
|
|
348
|
+
keep(state.added, record.addedNodes);
|
|
349
|
+
keep(state.removed, record.removedNodes);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (!burst) return;
|
|
353
|
+
state.bursts.push(performance.now());
|
|
354
|
+
if (state.bursts.length > ${MAX_BURSTS}) state.bursts.shift();
|
|
169
355
|
};
|
|
170
356
|
const observer = new MutationObserver(count);
|
|
171
357
|
const options = { subtree: true, childList: true, characterData: true, attributes: true, attributeOldValue: true };
|
|
@@ -188,6 +374,7 @@ export const EFFECTS_START_SCRIPT = `(() => {
|
|
|
188
374
|
if (event.type === 'copy' || event.type === 'cut') state.copied = true;
|
|
189
375
|
const path = event.composedPath().filter((node) => node.nodeType === 1);
|
|
190
376
|
if (path[0]) state.targets.add(path[0]);
|
|
377
|
+
if (path[0] && !state.keyTarget && event.type === 'keydown') state.keyTarget = path[0];
|
|
191
378
|
path.forEach((node) => state.path.add(node));
|
|
192
379
|
if (!state.pressScroll && pressTypes.includes(event.type)) state.pressScroll = [scrollX, scrollY];
|
|
193
380
|
};
|
|
@@ -206,13 +393,16 @@ export const EFFECTS_START_SCRIPT = `(() => {
|
|
|
206
393
|
return { href: location.href, messages: (${MESSAGES_JS})(ids) };
|
|
207
394
|
})()`;
|
|
208
395
|
/**
|
|
209
|
-
* Read after an action
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
396
|
+
* Read after an action, called with `(stop, shown)`: `stop` also stops
|
|
397
|
+
* watching, `shown` lists the elements the action showed
|
|
398
|
+
* ({@link SHOWN_ELEMENTS_JS}). Returns the URL, the messages shown and,
|
|
399
|
+
* when the snapshot is still there (same document), the number of changes
|
|
400
|
+
* counted (plus one when the page scrolled after the press), why "no
|
|
401
|
+
* effect" could not be claimed ({@link UNCERTAIN_JS}) and whether the page
|
|
402
|
+
* is still working ({@link SETTLE_JS}). `fresh` means a new document
|
|
403
|
+
* (everything shown is new).
|
|
214
404
|
*/
|
|
215
|
-
export const EFFECTS_READ_SCRIPT = `((stop) => {
|
|
405
|
+
export const EFFECTS_READ_SCRIPT = `((stop, shown) => {
|
|
216
406
|
const state = window.__bdgEffects;
|
|
217
407
|
if (!state) return { href: location.href, fresh: true, messages: (${MESSAGES_JS})({ map: new WeakMap(), next: 1 }) };
|
|
218
408
|
if (!state.stopped) state.flush();
|
|
@@ -226,7 +416,9 @@ export const EFFECTS_READ_SCRIPT = `((stop) => {
|
|
|
226
416
|
fresh: false,
|
|
227
417
|
changes: state.changes + (scrolled ? 1 : 0),
|
|
228
418
|
uncertain: (${UNCERTAIN_JS})(state, document.activeElement),
|
|
229
|
-
messages: (${MESSAGES_JS})(state.ids)
|
|
419
|
+
messages: (${MESSAGES_JS})(state.ids),
|
|
420
|
+
settle: (${SETTLE_JS})(state),
|
|
421
|
+
shown: shown ? (${SHOWN_ELEMENTS_JS})(state) : undefined
|
|
230
422
|
};
|
|
231
423
|
})`;
|
|
232
424
|
/** Stops the watch {@link EFFECTS_START_SCRIPT} left (when no read stopped it) */
|
|
@@ -26,6 +26,32 @@ export declare const WITHOUT_DECORATIONS_JS = "(el, text) => {\n if (!text || t
|
|
|
26
26
|
* unless `full` is set. Decorations are left out ({@link WITHOUT_DECORATIONS_JS}).
|
|
27
27
|
*/
|
|
28
28
|
export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const all = el.textContent || '';\n if (el.tagName === 'OPTION') return el.label;\n if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n if (!rendered(el)) {\n const boxless = el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';\n return boxless ? withoutDecorations(el, full ? all : all.slice(0, 2000)) : '';\n }\n if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let start = '';\n while (start.length < 1000 && walker.nextNode()) {\n const parent = walker.currentNode.parentElement;\n if (!parent || rendered(parent)) start += walker.currentNode.data;\n }\n return withoutDecorations(el, start);\n}";
|
|
29
|
+
/** Shown instead of a secret field value (the same for every length) */
|
|
30
|
+
export declare const MASKED_VALUE = "\u2022\u2022\u2022\u2022";
|
|
31
|
+
/**
|
|
32
|
+
* Page-side check whether a form control holds a secret whose value must
|
|
33
|
+
* never leave the page: a password field (live type or type attribute), a
|
|
34
|
+
* field shown masked by CSS (`-webkit-text-security` other than `none`),
|
|
35
|
+
* one whose `autocomplete` names a password, a payment card (`cc-*`) or a
|
|
36
|
+
* one-time code, or a field named like a password, one-time code or card code
|
|
37
|
+
* (`password`, `passwd`, `pwd`, `passcode`, `otp`, `cvv`, `cvc`), which
|
|
38
|
+
* covers a password field switched to text by a "show password" button.
|
|
39
|
+
*/
|
|
40
|
+
export declare const SENSITIVE_FIELD_JS = "(el) => {\n const autocomplete = el.getAttribute('autocomplete') || '';\n if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;\n if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;\n const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');\n if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;\n try {\n const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');\n return Boolean(security) && security !== 'none';\n } catch (e) {\n return false;\n }\n}";
|
|
41
|
+
/**
|
|
42
|
+
* Page-side live state of a form control, which its attributes do not show:
|
|
43
|
+
* the type and current value of an `<input>` (for checkboxes and radios
|
|
44
|
+
* `checked` and their `value` attribute, not the default "on"), the value of
|
|
45
|
+
* a `<textarea>`, the labels of a `<select>`'s selected options and the type
|
|
46
|
+
* of a `<button>` in a form (`submit` when it has none; outside a form only
|
|
47
|
+
* a type attribute is shown). Empty for other elements.
|
|
48
|
+
*
|
|
49
|
+
* Secrets never leave the page: a hidden input's value is left out, and the
|
|
50
|
+
* value (or selected option) of a sensitive field ({@link SENSITIVE_FIELD_JS})
|
|
51
|
+
* is replaced by {@link MASKED_VALUE}, whatever its length, with
|
|
52
|
+
* `sensitive: true`.
|
|
53
|
+
*/
|
|
54
|
+
export declare const ELEMENT_STATE_JS = "(el) => {\n const isSensitive = (el) => {\n const autocomplete = el.getAttribute('autocomplete') || '';\n if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;\n if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;\n const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');\n if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;\n try {\n const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');\n return Boolean(security) && security !== 'none';\n } catch (e) {\n return false;\n }\n};\n const mask = (value) => (value ? '\u2022\u2022\u2022\u2022' : '');\n const guarded = (state) => {\n if (!isSensitive(el)) return state;\n const result = { ...state, sensitive: true };\n if ('value' in result) result.value = mask(result.value);\n if ('selected' in result) result.selected = mask(result.selected);\n return result;\n };\n switch (el.localName) {\n case 'input':\n if (el.type === 'hidden') return { type: 'hidden' };\n return guarded(\n /^(checkbox|radio)$/.test(el.type)\n ? { type: el.type, checked: el.checked, value: el.getAttribute('value') || '' }\n : { type: el.type, value: el.value }\n );\n case 'textarea':\n return guarded({ value: el.value });\n case 'select':\n return guarded({ selected: Array.from(el.selectedOptions || [], (option) => option.label).join(', ') });\n case 'button':\n return el.form ? { type: el.type } : {};\n default:\n return {};\n }\n}";
|
|
29
55
|
/**
|
|
30
56
|
* Page-side short description of an element: tag, id and up to two classes,
|
|
31
57
|
* e.g. `button#save.primary.large`.
|
|
@@ -66,6 +66,71 @@ export const ELEMENT_TEXT_JS = `(el, full) => {
|
|
|
66
66
|
}
|
|
67
67
|
return withoutDecorations(el, start);
|
|
68
68
|
}`;
|
|
69
|
+
/** Shown instead of a secret field value (the same for every length) */
|
|
70
|
+
export const MASKED_VALUE = '••••';
|
|
71
|
+
/**
|
|
72
|
+
* Page-side check whether a form control holds a secret whose value must
|
|
73
|
+
* never leave the page: a password field (live type or type attribute), a
|
|
74
|
+
* field shown masked by CSS (`-webkit-text-security` other than `none`),
|
|
75
|
+
* one whose `autocomplete` names a password, a payment card (`cc-*`) or a
|
|
76
|
+
* one-time code, or a field named like a password, one-time code or card code
|
|
77
|
+
* (`password`, `passwd`, `pwd`, `passcode`, `otp`, `cvv`, `cvc`), which
|
|
78
|
+
* covers a password field switched to text by a "show password" button.
|
|
79
|
+
*/
|
|
80
|
+
export const SENSITIVE_FIELD_JS = `(el) => {
|
|
81
|
+
const autocomplete = el.getAttribute('autocomplete') || '';
|
|
82
|
+
if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;
|
|
83
|
+
if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;
|
|
84
|
+
const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');
|
|
85
|
+
if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;
|
|
86
|
+
try {
|
|
87
|
+
const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');
|
|
88
|
+
return Boolean(security) && security !== 'none';
|
|
89
|
+
} catch (e) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}`;
|
|
93
|
+
/**
|
|
94
|
+
* Page-side live state of a form control, which its attributes do not show:
|
|
95
|
+
* the type and current value of an `<input>` (for checkboxes and radios
|
|
96
|
+
* `checked` and their `value` attribute, not the default "on"), the value of
|
|
97
|
+
* a `<textarea>`, the labels of a `<select>`'s selected options and the type
|
|
98
|
+
* of a `<button>` in a form (`submit` when it has none; outside a form only
|
|
99
|
+
* a type attribute is shown). Empty for other elements.
|
|
100
|
+
*
|
|
101
|
+
* Secrets never leave the page: a hidden input's value is left out, and the
|
|
102
|
+
* value (or selected option) of a sensitive field ({@link SENSITIVE_FIELD_JS})
|
|
103
|
+
* is replaced by {@link MASKED_VALUE}, whatever its length, with
|
|
104
|
+
* `sensitive: true`.
|
|
105
|
+
*/
|
|
106
|
+
export const ELEMENT_STATE_JS = `(el) => {
|
|
107
|
+
const isSensitive = ${SENSITIVE_FIELD_JS};
|
|
108
|
+
const mask = (value) => (value ? '${MASKED_VALUE}' : '');
|
|
109
|
+
const guarded = (state) => {
|
|
110
|
+
if (!isSensitive(el)) return state;
|
|
111
|
+
const result = { ...state, sensitive: true };
|
|
112
|
+
if ('value' in result) result.value = mask(result.value);
|
|
113
|
+
if ('selected' in result) result.selected = mask(result.selected);
|
|
114
|
+
return result;
|
|
115
|
+
};
|
|
116
|
+
switch (el.localName) {
|
|
117
|
+
case 'input':
|
|
118
|
+
if (el.type === 'hidden') return { type: 'hidden' };
|
|
119
|
+
return guarded(
|
|
120
|
+
/^(checkbox|radio)$/.test(el.type)
|
|
121
|
+
? { type: el.type, checked: el.checked, value: el.getAttribute('value') || '' }
|
|
122
|
+
: { type: el.type, value: el.value }
|
|
123
|
+
);
|
|
124
|
+
case 'textarea':
|
|
125
|
+
return guarded({ value: el.value });
|
|
126
|
+
case 'select':
|
|
127
|
+
return guarded({ selected: Array.from(el.selectedOptions || [], (option) => option.label).join(', ') });
|
|
128
|
+
case 'button':
|
|
129
|
+
return el.form ? { type: el.type } : {};
|
|
130
|
+
default:
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
}`;
|
|
69
134
|
/**
|
|
70
135
|
* Page-side short description of an element: tag, id and up to two classes,
|
|
71
136
|
* e.g. `button#save.primary.large`.
|
|
@@ -249,7 +249,13 @@ async function pageDetails(cdp, chain, found, objectGroup, types) {
|
|
|
249
249
|
objectId: chain[0]?.objectId,
|
|
250
250
|
functionDeclaration: ELEMENT_INFO_JS,
|
|
251
251
|
arguments: [
|
|
252
|
-
{
|
|
252
|
+
{
|
|
253
|
+
value: listeners.map(({ position, listener }) => ({
|
|
254
|
+
position,
|
|
255
|
+
type: listener.type,
|
|
256
|
+
capture: listener.useCapture,
|
|
257
|
+
})),
|
|
258
|
+
},
|
|
253
259
|
{ value: types ?? null },
|
|
254
260
|
...chain.map((entry) => ({ objectId: entry.objectId })),
|
|
255
261
|
...handlers.map(objectArgument),
|
|
@@ -346,13 +352,13 @@ const UNKNOWN_SOURCE = { scriptId: '0', lineNumber: 0, columnNumber: 0 };
|
|
|
346
352
|
* Combine the page's report with the framework handlers' sources.
|
|
347
353
|
*
|
|
348
354
|
* @param info - Page report
|
|
349
|
-
* @param sources - Source and location of each jQuery handler,
|
|
350
|
-
* React prop handler, in report order
|
|
355
|
+
* @param sources - Source and location of each jQuery or Preact handler,
|
|
356
|
+
* then each React prop handler, in report order
|
|
351
357
|
* @returns Details per listener and the React prop handlers
|
|
352
358
|
*/
|
|
353
359
|
function toPageDetails(info, sources) {
|
|
354
360
|
let next = 0;
|
|
355
|
-
const details = info.listeners.map(({ name, identity, targetName, jquery }) => ({
|
|
361
|
+
const details = info.listeners.map(({ name, identity, targetName, jquery, preact }) => ({
|
|
356
362
|
name: name ?? undefined,
|
|
357
363
|
identity: identity ?? undefined,
|
|
358
364
|
targetName: targetName ?? undefined,
|
|
@@ -362,6 +368,10 @@ function toPageDetails(info, sources) {
|
|
|
362
368
|
name: handler.name,
|
|
363
369
|
...(sources[next++] ?? UNKNOWN_SOURCE),
|
|
364
370
|
})),
|
|
371
|
+
preact: preact && {
|
|
372
|
+
name: preact.name,
|
|
373
|
+
...(sources[next++] ?? UNKNOWN_SOURCE),
|
|
374
|
+
},
|
|
365
375
|
}));
|
|
366
376
|
const react = info.react.map((handler) => ({
|
|
367
377
|
...handler,
|
|
@@ -20,10 +20,8 @@ export type PointerAction = 'click' | 'double' | 'right' | 'hover';
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function mouseEvents(action: PointerAction, x: number, y: number): Array<Record<string, unknown>>;
|
|
22
22
|
/**
|
|
23
|
-
* Whether the mouse press just dispatched reached the target element
|
|
24
|
-
*
|
|
25
|
-
* Any doubt (no probe, evaluation failure) counts as reached, so only a
|
|
26
|
-
* press that the page provably never saw is reported.
|
|
23
|
+
* Whether the mouse press just dispatched reached the target element
|
|
24
|
+
* (see {@link readPressProbe}).
|
|
27
25
|
*
|
|
28
26
|
* @param cdp - CDP connection
|
|
29
27
|
* @returns False only when the target received no pointerdown/mousedown
|
|
@@ -35,5 +33,6 @@ export declare function pressReachedTarget(cdp: CDPConnection): Promise<boolean>
|
|
|
35
33
|
export declare function clickElement(cdp: CDPConnection, selector: string, options?: {
|
|
36
34
|
index?: number;
|
|
37
35
|
action?: PointerAction;
|
|
36
|
+
strict?: boolean;
|
|
38
37
|
}): Promise<ClickResult>;
|
|
39
38
|
//# sourceMappingURL=fill.d.ts.map
|