Skip to content

TagreeveTagreeve

Pingwright is now Tagreeve. The command pingwright still works until 0.5.0. Versions up to 0.3.0 are on npm as pingwright, and tagreeve starts at 0.4.0-rc.1. The steps to move over are in CHANGELOG.md.

Agent-first testing for tags and consent. Tagreeve plays a visitor in a real browser, records every tracking hit the site sends, and checks the result against what you expect. Tag-firing reports (which tags fired) are planned.

Status: 0.3 (0.3.0). Tagreeve plays a visitor in Chromium, records and decodes hits, answers the consent banner, checks YAML expectations and writes a JSONL record. 0.3 adds rules on payload fields and cookies, a deniedIdentifiers check, tagreeve suite, an HTML report, a Done control for handoff steps, role-based locators and a select step. The rest of this README describes where Tagreeve is going, and marks what is not built yet. The design is in docs/design.md. Feedback is welcome in issues.

In 0.1: tagreeve run with stub and live network modes; consent epochs, with withdrawals labelled apart from first rejects; seeded fake identities, hash checks and leak detection; decoders for GA4, Google Ads, Meta, Stape custom loader and a minimal server-container data client; banner handling through autoconsent; expectations; JSONL output.

In 0.2: --gtm-override runs the page against an unpublished GTM web workspace, also through a Stape-style custom loader; tagreeve diff compares two runs; a Pinterest decoder; a curated tracker list (LinkedIn, Microsoft UET, Reddit) that is stubbed and recorded as minimal hits, extendable with --tracker-list (X, for example); a record schema version; a Shopify checkout pattern (docs/shopify-checkout.md).

In 0.3: body.has and body.only rules on the field names of a hit; cookie rules; a deniedIdentifiers check that fails when an identifier goes out on a denied hit; tagreeve suite runs a folder of scenarios against several GTM builds; tagreeve report writes an HTML report of one run or a suite; a Done button in the handoff window; click and wait by role and name or from a list of selectors; a select step; the server network mode; record schema version 2. deniedIdentifiers fails by default, so a run that passed in 0.2 can exit 1. See CHANGELOG.md.

Not built yet: MCP server, GTM tag-firing reporters, the server tag reporter, Shopify bridge, a full Microsoft UET decoder, Firefox and WebKit.

Why ​

Tracking setups break quietly. A trigger change lets a pixel fire before consent. A container update sends hits to the wrong place. A consent banner update stops the analytics tags. The usual tools for catching this need a person:

  • Google Tag Assistant and GTM Preview show which tags fired, but only to a signed-in person clicking through a page.
  • Browser extensions decode hits in DevTools, but nothing can call them.
  • Commercial auditing services run in their own cloud, on their own schedule.

Tagreeve is built so that a coding agent, a CI job or a person can answer these questions with one command and get structured results:

  • What did the site send to each vendor, before and after the visitor's consent choice?
  • Which tags fired for each event, and which expected tags did not? (Planned, not built yet.)
  • Does an unpublished GTM workspace behave as intended, before you publish it? Run the same scenario with and without --gtm-override and compare the two records with tagreeve diff. (In 0.2.)

What it does ​

  1. Plays a visitor. A Playwright browser opens your site, answers the consent banner (accept, reject, or no answer), visits pages, fills forms with a fake identity and adds to cart. Steps that a tool must not do, such as paying or logging in, are handoff steps: a person acts in a visible window while recording continues.
  2. Records requests from the page and from all iframes, including sandboxed iframes such as Shopify custom pixels. Capture from service workers works in Chromium only, and requests to the collector origin from dedicated workers are captured too. Each run reports which sources it covered.
  3. Decodes hits. Vendor plugins turn raw requests into readable hits: GA4 events and consent state, Google Ads, Meta and Stape server-container traffic. Pinterest is decoded in 0.2. Hosts on the tracker list, such as LinkedIn, Microsoft UET and Reddit, give minimal hits with the vendor name. A full Microsoft UET decoder is not built yet.
  4. Reports tag firing (not built yet). An optional GTM template reports which tags fired or failed for each event, in the web container and the server container. Agents get this without a Google sign-in. Given the container's tag list, Tagreeve also reports which expected tags did not fire.
  5. Checks expectations. Each scenario lists what must and must not happen, for example "no Meta request before consent" or "GA4 hits after a reject carry no user ID". Tagreeve reports pass or fail, with the evidence. A scenario that names a vendor Tagreeve does not know is rejected when it is parsed.
  6. Checks hashing. Because Tagreeve knows the fake identity it typed, it can check that the hashed email and phone sent to Google and Meta are correct.
  7. Compares runs. tagreeve diff shows what changed per vendor and per event between two runs, for example before and after a GTM change. tagreeve suite does this for a folder of scenarios.
  8. Reports. tagreeve report turns a run or a suite into one HTML file for people who do not read JSONL.

A scenario ​

yaml
# scenarios/undecided-visitor.yml
start: /products/{product}
vars:
  product: fake-tee
