Skip to content

Migrated Data Collector functional tests to Vitest+Playwright+MSW#1539

Draft
carterworks wants to merge 38 commits into
migrate-integration/00-infrafrom
migrate-integration/07-data-collector
Draft

Migrated Data Collector functional tests to Vitest+Playwright+MSW#1539
carterworks wants to merge 38 commits into
migrate-integration/00-infrafrom
migrate-integration/07-data-collector

Conversation

@carterworks

@carterworks carterworks commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Changed Packages

  • core
  • reactor-extension

Description

Migrates the Data Collector functional tests to the new Vitest+Playwright+MSW harness. C455258, C8118, and C9369211 are preserved as documented test.skip (sendBeacon not interceptable by MSW in browser mode). C81182 is preserved as test.skip (originally skipped in TestCafe).

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/Data Collector/C2592.js
  • packages/browser/test/functional/specs/Data Collector/C75372.js
  • packages/browser/test/functional/specs/Data Collector/C225010.js
  • packages/browser/test/functional/specs/Data Collector/C1715149.js
  • packages/browser/test/functional/specs/Data Collector/C8119.js
  • packages/browser/test/functional/specs/Data Collector/C81181.js
  • packages/browser/test/functional/specs/Data Collector/C81182.js
  • packages/browser/test/functional/specs/Data Collector/C81183.js
  • packages/browser/test/functional/specs/Data Collector/C81184.js
  • packages/browser/test/functional/specs/Data Collector/C11693274.js
  • packages/browser/test/functional/specs/Data Collector/C455258.js
  • packages/browser/test/functional/specs/Data Collector/C8118.js
  • packages/browser/test/functional/specs/Data Collector/C9369211.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: 2689764

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 adds a new Vitest+Playwright+MSW integration spec that replaces 13 TestCafe functional tests for the Data Collector module. Four tests are deliberately skipped because sendBeacon cannot be intercepted by MSW in browser mode; the remaining tests cover sendEvent, click collection, consent handling, getLinkDetails, and onBeforeEventSend/onBeforeLinkClickSend callbacks.

  • All 9 active test cases wire up the MSW worker, configure an Alloy instance, and assert against networkRecorder or console spies; the fixture teardown in extend.js handles worker.resetHandlers() and networkRecorder.reset() automatically.
  • Several test-isolation issues carried over from the initial implementation have been noted in prior review rounds and are tracked there: DOM elements appended to document.body never removed, window.___getLinkDetails not deleted by cleanAlloy(), consoleSpy/unhandledrejection listener not cleaned up on unhappy path in C225010, and the C81183 third test's misleading name / unused result variable.

Confidence Score: 4/5

Safe to merge for its stated purpose; the new spec adds test coverage without touching production code, and the skipped tests are correctly documented.

The change is test-only and introduces no production code risk. Prior review rounds identified real test-isolation defects (DOM leakage, stale window globals, uncleaned spies) that remain open; those issues can cause non-deterministic failures across the suite but don't block the migration from landing. No new blocking issues were found in this pass.

packages/browser/test/integration/specs/Data Collector/dataCollector.spec.js — the open test-isolation issues flagged in previous threads (DOM cleanup, window.___getLinkDetails, consoleSpy teardown) should be addressed before or alongside this file.

Important Files Changed

