Skip to content

Migrated Consent functional tests to Vitest+Playwright+MSW#1542

Draft
carterworks wants to merge 4 commits into
migrate-integration/00-infrafrom
migrate-integration/10-consent
Draft

Migrated Consent functional tests to Vitest+Playwright+MSW#1542
carterworks wants to merge 4 commits into
migrate-integration/00-infrafrom
migrate-integration/10-consent

Conversation

@carterworks

@carterworks carterworks commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Changed Packages

  • core
  • reactor-extension

Description

Migrates the Consent functional tests to the new Vitest+Playwright+MSW harness. C1576777 and C5594870 are preserved as test.skip (flaky/requires special URL setup per original test suite).

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/C2593.js
  • packages/browser/test/functional/specs/Consent/C2594.js
  • packages/browser/test/functional/specs/Consent/C2660.js
  • packages/browser/test/functional/specs/Consent/C14404.js
  • packages/browser/test/functional/specs/Consent/C14405.js
  • packages/browser/test/functional/specs/Consent/C14406.js
  • packages/browser/test/functional/specs/Consent/C14407.js
  • packages/browser/test/functional/specs/Consent/C14409.js
  • packages/browser/test/functional/specs/Consent/C14410.js
  • packages/browser/test/functional/specs/Consent/C14411.js
  • packages/browser/test/functional/specs/Consent/C14414.js
  • packages/browser/test/functional/specs/Consent/C1472433.js
  • packages/browser/test/functional/specs/Consent/C1472434.js
  • packages/browser/test/functional/specs/Consent/C1472435.js
  • packages/browser/test/functional/specs/Consent/C1472436.js
  • packages/browser/test/functional/specs/Consent/C1472437.js
  • packages/browser/test/functional/specs/Consent/C1472438.js
  • packages/browser/test/functional/specs/Consent/C1576777.js
  • packages/browser/test/functional/specs/Consent/C1631712.js
  • packages/browser/test/functional/specs/Consent/C225953.js
  • packages/browser/test/functional/specs/Consent/C25148.js
  • packages/browser/test/functional/specs/Consent/C28754.js
  • packages/browser/test/functional/specs/Consent/C5594870.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: 2a0d120

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 23 TestCafe consent functional tests to the Vitest+Playwright+MSW harness, adding a new consent.spec.js integration spec that covers queuing, persistence, opt-in/opt-out toggling, Adobe 2.0 consent, identity-hash deduplication, and reload simulation via cleanAlloy()/setupAlloy(). Two tests (C1576777, C5594870) are preserved as test.skip with explanatory comments matching their original flaky/URL-dependent status.

  • Adds packages/browser/test/integration/specs/Consent/consent.spec.js (1011 lines) covering 23 original test IDs using MSW handlers (sendEventHandler, setConsentHandler, and a locally-defined sendEventWithIdentityHandler) and a shared networkRecorder to assert request counts/bodies.
  • Reload simulations (C14407, C14409, C14411, C1472433\u2013C1472436) use cleanAlloy() + setupBaseCode() + setupAlloy() mid-test to recreate the page-load lifecycle, with a manual identity-cookie write to replicate the edge side-effect that the MSW setConsentHandler omits.
  • C1576777 and C5594870 are test.skip preserving original decisions; comments explain the MSW limitation or special URL requirement.

Confidence Score: 4/5

Safe to merge; the test file is well-structured and the two skipped tests are explicitly documented.

The migration faithfully preserves 21 of 23 test scenarios with correct MSW handler registration, proper cookie/localStorage cleanup in beforeEach, and consistent use of networkRecorder for assertion. The C14410 second sub-test never submits invalid consent data and asserts nothing about error handling; the C14414 test assumes strict Alloy command-queue serialisation without documentation. Neither issue is a runtime regression.

The second sub-test of C14410 (lines 435–453) and the concurrent-command assertions in C14414 (lines 519–540) deserve a second look.

Important Files Changed

Filename Overview
packages/browser/test/integration/specs/Consent/consent.spec.js New 1011-line integration spec; structurally sound with correct MSW setup/teardown, but the second C14410 sub-test is vacuous (never calls setConsent with invalid purposes and asserts nothing about error handling).

Sequence Diagram

