@inizioevoke/veeva-astroclm-core 1.1.0 → 1.1.2
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.
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: analytics-auditor
|
|
3
|
+
description: >
|
|
4
|
+
Starts the Astro dev server, visits every page, clicks configurable CSS selectors,
|
|
5
|
+
captures all browser console messages, and writes a deduplication report of unique
|
|
6
|
+
log messages grouped by page. Use this skill whenever the user asks to audit,
|
|
7
|
+
record, or report on analytics, metrics, or events.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Analytics Auditor Skill
|
|
11
|
+
|
|
12
|
+
## Purpose
|
|
13
|
+
|
|
14
|
+
Start the dev server, click interactive elements, harvest every `console.*` message,
|
|
15
|
+
and produce a timestamped report of **unique messages** (grouped by page) written to
|
|
16
|
+
`analytics-audit-report-<timestamp>.md` in the project root.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
### Step 0 — Load the Playwright MCP tool schemas
|
|
23
|
+
|
|
24
|
+
Before doing anything else, call:
|
|
25
|
+
```
|
|
26
|
+
ToolSearch({ query: "select:mcp__plugin_playwright_playwright__browser_navigate,mcp__plugin_playwright_playwright__browser_click,mcp__plugin_playwright_playwright__browser_console_messages,mcp__plugin_playwright_playwright__browser_snapshot,mcp__plugin_playwright_playwright__browser_wait_for,mcp__plugin_playwright_playwright__browser_evaluate,mcp__plugin_playwright_playwright__browser_handle_dialog,mcp__plugin_playwright_playwright__browser_close" })
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You will need all of those tools to be loaded and callable.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
### Step 1 — Start the dev server and detect its port
|
|
34
|
+
|
|
35
|
+
Always start a fresh dev server. Use a timestamped log filename so the poll can never
|
|
36
|
+
accidentally read the port from a previously running server's log:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ASTRO_LOG="/tmp/astro-dev-$(date +%s).log"
|
|
40
|
+
npm run dev > "$ASTRO_LOG" 2>&1 &
|
|
41
|
+
echo "$ASTRO_LOG"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Save the log path printed by the `echo` — you will need it for polling and cleanup.
|
|
45
|
+
|
|
46
|
+
Then poll the log file until Astro prints its ready URL. **Do not use `grep -P`** —
|
|
47
|
+
it is not supported on Windows. Use this portable polling loop instead:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
port=""
|
|
51
|
+
for i in $(seq 1 30); do
|
|
52
|
+
port=$(grep -o 'localhost:[0-9]*' "$ASTRO_LOG" | head -1 | grep -o '[0-9]*$')
|
|
53
|
+
if [ -n "$port" ]; then echo "$port"; break; fi
|
|
54
|
+
sleep 1
|
|
55
|
+
done
|
|
56
|
+
if [ -z "$port" ]; then
|
|
57
|
+
echo "ERROR: dev server did not produce a port within 30 seconds" >&2
|
|
58
|
+
cat "$ASTRO_LOG" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If the loop exits without a port, stop and report the error to the user — do not
|
|
64
|
+
continue with subsequent steps.
|
|
65
|
+
|
|
66
|
+
Extract the port from that output and use it as `DEV_PORT` for all subsequent URLs
|
|
67
|
+
(e.g. `http://localhost:${DEV_PORT}/home/`).
|
|
68
|
+
|
|
69
|
+
Once the port has been extracted, delete the log file:
|
|
70
|
+
```bash
|
|
71
|
+
rm -f "$ASTRO_LOG"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### Step 2 — Capture timestamp, discover pages, and define click selectors
|
|
77
|
+
|
|
78
|
+
#### Report timestamp
|
|
79
|
+
|
|
80
|
+
Before starting any browser work, generate the timestamp that will be used in the
|
|
81
|
+
report filename. Use `browser_evaluate` once the browser is open, or compute it via
|
|
82
|
+
any available JS execution context:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
new Date().toISOString().replace(/[^\d]/g, '').substring(0, 14)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Store the result as **`TIMESTAMP`** (e.g. `20260616143022`). It is used once in Step 5
|
|
89
|
+
to name the output file.
|
|
90
|
+
|
|
91
|
+
#### Page URLs
|
|
92
|
+
|
|
93
|
+
Do **not** hardcode the page list. Instead, read the `src/pages` directory to discover
|
|
94
|
+
all `.astro` files:
|
|
95
|
+
```bash
|
|
96
|
+
find src/pages -name "*.astro" | sort
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Skip any file whose name contains `[` or `]`** — these are dynamic route templates
|
|
100
|
+
that require parameter values and cannot be visited directly. Record each skipped file
|
|
101
|
+
in a list for the report's header section.
|
|
102
|
+
|
|
103
|
+
Convert each remaining file path to a URL route using these rules:
|
|
104
|
+
- Strip the `src/pages/` prefix and `.astro` extension
|
|
105
|
+
- A file named `index.astro` maps to `/`
|
|
106
|
+
- All other files map to `/<name>/` (trailing slash)
|
|
107
|
+
- Nested files (e.g. `src/pages/about/moa.astro`) map to `/about/moa/`
|
|
108
|
+
|
|
109
|
+
Prepend `http://localhost:${DEV_PORT}` from Step 1 to form the full URL.
|
|
110
|
+
|
|
111
|
+
Example — `src/pages/home.astro` → `http://localhost:${DEV_PORT}/home/`
|
|
112
|
+
|
|
113
|
+
#### Click selectors
|
|
114
|
+
|
|
115
|
+
**Before finalizing the selector list, read `src/lib/client/main.ts`** (and any other
|
|
116
|
+
files it imports from `src/lib/client/`) and check whether `IVA.configure()` or
|
|
117
|
+
`IVA.init()` is called with a config object that contains any of these properties:
|
|
118
|
+
|
|
119
|
+
- **`selectors.clickable`** — if present, it is a CSS selector string that replaces
|
|
120
|
+
the non-`[data-evotrk]` selectors in the default list. Replace those defaults with
|
|
121
|
+
this value, but **keep `[data-evotrk]`** (or the `attrs.data`-derived selector, see
|
|
122
|
+
below) in the final list.
|
|
123
|
+
|
|
124
|
+
- **`attrs.data`** — if present, it is the attribute name used instead of
|
|
125
|
+
`data-evotrk` to mark tracked elements. It may or may not include surrounding
|
|
126
|
+
brackets. Normalize it: if it already starts with `[`, use it as-is; otherwise wrap
|
|
127
|
+
it as `[<value>]`. Replace `[data-evotrk]` in the selector list with this normalized
|
|
128
|
+
value.
|
|
129
|
+
|
|
130
|
+
- **`attrs.navigateDisabled`** — if present, it is the attribute name IVA checks to
|
|
131
|
+
decide whether to skip its `navigate()` call. Store this value (without brackets)
|
|
132
|
+
as **`NAVIGATE_DISABLED_ATTR`** for use in Step 3. Default if absent:
|
|
133
|
+
`data-navigate-disabled`.
|
|
134
|
+
|
|
135
|
+
- **`attrs.gotoslide`** — if present, it is the attribute name IVA uses to identify
|
|
136
|
+
which `<a>` elements it has bound its click handler to. Store this value (without
|
|
137
|
+
brackets) as **`GOTO_SLIDE_ATTR`** for use in Step 3. Default if absent:
|
|
138
|
+
`data-gotoslide`.
|
|
139
|
+
|
|
140
|
+
If none of these properties are found, use this default list unchanged:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
[data-evotrk]
|
|
144
|
+
a
|
|
145
|
+
button
|
|
146
|
+
[role="button"]
|
|
147
|
+
[role="link"]
|
|
148
|
+
[role="tab"]
|
|
149
|
+
input[type="checkbox"]
|
|
150
|
+
input[type="radio"]
|
|
151
|
+
[role="checkbox"]
|
|
152
|
+
[role="radio"]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
On every page, query for all elements matching the final selector list and click each
|
|
156
|
+
one that is visible and not disabled.
|
|
157
|
+
|
|
158
|
+
Use a single combined CSS selector string joined with commas when querying the page.
|
|
159
|
+
The user may supply additional or replacement selectors in their request — merge them
|
|
160
|
+
into this list rather than replacing it entirely unless the user says otherwise.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Step 3 — Per-page procedure
|
|
165
|
+
|
|
166
|
+
For **each page URL** discovered in Step 2, also initialize a **`pageMessages`** list
|
|
167
|
+
(reset to empty for each new page) that accumulates all console messages seen on the
|
|
168
|
+
current page.
|
|
169
|
+
|
|
170
|
+
1. Navigate to the page URL.
|
|
171
|
+
2. Wait for the page to load (use `browser_wait_for` with a known selector like `h1`).
|
|
172
|
+
3. **Disable navigation on all links** using `browser_evaluate`. Pass
|
|
173
|
+
`NAVIGATE_DISABLED_ATTR` as the attribute name. This approach is reliable because
|
|
174
|
+
it works at the DOM level before any click handler fires:
|
|
175
|
+
|
|
176
|
+
- **IVA-bound links** — if the `<a>` element has the `GOTO_SLIDE_ATTR` attribute,
|
|
177
|
+
IVA has registered its click handler on it. Set `<NAVIGATE_DISABLED_ATTR>="true"`
|
|
178
|
+
on the element so IVA skips its `navigate()` call. IVA already calls
|
|
179
|
+
`e.preventDefault()` unconditionally, so browser default navigation is also
|
|
180
|
+
suppressed.
|
|
181
|
+
|
|
182
|
+
- **All other links** — if the `<a>` element does not have `GOTO_SLIDE_ATTR`
|
|
183
|
+
(external links, PDF links, plain anchors, etc.), remove the `href` attribute
|
|
184
|
+
entirely (store the original value in `data-original-href`). Without an `href`
|
|
185
|
+
the browser will not navigate; any IVA tracking handler still fires normally.
|
|
186
|
+
|
|
187
|
+
Use this script (substitute the actual attribute names for the placeholders):
|
|
188
|
+
```js
|
|
189
|
+
(gotoSlideAttr, navigateDisabledAttr) => {
|
|
190
|
+
document.querySelectorAll('a').forEach(a => {
|
|
191
|
+
if (a.hasAttribute(gotoSlideAttr)) {
|
|
192
|
+
// IVA-bound link — use IVA's own escape hatch
|
|
193
|
+
a.setAttribute(navigateDisabledAttr, 'true');
|
|
194
|
+
} else {
|
|
195
|
+
// Not IVA-bound — strip href to prevent browser navigation
|
|
196
|
+
const href = a.getAttribute('href');
|
|
197
|
+
if (href) {
|
|
198
|
+
a.dataset.originalHref = href;
|
|
199
|
+
a.removeAttribute('href');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Call this evaluate **after** the page loads (Step 3.2) and **again after any
|
|
207
|
+
DOM mutation** (modal open, tab switch, dynamic content) that may have added new
|
|
208
|
+
`<a>` elements.
|
|
209
|
+
|
|
210
|
+
4. Take a `browser_snapshot` to get the current accessibility tree. Use the refs from
|
|
211
|
+
this snapshot for all subsequent clicks on this page.
|
|
212
|
+
|
|
213
|
+
5. For **each matched element** (visible, not disabled):
|
|
214
|
+
a. Click it using `browser_click`.
|
|
215
|
+
b. Wait 500ms for async effects.
|
|
216
|
+
c. **Call `browser_console_messages` with `level: "debug"`** to get all messages
|
|
217
|
+
accumulated on this page so far. Identify the **newly appeared messages** by
|
|
218
|
+
comparing the full returned list against `pageMessages` (new = any message whose
|
|
219
|
+
de-timestamped text is not already present in `pageMessages`). Add all new
|
|
220
|
+
messages to `pageMessages`.
|
|
221
|
+
d. Take a `browser_snapshot`. If the tree is structurally unchanged from the
|
|
222
|
+
previous snapshot, the existing refs remain valid — no action needed. If the
|
|
223
|
+
tree changed, use the new snapshot's refs going forward.
|
|
224
|
+
e. If the snapshot shows a modal is now open (`Modal state` present in the tree):
|
|
225
|
+
i. Use `browser_evaluate` to collect identifying information for all clickable
|
|
226
|
+
elements inside the modal:
|
|
227
|
+
```js
|
|
228
|
+
() => {
|
|
229
|
+
const modal = document.querySelector('[role="dialog"], [aria-modal="true"]');
|
|
230
|
+
if (!modal) return [];
|
|
231
|
+
const sel = 'a,button,[role="button"],[role="link"],[role="tab"],input[type="checkbox"],input[type="radio"],[role="checkbox"],[role="radio"]';
|
|
232
|
+
return Array.from(modal.querySelectorAll(sel))
|
|
233
|
+
.filter(el => !el.disabled && el.offsetParent !== null)
|
|
234
|
+
.map(el => el.getAttribute('aria-label') || el.textContent.trim() || el.tagName);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
Cross-reference the returned labels/text against the current snapshot to
|
|
238
|
+
identify the corresponding accessibility-tree refs for clicking.
|
|
239
|
+
ii. For each visible, non-disabled modal element: click it using its ref, wait
|
|
240
|
+
500ms, then apply the same **step 5c** above (call `browser_console_messages`
|
|
241
|
+
and diff). Re-snapshot if the modal content changes.
|
|
242
|
+
iii. After all modal elements have been clicked, dismiss the modal by clicking
|
|
243
|
+
its close button — look for a button with `aria-label="Close"` or visible
|
|
244
|
+
text "×", "Close", or "Dismiss" in the snapshot. Only use
|
|
245
|
+
`browser_handle_dialog` if a **native** browser dialog (`alert`/`confirm`/
|
|
246
|
+
`prompt`) is present — it has no effect on custom DOM modals.
|
|
247
|
+
iv. Take a fresh `browser_snapshot` after the modal closes before continuing
|
|
248
|
+
to the next page-level element — refs from before the modal opened are stale.
|
|
249
|
+
f. If no modal appeared but the DOM changed significantly, use the new snapshot's
|
|
250
|
+
refs before proceeding.
|
|
251
|
+
|
|
252
|
+
6. After all clicks on the page, call `browser_console_messages` **once more** with
|
|
253
|
+
`level: "debug"` to catch any async messages that fired after the last click. Diff
|
|
254
|
+
against `pageMessages` and append any remaining new messages.
|
|
255
|
+
|
|
256
|
+
7. Store all messages in `pageMessages` with the page route (e.g. `/home/`) as their
|
|
257
|
+
source.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
### Step 4 — Parse, deduplicate, and structure the data
|
|
262
|
+
|
|
263
|
+
#### Identify tracking events
|
|
264
|
+
|
|
265
|
+
When an element is clicked the app emits a tracking log in this format:
|
|
266
|
+
```
|
|
267
|
+
[HH:MM:SS.mmm] DEBUG: track {slide: 'home', type: 'button', name: 'button_evo_flip_card_trigger'}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
For every console message, check whether its text matches this pattern:
|
|
271
|
+
```
|
|
272
|
+
DEBUG: track {…}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
If it matches, parse out the object payload `{ slide, type, name }` — these are the
|
|
276
|
+
primary data points of interest. Store them as structured tracking events separate from
|
|
277
|
+
general log messages.
|
|
278
|
+
|
|
279
|
+
#### Collect all messages
|
|
280
|
+
|
|
281
|
+
Gather every console message captured across all pages into a flat list:
|
|
282
|
+
```
|
|
283
|
+
{
|
|
284
|
+
page: string, // route, e.g. "/home/"
|
|
285
|
+
level: "log"|"warn"|"error"|"info"|"debug",
|
|
286
|
+
text: string, // full raw message text
|
|
287
|
+
tracking: { // only present when the message is a tracking event
|
|
288
|
+
slide: string,
|
|
289
|
+
type: string,
|
|
290
|
+
name: string
|
|
291
|
+
} | null
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### Deduplicate
|
|
296
|
+
|
|
297
|
+
Before comparing, **strip the leading timestamp prefix** from each message's `text`
|
|
298
|
+
(the `[HH:MM:SS.mmm]` portion) — timestamps differ per run and would prevent identical
|
|
299
|
+
messages from being recognized as duplicates. Compare only the de-timestamped text.
|
|
300
|
+
|
|
301
|
+
Two messages are duplicates if their de-timestamped texts are equal. When the same
|
|
302
|
+
message appears on multiple pages, record all pages it appeared on.
|
|
303
|
+
|
|
304
|
+
#### Classify global vs. slide-specific tracking events
|
|
305
|
+
|
|
306
|
+
After collecting all tracking events, split them into two groups. An event is **Global**
|
|
307
|
+
if **either** condition is true:
|
|
308
|
+
|
|
309
|
+
1. Its `type` starts with `header-`, `footer-`, or `nav-`; **or**
|
|
310
|
+
2. The `(type, name)` pair appears on **≥ 80%** of the total audited pages.
|
|
311
|
+
|
|
312
|
+
Everything else is a **Slide Event**.
|
|
313
|
+
|
|
314
|
+
For global events, drop the `slide` dimension entirely — only `type` and `name` are needed.
|
|
315
|
+
For slide events, retain `slide`, `type`, and `name`.
|
|
316
|
+
|
|
317
|
+
#### Tiers
|
|
318
|
+
|
|
319
|
+
Organize into four tiers:
|
|
320
|
+
1. **Tracking Events** — messages matching the `DEBUG: track` pattern (split into Global and Slide sub-sections, see Step 5)
|
|
321
|
+
2. **Errors** — `console.error`
|
|
322
|
+
3. **Warnings** — `console.warn`
|
|
323
|
+
4. **Info / Log** — `console.log`, `console.info`, `console.debug` (excluding tracking events)
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
### Step 5 — Write the report
|
|
328
|
+
|
|
329
|
+
Before inserting any message text into a Markdown table cell, **escape every `|`
|
|
330
|
+
character as `\|`** to prevent table corruption.
|
|
331
|
+
|
|
332
|
+
Write a Markdown file to `analytics-audit-report-<TIMESTAMP>.md` in the project root with this
|
|
333
|
+
structure:
|
|
334
|
+
|
|
335
|
+
```markdown
|
|
336
|
+
# Browser Console Log Audit
|
|
337
|
+
|
|
338
|
+
**Date:** <today's date>
|
|
339
|
+
**Pages audited:** /home/, /about/, /about/moa/
|
|
340
|
+
**Total unique messages:** <N> tracking events (<G> global, <S> slide-specific), <E> errors, <W> warnings, <I> info/log
|
|
341
|
+
|
|
342
|
+
> **Skipped (dynamic routes):** src/pages/[slug].astro, src/pages/[...path].astro
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Tracking Events (<count>)
|
|
347
|
+
|
|
348
|
+
> Events are grouped by slide. Each slide's `type` and `name` are set by the IVA tracking library at click time; the `slide` value is defined by each page's own configuration.
|
|
349
|
+
|
|
350
|
+
### Global Events (<G> — type prefixed `header-`/`footer-`/`nav-`, or present on ≥80% of pages)
|
|
351
|
+
|
|
352
|
+
These `(type, name)` pairs represent shared UI (navigation, ISI controls, header/footer
|
|
353
|
+
links, etc.). The slide name is omitted since it is not meaningful here.
|
|
354
|
+
|
|
355
|
+
| Type | Name |
|
|
356
|
+
|------|------|
|
|
357
|
+
| `nav-button` | `nav-button_about_pbc` |
|
|
358
|
+
|
|
359
|
+
### Slide Events (<S> — page-specific)
|
|
360
|
+
|
|
361
|
+
These events fire on fewer than 80% of pages and are meaningful to a specific slide or
|
|
362
|
+
feature.
|
|
363
|
+
|
|
364
|
+
| Slide | Type | Name |
|
|
365
|
+
|-------|------|------|
|
|
366
|
+
| `home` | `button` | `button_important_safety_information` |
|
|
367
|
+
|
|
368
|
+
## Errors (<count>)
|
|
369
|
+
|
|
370
|
+
| Message | Pages |
|
|
371
|
+
|---------|-------|
|
|
372
|
+
| `<message text>` | /home/, /about/ |
|
|
373
|
+
|
|
374
|
+
## Warnings (<count>)
|
|
375
|
+
|
|
376
|
+
| Message | Pages |
|
|
377
|
+
|---------|-------|
|
|
378
|
+
| `<message text>` | /home/ |
|
|
379
|
+
|
|
380
|
+
## Info / Log (<count>)
|
|
381
|
+
|
|
382
|
+
| Message | Pages |
|
|
383
|
+
|---------|-------|
|
|
384
|
+
| `<message text>` | /about/moa/ |
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
*Generated by the analytics-auditor skill.*
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Omit the "Skipped" blockquote if no dynamic routes were found. If a tier has zero
|
|
392
|
+
messages, write `_None_` instead of a table. If there are no slide-specific events,
|
|
393
|
+
write `_None_` under that sub-section. Omit the Global Events sub-section header note
|
|
394
|
+
if there are no global events.
|
|
395
|
+
|
|
396
|
+
After writing the file, tell the user the full filename (e.g.
|
|
397
|
+
`analytics-audit-report-20260616143022.md`) and give a brief summary (e.g. "Found 8
|
|
398
|
+
tracking events (5 global, 3 slide-specific), 2 errors, 1 warning, 5 info messages
|
|
399
|
+
across 3 pages").
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### Step 6 — Close the browser and clean up
|
|
404
|
+
|
|
405
|
+
Call `browser_close` when done so the Playwright session is cleaned up.
|
|
406
|
+
|
|
407
|
+
Then delete the `.playwright-mcp` directory that Playwright creates for snapshots and
|
|
408
|
+
console logs:
|
|
409
|
+
```bash
|
|
410
|
+
rm -rf .playwright-mcp
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Do **not** stop the dev server — the user likely wants to keep it running.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Notes
|
|
418
|
+
|
|
419
|
+
- The Astro dev server defaults to port **4321** but increments on collision — always
|
|
420
|
+
read the actual port from the server output as described in Step 1.
|
|
421
|
+
- **`grep -P` is not supported on Windows** — use `grep -o` with basic regex patterns.
|
|
422
|
+
- **Snapshot refs go stale** after DOM mutations (modal open/close, tab switches). Always
|
|
423
|
+
re-snapshot after any click that visibly changes the page structure.
|
|
424
|
+
- **`browser_console_messages` fails if a native dialog is showing** — dismiss with
|
|
425
|
+
`browser_handle_dialog` before calling it.
|
|
426
|
+
- **`browser_handle_dialog` only works for native browser dialogs** (`alert`, `confirm`,
|
|
427
|
+
`prompt`) — it has no effect on custom DOM modals. Dismiss those by clicking their
|
|
428
|
+
close button.
|
|
429
|
+
- **Navigation prevention uses DOM attributes, not event listeners.** IVA's click
|
|
430
|
+
handler calls `e.preventDefault()` unconditionally and then checks
|
|
431
|
+
`<navigateDisabledAttr>="true"` before calling `navigate()`. Setting that attribute
|
|
432
|
+
on IVA-bound links is therefore the most reliable way to suppress navigation without
|
|
433
|
+
interfering with tracking. All other links have their `href` removed instead.
|
|
434
|
+
- **Re-run the link-disabling evaluate after DOM mutations** (modal open, dynamic
|
|
435
|
+
tab content, etc.) — newly inserted `<a>` elements will not have the attribute yet.
|
|
436
|
+
- **`browser_console_messages` is called after every click** so that tracking events
|
|
437
|
+
are captured incrementally. The final per-page call catches any late async messages.
|
|
438
|
+
- If the user provides their own list of selectors or pages, replace the defaults in
|
|
439
|
+
Step 2 before proceeding.
|
|
440
|
+
- If a selector does not match any element on a page, log a warning in the report's
|
|
441
|
+
header section rather than failing.
|