Filename Overview
packages/browser/test/integration/specs/Data Collector/dataCollector.spec.js New integration spec migrating 13 Data Collector functional tests to Vitest+Playwright+MSW. Four tests are correctly skipped (sendBeacon not interceptable). Issues flagged in prior review rounds include DOM element accumulation, window.___getLinkDetails leaking across tests, consoleSpy not restored on unhappy paths, and a misleading third C81183 test name with an unused result variable.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph Fixture["Test Fixture (extend.js, auto:true)"]
        direction TB
        NR_RESET["networkRecorder.reset()"]
        WORKER_START["worker.start() (once)"]
        SETUP_BASE["setupBaseCode()"]
        SETUP_ALLOY["setupAlloy()"]
        USE["yield alloy to test"]
        CLEAN["cleanAlloy() (deletes __alloyMonitors, alloy)"]
        WORKER_RESET["worker.resetHandlers()"]
        NR_RESET2["networkRecorder.reset()"]
    end
    subgraph TestBody["Test Body"]
        WORKER_USE["worker.use(handler)"]
        CONFIGURE["alloy('configure', ...)"]
        ACTION["sendEvent / link.click()"]
        FIND_CALL["networkRecorder.findCall(pattern)"]
        ASSERT["expect(...)"]
    end
    subgraph MSW["MSW Service Worker"]
        INTERCEPT["Intercept fetch()"]
        RECORD_REQ["networkRecorder.captureRequest()"]
        HANDLE["handler responds"]
        RECORD_RESP["networkRecorder.captureResponse()"]
    end
    NR_RESET --> WORKER_START --> SETUP_BASE --> SETUP_ALLOY --> USE
    USE --> WORKER_USE --> CONFIGURE --> ACTION --> FIND_CALL --> ASSERT
    ASSERT --> CLEAN --> WORKER_RESET --> NR_RESET2
    ACTION -- "fetch to edge" --> INTERCEPT --> RECORD_REQ --> HANDLE --> RECORD_RESP
    FIND_CALL -- "polls calls[]" --> RECORD_RESP
Loading

Reviews (2): Last reviewed commit: "test(integration): migrate data collecto..." | Re-trigger Greptile

Comment on lines +548 to +668
// Skipped: tests that assert collect-vs-interact routing depend on sendBeacon
// interception via MSW, which is not reliably supported in browser mode, and
// this test was a baseline failure in the functional suite.
// See FUNCTIONAL_MIGRATION_PLAN.md §1.
test.skip("C8118 - link click routes to interact (no identity) then collect (identity established)", () => {});

// ---------------------------------------------------------------------------
// C9369211 – sendEvent includes a Referer header
// ---------------------------------------------------------------------------
// Skipped: the collect-endpoint portion of this test uses sendBeacon, which
// MSW cannot intercept in browser mode. The interact portion relies on
// inspecting request headers that MSW/networkRecorder may not surface
// consistently. This was a baseline failure in the functional suite.
// See FUNCTIONAL_MIGRATION_PLAN.md §1.
test.skip("C9369211 - sendEvent includes a Referer header on interact and collect requests", () => {});

// ---------------------------------------------------------------------------
// C81182 – onBeforeLinkClickSend with personalization metric
// ---------------------------------------------------------------------------
// Skipped: all sub-tests in the source functional file are already marked
// test.skip because they require a specific personalization response from the
// live edge that is difficult to reproduce deterministically with MSW mocks.
test.skip("C81182 - onBeforeLinkClickSend interacts with personalization metric on link (source tests skipped)", () => {});

