Skip to content

Commit 182bfdb

Browse files
committed
Ensure beacon collectors are reattached after init
1 parent 06db0f4 commit 182bfdb

File tree

4 files changed

+94
-19
lines changed

4 files changed

+94
-19
lines changed

src/beacon.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type BeaconOptions = {
1919
};
2020

2121
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22-
type CollectorFunction = (config: UserConfig) => any;
22+
export type CollectorFunction = (config: UserConfig) => any;
2323

2424
const sendBeaconFallback = (url: string | URL, data?: BodyInit | null) => {
2525
const xhr = new XMLHttpRequest();

src/lux.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { Beacon, BeaconMetricKey, fitUserTimingEntries, shouldReportValue } from "./beacon";
1+
import {
2+
Beacon,
3+
BeaconMetricKey,
4+
CollectorFunction,
5+
fitUserTimingEntries,
6+
shouldReportValue,
7+
} from "./beacon";
28
import onPageLoad from "./beacon-triggers/page-load";
39
import * as Config from "./config";
410
import { BOOLEAN_TRUE, END_MARK, START_MARK } from "./constants";
@@ -121,17 +127,7 @@ LUX = (function () {
121127
// on the same page.
122128
let _thisCustomerId = LUX.customerid;
123129

124-
const initPostBeacon = () => {
125-
return new Beacon({
126-
config: globalConfig,
127-
logger,
128-
customerId: getCustomerId(),
129-
sessionId: gUid,
130-
pageId: gSyncId,
131-
});
132-
};
133-
134-
let beacon = initPostBeacon();
130+
const beaconCollectors: [BeaconMetricKey, CollectorFunction][] = [];
135131

136132
const logEntry = <T extends PerformanceEntry>(entry: T) => {
137133
logger.logEvent(LogEvent.PerformanceEntryReceived, [entry]);
@@ -157,7 +153,7 @@ LUX = (function () {
157153
LCP.processEntry(entry);
158154
})
159155
) {
160-
beacon.addCollector(BeaconMetricKey.LCP, LCP.getData);
156+
beaconCollectors.push([BeaconMetricKey.LCP, LCP.getData]);
161157
}
162158

163159
if (
@@ -166,7 +162,7 @@ LUX = (function () {
166162
logEntry(entry);
167163
})
168164
) {
169-
beacon.addCollector(BeaconMetricKey.CLS, CLS.getData);
165+
beaconCollectors.push([BeaconMetricKey.CLS, CLS.getData]);
170166
}
171167

172168
if (
@@ -175,7 +171,7 @@ LUX = (function () {
175171
logEntry(entry);
176172
})
177173
) {
178-
beacon.addCollector(BeaconMetricKey.LoAF, LoAF.getData);
174+
beaconCollectors.push([BeaconMetricKey.LoAF, LoAF.getData]);
179175
}
180176

181177
const handleINPEntry = (entry: PerformanceEventTiming) => {
@@ -220,12 +216,30 @@ LUX = (function () {
220216
{ durationThreshold: 0 },
221217
)
222218
) {
223-
beacon.addCollector(BeaconMetricKey.INP, INP.getData);
219+
beaconCollectors.push([BeaconMetricKey.INP, INP.getData]);
224220
}
225221
} catch (e) {
226222
logger.logEvent(LogEvent.PerformanceObserverError, [e]);
227223
}
228224

225+
const initPostBeacon = () => {
226+
const b = new Beacon({
227+
config: globalConfig,
228+
logger,
229+
customerId: getCustomerId(),
230+
sessionId: gUid,
231+
pageId: gSyncId,
232+
});
233+
234+
beaconCollectors.forEach(([metric, collector]) => {
235+
b.addCollector(metric, collector);
236+
});
237+
238+
return b;
239+
};
240+
241+
let beacon = initPostBeacon();
242+
229243
if (_sample()) {
230244
logger.logEvent(LogEvent.SessionIsSampled, [globalConfig.samplerate]);
231245
} else {

tests/integration/post-beacon/cls.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ test.describe("POST beacon CLS", () => {
2222
});
2323

2424
test("CLS is reset between SPA page transitions", async ({ page }) => {
25+
const layoutShiftsSupported = await entryTypeSupported(page, "layout-shift");
2526
const luxRequests = new RequestInterceptor(page).createRequestMatcher("/store/");
2627
await page.goto("/layout-shifts.html?noShiftDelay&injectScript=LUX.auto=false;");
2728
await luxRequests.waitForMatchingRequest(() => page.evaluate(() => LUX.send()));
2829
let b = luxRequests.get(0)!.postDataJSON() as BeaconPayload;
29-
const layoutShiftsSupported = await entryTypeSupported(page, "layout-shift");
3030

3131
if (layoutShiftsSupported) {
3232
const responseEnd = await getNavigationTimingMs(page, "responseEnd");
@@ -41,6 +41,12 @@ test.describe("POST beacon CLS", () => {
4141
await luxRequests.waitForMatchingRequest(() => page.evaluate(() => LUX.send()));
4242

4343
b = luxRequests.get(1)!.postDataJSON() as BeaconPayload;
44-
expect(b.cls).toBeUndefined();
44+
45+
if (layoutShiftsSupported) {
46+
expect(b.cls!.value).toEqual(0);
47+
expect(b.cls!.startTime).toEqual(null);
48+
} else {
49+
expect(b.cls).toBeUndefined();
50+
}
4551
});
4652
});

tests/integration/post-beacon/loaf.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,61 @@ test.describe("POST beacon LoAF", () => {
2525
}
2626
});
2727

28+
test("LoAFs are reset between SPA page transitions", async ({ page }) => {
29+
const loafSupported = await entryTypeSupported(page, "long-animation-frame");
30+
const luxRequests = new RequestInterceptor(page).createRequestMatcher("/store/");
31+
await page.goto("/long-animation-frames.html?injectScript=LUX.auto=false;", {
32+
waitUntil: "networkidle",
33+
});
34+
await luxRequests.waitForMatchingRequest(() => page.evaluate(() => LUX.send()));
35+
let b = luxRequests.get(0)!.postDataJSON() as BeaconPayload;
36+
37+
// First beacon has LoAFs
38+
if (loafSupported) {
39+
const loaf = b.loaf!;
40+
expect(loaf.totalBlockingDuration).toBeGreaterThan(0);
41+
expect(loaf.totalDuration).toBeGreaterThan(0);
42+
expect(loaf.totalEntries).toBeGreaterThan(0);
43+
expect(loaf.totalStyleAndLayoutDuration).toBeGreaterThan(0);
44+
expect(loaf.totalWorkDuration).toBeGreaterThan(0);
45+
expect(loaf.entries.length).toBeGreaterThan(0);
46+
expect(loaf.scripts.length).toBeGreaterThan(0);
47+
} else {
48+
expect(b.loaf).toBeUndefined();
49+
}
50+
51+
// Second beacon has no LoAFs
52+
await page.evaluate(() => LUX.init());
53+
await page.waitForTimeout(200);
54+
await luxRequests.waitForMatchingRequest(() => page.evaluate(() => LUX.send()));
55+
b = luxRequests.get(1)!.postDataJSON() as BeaconPayload;
56+
57+
if (loafSupported) {
58+
const loaf = b.loaf!;
59+
expect(loaf.totalDuration).toEqual(0);
60+
expect(loaf.entries.length).toEqual(0);
61+
expect(loaf.scripts.length).toEqual(0);
62+
} else {
63+
expect(b.loaf).toBeUndefined();
64+
}
65+
66+
// Third beacon has LoAFs again
67+
await page.evaluate(() => LUX.init());
68+
await page.locator("#create-long-task").click();
69+
await page.waitForTimeout(50);
70+
await luxRequests.waitForMatchingRequest(() => page.evaluate(() => LUX.send()));
71+
b = luxRequests.get(2)!.postDataJSON() as BeaconPayload;
72+
73+
if (loafSupported) {
74+
const loaf = b.loaf!;
75+
expect(loaf.totalDuration).toBeGreaterThan(0);
76+
expect(loaf.entries.length).toBeGreaterThan(0);
77+
expect(loaf.scripts.length).toBeGreaterThan(0);
78+
} else {
79+
expect(b.loaf).toBeUndefined();
80+
}
81+
});
82+
2883
test("LoAFs are collected as INP attribution", async ({ page }) => {
2984
const luxRequests = new RequestInterceptor(page).createRequestMatcher("/store/");
3085
await page.goto("/long-animation-frames.html", { waitUntil: "networkidle" });

0 commit comments

Comments
 (0)