sequenceDiagram
    participant Test
    participant Alloy
    participant MSW
    participant NetworkRecorder

    Note over Test,NetworkRecorder: Reload-simulation pattern (C14407, C14409, C1472433–C1472436)

    Test->>Alloy: configure(defaultConsent:pending)
    Test->>Alloy: setConsent(IN)
    Alloy->>MSW: POST /v1/privacy/set-consent
    MSW-->>Alloy: "200 state:store consent=in"
    Alloy-->>Test: resolved
    Note over Test: write identity cookie manually

    Test->>Alloy: cleanAlloy() phase 1 teardown
    Test->>Alloy: setupBaseCode() + setupAlloy() new instance
    Test->>NetworkRecorder: reset()

    Test->>Alloy: alloy2.configure(defaultConsent:pending)
    Note over Alloy: sees consent+identity cookie, restores consent=IN
    Test->>Alloy: alloy2.sendEvent()
    Alloy->>MSW: POST /v1/interact
    MSW-->>NetworkRecorder: capture request+response
    MSW-->>Alloy: 200 identity:result, state:store
    Alloy-->>Test: resolved

    Test->>NetworkRecorder: findCalls(v1/interact)
    NetworkRecorder-->>Test: 1 call
Loading

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

Comment on lines +435 to +453
// (the mock handler accepts it). The real edge validates unknown purposes.
worker.use(setConsentHandler);

await alloy("configure", {
...alloyConfig,
defaultConsent: "pending",
});

// After a rejected setConsent, calling with valid values should succeed
// (We cannot replicate the server-side EXEG-0102-400 rejection in MSW mock
// without a purpose-specific error handler, so just verify recovery.)
await alloy("setConsent", CONSENT_IN);
});

// C14411: User consents to no purposes after consenting to no purposes
test("C14411: calling setConsent(out) twice does not throw", async ({
alloy,
worker,
}) => {

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 C14410 second sub-test is a vacuous pass

The test is named "setting consent for unknown purposes produces server 400 error" but it only calls setConsent(CONSENT_IN) — a fully valid Adobe 1.0 consent object. No invalid/unknown purpose is ever submitted, no 400-path error handler is installed, and no assertion is made about error handling or rejection behaviour. The comment inside acknowledges the MSW mock cannot replicate the EXEG-0102-400 response, but that makes this test equivalent to a no-op that will always pass regardless of whether the error path is broken. Consider either (a) adding a purposeful MSW handler that returns a 400 and asserting Alloy surfaces/rejects it, or (b) marking this sub-test as test.skip with an explanatory comment (consistent with how C1576777 and C5594870 are handled) so coverage gaps are explicit.

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 +519 to +540
await setConsentPromise2;
await sendEventPromise;

// Two set-consent calls should have been made
const consentCalls = await networkRecorder.findCalls(
/v1\/privacy\/set-consent/,
{ retries: 10, minCalls: 2 },
);
expect(consentCalls.length).toBe(2);

// Event should be blocked because final consent is out
expect(
networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
).length,
).toBe(0);
});