// ---------------------------------------------------------------------------
// C81183 – getLinkDetails monitoring hook
// ---------------------------------------------------------------------------
describe("C81183 - getLinkDetails monitoring hook via __alloyMonitors", () => {
test("getLinkDetails returns correct link info for a visible link element", async ({
alloy,
}) => {
// Install monitor BEFORE configure so onInstanceConfigured fires
window.__alloyMonitors = window.__alloyMonitors || [];
window.__alloyMonitors.push({
onInstanceConfigured(data) {
window.___getLinkDetails = data.getLinkDetails;
},
});

await alloy("configure", {
...alloyConfig,
clickCollectionEnabled: true,
});

const link = document.createElement("a");
link.id = "alloy-link-test";
link.href = "https://example.com/valid.html";
link.textContent = "Test Link";
document.body.appendChild(link);

const result = window.___getLinkDetails(link);
expect(result).toBeTruthy();
expect(result.linkName).toBeTruthy();
expect(result.linkUrl).toContain("example.com");
expect(result.linkType).toBe("exit");
});

test("getLinkDetails returns results even when clickCollectionEnabled is false", async ({
alloy,
}) => {
window.__alloyMonitors = window.__alloyMonitors || [];
window.__alloyMonitors.push({
onInstanceConfigured(data) {
window.___getLinkDetails = data.getLinkDetails;
},
});

await alloy("configure", {
...alloyConfig,
clickCollectionEnabled: false,
});

const link = document.createElement("a");
link.id = "alloy-link-test-disabled";
link.href = "https://example.com/";
link.textContent = "External Link";
document.body.appendChild(link);

const result = window.___getLinkDetails(link);
expect(result).toBeTruthy();
expect(result.linkName).toBeTruthy();
expect(result.linkType).toBe("exit");
});

test("getLinkDetails returns falsy for an element that would not produce a click event", async ({
alloy,
}) => {
window.__alloyMonitors = window.__alloyMonitors || [];
window.__alloyMonitors.push({
onInstanceConfigured(data) {
window.___getLinkDetails = data.getLinkDetails;
},
});

await alloy("configure", {
...alloyConfig,
clickCollectionEnabled: true,
onBeforeLinkClickSend: (options) => {
const { clickedElement } = options;
if (clickedElement.id === "cancel-alloy-link-test") {
return false;
}
return true;
},
});

const cancelLink = document.createElement("a");
cancelLink.id = "cancel-alloy-link-test";
cancelLink.href = "https://example.com/canceled.html";
cancelLink.textContent = "Canceled Link";
document.body.appendChild(cancelLink);

// getLinkDetails itself doesn't invoke onBeforeLinkClickSend — it returns
// the raw link details regardless of the callback. This just proves the
// monitor hook is accessible and returns a value.
const result = window.___getLinkDetails(cancelLink);
// The result may or may not be defined depending on implementation, but
// the call itself must not throw.
expect(() => window.___getLinkDetails(cancelLink)).not.toThrow();
});
});

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 C81183: window.___getLinkDetails never cleaned up between tests

cleanAlloy() deletes window.__alloyMonitors and window.alloy, but window.___getLinkDetails (set inside each onInstanceConfigured callback) is never removed. If configure fails or onInstanceConfigured does not fire in a later test, the test would silently execute against the getLinkDetails function from the previous alloy instance rather than fail loudly. Adding delete window.___getLinkDetails to the teardown in clean.js would make the dependency explicit.

Comment on lines +625 to +660

const result = window.___getLinkDetails(link);
expect(result).toBeTruthy();
expect(result.linkName).toBeTruthy();
expect(result.linkType).toBe("exit");
});

test("getLinkDetails returns falsy for an element that would not produce a click event", async ({
alloy,
}) => {
window.__alloyMonitors = window.__alloyMonitors || [];
window.__alloyMonitors.push({
onInstanceConfigured(data) {
window.___getLinkDetails = data.getLinkDetails;
},
});

await alloy("configure", {
...alloyConfig,
clickCollectionEnabled: true,
onBeforeLinkClickSend: (options) => {
const { clickedElement } = options;
if (clickedElement.id === "cancel-alloy-link-test") {
return false;
}
return true;
},
});

const cancelLink = document.createElement("a");
cancelLink.id = "cancel-alloy-link-test";
cancelLink.href = "https://example.com/canceled.html";
cancelLink.textContent = "Canceled Link";
document.body.appendChild(cancelLink);

// getLinkDetails itself doesn't invoke onBeforeLinkClickSend — it returns

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 C81183 third test: name does not match assertion

The test is titled "getLinkDetails returns falsy for an element that would not produce a click event", but the only assertion is expect(() => window.___getLinkDetails(cancelLink)).not.toThrow(). The comment directly contradicts the name ("The result may or may not be defined"). The variable result on line 649 is also unused. Either the test should assert the return value or the name and comment should be updated to accurately describe what is being verified.

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 +162 to +200

test("callback returning false cancels the event and sendEvent resolves with {}", async ({
alloy,
worker,
networkRecorder,
}) => {
worker.use(sendEventHandler);

let callbackInvoked = false;

const consoleSpy = vi.spyOn(console, "info");

await alloy("configure", {
...alloyConfig,
debugEnabled: true,
onBeforeEventSend: () => {
callbackInvoked = true;
return false;
},
});

const result = await alloy("sendEvent");

expect(callbackInvoked).toBe(true);
expect(result).toEqual({});

const interactCalls = networkRecorder.calls.filter((c) =>
/v1\/interact/.test(c.request?.url ?? ""),
);
expect(interactCalls.length).toBe(0);

expect(searchForLogMessage(consoleSpy, "Event was canceled")).toBe(true);

consoleSpy.mockRestore();
});
});

