@inizioevoke/veeva-astroclm-core 1.1.1 → 1.1.3
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.
|
@@ -12,8 +12,8 @@ description: >
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
14
|
Start the dev server, click interactive elements, harvest every `console.*` message,
|
|
15
|
-
and produce a report of **unique messages** (grouped by page) written to
|
|
16
|
-
`analytics-audit-report
|
|
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
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -32,34 +32,61 @@ You will need all of those tools to be loaded and callable.
|
|
|
32
32
|
|
|
33
33
|
### Step 1 — Start the dev server and detect its port
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
actually binds to.
|
|
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:
|
|
38
37
|
|
|
39
|
-
Run this command to start the server and tee its output to a temp file:
|
|
40
38
|
```bash
|
|
41
|
-
|
|
39
|
+
ASTRO_LOG="/tmp/astro-dev-$(date +%s).log"
|
|
40
|
+
npm run dev > "$ASTRO_LOG" 2>&1 &
|
|
41
|
+
echo "$ASTRO_LOG"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Save the log path printed by the `echo` — you will need it for polling and cleanup.
|
|
45
|
+
|
|
44
46
|
Then poll the log file until Astro prints its ready URL. **Do not use `grep -P`** —
|
|
45
47
|
it is not supported on Windows. Use this portable polling loop instead:
|
|
48
|
+
|
|
46
49
|
```bash
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
50
54
|
sleep 1
|
|
51
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
|
|
52
61
|
```
|
|
53
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
|
+
|
|
54
66
|
Extract the port from that output and use it as `DEV_PORT` for all subsequent URLs
|
|
55
67
|
(e.g. `http://localhost:${DEV_PORT}/home/`).
|
|
56
68
|
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
Once the port has been extracted, delete the log file:
|
|
70
|
+
```bash
|
|
71
|
+
rm -f "$ASTRO_LOG"
|
|
72
|
+
```
|
|
59
73
|
|
|
60
74
|
---
|
|
61
75
|
|
|
62
|
-
### Step 2 —
|
|
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.
|
|
63
90
|
|
|
64
91
|
#### Page URLs
|
|
65
92
|
|
|
@@ -69,7 +96,11 @@ all `.astro` files:
|
|
|
69
96
|
find src/pages -name "*.astro" | sort
|
|
70
97
|
```
|
|
71
98
|
|
|
72
|
-
|
|
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:
|
|
73
104
|
- Strip the `src/pages/` prefix and `.astro` extension
|
|
74
105
|
- A file named `index.astro` maps to `/`
|
|
75
106
|
- All other files map to `/<name>/` (trailing slash)
|
|
@@ -81,10 +112,35 @@ Example — `src/pages/home.astro` → `http://localhost:${DEV_PORT}/home/`
|
|
|
81
112
|
|
|
82
113
|
#### Click selectors
|
|
83
114
|
|
|
84
|
-
|
|
85
|
-
|
|
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:
|
|
86
141
|
|
|
87
142
|
```
|
|
143
|
+
[data-evotrk]
|
|
88
144
|
a
|
|
89
145
|
button
|
|
90
146
|
[role="button"]
|
|
@@ -96,6 +152,9 @@ input[type="radio"]
|
|
|
96
152
|
[role="radio"]
|
|
97
153
|
```
|
|
98
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
|
+
|
|
99
158
|
Use a single combined CSS selector string joined with commas when querying the page.
|
|
100
159
|
The user may supply additional or replacement selectors in their request — merge them
|
|
101
160
|
into this list rather than replacing it entirely unless the user says otherwise.
|
|
@@ -104,25 +163,49 @@ into this list rather than replacing it entirely unless the user says otherwise.
|
|
|
104
163
|
|
|
105
164
|
### Step 3 — Per-page procedure
|
|
106
165
|
|
|
107
|
-
For **each page URL** discovered in Step 2
|
|
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.
|
|
108
169
|
|
|
109
170
|
1. Navigate to the page URL.
|
|
110
171
|
2. Wait for the page to load (use `browser_wait_for` with a known selector like `h1`).
|
|
111
|
-
3. **
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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):
|
|
115
188
|
```js
|
|
116
|
-
() => {
|
|
117
|
-
document.
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
202
|
});
|
|
121
203
|
}
|
|
122
204
|
```
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
that
|
|
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.
|
|
126
209
|
|
|
127
210
|
4. Take a `browser_snapshot` to get the current accessibility tree. Use the refs from
|
|
128
211
|
this snapshot for all subsequent clicks on this page.
|
|
@@ -130,17 +213,48 @@ For **each page URL** discovered in Step 2:
|
|
|
130
213
|
5. For **each matched element** (visible, not disabled):
|
|
131
214
|
a. Click it using `browser_click`.
|
|
132
215
|
b. Wait 500ms for async effects.
|
|
133
|
-
c.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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.
|
|
144
258
|
|
|
145
259
|
---
|
|
146
260
|
|
|
@@ -180,13 +294,30 @@ Gather every console message captured across all pages into a flat list:
|
|
|
180
294
|
|
|
181
295
|
#### Deduplicate
|
|
182
296
|
|
|
183
|
-
|
|
184
|
-
|
|
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`.
|
|
185
316
|
|
|
186
317
|
#### Tiers
|
|
187
318
|
|
|
188
319
|
Organize into four tiers:
|
|
189
|
-
1. **Tracking Events** — messages matching the `DEBUG: track` pattern
|
|
320
|
+
1. **Tracking Events** — messages matching the `DEBUG: track` pattern (split into Global and Slide sub-sections, see Step 5)
|
|
190
321
|
2. **Errors** — `console.error`
|
|
191
322
|
3. **Warnings** — `console.warn`
|
|
192
323
|
4. **Info / Log** — `console.log`, `console.info`, `console.debug` (excluding tracking events)
|
|
@@ -195,22 +326,44 @@ Organize into four tiers:
|
|
|
195
326
|
|
|
196
327
|
### Step 5 — Write the report
|
|
197
328
|
|
|
198
|
-
|
|
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:
|
|
199
334
|
|
|
200
335
|
```markdown
|
|
201
336
|
# Browser Console Log Audit
|
|
202
337
|
|
|
203
338
|
**Date:** <today's date>
|
|
204
339
|
**Pages audited:** /home/, /about/, /about/moa/
|
|
205
|
-
**Total unique messages:** <N>
|
|
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
|
|
206
343
|
|
|
207
344
|
---
|
|
208
345
|
|
|
209
346
|
## Tracking Events (<count>)
|
|
210
347
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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` |
|
|
214
367
|
|
|
215
368
|
## Errors (<count>)
|
|
216
369
|
|
|
@@ -235,25 +388,28 @@ Write a Markdown file to `analytics-audit-report.md` in the project root with th
|
|
|
235
388
|
*Generated by the analytics-auditor skill.*
|
|
236
389
|
```
|
|
237
390
|
|
|
238
|
-
|
|
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.
|
|
239
395
|
|
|
240
|
-
After writing the file, tell the user
|
|
241
|
-
summary (e.g. "Found 8
|
|
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").
|
|
242
400
|
|
|
243
401
|
---
|
|
244
402
|
|
|
245
|
-
### Step 6 —
|
|
403
|
+
### Step 6 — Shut down and clean up
|
|
246
404
|
|
|
247
|
-
Call `browser_close`
|
|
405
|
+
Call `browser_close` to end the Playwright session.
|
|
248
406
|
|
|
249
|
-
Then
|
|
250
|
-
console logs:
|
|
407
|
+
Then stop the dev server and delete the Playwright working directory:
|
|
251
408
|
```bash
|
|
409
|
+
kill $(lsof -ti tcp:$DEV_PORT) 2>/dev/null || true
|
|
252
410
|
rm -rf .playwright-mcp
|
|
253
411
|
```
|
|
254
412
|
|
|
255
|
-
Do **not** stop the dev server — the user likely wants to keep it running.
|
|
256
|
-
|
|
257
413
|
---
|
|
258
414
|
|
|
259
415
|
## Notes
|
|
@@ -263,11 +419,20 @@ Do **not** stop the dev server — the user likely wants to keep it running.
|
|
|
263
419
|
- **`grep -P` is not supported on Windows** — use `grep -o` with basic regex patterns.
|
|
264
420
|
- **Snapshot refs go stale** after DOM mutations (modal open/close, tab switches). Always
|
|
265
421
|
re-snapshot after any click that visibly changes the page structure.
|
|
266
|
-
- **`browser_console_messages` fails if a dialog is showing** —
|
|
267
|
-
`
|
|
268
|
-
-
|
|
269
|
-
|
|
270
|
-
|
|
422
|
+
- **`browser_console_messages` fails if a native dialog is showing** — dismiss with
|
|
423
|
+
`browser_handle_dialog` before calling it.
|
|
424
|
+
- **`browser_handle_dialog` only works for native browser dialogs** (`alert`, `confirm`,
|
|
425
|
+
`prompt`) — it has no effect on custom DOM modals. Dismiss those by clicking their
|
|
426
|
+
close button.
|
|
427
|
+
- **Navigation prevention uses DOM attributes, not event listeners.** IVA's click
|
|
428
|
+
handler calls `e.preventDefault()` unconditionally and then checks
|
|
429
|
+
`<navigateDisabledAttr>="true"` before calling `navigate()`. Setting that attribute
|
|
430
|
+
on IVA-bound links is therefore the most reliable way to suppress navigation without
|
|
431
|
+
interfering with tracking. All other links have their `href` removed instead.
|
|
432
|
+
- **Re-run the link-disabling evaluate after DOM mutations** (modal open, dynamic
|
|
433
|
+
tab content, etc.) — newly inserted `<a>` elements will not have the attribute yet.
|
|
434
|
+
- **`browser_console_messages` is called after every click** so that tracking events
|
|
435
|
+
are captured incrementally. The final per-page call catches any late async messages.
|
|
271
436
|
- If the user provides their own list of selectors or pages, replace the defaults in
|
|
272
437
|
Step 2 before proceeding.
|
|
273
438
|
- If a selector does not match any element on a page, log a warning in the report's
|