@reticlehq/browser 3.9.0 → 3.10.1
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/dist/actions/action-report.d.ts +14 -0
- package/dist/actions/action-report.js +95 -22
- package/dist/actions/actions-dom.d.ts +16 -6
- package/dist/actions/actions-dom.js +94 -46
- package/dist/actions/actions-press.d.ts +10 -0
- package/dist/actions/actions-press.js +146 -2
- package/dist/actions/actions.js +52 -22
- package/dist/actions/insert-text.d.ts +3 -0
- package/dist/actions/insert-text.js +90 -0
- package/dist/actions/scroll.d.ts +1 -1
- package/dist/actions/scroll.js +25 -1
- package/dist/actions/value-input.d.ts +2 -0
- package/dist/actions/value-input.js +30 -9
- package/dist/commands/commands.js +82 -6
- package/dist/connect-options.d.ts +11 -1
- package/dist/connect-options.js +27 -1
- package/dist/dom/a11y.js +4 -3
- package/dist/dom/chart-geometry.d.ts +7 -0
- package/dist/dom/chart-geometry.js +39 -0
- package/dist/dom/occlusion.js +21 -0
- package/dist/dom/query-finders.d.ts +1 -1
- package/dist/dom/query-finders.js +10 -7
- package/dist/dom/query.d.ts +1 -0
- package/dist/dom/query.js +78 -51
- package/dist/dom/scope-roots.d.ts +8 -0
- package/dist/dom/scope-roots.js +44 -0
- package/dist/dom/snapshot.d.ts +3 -0
- package/dist/dom/snapshot.js +9 -2
- package/dist/dom/text-match.js +4 -3
- package/dist/dom/theme.js +6 -7
- package/dist/dom/visibility.d.ts +2 -0
- package/dist/dom/visibility.js +40 -8
- package/dist/observers/canvas.d.ts +1 -0
- package/dist/observers/canvas.js +10 -1
- package/dist/observers/console.js +46 -35
- package/dist/observers/dom.js +28 -4
- package/dist/observers/net-detail/resource-timing.d.ts +12 -0
- package/dist/observers/net-detail/resource-timing.js +69 -0
- package/dist/observers/network.js +12 -45
- package/dist/observers/perf.js +15 -1
- package/dist/presenter/chrome/presenter-verbs.js +1 -0
- package/dist/presenter/presenter-config.d.ts +1 -0
- package/dist/presenter/presenter-config.js +1 -0
- package/dist/presenter/presenter-shell.d.ts +1 -0
- package/dist/presenter/presenter-shell.js +6 -3
- package/dist/presenter/tour/tour.js +4 -0
- package/dist/recorder/recorder.js +82 -39
- package/dist/reticle-inject.js +597 -597
- package/dist/reticle.d.ts +3 -19
- package/dist/reticle.js +74 -108
- package/dist/security/serialization.js +35 -0
- package/dist/storage-keys.js +2 -1
- package/dist/timers/native/native-timers.js +21 -4
- package/dist/transport/bridge-url.d.ts +8 -0
- package/dist/transport/bridge-url.js +52 -0
- package/package.json +10 -10
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { readTestId } from '../dom/addressing/testid-attr.js';
|
|
2
|
-
import { ActionType, AnchorKind, AnnotationKind, DEGRADED_ANCHOR_ROLE, EventType, FLOW_FILE_VERSION,
|
|
2
|
+
import { ActionType, AnchorKind, AnnotationKind, DEGRADED_ANCHOR_ROLE, EventType, FLOW_FILE_VERSION, ACT_STEP_TOOL, RecorderPhase, PredicateKind, } from '@reticlehq/core';
|
|
3
3
|
import { REDACTED_VALUE } from '@reticlehq/core';
|
|
4
4
|
import { sourceFromDom } from '../dom/addressing/source.js';
|
|
5
5
|
import { getAccessibleName, getRole, isSensitiveField } from '../dom/a11y.js';
|
|
6
|
+
import { positionAmongNamesakes } from '../actions/action-report.js';
|
|
6
7
|
import { isReticleOverlay } from '../dom/dom-ignore.js';
|
|
7
8
|
import { getCapabilities } from '../registry/capabilities.js';
|
|
8
9
|
import { TOOLBAR_CSS, BTN_CSS, NAME_CSS, STATUS_CSS, MENU_CSS } from './recorder-styles.js';
|
|
@@ -10,7 +11,7 @@ export const RECORDER_EMPTY_MSG = 'recorded 0 steps';
|
|
|
10
11
|
const STATUS_RECORDING = 'recording…';
|
|
11
12
|
const STATUS_IDLE = 'ready';
|
|
12
13
|
const STATUS_ANNOTATE = 'pick an annotation';
|
|
13
|
-
const TOOL =
|
|
14
|
+
const TOOL = ACT_STEP_TOOL;
|
|
14
15
|
const BUTTON_LABEL = {
|
|
15
16
|
record: 'Record',
|
|
16
17
|
stop: 'Stop',
|
|
@@ -38,33 +39,45 @@ export function anchorFor(el) {
|
|
|
38
39
|
const role = getRole(el);
|
|
39
40
|
const name = getAccessibleName(el);
|
|
40
41
|
if (role !== 'generic' && (role.length > 0 || name.length > 0)) {
|
|
41
|
-
const
|
|
42
|
+
const position = positionAmongNamesakes(el, role, name);
|
|
43
|
+
const anchor = {
|
|
44
|
+
kind: AnchorKind.ROLE,
|
|
45
|
+
role,
|
|
46
|
+
...(name.length > 0 ? { name } : {}),
|
|
47
|
+
...(null === position ? {} : position),
|
|
48
|
+
};
|
|
42
49
|
return { anchor, degraded: false };
|
|
43
50
|
}
|
|
44
51
|
return { anchor: { kind: AnchorKind.ROLE, role: DEGRADED_ANCHOR_ROLE }, degraded: true };
|
|
45
52
|
}
|
|
46
53
|
function isTextbox(el) {
|
|
47
|
-
if (el instanceof HTMLTextAreaElement)
|
|
54
|
+
if (el instanceof HTMLTextAreaElement) {
|
|
48
55
|
return true;
|
|
56
|
+
}
|
|
49
57
|
return el instanceof HTMLInputElement && 'textbox' === inputRole(el);
|
|
50
58
|
}
|
|
51
59
|
function inputRole(el) {
|
|
52
60
|
const type = el.type.toLowerCase();
|
|
53
|
-
if ('checkbox' === type)
|
|
61
|
+
if ('checkbox' === type) {
|
|
54
62
|
return 'checkbox';
|
|
55
|
-
|
|
63
|
+
}
|
|
64
|
+
if ('radio' === type) {
|
|
56
65
|
return 'radio';
|
|
57
|
-
|
|
66
|
+
}
|
|
67
|
+
if (['text', 'email', 'tel', 'url', 'search', 'password', ''].includes(type)) {
|
|
58
68
|
return 'textbox';
|
|
69
|
+
}
|
|
59
70
|
return 'other';
|
|
60
71
|
}
|
|
61
72
|
function buildStep(el, action, args) {
|
|
62
73
|
const { anchor, degraded } = anchorFor(el);
|
|
63
74
|
const step = { tool: TOOL, anchor, action };
|
|
64
|
-
if (args !== undefined)
|
|
75
|
+
if (args !== undefined) {
|
|
65
76
|
step.args = args;
|
|
66
|
-
|
|
77
|
+
}
|
|
78
|
+
if (degraded) {
|
|
67
79
|
step.degraded = true;
|
|
80
|
+
}
|
|
68
81
|
const source = sourceFromDom(el);
|
|
69
82
|
if (source !== undefined) {
|
|
70
83
|
step.source =
|
|
@@ -82,13 +95,15 @@ export function compileRecording(name, steps, annotations, createdAt, startPath)
|
|
|
82
95
|
continue;
|
|
83
96
|
}
|
|
84
97
|
if (ann.kind === AnnotationKind.SUCCESS_STATE) {
|
|
85
|
-
if (ann.signal !== undefined)
|
|
98
|
+
if (ann.signal !== undefined) {
|
|
86
99
|
success = { kind: PredicateKind.SIGNAL, name: ann.signal };
|
|
100
|
+
}
|
|
87
101
|
continue;
|
|
88
102
|
}
|
|
89
103
|
const target = out.at(-1);
|
|
90
|
-
if (target === undefined)
|
|
104
|
+
if (target === undefined) {
|
|
91
105
|
continue;
|
|
106
|
+
}
|
|
92
107
|
if (ann.kind === AnnotationKind.ASSERT_SIGNAL && ann.signal !== undefined) {
|
|
93
108
|
target.expect = alsoAssert(target.expect, { kind: PredicateKind.SIGNAL, name: ann.signal });
|
|
94
109
|
}
|
|
@@ -100,25 +115,30 @@ export function compileRecording(name, steps, annotations, createdAt, startPath)
|
|
|
100
115
|
}
|
|
101
116
|
}
|
|
102
117
|
const flow = { version: FLOW_FILE_VERSION, name, createdAt, steps: out };
|
|
103
|
-
if (startPath !== undefined && startPath.length > 0)
|
|
118
|
+
if (startPath !== undefined && startPath.length > 0) {
|
|
104
119
|
flow.startPath = startPath;
|
|
105
|
-
|
|
120
|
+
}
|
|
121
|
+
if (dynamic.length > 0) {
|
|
106
122
|
flow.dynamic = dynamic;
|
|
107
|
-
|
|
123
|
+
}
|
|
124
|
+
if (success !== undefined) {
|
|
108
125
|
flow.success = success;
|
|
126
|
+
}
|
|
109
127
|
return flow;
|
|
110
128
|
}
|
|
111
129
|
function alsoAssert(existing, clause) {
|
|
112
|
-
if (existing === undefined)
|
|
130
|
+
if (existing === undefined) {
|
|
113
131
|
return clause;
|
|
132
|
+
}
|
|
114
133
|
if (existing.kind === PredicateKind.ALL_OF) {
|
|
115
134
|
return { kind: PredicateKind.ALL_OF, predicates: [...existing.predicates, clause] };
|
|
116
135
|
}
|
|
117
136
|
return { kind: PredicateKind.ALL_OF, predicates: [existing, clause] };
|
|
118
137
|
}
|
|
119
138
|
function anchorToElement(anchor) {
|
|
120
|
-
if (anchor.kind === AnchorKind.TESTID)
|
|
139
|
+
if (anchor.kind === AnchorKind.TESTID) {
|
|
121
140
|
return { testid: anchor.value };
|
|
141
|
+
}
|
|
122
142
|
if (anchor.kind === AnchorKind.ROLE) {
|
|
123
143
|
return anchor.name !== undefined
|
|
124
144
|
? { role: anchor.role, name: anchor.name }
|
|
@@ -148,10 +168,12 @@ class Recorder {
|
|
|
148
168
|
return this.#steps.map((s) => ({ ...s }));
|
|
149
169
|
}
|
|
150
170
|
mount() {
|
|
151
|
-
if (this.#root !== undefined)
|
|
171
|
+
if (this.#root !== undefined) {
|
|
152
172
|
return;
|
|
153
|
-
|
|
173
|
+
}
|
|
174
|
+
if ('undefined' === typeof document) {
|
|
154
175
|
return;
|
|
176
|
+
}
|
|
155
177
|
this.#buildToolbar();
|
|
156
178
|
this.#installCapture();
|
|
157
179
|
}
|
|
@@ -179,22 +201,26 @@ class Recorder {
|
|
|
179
201
|
}
|
|
180
202
|
#ignore(ev) {
|
|
181
203
|
const target = ev.target;
|
|
182
|
-
if (!(target instanceof Element))
|
|
204
|
+
if (!(target instanceof Element)) {
|
|
183
205
|
return undefined;
|
|
184
|
-
|
|
206
|
+
}
|
|
207
|
+
if (isReticleOverlay(target)) {
|
|
185
208
|
return undefined;
|
|
209
|
+
}
|
|
186
210
|
return target;
|
|
187
211
|
}
|
|
188
212
|
#onClick(ev) {
|
|
189
213
|
const target = this.#ignore(ev);
|
|
190
|
-
if (target === undefined)
|
|
214
|
+
if (target === undefined) {
|
|
191
215
|
return;
|
|
216
|
+
}
|
|
192
217
|
if (this.#phase === RecorderPhase.ANNOTATING) {
|
|
193
218
|
this.#captureAnnotationTarget(target);
|
|
194
219
|
return;
|
|
195
220
|
}
|
|
196
|
-
if (this.#phase !== RecorderPhase.RECORDING)
|
|
221
|
+
if (this.#phase !== RecorderPhase.RECORDING) {
|
|
197
222
|
return;
|
|
223
|
+
}
|
|
198
224
|
if ((target instanceof HTMLInputElement &&
|
|
199
225
|
('checkbox' === inputRole(target) || 'radio' === inputRole(target))) ||
|
|
200
226
|
target instanceof HTMLSelectElement) {
|
|
@@ -204,19 +230,23 @@ class Recorder {
|
|
|
204
230
|
this.#steps.push(buildStep(target, ActionType.CLICK));
|
|
205
231
|
}
|
|
206
232
|
#onInput(ev) {
|
|
207
|
-
if (this.#phase !== RecorderPhase.RECORDING)
|
|
233
|
+
if (this.#phase !== RecorderPhase.RECORDING) {
|
|
208
234
|
return;
|
|
235
|
+
}
|
|
209
236
|
const target = this.#ignore(ev);
|
|
210
|
-
if (target === undefined || !isTextbox(target))
|
|
237
|
+
if (target === undefined || !isTextbox(target)) {
|
|
211
238
|
return;
|
|
239
|
+
}
|
|
212
240
|
this.#pendingFill = { el: target, value: target.value };
|
|
213
241
|
}
|
|
214
242
|
#onChange(ev) {
|
|
215
|
-
if (this.#phase !== RecorderPhase.RECORDING)
|
|
243
|
+
if (this.#phase !== RecorderPhase.RECORDING) {
|
|
216
244
|
return;
|
|
245
|
+
}
|
|
217
246
|
const target = this.#ignore(ev);
|
|
218
|
-
if (target === undefined)
|
|
247
|
+
if (target === undefined) {
|
|
219
248
|
return;
|
|
249
|
+
}
|
|
220
250
|
if (target instanceof HTMLInputElement && 'checkbox' === inputRole(target)) {
|
|
221
251
|
this.#flushPendingFill();
|
|
222
252
|
this.#steps.push(buildStep(target, target.checked ? ActionType.CHECK : ActionType.UNCHECK));
|
|
@@ -238,31 +268,36 @@ class Recorder {
|
|
|
238
268
|
}
|
|
239
269
|
}
|
|
240
270
|
#onSubmit(ev) {
|
|
241
|
-
if (this.#phase !== RecorderPhase.RECORDING)
|
|
271
|
+
if (this.#phase !== RecorderPhase.RECORDING) {
|
|
242
272
|
return;
|
|
273
|
+
}
|
|
243
274
|
const target = this.#ignore(ev);
|
|
244
|
-
if (target === undefined)
|
|
275
|
+
if (target === undefined) {
|
|
245
276
|
return;
|
|
277
|
+
}
|
|
246
278
|
this.#flushPendingFill();
|
|
247
279
|
this.#steps.push(buildStep(target, ActionType.SUBMIT));
|
|
248
280
|
}
|
|
249
281
|
#flushPendingFill() {
|
|
250
282
|
const pending = this.#pendingFill;
|
|
251
|
-
if (pending === undefined)
|
|
283
|
+
if (pending === undefined) {
|
|
252
284
|
return;
|
|
285
|
+
}
|
|
253
286
|
this.#pendingFill = undefined;
|
|
254
287
|
const value = isSensitiveField(pending.el) ? REDACTED_VALUE : pending.value;
|
|
255
288
|
this.#steps.push(buildStep(pending.el, ActionType.FILL, { value }));
|
|
256
289
|
}
|
|
257
290
|
#captureAnnotationTarget(target) {
|
|
258
291
|
const draft = this.#draft;
|
|
259
|
-
if (draft === undefined)
|
|
292
|
+
if (draft === undefined) {
|
|
260
293
|
return;
|
|
294
|
+
}
|
|
261
295
|
const { anchor } = anchorFor(target);
|
|
262
296
|
const ann = { kind: draft.kind, anchor };
|
|
263
297
|
const signal = this.#selectedSignal();
|
|
264
|
-
if (signal !== undefined)
|
|
298
|
+
if (signal !== undefined) {
|
|
265
299
|
ann.signal = signal;
|
|
300
|
+
}
|
|
266
301
|
this.#annotations.push(ann);
|
|
267
302
|
this.#draft = undefined;
|
|
268
303
|
this.#setPhase(RecorderPhase.RECORDING);
|
|
@@ -270,8 +305,9 @@ class Recorder {
|
|
|
270
305
|
}
|
|
271
306
|
#confirmAnnotationOnPrior() {
|
|
272
307
|
const draft = this.#draft;
|
|
273
|
-
if (draft === undefined)
|
|
308
|
+
if (draft === undefined) {
|
|
274
309
|
return;
|
|
310
|
+
}
|
|
275
311
|
const last = this.#steps.at(-1);
|
|
276
312
|
if (last === undefined) {
|
|
277
313
|
this.#draft = undefined;
|
|
@@ -281,8 +317,9 @@ class Recorder {
|
|
|
281
317
|
}
|
|
282
318
|
const ann = { kind: draft.kind, anchor: last.anchor };
|
|
283
319
|
const signal = this.#selectedSignal();
|
|
284
|
-
if (signal !== undefined)
|
|
320
|
+
if (signal !== undefined) {
|
|
285
321
|
ann.signal = signal;
|
|
322
|
+
}
|
|
286
323
|
this.#annotations.push(ann);
|
|
287
324
|
this.#draft = undefined;
|
|
288
325
|
this.#setPhase(RecorderPhase.RECORDING);
|
|
@@ -312,8 +349,9 @@ class Recorder {
|
|
|
312
349
|
this.#closeMenu();
|
|
313
350
|
}
|
|
314
351
|
#openAnnotateMenu() {
|
|
315
|
-
if (this.#phase !== RecorderPhase.RECORDING)
|
|
352
|
+
if (this.#phase !== RecorderPhase.RECORDING) {
|
|
316
353
|
return;
|
|
354
|
+
}
|
|
317
355
|
this.#setPhase(RecorderPhase.ANNOTATING);
|
|
318
356
|
this.#setStatus(STATUS_ANNOTATE);
|
|
319
357
|
this.#renderMenu();
|
|
@@ -357,12 +395,14 @@ class Recorder {
|
|
|
357
395
|
}
|
|
358
396
|
#renderMenu() {
|
|
359
397
|
const menu = this.#menuEl;
|
|
360
|
-
if (menu === undefined)
|
|
398
|
+
if (menu === undefined) {
|
|
361
399
|
return;
|
|
400
|
+
}
|
|
362
401
|
menu.textContent = '';
|
|
363
402
|
for (const kind of Object.values(AnnotationKind)) {
|
|
364
|
-
if (kind === AnnotationKind.INTENT)
|
|
403
|
+
if (kind === AnnotationKind.INTENT) {
|
|
365
404
|
continue;
|
|
405
|
+
}
|
|
366
406
|
const item = document.createElement('button');
|
|
367
407
|
item.setAttribute('data-reticle-annkind', kind);
|
|
368
408
|
item.textContent = ANNOTATION_LABEL[kind];
|
|
@@ -374,8 +414,9 @@ class Recorder {
|
|
|
374
414
|
#chooseKind(kind) {
|
|
375
415
|
this.#draft = { kind };
|
|
376
416
|
const menu = this.#menuEl;
|
|
377
|
-
if (menu === undefined)
|
|
417
|
+
if (menu === undefined) {
|
|
378
418
|
return;
|
|
419
|
+
}
|
|
379
420
|
if (NEEDS_SIGNAL.has(kind)) {
|
|
380
421
|
const select = document.createElement('select');
|
|
381
422
|
select.setAttribute('data-reticle-signal', '');
|
|
@@ -396,12 +437,14 @@ class Recorder {
|
|
|
396
437
|
menu.appendChild(confirm);
|
|
397
438
|
}
|
|
398
439
|
#closeMenu() {
|
|
399
|
-
if (this.#menuEl !== undefined)
|
|
440
|
+
if (this.#menuEl !== undefined) {
|
|
400
441
|
this.#menuEl.textContent = '';
|
|
442
|
+
}
|
|
401
443
|
}
|
|
402
444
|
#setStatus(text) {
|
|
403
|
-
if (this.#statusEl !== undefined)
|
|
445
|
+
if (this.#statusEl !== undefined) {
|
|
404
446
|
this.#statusEl.textContent = text;
|
|
447
|
+
}
|
|
405
448
|
}
|
|
406
449
|
#nameField() {
|
|
407
450
|
const input = this.#root?.querySelector('[data-reticle-name]');
|