// ---------------------------------------------------------------------------
// C8119 – Click collection disabled: link click does not send an event

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 DOM elements appended to document.body are never removed

Every test that creates an <a> element appends it to document.body but does not remove it in a teardown. Multiple tests reuse id="alloy-link-test", so after a few tests have run there are several elements with the same ID in the document — invalid HTML that some browsers de-duplicate in unexpected ways. Because click collection is listening on document, accumulated anchors from prior tests remain live targets. Adding an afterEach (or using document.body.innerHTML = "" in the existing alloy fixture's cleanup) would prevent this drift across the suite.

@carterworks carterworks force-pushed the migrate-integration/06-config-overrides branch from fe8d1b8 to 15655d5 Compare June 12, 2026 17:31
@carterworks carterworks force-pushed the migrate-integration/07-data-collector branch from 758f9f6 to 21d9792 Compare June 12, 2026 17:31
This was referenced Jun 12, 2026
@carterworks carterworks force-pushed the migrate-integration/06-config-overrides branch from 15655d5 to c1b8db1 Compare June 12, 2026 17:56
@carterworks carterworks force-pushed the migrate-integration/07-data-collector branch from 21d9792 to e563a97 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 and others added 26 commits June 26, 2026 14:20
The third C81183 test only asserted not.toThrow(), silently dropping the
coverage from original test 2 which verified getLinkDetails returns no
meaningful data for a non-existent (null) element.

Restore it: call getLinkDetails(null) and assert linkName, linkType, and
linkUrl are all undefined, matching the original eql({linkName: undefined,
...}) assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The original C81181 functional file had 8 tests; the initial migration
included only 3. The 5 dropped tests do not depend on sendBeacon/collect
endpoint interception — they are deferred scope, not blocked.

Add test.skip stubs so the gap is documented rather than silently blessed.

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

Adds a permanent navigator.sendBeacon recorder (installed before the library
loads, since alloy binds sendBeacon once at instance creation) so collect-vs-
interact routing can be observed in-page — the same approach the functional
suite used. Keeps the suite hermetic: the recorder returns true and no request
hits the network. Adds sendEventWithIdentityHandler to establish identity from
the interact response so the routing transition can be exercised.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verified empirically that networkRecorder never captures the Referer header:
it is a browser-set forbidden header added after MSW's service worker sees the
request. Replaces the speculative skip rationale with the confirmed reason.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes comments that duplicated the NO_REQUEST_WAIT_MS constant's rationale or
restated their assertion, condenses the verbose ones, and corrects the C8118
skip note (collect routing is now feasible via the sendBeacon recorder, not
blocked as the stale comment claimed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The functional original sent a datasetId and asserted meta.configOverrides,
but its `.event` key was undefined so the check was a no-op. Restores the
meaningful assertion: the datasetId propagates to
meta.configOverrides.com_adobe_experience_platform.datasets.event.datasetId.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the functional original, which pinned the entire webInteraction object;
the migration had dropped name, region, and linkClicks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The functional original pinned the whole webInteraction object; the migration
checked only the augmented name. Adds region/type/linkClicks and a loose URL
origin check (the URL resolves against the localhost test page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Note that the collect-side referer was already a TODO in the functional source
and that collect routing is covered by C455258, rather than implying C2592
covers everything.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Moves DOM cleanup and the sendBeacon-recorder reset out of the spec and into
the shared alloy fixture, and the ___getLinkDetails teardown into cleanAlloy
(next to __alloyMonitors). installSendBeaconRecorder now only installs; the
fixture owns the per-test reset. Removes the unusual top-level afterEach from
the spec and documents why the recorder install lives in setupBaseCode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The __alloyMonitors install was copy-pasted in all three C81183 tests. A
beforeEach installs it once; cleanAlloy already clears it between tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Groups the three loose top-level test.skip stubs into a "Not migrated" describe
so they read as intentional, and replaces the inline setTimeout promises with
the existing waitFor util.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration only counted one sendBeacon call. Restore the original's intent
by asserting the beacon hit /v1/collect and carries a valid events payload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The loose /v1\/interact/ match would count any URL containing that
substring. Anchor it to the edge host and the configId query so a wrong host
or malformed path is not mistaken for a valid interact call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add appendHtmlToBody so anchor structures from the functional originals (spans,
download/data-* attributes, custom-region wrappers) can be built as-is, and a
preventNavigation option on clickLink so a test can let real hash navigation
proceed. Default click behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The migration clicked via the navigation-preventing clickLink, so a regression
that blocked default navigation would have passed. Let the click navigate and
assert the hash changed, matching the original's getLocation().contains(#foo).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The migration loosened these to truthy/contains and changed the link to
external (exit). Restore the original's exact internal-link pinning —
linkName, linkRegion, linkType:other, pageIDType, resolved linkUrl and
pageName (adapted to the localhost page) — and assert all five fields are
undefined for a null element.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The migration only checked the augmented webInteraction name plus customField.
Restore the original's full coverage: eventType, the complete webInteraction
object, and the activity-map data (whose link stays "Test Link" since the
callback augmented only the xdm). Adds reusable link-assertion helpers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These were stubbed as skips; none depends on sendBeacon, so port them: no-callback
collection, conditional cancel via onBeforeLinkClickSend and via filterClickDetails,
filterClickDetails augmentation, and sessionStorageEnabled:false. Each pins the full
webInteraction (and activity map where the original did), adapted to localhost URLs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the wholesale C8118 skip with the full suite: the interact-then-collect
routing case (via the sendBeacon recorder once the interact response establishes
identity) plus the 14 interact-only cases — enabled/disabled download, internal
and external links, event grouping, cached click on page view, custom region,
custom link type, custom activity map, multiple grouped clicks, and custom XDM.
URLs are adapted to the localhost page; the grouped-clicks payload shape stays
tolerant, matching the original's hedge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer flagged the global sendBeacon override as potentially masking
collect/fallback behavior in unrelated specs. Opt-in per spec was attempted and
verified to fail: alloy binds navigator.sendBeacon by value when it creates its
network service at bundle load, before any per-test beforeEach runs, so a later
swap is ignored (the routing assertions saw zero beacons). Document that the
global install is therefore required and that always returning true is the
deliberate hermetic default mirroring the functional suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
For consistency with the other restored assertions, pin the whole
webPageDetails object (URL, name, pageViews) instead of just name and
pageViews. webPageDetails.URL is window.location.href, the same value already
used for the activity-map page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI failed 10 link-click assertions with findCall returning undefined. Unlike the
sendEvent tests, which await the command (so the call is already complete),
clickLink returns before the request is sent, so findCall must outwait the
click → event → fetch → MSW round-trip. Its default (5 retries × 10ms ≈ 50ms)
is too short under CI load — the latent race only surfaced once this PR added
~20 more click tests. Route all click-driven interact lookups through a
findInteractCall helper that polls up to ~2s, returning as soon as the call
completes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the retry-tuned findCall with an event-driven networkRecorder.waitForCall:
it resolves the instant the matching response is captured (driven by the MSW
request/response events the recorder already listens to), so there is no polling
interval to tune and no CI-load race. Link clicks are fire-and-forget — clickLink
returns before the request is sent — so this is the right tool where there is no
command promise to await. Falls back to undefined after a safety timeout so
presence is still asserted with toBeDefined().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@carterworks carterworks force-pushed the migrate-integration/07-data-collector branch from 2719f9c to 276d06e Compare June 26, 2026 20:31
@carterworks

Copy link
Copy Markdown
Collaborator Author

Verdict: Approve with comments — 🟡 Parity on the two rich suites (C8118, C81181) is excellent; the two active skips both have rationale; no test that cleanly ran is silently dropped. Three items warrant a second look: the C2592 domain assertion is environment-forced (🟡 acceptable), C9369211's interact-side test ran and passed but can't be replicated in MSW (🟡 not a silent drop — old file is retained), and a minor getLinkDetails-for-external-link path from C81184 is not re-tested.


Summary table

Criterion Status Note
1a C-file coverage 🟢 13 old → 11 describe + 2 labeled skips
1b Subtest coverage 🟢 All active sub-tests accounted for (see matrix)
1c Assertion fidelity 🟡 C2592 domain weakened; env-forced; documented
2 Skip justification 🟡 C9369211 interact test ran and passed; not a silent drop (old file kept), but rationale should note SW can't read forbidden headers
3 Mock fidelity 🟢 sendEventHandler → no identity cookie (interact stays); sendEventWithIdentityCookieHandler_identity state cookie (routing flips to collect) — correct
4 Timing / flake 🟢 Negative assertions use waitFor(200); fire-and-forget clicks use event-driven waitForCall
5 Isolation 🟢 networkRecorder and worker reset each test; cookies cleared; DOM cleaned; sendBeaconCalls reset
6 Hygiene 🟡 License header present; test.meta SEVERITY/TEST_RUN lost (accepted Vitest tradeoff); deletions align with migrated set; spec auto-picked by integration/**/*.spec.js glob

Parity matrix

Old C-file Describe in new spec Old subtests New active Notes
C2592.js C2592 1 1 domain assertion weakened (see §1c)
C75372.js C75372 1 1 Exact parity
C1715149.js C1715149 3 3 Exact parity
C8119.js C8119 1 1 Exact parity
C81184.js C81184 4 4 Old test 4 (getLinkDetails external-link) is replaced by warning variant; see §1b note
C81181.js C81181 8 8 Exact parity
C11693274.js C11693274 1 1 Exact parity
C225010.js C225010 1 1 Exact parity
C455258.js C455258 1 1 Exact parity
C8118.js C8118 14 14 Exact parity
C81183.js C81183 3 3 Test 3 changed from cancel-alloy-link-test element to null; spirit preserved
C9369211.js "Not migrated" 2 active 0 🟡 interact test ran and passed (see §2)
C81182.js "Not migrated" 3 (all test.skip) 0 🟢 all originals were skipped

Deletion alignment: The --diff-filter=D output lists exactly 11 deleted functional files, matching the 11 active describes. C9369211.js and C81182.js are not deleted — their functional coverage persists if the functional suite runs. Correct.


§1c — Assertion fidelity

C2592 domain (dataCollector.spec.js:110):

// Old (testcafe):  await t.expect(request.meta.state.domain).ok();
// New (vitest):    expect(typeof body.meta.state.domain).toBe("string");

Old .ok() requires the value to be truthy, which "" (empty string on localhost) would fail. The weakening to typeof === "string" is environment-forced: localhost returns domain: "" and any stronger check would always fail in the MSW/localhost environment. The comment at line 109 documents the reason. 🟡 Acceptable; documented.

configOverrides shape (dataCollector.spec.js:101-107): The old test asserted the entire com_adobe_experience_platform.event object; the new test only asserts datasets.event.datasetId. This is a minor scope reduction — not a hardening regression, but the old test verified the full overrides round-trip. Worth noting but not a blocker.


§2 — Skip justification

C9369211 (dataCollector.spec.js:1263): The skip comment says "forbidden header ... MSW can't capture it (verified)." The interact-side original test ran and passed against real edge (referer === TEST_PAGE). The collect-side test was already a TODO — the header check was commented out in the functional source itself.

The rationale is plausible: Referer is a forbidden request header that browsers set after the service worker intercepts the request, so the SW never sees it in request.headers. However: the Request property request.referrer (distinct from the Referer header) might be capturable — this wasn't explored in the PR. Since the old file is not deleted, functional coverage survives. 🟡 should-note — the "interact-side test ran and passed" provenance should be explicit in the skip rationale, and request.referrer (the property) could be evaluated as a partial alternative.

C81182 (dataCollector.spec.js:1267): All three originals were test.skip in the functional source. 🟢 Justified.


§3 — Mock fidelity

sendEventHandler → returns sendEventResponse.json which includes identity:result (ECID) and state:store containing only kndctr_..._cluster (no _identity cookie). This means clicks on the first interaction correctly stay on interact, not collect. Good.

sendEventWithIdentityCookieHandler → returns sendEventWithIdentityCookieResponse.json which includes state:store with kndctr_..._identity (the 34-day identity cookie). This correctly causes the SDK to flip subsequent documentUnloading calls to sendBeacon/collect. The C455258 and C8118 routing assertions depend on this behavior and the mock faithfully replicates it.

setConsentHandler → full consent-out logic implemented, sets general=out cookie. Adequate for C225010.


§4 — Timing / flake

Negative assertions ("no request fired") all use waitFor(NO_REQUEST_WAIT_MS) where NO_REQUEST_WAIT_MS = 200 (dataCollector.spec.js:39). The constant is named and documented at line 38. Good.

Fire-and-forget link-click positives use findInteractCallnetworkRecorder.waitForCall(/v1\/interact/) which is event-driven (resolves on notifyWaiters, not retry-polling). The waitForCall has a 5 s timeout (line 214 of networkRecorder.js), which is generous. This is the right approach and avoids the flake that plagued prior iterations.

One pattern in C8118's second click test uses expect.poll(() => sendBeaconCalls().length).toBe(1) (line 790) — this is correct for sendBeacon which doesn't produce a network event, so polling is the only available signal.


§5 — Isolation

  • networkRecorder.reset() called before and after each test via the networkRecorder fixture (extend.js:49-55).
  • worker.resetHandlers() called after each test (extend.js:43), preventing handler bleed.
  • Cookies cleared via cookieStore.getAll() + cookieStore.delete() before each alloy setup (extend.js:61-64).
  • cleanupDom() called after each test (extend.js:74).
  • resetSendBeaconCalls() called in alloy fixture setup (extend.js:68).
  • Tests that do inline resets (C455258, C8118 line 787-788) are correct — they reset mid-test after establishing identity.

Clean.


§6 — Hygiene

  • License header: present, year 2026. ✅
  • test.meta (SEVERITY/TEST_RUN): dropped across all new specs — the Vitest framework has no equivalent mechanism. Accepted tradeoff for the whole migration; flag once. 🟡
  • Spec registration: vitest.projects.js uses a glob packages/browser/test/integration/**/*.{test,spec}.?(c|m)[jt]s?(x) — the new file is auto-included. ✅
  • Comments: explain why (environment constraints, fire-and-forget semantics, sendBeacon binding timing). Follow the style guide. ✅
  • C81183 old test 3 used cancel-alloy-link-test (a valid element with onBeforeLinkClickSend returning false); new test 3 uses null. Both exercise the "no-link element → undefined fields" path. The old test 3 also contained a positive case (alloy-link-test → full details), but that's covered by tests 1 and 2. Equivalent coverage. ✅
  • Old C81184 test 4 (getLinkDetails works regardless of clickCollectionEnabled) tested an external link (linkType: "exit"). The new spec covers the spirit via C81183 test 2 (internal link, clickCollectionEnabled: false) and C8118 external-link click tests, but the hook-API (window.___getLinkDetails) is only exercised for internal links. Minor gap — exit type in the hook path isn't explicitly tested. 🟡 Minor.

Recommendation

Approve with the following comments:

  1. 🟡 C9369211 skip comment — add "the interact-side functional test (C9369211.js) ran and passed against real edge; the Referer header is browser-forbidden and added after MSW's SW intercepts the request. request.referrer (the property) was not evaluated as an alternative."
  2. 🟡 C2592 configOverrides scope — consider also asserting body.meta.configOverrides.com_adobe_experience_platform.datasets.event.datasetId matches datasetId (already done, line 104–107) — this is actually present; the reduced assertion vs. old (full event object) is cosmetic.
  3. 🟡 Minor — C81184 old test 4 (external link via getLinkDetails hook) has no direct counterpart. Either add a test or note it as out-of-scope.

No blockers. The migration is solid.


This is a PR comment, not a formal approval or request-for-changes review.

Rebasing onto 00-infra collided 07's sendEventWithIdentityHandler with the
identically named handler main added (used by platformServicesCookieWiring).
07's handler was renamed to sendEventWithIdentityCookieHandler during conflict
resolution; the C8118 link-click test still referenced the old name.
@carterworks carterworks force-pushed the migrate-integration/07-data-collector branch from c685d7a to 2689764 Compare June 26, 2026 22:38
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