Skip to content

Migrated IAB TCF consent functional tests to Vitest+Playwright+MSW#1543

Draft
carterworks wants to merge 4 commits into
migrate-integration/00-infrafrom
migrate-integration/11-iab
Draft

Migrated IAB TCF consent functional tests to Vitest+Playwright+MSW#1543
carterworks wants to merge 4 commits into
migrate-integration/00-infrafrom
migrate-integration/11-iab

Conversation

@carterworks

@carterworks carterworks commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Changed Packages

  • core
  • reactor-extension

Description

Migrates the IAB TCF consent functional tests to the new Vitest+Playwright+MSW harness. C224675 (server-side 400/422 validation) is preserved as test.skip (requires live edge endpoint).

Related Issue

Part of the functional test → integration test migration. See packages/browser/test/FUNCTIONAL_MIGRATION_PLAN.md.

Motivation and Context

The existing TestCafe functional test suite is being migrated to Vitest+Playwright+MSW to enable faster, more reliable CI testing without a running server. This PR is part of a stacked series — each PR migrates one test file.

Functional tests replaced:

  • packages/browser/test/functional/specs/Consent/IAB/C224670.js
  • packages/browser/test/functional/specs/Consent/IAB/C224671.js
  • packages/browser/test/functional/specs/Consent/IAB/C224672.js
  • packages/browser/test/functional/specs/Consent/IAB/C224673.js
  • packages/browser/test/functional/specs/Consent/IAB/C224674.js
  • packages/browser/test/functional/specs/Consent/IAB/C224675.js
  • packages/browser/test/functional/specs/Consent/IAB/C224676.js
  • packages/browser/test/functional/specs/Consent/IAB/C224677.js
  • packages/browser/test/functional/specs/Consent/IAB/C224678.js

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Improvement (non-breaking change which does not add functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have added a Changeset file with a consumer-facing description of my changes.
  • I have signed the Adobe Open Source CLA or I'm an Adobe employee.
  • I have made any necessary test changes and all tests pass.
  • I have run the Sandbox successfully.

Stack

  1. Added MSW handlers, mock fixtures, and Vitest+Playwright test harness for functional test migration #1532
  2. Migrated Install SDK functional tests to Vitest+Playwright+MSW #1533

@carterworks carterworks added the ignore-for-release Do not include this PR in release notes label Jun 12, 2026
@carterworks carterworks self-assigned this Jun 12, 2026
@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4e0aafe

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates nine IAB TCF consent functional tests from TestCafe to the Vitest+Playwright+MSW integration-test harness. C224675 is preserved as test.skip with a clear explanation, and server-side assertions that genuinely cannot be replicated in a mocked environment are documented with comments.

  • Nine TestCafe IAB TCF test files are replaced by a single iab.spec.js that covers the same scenarios using MSW handlers and the shared networkRecorder infrastructure.
  • The original C224671 loop is split into two independent test() calls that both keep the C224671 label, and several assertions that the mock environment does support (consent cookie value after opt-out) are dropped without explanation.

Confidence Score: 4/5

Safe to merge; the new tests are additive and the functional tests being replaced remain on disk until the stacked migration is complete.

The migration is straightforward and the MSW handler correctly models the opt-in/opt-out split. The main issues are: two tests share the C224671 identifier which can confuse test reports, one verifiable cookie assertion from the originals is dropped without a comment, and the opt-out zero-calls check uses a raw synchronous filter instead of the established findCalls pattern used everywhere else. None of these affect correctness of the code under test.

packages/browser/test/integration/specs/Consent/iab.spec.js — duplicate test IDs and the dropped cookie assertion in the C224671 opt-out tests deserve a second look.

Important Files Changed

Filename Overview
packages/browser/test/integration/specs/Consent/iab.spec.js New integration test file covering 9 IAB TCF consent scenarios using Vitest+Playwright+MSW; two tests share the C224671 ID, a verifiable cookie assertion from the originals is silently dropped in the C224671 opt-out tests, and some negative-path tests use a raw synchronous filter inconsistent with the rest of the file.

Sequence Diagram

sequenceDiagram
    participant Test as Vitest Test
    participant Alloy as Alloy SDK (browser)
    participant MSW as MSW Worker
    participant NR as NetworkRecorder

    Test->>Alloy: configure(pendingConfig)
    Test->>Alloy: "setConsent(IAB_CONSENT_*)"
    Alloy->>MSW: POST /v1/privacy/set-consent
    MSW->>NR: captureRequest
    MSW-->>Alloy: "{ state:store [general=in|out] }"
    MSW->>NR: captureResponse
    Test->>NR: findCalls(/set-consent/)
    NR-->>Test: [call with request+response]
    Test->>Alloy: sendEvent()
    alt "consent = in"
        Alloy->>MSW: POST /v1/interact
        MSW->>NR: captureRequest + captureResponse
        Test->>NR: findCalls(/v1\/interact/)
        NR-->>Test: [1 call]
    else "consent = out"
        Note over Alloy: Alloy blocks call (no request sent)
        Test->>NR: calls.filter(v1/interact)
        NR-->>Test: [] (zero calls)
    end
Loading

Reviews (1): Last reviewed commit: "test(integration): migrate iab functiona..." | Re-trigger Greptile

Comment on lines +68 to +95
test("C224671: opt out of IAB with no Purpose 1; subsequent sendEvent is blocked", async ({
alloy,
worker,
networkRecorder,
}) => {
worker.use(setConsentHandler);

await alloy("configure", pendingConfig);
await alloy("setConsent", IAB_NO_PURPOSE_ONE);

const consentCalls = await networkRecorder.findCalls(
/v1\/privacy\/set-consent/,
);
expect(consentCalls.length).toBe(1);
expect(consentCalls[0].response.status).toBeGreaterThanOrEqual(200);
expect(consentCalls[0].response.status).toBeLessThanOrEqual(207);

// After opting out, sendEvent should not fire
await alloy("sendEvent");
expect(
networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
).length,
).toBe(0);
});