// C1472433: Set-consent is not called when consent is the same after reload
test("C1472433: set-consent not called again when consent hash matches after simulated reload", async ({
alloy,
worker,

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 C14414 relies on Alloy's command queue being strictly sequential across consent changes

Three commands are dispatched concurrently — setConsent(IN), setConsent(OUT), sendEvent — then awaited in declaration order. The test asserts 0 interact calls, which is only correct if Alloy keeps the sendEvent queued until all prior consent operations (including the trailing OUT) complete before evaluating the event's consent gate. If Alloy unblocks queued events as soon as the first IN consent resolves (before setConsent(OUT) starts), the event will fire and the final synchronous filter check at line 536 will see 1 call, failing the test intermittently. No retry/wait is used for the interact check — it is a direct synchronous filter on networkRecorder.calls after await sendEventPromise. If this test has been passing reliably in CI it confirms Alloy's queue is truly serial; worth noting in a code comment so future maintainers understand the dependency.

@carterworks carterworks force-pushed the migrate-integration/09-id-migration branch from fda693a to d828d45 Compare June 12, 2026 17:31
@carterworks carterworks force-pushed the migrate-integration/10-consent branch from f4ee395 to ea1897b Compare June 12, 2026 17:31

@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/09-id-migration branch from d828d45 to 87c58ea Compare June 12, 2026 17:56
@carterworks carterworks force-pushed the migrate-integration/10-consent branch from ea1897b to f9440d5 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/09-id-migration 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/10-consent branch 4 times, most recently from e41bc21 to 15fc2fb 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/10-consent branch from 15fc2fb to 0eb6c5a Compare June 15, 2026 19:59
carterworks and others added 2 commits June 26, 2026 14:31
@carterworks carterworks force-pushed the migrate-integration/10-consent branch from 0eb6c5a to bb8eb8e Compare June 26, 2026 20:31
…ation suite

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

Copy link
Copy Markdown
Collaborator Author

Integration migration review — PR #1542 (Consent)

Verdict: Approve with comments. Solid migration; 21 of 23 non-IAB C-files land cleanly. Two issues need attention: one red (C2660 drops the only assertion that makes the test meaningful) and a handful of yellows (C14414 name/body mismatch, C14410 partial coverage, C28754 and C5594870 weakening notes).

IAB C-files (Consent/IAB/C224670–C224678) are out of scope for this PR — they are correctly not deleted and presumably handled in a later PR (11-iab). I have not counted them against parity.


Summary table

Criterion Notes
1a. C-file coverage 🟢 21 active files deleted, 2 skip-stubs kept; all 23 accounted for
1b. Subtest counts 🟡 No silent drops; C14410 gains a stub test but loses the EXEG-400 assertion
1c. Assertion fidelity 🔴 C2660 drops the defining assertion; C14414 name doesn't match body
2. Skip justification 🟡 C1576777 🟢 (was already test.skip); C5594870 🟡 (was active P0, now stub)
3. Mock fidelity 🟢 setConsentHandler interprets Adobe 1.0/2.0 and IAB correctly; identity mock writes cookie
4. Timing/flake 🟢 findCalls with retries used consistently; fire-and-forget awaited before count assertions
5. Isolation 🟢 extend.js runs worker.resetHandlers() and networkRecorder.reset() auto-after-each; cookie/localStorage cleared in beforeEach
6. Hygiene 🟢 License header present, glob covers spec automatically, no test.meta (consistent with rest of integration suite)

Parity matrix

All 23 non-IAB C-files
Old C-file Deleted New test name(s) Old subtests → New Notes
C2593 C2593: queued event is sent after user opts in 1 → 1
C2594 C2594: sendEvent resolves with {} … (describe) 1 → 1 result assertion weakened (see 1c)
C2660 C2660: sendEvent queued before consent fires … 1 → 1 defining assertion dropped (see 1c)
C14404 C14404: user can switch from out to in 1 → 1
C14405 C14405: unidentified user can consent … 1 → 1
C14406 C14406: sendEvent is blocked … (describe) 1 → 1
C14407 C14407: consenting to all purposes persists … 1 → 1
C14409 C14409: consenting to no purposes persists … (describe) 1 → 1
C14410 C14410: configuring defaultConsent to unknown fails + C14410: setting consent … produces server 400 error 2 → 2 second subtest body asserts nothing about 400 (see 1c)
C14411 C14411: calling setConsent(out) twice … + C14411: setConsent(out) after reload … 2 → 2
C14414 C14414: set-consent requests are sequential … 1 → 1 name asserts sequentiality; body does not (see 1c)
C1472433 C1472433: set-consent not called again … 1 → 1
C1472434 C1472434: set-consent called when consent changes … 1 → 1
C1472435 C1472435: set-consent re-sent when identity cookie is missing … 1 → 1
C1472436 C1472436: set-consent re-sent when consent cookie is missing … 1 → 1
C1472437 C1472437: Adobe consent v2.0 collect:y unblocks … 1 → 1
C1472438 C1472438: Adobe consent v2.0 collect:n drops … (describe) 1 → 1
C1576777 ❌ kept test.skip stub Was already test.skip in TestCafe 🟢
C1631712 C1631712: … (describe) 1 → 1 result assertion weakened (see 1c)
C225953 C225953: identityMap can be sent … 1 → 1 old checked status only; new also checks body contains "id123" — strengthened 🟢
C25148 C25148: … (describe) 1 → 1
C28754 C28754: setConsent(out) completes with a 200/207 response 1 → 1 old checked payload emptiness; new checks status only (see 1c)
C5594870 ❌ kept test.skip stub Was active P0 🟡

Criterion 1c — Assertion fidelity

🔴 C2660 — defining assertion dropped (consent.spec.js:190-218)

The original test's point is that context is captured at queue time, not at flush time: it changes window.location.hash after queuing the event and asserts requests[0] URL == base URL and requests[1] == ...#foo. The new test (lines 190-218) drops both URL assertions and checks only calls.length == 2.

The comment at line 191-192 says "hash-change portion requires browser navigation" — but window.location.hash = "foo" does not navigate or reload. It fires a hashchange event in-place and is entirely scriptable from Playwright's page context (just as it was in TestCafe with t.eval()). The assertion drop is not env-forced.

Recommendation: restore the URL assertions using calls[0].request.body.events[0].xdm.web.webPageDetails.URL.

🟡 C14414 — name asserts sequentiality; body does not (consent.spec.js:494-529)

Test name: "set-consent requests are sequential and event is blocked by out consent". Body only checks consentCalls.length == 2 and interactCalls.length == 0 (event blocked). The sequentiality assertion — the stated point of C14414 — is gone. The old test used SequentialHook to verify .haveRequestsBeenSequential().

Recommendation: either add an ordering check on request timestamps/order in calls, or rename to reflect what's actually asserted.

🟡 C14410 subtest 2 — named "produces server 400 error" but asserts nothing about 400 (consent.spec.js:423-441)

Body calls setConsent(CONSENT_IN) and returns. The comment at lines 427-431 acknowledges MSW can't replicate EXEG-0102-400 rejection per-purpose, but the test name still says "produces server 400 error." The old test asserted EXEG-0102-400 in the error message.

Recommendation: rename to something like "setting consent for unknown purposes is not validated client-side (server-side EXEG-0102-400 not reproducible under MSW)", or add a note/TODO in the test body.

🟡 C2594 / C1631712 — result eql {}result.propositions/decisions ?? [] toEqual [] (consent.spec.js:174, 871)

Old tests did a strict expect(result).eql({}). New tests check result.propositions ?? [] and result.decisions ?? []. This passes even if alloy returns extra properties, whereas the original confirmed the object was exactly empty. Minor — likely intentional given how alloy's opt-out path evolved — but worth noting the softening.

🟡 C28754 — response payload assertions dropped (consent.spec.js:977-1001)

Old test extracted and checked idSyncs, personalization:decisions, and activation:push payloads were all []. New test checks only HTTP status 200-207. Comment at line 983-986 is honest about the tradeoff. Defensible under MSW, but it's a coverage loss for the "no data handles" guarantee.


Criterion 2 — Skip justification

C1576777 🟢 — Confirmed: old source (C1576777.js) was already test.skip(...). No active behavior lost.

C5594870 🟡 — Old source (C5594870.js) was an active, passing P0 test that navigated to ?adobe_mc=<encoded ECID> and asserted the returned ECID matched. The skip is technically correct (requires real edge to derive ECID from adobe_mc param; MSW mock can't replicate the server-side identity mapping). However, this was an active P0 — the stub should carry a // TODO: track in <ticket> comment pointing to a decision about whether to add an adobe_mc integration scenario at a higher level (e2e or contract test).


Criterion 3 — Mock fidelity

setConsentHandler (handlers.js:202) correctly interprets Adobe 1.0 general=out, Adobe 2.0 collect.val=n, and two IAB out-strings to set generalConsent = "out", then writes the consent cookie accordingly. The sendEventWithIdentityHandler writes a realistic state:store response with MAIN_IDENTITY_COOKIE_NAME, which is required for the reload scenarios in C14407, C1472433-1472436. Both handlers are well-constructed.


Criterion 4 — Timing / flake

All fire-and-forget patterns (queue event → setConsent → await event) correctly await sendEventPromise before count assertions. findCalls uses retries: 10 and minCalls for non-trivial waits (e.g., C14404 line 237-240, C14414 line 517-519). Negative assertions (no interact calls) use synchronous networkRecorder.calls.filter(...) immediately after a synchronous operation — appropriate since there's no async to wait for.


Criterion 5 — Isolation

extend.js auto-fixtures ensure:

  • worker.resetHandlers() after each test (line 41)
  • networkRecorder.reset() before and after each test (lines 47-53)
  • All cookies cleared via cookieStore.deleteAll() before each test (lines 58-62)
  • cleanAlloy() after each test (line 70)

The spec-level beforeEach additionally clears localStorage (line 103) and nukes any document cookies that cookieStore might miss. Isolation is thorough.


Criterion 6 — Hygiene

  • License header: present, year 2026 ✅
  • vitest.projects.js: glob packages/browser/test/integration/**/*.{test,spec}.?(c|m)[jt]s?(x) already covers consent.spec.js; no registration needed ✅
  • test.meta tags: not used in Vitest integration suite (confirmed across all migrated specs) ✅
  • Comments: predominantly why-not-what; the workaround comments on sendEventWithIdentityHandler and MAIN_IDENTITY_COOKIE_NAME are appropriately explanatory ✅
  • Deletion alignment: 21 active files deleted, 2 skip-stubs (C1576777.js, C5594870.js) kept, 9 IAB files untouched (out of scope) ✅

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