steps:
  - consent: none          # the banner must appear, and gets no answer
  - wait: 4s
  - visit: /collections/all
expect:
  mustNot:
    - { vendor: [meta, google-ads], before: consent }
    - { vendor: ga4, ids.user_id: present, consent.analytics: denied }
  must:
    - { vendor: ga4, event: page_view, consent.analytics: denied, count: ">=2" }
bash
tagreeve run scenarios/undecided-visitor.yml --site https://staging.example.com

Install ​

bash
npm i -g tagreeve@next
tagreeve install-browser

Node 22.18 or later. npm i -g tagreeve@next installs the newest 0.4 release candidate. Other ways to install, from a git tag or a tarball, are in docs/cli.md.

Quick start ​

  1. npm i -g tagreeve@next

  2. tagreeve install-browser

  3. Write scenario.yml:

    yaml
    start: /
    steps:
      - consent: none
      - wait: 4s
    expect:
      mustNot:
        - { vendor: [meta, google-ads], before: consent }
  4. tagreeve run scenario.yml --site https://staging.example.com

  5. Read the summary, and the full record in runs/run_<id>.jsonl.

consent: none needs a banner that autoconsent detects. If none is shown, the run stops with "banner not shown" and exit code 2. Vendor keys are ga4, google-ads, meta, pinterest, sgtm, and every vendor on the tracker list (linkedin, microsoft-uet, reddit) or in a --tracker-list file. A scenario that names any other vendor is rejected when it is parsed.

Stub mode is the default: known collection endpoints are recorded and answered locally. Unknown destinations still receive hits and are flagged as unknownDestination, which fails the run. The registrable domain of --site is first party: for --site https://www.shop.co.uk, shop.co.uk and every *.shop.co.uk host are not unknown. Data-carrying requests to those subdomains that no decoder matches, such as server-side GTM on sgtm.shop.co.uk/collect, are listed as firstPartySubdomain flags, grouped by host. They do not fail the run. Hosts without a registrable domain, such as IP addresses and localhost, are first party only as the exact --site host. If a site serves its own assets from another domain, list it under options: { firstPartyHosts: ['i.example-cdn.com', '*.example-static.com'] }. To accept specific unknown hosts without calling them first party, use options: { allowUnknownDestination: ['tracker.example.net'] }; every other unknown destination still fails. allowUnknownDestination: true allows all of them, and the summary still lists them. A request sent while a sandboxed srcdoc iframe is loading can escape stubbing and recording (see design §5.3). Use --network live to let everything through. Tagreeve decodes GA4, Google Ads, Meta, Pinterest and Stape traffic, and stubs the hosts on the tracker list. Hits to other vendors, such as TikTok, are not stubbed unless you add them with --tracker-list: they pass through and can only show up as unknownDestination flags.

Override and diff quick start ​

Run one scenario against the published container and against an unpublished workspace, then compare.

  1. Get the preview values for the workspace from the GTM UI, a GTM API client or a GTM MCP server. Tagreeve never calls the GTM API. Write them to override.json:

    json
    {"container": "GTM-XXXXXXX", "gtmAuth": "…", "gtmPreview": "env-12"}
  2. Run the scenario twice:

    bash
    tagreeve run scenario.yml --site https://staging.example.com --out runs/published
    tagreeve run scenario.yml --site https://staging.example.com --out runs/workspace --gtm-override override.json
  3. Compare the two records:

    bash
    tagreeve diff runs/published/run_<id>.jsonl runs/workspace/run_<id>.jsonl

diff lists added, removed and changed hits per consent epoch, vendor and event, and changed flag counts per host. Add --by-page to pair hits page by page, or --json for the full result. It exits 0 whether or not the runs differ; the exit code of each run tells you whether its rules passed.

The override fetches the workspace build of gtm.js?id=<container> and answers the page with it. If no request matched, or the build did not return 200, the run exits 2. The gtmAuth value never appears in the record or in any output. For a site that loads the container through a Stape-style custom loader, the override works through the loader by default. Add "loader": {"replace": "<script URL>"} to replace the loader script instead. Details are in docs/cli.md.

Suite and report quick start ​

Run a folder of scenarios against the published container and an unpublished workspace, then make a report that people can read without JSONL.

bash
tagreeve suite scenarios/ --site https://staging.example.com \
  --variant published --variant workspace=override.json --out runs
tagreeve report runs/suite_<id> --html suite-report.html

suite runs every *.yml file in the folder once per variant, and prints a table with the result of each run and the diff between the variants. The runs and a suite.json manifest go to runs/suite_<id>/. The exit code is 2 if a run did not finish, 1 if an expectation failed, else 0. A scenario with a handoff step is skipped unless you add --with-handoff. report writes one HTML file with a timeline per vendor and consent state, the expectations, the flags and the diff. It has names and counts only, no script and no network request. tagreeve report runs/run_<id>.jsonl --html report.html makes the report for one run. Details are in docs/cli.md.

Rules and steps added in 0.3 ​

yaml
steps:
  - consent: reject
  - click: { role: radio, name: "White (01)" }
  - click: { any: ['[role=radio][title$="White (01)"]', 'text=White (01)'] }
  - select: { "select[name=countryCode]": DE }
expect:
  mustNot:
    - { vendor: sgtm, body.has: [email, phone, "user_data.*", "cookie*"], in: epoch(denied) }
    - { cookie: "_ga*", in: epoch(denied) }
  must:
    - { vendor: sgtm, event: page_view, body.only: [event_name, page_location, page_title, page_referrer] }
options:
  builtins: { deniedIdentifiers: warn }
  • body.has matches hits by the names of the fields they sent, and body.only is an allowlist of field names. They read names, never values.
  • cookie checks which cookies were set, by name, before or after consent. In stub mode a stubbed endpoint sets no cookies, so use --network server with options.serverHosts to see the cookies of a server container.
  • deniedIdentifiers is a built-in check. It fails when a hit carries user_id, uid, a hashed user_data field or another identifier while the matching consent signal is denied. It is fail by default. Set it to warn to keep the old exit code while you fix the cause.
  • click and wait take { role, name } or { any: [selectors] }, so a theme change does not break every scenario. select chooses an option of a <select> by value, then by label, and reads it back.

Tracker list ​

In stub mode, Tagreeve stubs and records requests to a curated list of tracker hosts, so a pixel without a decoder no longer leaks to the vendor. The list is small, kept in the repo, and each entry links to the vendor's own docs. A listed request becomes a minimal hit with the vendor name, so rules, flags and diff work on it. Add your own entries with --tracker-list:

json
[{"vendor": "acme", "host": "t.example.com", "paths": ["/collect"], "source": "https://docs.example.com/pixel"}]
bash
tagreeve run scenario.yml --site https://staging.example.com --tracker-list trackers.json

vendor is a lowercase key you can then use in expect rules. host is an exact host or *.domain. paths is optional. See docs/cli.md for the matching rules.

Safe by default ​

  • Known collection endpoints are stubbed by default. Scripts load normally, but requests to known collection endpoints are recorded and answered locally. Unknown destinations pass through, so test hits can still reach vendors that way. Requests to hosts outside the site's registrable domain are flagged when they are fetch, xhr, ping or beacon requests, or images or other requests that carry a query or a body. They are flagged as unknownDestination. Scripts, stylesheets, fonts and documents are not flagged. Requests a sandboxed srcdoc iframe sends while it loads can escape stubbing and recording (design §5.3). Tagreeve lets them through because blocking by host would stop vendor scripts from loading and make a broken setup look compliant. Use --network live to let everything through.
  • Fake identities. Every run uses a generated test identity. Tagreeve flags any email, phone number or hashed identifier in a recording that does not belong to that identity.
  • No log-ins, and payments only as an opt-in test. On a hosted site a log-in is always a handoff step for a person, who should use test accounts. A payment is a handoff step too, except for the opt-in payment step, which types a provider's documented test card after three checks: --allow-test-payment <host>, the host in your allowlist file, and the provider's test mode on the page. Only loopback hosts, such as the fixture shop, may be automated through a fake payment form.
  • You own the data. There is no Tagreeve-hosted service. When server-side reports are built, they will go to a sink you run: a tunnel to your machine, or a small Worker in your own Cloudflare account.

For agents ​

The MCP server is not built yet. It will let an agent run a scenario, query the records, read tag firing per event, and compare runs. Today an agent can call the CLI: run --json, diff --json and suite --json print structured results. Every record has a stable ID, and every result names the run and step.

Tagreeve will not change GTM containers or sign in to Google. An agent that edits a workspace with a GTM API client (for example a GTM MCP server) passes the workspace preview values to Tagreeve for one run with --gtm-override.

Packages ​

PackageJobState
coreIDs, clock, record types, record schema version, JSONL run store, fake identity0.3
recorderLaunch the browser, play steps, capture requests, responses, cookies and dataLayer events, GTM override, Done control, locators, server mode0.3
decodersOne plugin per vendor, with synthetic fixtures as tests0.3 (GA4, Google Ads, Meta, Pinterest, Stape, server-container data client, tracker list; hit field names)
consentBanner handling through DuckDuckGo autoconsent, Consent Mode probe, epochs0.1
expectScenario format, expectations, deniedIdentifiers, diff engine0.3
clitagreeve run, diff, suite, report, install-browser0.3
gtmTag-firing reporter templatesnot built yet
platforms/shopifyCustom-pixel bridge, sandbox URL handling, checkout handoffnot built yet
mcpMCP servernot built yet
  • EDPS website-evidence-collector records cookies and beacons, including before consent. Tagreeve adds vendor decoding, expectations and diffs. Tag reports are planned.
  • DuckDuckGo autoconsent handles consent banners. Tagreeve uses it.
  • Snowplow Micro does local, assertable event collection for Snowplow trackers.
  • ObservePoint, DataTrue and Trackingplan are commercial services for tag auditing and monitoring.
  • Avo, Segment Protocols and Amplitude Data govern the schema of your own product events.

License ​

MIT