analytics-tracking-testing

Validate that analytics and marketing tracking fire CORRECTLY: GA4/GTM dataLayer events, Meta/TikTok/LinkedIn pixels, and ad-tech tags. Covers building a tracking plan as the contract, intercepting collect-endpoint beacons and dataLayer.push in Playwright, asserting event name + params + values + ti

By petrkindlmann · 581 installs

npx skills add petrkindlmann/qa-skills --skill analytics-tracking-testing

Source repository · Upstream listing

<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 Situation Go to Assert one GA4 event fired with correct params [Intercepting GA4 beacons]( intercepting ga4 beacons) Treat the tracking plan as a validated contract [The Tracking Plan Is the Contract]( the tracking plan is the contract) Verify dataLayer.push shape (not the beacon) [Asserting dataLayer.push]( asserting datalayerpush) Pixel + server CAPI deduplication (event id) [Pixels and Server Side Deduplication]( pixels and server side deduplication) Events must/mustn't fire by consent state [Consent Mode v2 Gating]( consent mode v2 gating) Capture every pixel in one reusable fixture references/capture fixture.md News media: article view, scroll depth, paywall [News Media Events]( news media events) Fail the build on a tracking regression [Regression Gating in CI]( regression gating in ci) Hundreds of events, many domains, small team [Buy vs Build]( buy 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). An unfilled template answers nothing; treat it as absent and take the answers from the application's contract or configuration. 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. 2. 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. 3. 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. 4. Consent state is an input dimension. Test the approved regional policy in basic or advanced mode. Observe the application's actual four signal default and updates; do not supply a default from the test. Basic mode blocks tags before consent, while advanced mode may emit permitted measurements without cookies. Assert the planned post accept events and relevant storage independently. 5. 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. 6. 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 web tags use /g/collect requests, sometimes with several events in a POST body and common fields in the query. The following parser handles an observed web tag format, not the public JSON Measurement Protocol. Retain captures from the deployed tag version; first party proxy and server side collection need explicit adapters. A request proves browser emission, not successful downstream ingestion. GA4 /g/collect URL grammar you assert on: Param Meaning Example v=2 Version field in the observed web tag transport v=2 tid=G XXXXXXX Measurement ID tid=G ABC123 en= Event name en=add to cart ep.<name = Custom event parameter, string type ep.campaign label=spring epn.<name = Event parameter, number type epn.value=49.99 gcs= / gcd= Consent state (see Consent Mode v2 section) gcs=G111 Preserve wire type information. Custom parameters can use ep. or epn. , but reserved ecommerce fields and items have their own web tag encodings. Do not assume standard currency is ep.currency or an item ID is a flat ep.item id . Google's logical ecommerce contract uses event level currency and nested items[] ; decode the observed transport through a capture tested adapter before checking that contract. Intercept with page.waitForRequest (single expected event) or page.on('request', ...) (collect many). Attach before the action. Match the exact /g/collect path and an approved hostname. Merge each POST event's parameters over the URL query with parseGA4 from references/ga4 interception.md ; URL only matching misses batched events. Use a test property or a local collector so tests do not pollute production analytics. Pattern using the parser below plus your application's verified ecommerce decoder and logical tracking plan. decodeEcommerceWebEvent is a required project adapter, not a package supplied by this skill; unsupported or missing wire fields must fail. See references/ga4 interception.md and references/tracking plan.md . 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 check the schema and the known scenario values. Validate the normalized logical payload and return every missing field and type violation. The helper in references/tracking plan.md checks finite numbers, nonempty strings and nested nonempty item arrays. It rejects unsupported or empty schemas. Zero is a valid number; an absent field, empty string, null , Infinity or a numeric string is not a typed zero. Add scenario specific expected values after schema checks. A matching schema cannot prove that a price is correct. Compare the payload with the known test cart, then assert event counts at the application's tracking completion boundary. Keep raw input events and raw emitted records so a decoder cannot hide a missing field, a tag transformation or a duplicate purchase. Asserting dataLayer.push When the question is specifically the GTM input — "is the right object pushed to dataLayer when the page loads?" — assert the push, not the GA4 beacon. This is the inverse of beacon interception: here the dataLayer push IS the target. Install the passive observer from references/datalayer capture.md before navigation. Leave an absent data layer absent, observe the application's first assignment, preserve push behavior and flag later replacement or lost observation. Do not initialize or repair the application queue in a test intended to measure the application's setup. Use a completion signal from the application before inspecting the captured objects. For GTM ecommerce object events, assert ecommerce.currency , ecommerce.value , and ecommerce.items[] with each item's expected ID, price and quantity. Currency is at event level in Google's ecommerce contract. Check counts as well as shape; selecting the first matching event conceals duplicates. Direct gtag command arrays need the corresponding command adapter rather than an object event parser. The reference contains the observer and full example. Use both observed input and network output when the question concerns the whole tracking path, while recording what each observation establishes. When the push is the only observable. Some applications push to dataLayer and load no tag manager and no collect endpoint; the push is the whole tracking contract. Then the push tests are the gate, not a stopgap: keep the versioned plan, the schema validation, the count assertions and the scenario literals, and cross check each purchase against the order the public API reports. In the report mark the beacon, deduplication, consent, pixel fixture and news media items of Done When as not applicable with the reason, rather than leaving them unmet; a reviewer must see that nothing left the browser because nothing was meant to. Pixels and Server Side Deduplication Marketing pixels send their own beacons. Endpoints to intercept: Destination Endpoint Event param Dedup key Meta Pixel facebook.com/tr (also /tr? ) ev=PageView , ev=Purchase eid / event id TikTok analytics.tiktok.com event in body/params event id LinkedIn px.ads.linkedin.com conversion id — For Meta, asserting that connect.facebook.net/en US/fbevents.js loaded, or that fbq('track','Purchase') ran, is a load/count check — it does not prove correctness. The correctness property for a Purchase that fires both client side (Pixel) and server side (Conversions API / CAPI) is deduplication : both must carry the same event id so Meta merges them into one conversion instead of double counting. Test it: capture the browser facebook.com/tr beacon for ev=Purchase , read its event id , and assert it equals the event id your server sent to CAPI (from a mocked/captured server call or a known fixture value). Skeleton: See references/pixels and dedup.md for parsing TikTok/LinkedIn payloads and capturing the server CAPI call. Consent Mode v2 Gating Choose the approved regional defaults and basic or advanced implementation before writing a gate. The examples in the reference use a