// C224671: Opt out of IAB — no Adobe vendor
test("C224671: opt out of IAB with no Adobe vendor; subsequent sendEvent is blocked", async ({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicate test ID for two distinct scenarios

Both the "no Purpose 1" test (line 68) and the "no Adobe vendor" test (line 95) share the identifier C224671. In the original TestCafe file both variants ran inside a forEach loop under one test.meta({ ID: "C224671" }) declaration, but in Vitest each test() call registers an independent entry. Test-reporting tools and CI systems that track coverage by case ID will see two separate C224671 results that may overwrite each other or trigger duplicate-ID warnings. Consider differentiating the names, e.g., C224671-no-purpose-one and C224671-no-adobe-vendor, or C224671 (No Purpose 1) / C224671 (No Adobe vendor).

Comment on lines +85 to +118
// After opting out, sendEvent should not fire
await alloy("sendEvent");
expect(
networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
).length,
).toBe(0);
});

// C224671: Opt out of IAB — no Adobe vendor
test("C224671: opt out of IAB with no Adobe vendor; subsequent sendEvent is blocked", async ({
alloy,
worker,
networkRecorder,
}) => {
worker.use(setConsentHandler);

await alloy("configure", pendingConfig);
await alloy("setConsent", IAB_NO_ADOBE_VENDOR);

const consentCalls = await networkRecorder.findCalls(
/v1\/privacy\/set-consent/,
);
expect(consentCalls.length).toBe(1);
expect(consentCalls[0].response.status).toBeGreaterThanOrEqual(200);
expect(consentCalls[0].response.status).toBeLessThanOrEqual(207);

// After opting out, sendEvent should not fire
await alloy("sendEvent");
expect(
networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
).length,
).toBe(0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inconsistent pattern for asserting zero network calls

Both C224671 opt-out tests assert no v1/interact call by directly filtering the synchronous networkRecorder.calls array, while every other test in the file uses the async networkRecorder.findCalls() helper. Although the synchronous check is safe here (when consent is out, Alloy never enqueues the request so there is nothing to wait for), the inconsistency could mislead future contributors into thinking a raw .filter is the accepted way to check for absent calls, which would be incorrect in cases where the request could arrive with a delay. Using findCalls with retries: 0 (or a dedicated assertNoCalls helper) would keep the pattern uniform.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +66 to +119
// C224671: Opt out of IAB using the setConsent command
// IAB_NO_PURPOSE_ONE — no Purpose 1, results in general=out
test("C224671: opt out of IAB with no Purpose 1; subsequent sendEvent is blocked", async ({
alloy,
worker,
networkRecorder,
}) => {
worker.use(setConsentHandler);

await alloy("configure", pendingConfig);
await alloy("setConsent", IAB_NO_PURPOSE_ONE);

const consentCalls = await networkRecorder.findCalls(
/v1\/privacy\/set-consent/,
);
expect(consentCalls.length).toBe(1);
expect(consentCalls[0].response.status).toBeGreaterThanOrEqual(200);
expect(consentCalls[0].response.status).toBeLessThanOrEqual(207);

// After opting out, sendEvent should not fire
await alloy("sendEvent");
expect(
networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
).length,
).toBe(0);
});

// C224671: Opt out of IAB — no Adobe vendor
test("C224671: opt out of IAB with no Adobe vendor; subsequent sendEvent is blocked", async ({
alloy,
worker,
networkRecorder,
}) => {
worker.use(setConsentHandler);

await alloy("configure", pendingConfig);
await alloy("setConsent", IAB_NO_ADOBE_VENDOR);

const consentCalls = await networkRecorder.findCalls(
/v1\/privacy\/set-consent/,
);
expect(consentCalls.length).toBe(1);
expect(consentCalls[0].response.status).toBeGreaterThanOrEqual(200);
expect(consentCalls[0].response.status).toBeLessThanOrEqual(207);

// After opting out, sendEvent should not fire
await alloy("sendEvent");
expect(
networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
).length,
).toBe(0);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 C224671: verifiable consent-cookie assertion is silently dropped

The original C224671 functional test verified that after opting out the consent cookie equalled "general=out". The setConsentHandler in this PR already returns { type: "state:store", payload: [{ key: "kndctr_…_consent", value: "general=out" }] }, so Alloy will write that cookie during the test. The assertion is therefore feasible in the mock environment but was dropped without an explanation comment (unlike the well-documented skip in C224675 and the limitation notes in C224678). Adding a expect(document.cookie).toContain("general=out") check after setConsent would make these tests equivalent to the originals on the cookie dimension.

@carterworks carterworks force-pushed the migrate-integration/10-consent branch from f4ee395 to ea1897b Compare June 12, 2026 17:32
@carterworks carterworks force-pushed the migrate-integration/11-iab branch from bd904d8 to 86474d3 Compare June 12, 2026 17:32

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

carterworks has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

This was referenced Jun 12, 2026
@carterworks carterworks force-pushed the migrate-integration/10-consent branch from ea1897b to f9440d5 Compare June 12, 2026 17:56
@carterworks carterworks force-pushed the migrate-integration/11-iab branch from 86474d3 to 7208bec Compare June 12, 2026 17:56

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

carterworks has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@carterworks carterworks changed the base branch from migrate-integration/10-consent to migrate-integration/00-infra June 12, 2026 17:57

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

carterworks has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@carterworks carterworks force-pushed the migrate-integration/11-iab branch 4 times, most recently from c941202 to eeef5eb Compare June 12, 2026 21:20
@carterworks carterworks marked this pull request as draft June 15, 2026 16:38
@carterworks carterworks force-pushed the migrate-integration/11-iab branch from eeef5eb to 6c320f2 Compare June 15, 2026 19:59
@carterworks carterworks force-pushed the migrate-integration/11-iab branch from 6c320f2 to 882177b Compare June 26, 2026 20:31
…tegration suite

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@carterworks

Copy link
Copy Markdown
Collaborator Author

Integration migration review — Consent/IAB (iab.spec.js)

Verdict: 🟡 LGTM with two notes — coverage is complete, the skip is well-justified, and the MSW handler faithfully gates opt-out behavior. Two non-blocking items worth addressing before merge.


Parity matrix

Old file Migrated Subtests old → new Notes
C224670 1 → 1
C224671 2 → 2 (forEach expanded) Both IAB_NO_PURPOSE_ONE and IAB_NO_ADOBE_VENDOR as separate test() blocks
C224672 1 → 1 New adds gdprContainsPersonalData body assertion — improvement
C224673 1 → 1
C224674 1 → 1 Old typo IAB_NO_PURPOSE_ONE_NO_GRPR corrected to IAB_NO_PURPOSE_ONE_NO_GDPR
C224675 ⏭️ skipped See §1
C224676 1 → 1 New adds request-body XDM assertion — improvement
C224677 1 → 1 Warning-not-present assertion dropped; see §3
C224678 1 → 1 Scope narrowed; see §4

1. Skip justification — C224675 🟢

Justified and double-covered. The old test asserts the server rejects four invalid IAB strings with specific error codes: EXEG-0102-400, EXEG-0103-400, EXEG-0104-422. These are server-side TCF string validation errors. The MSW setConsentHandler matches only on configId and always returns a 200 state:store — faithfully simulating per-request validation would require a full TCF TC string decoder. The skip comment at iab.spec.js:199-202 says exactly this.

Bonus: C224675.js was not deleted in this PR and remains in the functional suite, so server-validation coverage is preserved end-to-end.


2. Dropped assertions: ECID and cookie 🟡

setConsent-path tests (C224670–C224674, C224677):

Old tests extracted identity:result from the set-consent response and asserted ECID was present. The setConsentHandler (handlers.js:241-255) returns only a state:store payload — no identity:result — so the ECID assertion is genuinely non-replicable for these tests. 🟢 env-forced.

Cookie assertions (consentCookieValue === "general=in/out") are also dropped for these tests. The handler does return a state:store value that alloy uses to write the cookie (e.g. general=out for IAB_NO_PURPOSE_ONE at handlers.js:217-218). The behavioral signal that replaces it — whether a subsequent sendEvent fires or not — is a stronger proxy. 🟢 acceptable trade.

sendEvent-path tests (C224676, C224678):

sendEventResponse.json (lines 5-14) does include an identity:result handle with ECID. The ECID assertion was droppable-but-portable for these two tests and was dropped by choice. Worth a comment if ECID presence is a stated requirement of these cases. 🟡 minor.


3. C224677: warning-not-present assertion dropped 🟡

iab.spec.js:251-273 — the old test (C224677.js:60-72) verified that with purpose 10 false (but purpose 1 true), alloy does not record an EXEG-0301-200 opt-out warning:

const warningTypes = eventResponse.getWarnings().map((w) => w.type);
await t.expect(warningTypes).notContains("https://ns.adobe.com/aep/errors/EXEG-0301-200");

sendEventResponse.json has no warnings handle, so this would vacuously pass — but it's worth a comment noting the assertion was skipped because the mock response has no warnings array, rather than silently omitting it.


4. C224678: second-event-blocked assertion dropped 🟡

iab.spec.js:280-316 — the old test (C224678.js:85-96) called alloy.sendEvent() twice: first with a no-Purpose-1 consent string, then bare — and verified the second event was blocked (total interact count stayed 1, opt-out state was set by the server's EXEG-0301-200 warning).

The sendEventResponse.json mock has no EXEG-0301-200 warning payload, so the SDK never flips its internal consent state — a second sendEvent in the new test would succeed, making the assertion unreplicable as-is. The comment at iab.spec.js:277-279 explains the cookie/warning gap but doesn't mention the second-event-block assertion specifically. Consider adding that to the comment.

The fix exists if needed: setConsentHandler already has the pattern — it reads the IAB string from the body and returns a consent-state response. A sendEventOptOutHandler that checks for the known no-Purpose-1 string (CO052oTO052oTDGAMBFRACBgAABAAAAAAIYgEawAQEagAAAA) and returns a warnings handle with EXEG-0301-200 would restore both the warning assertion and the second-event-block.

Also dropped (not mentioned in comments): the old test asserted that activation:push, identity:exchange, and personalization:decisions handles were absent from the opt-out response. sendEventResponse.json includes activation:push and personalization:decisions payloads, so this couldn't be asserted against the mock. Worth documenting.


Isolation / hygiene 🟢

  • worker.resetHandlers() fires after every test via the worker fixture (extend.js:41).
  • networkRecorder.reset() fires before and after every test (extend.js:47-50).
  • All cookies are cleared before each test via cookieStore.getAll() / cookieStore.delete() (extend.js:59-62).
  • License header present (iab.spec.js:1-11), year 2026 correct.
  • iab.spec.js picked up by vitest.projects.js:62 glob (packages/browser/test/integration/**/*.spec.js) — no registration needed.
  • No test.meta tags in the new spec — consistent with other migrated specs in this harness.

Negative-case timing / flake 🟢

Both C224671 opt-out tests (iab.spec.js:87-91, 114-118) assert zero interact calls using a synchronous networkRecorder.calls.filter(...) snapshot rather than findCalls. Because alloy("sendEvent") resolves immediately when consent is out (SDK blocks before hitting the network), no poll is needed. Correct.


Deletions alignment 🟢

Eight files deleted (C224670–C224674, C224676–C224678), one retained (C224675). Matches the new spec's coverage exactly.


Reviewed against: packages/browser/test/integration/specs/Consent/iab.spec.js (new), packages/browser/test/functional/specs/Consent/IAB/C224670–C224678.js (old, via migrate-integration/00-infra), helpers/mswjs/handlers.js, helpers/mswjs/networkRecorder.js, helpers/constants/consent.js, helpers/testsSetup/extend.js, helpers/mocks/sendEventResponse.json.

Keep the original testcafe functional specs alongside the new
Vitest+Playwright+MSW integration suite until these migration branches
merge, so reviewers retain the pre-migration signal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ignore-for-release Do not include this PR in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant