petrkindlmann/qa-skillsMIT9 files

Analytics Tracking Testing

>-

Specification
Skill ID
petrkindlmann/qa-skills/analytics-tracking-testing
Publisher
petrkindlmann
Repository
qa-skills
Installs
0
Files
9
License
MIT
Synced
Sep 23, 2026
How to use it

Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.

petrkindlmann/qa-skills/analytics-tracking-testingInstalls these files
  • SKILL.md
  • references/capture-fixture.md
  • references/ci-gating.md
  • references/consent-mode.md
  • references/datalayer-capture.md
  • references/ga4-interception.md
  • references/news-media.md
  • references/pixels-and-dedup.md
  • references/tracking-plan.md

What this skill tells the agent

<objective> Tracking that "looks fine" in GA4 DebugView still drops events silently after a refactor, sends the wrong currency, or double-counts a purchase. Reading window.dataLayer or asserting the button is toBeVisible() proves nothing — the beacon may never leave the browser. This skill makes you intercept the real network beacon (google-analytics.com/g/collect, facebook.com/tr, analytics.tiktok.com, px.ads.linkedin.com), parse the event name and parameters out of the request, and assert them against a tracking plan that is a typed contract — then gate that contract in CI so a dropped event fails the build. </objective>

Quick Route

SituationGo to
Assert one GA4 event fired with correct paramsIntercepting GA4 beacons
Treat the tracking plan as a validated contractThe Tracking Plan Is the Contract
Verify dataLayer.push shape (not the beacon)Asserting dataLayer.push
Pixel + server CAPI deduplication (event_id)Pixels and Server-Side Deduplication
Events must/mustn't fire by consent stateConsent Mode v2 Gating
Capture every pixel in one reusable fixturereferences/capture-fixture.md
News-media: article-view, scroll-depth, paywallNews-Media Events
Fail the build on a tracking regressionRegression-Gating in CI
Hundreds of events, many domains, small teamBuy vs Build

Discovery Questions

First: check .agents/qa-project-context.md in the project root and skip anything it already answers (stack, tag manager, consent platform, target environments).

  • Which tracking destinations are live? GA4 (/g/collect), Meta Pixel (facebook.com/tr), TikTok (analytics.tiktok.com), LinkedIn (px.ads.linkedin.com), ad-tech tags — each has a different endpoint grammar, so the capture helper must know them all.
  • GTM or hardcoded gtag? GTM means the truth flows through window.dataLayer.push first, then GTM fires the beacon. You may assert at the push layer (input contract) AND the beacon (output contract); they are different tests.
  • Is there a tracking plan? If not, build one first — it is the contract everything else validates against. No plan means no objective pass/fail.
  • Consent platform and default consent state? Consent Mode v2 changes which beacons are even allowed to fire before consent. You need the CMP's accept/reject selectors to drive the test.
  • Client + server (CAPI) dedup in play? If purchases fire both client Pixel and server Conversions API, the event_id must match or you double-count. This is the correctness property, not "did fbq run."
  • News-media surface? Article pages add article-view, scroll-depth thresholds (25/50/75/100), and paywall-meter events that generic e-commerce plans miss.

Core Principles

  1. Intercept the beacon, never trust the DOM or `dataLayer` alone. A button click that updates the DOM, or a dataLayer.push that GTM silently drops, leaves no GA4 hit. The only proof an event was sent is the outbound request to the collect endpoint. Assert on the network beacon's URL params; reading window.dataLayer via page.evaluate only proves the push happened, not that anything left the browser.
  1. The tracking plan is a typed contract, not a comment. Every expected event lives in an external schema (JSON/YAML/TS interface) with required params and their types. Tests validate captured events against it and report missing params and type violations. Hardcoding one expected value inline asserts nothing about the other twenty params and rots on the first plan change.
  1. De-duplication is the real correctness property for purchases. Checking that fbevents.js loaded or counting that fbq('track','Purchase') ran misses double-counting entirely. The property that matters: the browser Pixel and the server CAPI send the same event_id/eventID so Meta collapses them into one conversion.
  1. Consent state is an input dimension, not a footnote. The same page produces different beacons before vs after consent. Test both: before consent → no beacon (or only a cookieless consent ping); after accept → full beacon. And the default must be denied for all four Consent Mode v2 signals — a granted default is a compliance bug AND makes the gating test meaningless.
  1. Drive real user actions and wait on requests, never the clock. Scroll depth fires from actual scrolling (mouse.wheel, scrollIntoView, evaluate(scrollTo)), and you wait for the beacon with waitForRequest, not waitForTimeout. A fixed sleep is flaky and hides the very timing bug you should catch.
  1. A tracking test that can't fail the build is theater. "Check it in GA4 DebugView" and "monitor production" never block a regression. The contract must run in CI and exit non-zero when an event drops or a required param goes missing.

Intercepting GA4 Beacons

GA4 (gtag.js / GTM) sends every event as an HTTP request to https://www.google-analytics.com/g/collect (region variants like region1.google-analytics.com/g/collect also occur). The event identity lives in the URL query string — you do not need the response.

GA4 /g/collect URL grammar you assert on:

ParamMeaningExample
v=2Measurement Protocol version (always 2 for GA4)v=2
tid=G-XXXXXXXMeasurement IDtid=G-ABC123
en=Event nameen=add_to_cart
ep.<name>=Event parameter, string typeep.currency=USD
epn.<name>=Event parameter, number typeepn.value=49.99
gcs= / gcd=Consent state (see Consent Mode v2 section)gcs=G111

The string/number split is load-bearing: GA4 types params automatically, so price arrives as epn.value (number) and currency as ep.currency (string). Asserting ep.value when it is really epn.value silently fails.

Intercept with page.waitForRequest (single expected event), page.on('request', ...) (collect many), or page.route (inspect then continue — never abort). Parse params from new URL(request.url()).searchParams. Then expect(...).toBe(...) / toEqual / toContain on the parsed values.

Minimal pattern (full version with helper in references/ga4-interception.md):

test('add_to_cart fires with correct params', async ({ page }) => {
  await page.goto('/product/42');
  const [request] = await Promise.all([
    page.waitForRequest(r => r.url().includes('/g/collect') && r.url().includes('en=add_to_cart')),
    page.getByRole('button', { name: 'Add to cart' }).click(),
  ]);
  const params = new URL(request.url()).searchParams;
  expect(params.get('v')).toBe('2');
  expect(params.get('tid')).toContain('G-');
  expect(params.get('en')).toBe('add_to_cart');
  expect(params.get('ep.currency')).toBe('USD');
  expect(Number(params.get('epn.value'))).toBe(49.99);
});

Never substitute page.evaluate(() => window.dataLayer) as the only assertion, toBeVisible() on the button, or waitForTimeout() to "let the beacon send." See references/ga4-interception.md for batched-event parsing (GA4 can pack multiple events into one POST body) and region-endpoint handling.


The Tracking Plan Is the Contract

A tracking plan is the source of truth: for every event, its name, required params, and each param's type. Keep it as a versioned file (tracking-plan.json / .yaml, or a TS interface/Zod schema) that both the app team and the tests import. Tests read the captured event and validate it against the plan — they do not hardcode expected values inline.

Validation produces a structured result, not a pass/fail boolean: list every missing required param and every type mismatch/violation. Example plan entry and validator:

Use this skill in a project

Open any RiverX project, search the Skills panel for petrkindlmann/qa-skills/analytics-tracking-testing, and install it in one click.

Use this skill