# Confirmation tests

How to run the rows in [../assets/test-matrix.csv](../assets/test-matrix.csv) so that each one produces evidence you can find again. Facts were checked against Google's live docs on 2026-10-17; re-check menu paths and filter behaviour at run time.

## 0. Read the data filter states before any run

Data filters decide whether a test session can ever reach reports or the export.

- Open Admin, Data collection and modification, Data filters (the path Google's internal traffic page gave on 2026-10-17). Record each filter's name, type and state (Testing, Active or Inactive) in the incident record.
- Google's data filter page says data excluded by an active filter is never processed and is never available in Google Analytics or BigQuery. A test session it catches is gone for good, and it looks exactly like a failed fix.
- Developer traffic filter: it filters out activity sent in debug mode. Google's DebugView page says Tag Assistant and preview mode turn on debug mode for your device. While this filter is Active, nothing sent during a Tag Assistant session reaches reports or the export.
- Internal traffic filter: it matches events that carry the `traffic_type` value set by the property's internal traffic rules (IP addresses; the default value is `internal`). A tester inside those rules is invisible while the filter is Active.
- Hostname filters: Exclude filters (released 11 June 2026) and Include filters (released 21 September 2026). An Include filter also drops events with an empty hostname, and does not apply to Measurement Protocol events. A test on a host missing from the include list is invisible.

If the owner wants test traffic marked rather than avoided, the owner switches the filter to Testing for the window. In Testing, matching data is labelled with the Test data filter name dimension instead of excluded, so read the tests through that dimension. All other matching traffic also reaches reports for that window: record the start and end times, annotate them, and switch the filter back. This is an Admin change and needs the owner's approval.

## 1. Two kinds of run

Debug runs (Tag Assistant or GTM preview):

- Evidence: same-day sequence only. Consent default before the Google tag; `update` after the choice; then `page_view`; the landing URL keeps its parameters; `_gl` in the destination URL after a hop to a different registrable domain; the return `page_view` request carries `ignore_referrer` after an unwanted referral fix.
- Google's consent debugging page shows where to read this: the earliest Consent event (or the On-page Default column on the tag's Consent tab) for the default, the latest Consent event (or On-page Update) for the update.
- Do not expect these sessions in the export or reports while a developer traffic filter is Active.

Confirmation runs:

- A normal browser profile with no Tag Assistant connection and no `debug_mode`, on a network outside the internal traffic rules while that filter is Active.
- Evidence: the next-day export (query Q8) and the processed report after 48 hours.
- Purchases on production: an agreed test product, run by a named person with written approval. Record each `transaction_id` so the order can be refunded and excluded downstream by that ID. Do not mark the device as internal to exclude it.

## 2. Record identity and time

Q8 finds a test session by its client ID and a time window. Record both in the test matrix.

- Read the client ID in the test browser with the Google tag's `get` command: `gtag('get', 'G-XXXXXXXXXX', 'client_id', (id) => console.log(id))`. Google's tag reference lists `client_id` and `session_id` as fields `get` returns for Google Analytics 4. If `gtag` is not defined on the page, read the `_ga` cookie and compare it with `user_pseudo_id` on the first test you find in the export, so you learn the format once.
- A clean browser has no client ID until the first page loads. For a first-visit test, read it on the landing page right after it loads; for a return-visit test (saved choice), read it before the click.
- Read it again after each domain hop and on the success page. A change is the identity break the cross-domain checks look for.
- Write down the UTC time of the first click and of each step.
- Denied runs in advanced consent mode have no persistent client ID. Google's reporting surfaces page says each cookieless session has a different `user_pseudo_id` in the export. Find these runs by time window, landing `page_location` and `analytics_storage = 'No'` (Q8 shows the variant).

## 3. Next-day and 48-hour checks

- Run QC for the test days first. A test row missing from a partial export proves nothing.
- Q8 for each confirmation run: one `session_start`, a session ID on every browser event, `page_view` first among business events, the expected `collected_traffic_source` values on the landing event, a populated `session_traffic_source_last_click`, and for purchase tests the recorded `transaction_id` in the same session.
- Server events: Q3 shows distinct `user_pseudo_id` close to distinct orders, and Q3's join shows server session IDs that match browser sessions.
- After 48 hours: the processed report, filtered to the test landing page and time, puts each test session in the expected channel. Very small test volumes can be withheld by thresholds; if the report shows a thresholding message, rely on the export.

## 4. Window comparison with approximate counts

- Google's developer blog says GA4 estimates Sessions and Active Users with HyperLogLog++, so counts in the UI and the Data API are approximations. It gives `COUNT(DISTINCT ...)` in BigQuery as the way to get exact counts.
- At the precision that post gives for sessions (12), the relative standard error is about 1.6% (1.04 divided by the square root of 4,096). That is why the acceptance check asks for exact counts when the difference to explain is under 2% of total sessions. The post is from 2022; check it again at run time.
- Exact sessions: `COUNT(DISTINCT CONCAT(user_pseudo_id, '.', CAST(session_id AS STRING)))` over the same days, streams and consent states in both windows. Compare the change between windows, not the level: the export holds cookieless pings and no modelled data, so its level differs from the UI.
