Skip to content

Commit 182cbdb

Browse files
authored
refactor(playwright): extract browser-side evaluate callbacks (#8060)
Playwright's `page.evaluate(fn)` stringifies the callback with `fn.toString()` and ships the source into the chromium worker, where it runs with no access to the Node.js module graph. That is normally fine, but it breaks the moment the calling file is instrumented by NYC: the serialized body references NYC's counter globals (`cov_xxxx.f[0]++`, etc.) which do not exist in the browser, so the evaluate fails at runtime with `ReferenceError: cov_xxxx is not defined`. The symptom is silent because Playwright surfaces the browser error asynchronously — tests just start flapping. Three coordinated pieces close the foot-gun for the whole codebase: 1. `packages/datadog-instrumentations/src/playwright-browser-scripts.js` is a new file that holds the two inline callbacks (`detectRum` and `stopRumSession`) that used to live in `playwright.js`. The file is required by the instrumentation and the two function values are passed through to `page.evaluate(...)` instead of anonymous arrows. 2. `nyc.config.js` adds `**/*-browser-scripts.js` to the exclusion list, so NYC never instruments these files. Renaming the file pattern requires updating both this glob and the eslint rule below — the in-source comment calls that out. 3. `eslint.config.mjs` adds a targeted `no-restricted-syntax` rule that errors when any `.evaluate(<inline function>)` call appears anywhere in the repo, pointing the author at the `*-browser-scripts.js` convention. Without the lint rule this class of bug would creep right back in the next time someone inlines a callback for convenience. Behavioral parity with the old code is intentional — both helpers return the exact same shapes they did when they were inline. Only the call sites change.
1 parent 734ceff commit 182cbdb

4 files changed

Lines changed: 44 additions & 17 deletions

File tree

eslint.config.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,17 @@ export default [
463463
'eslint-rules/eslint-env-aliases': 'error',
464464
'eslint-rules/eslint-log-printf-style': 'error',
465465

466+
// Inline `.evaluate(<fn>)` callbacks (Playwright/Puppeteer) are serialized with
467+
// `toString()` and run in chromium — coverage counters inside would ReferenceError.
468+
'no-restricted-syntax': ['error', {
469+
selector:
470+
"CallExpression[callee.property.name='evaluate']" +
471+
":matches([arguments.0.type='ArrowFunctionExpression'], [arguments.0.type='FunctionExpression'])",
472+
message:
473+
'Move the inline `.evaluate(...)` callback into a `*-browser-scripts.js` file ' +
474+
'(NYC-excluded in nyc.config.js) and import it here.',
475+
}],
476+
466477
'n/no-restricted-require': ['error', [
467478
...GLOBAL_RESTRICTED_REQUIRES,
468479
{

nyc.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module.exports = {
3030
],
3131
exclude: [
3232
'**/.bun/**',
33+
'**/*-browser-scripts.js', // Serialized into browsers; coverage counters would ReferenceError.
3334
'**/*.spec.*',
3435
'**/fixtures/**',
3536
'**/integration-tests/**',
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict'
2+
3+
// Serialized into chromium via Playwright's `page.evaluate`. Excluded from coverage by filename.
4+
// Rename only if you update that glob too.
5+
6+
/** @returns {{ isRumInstrumented: boolean, isRumActive: boolean, rumSamplingRate: number | null }} */
7+
function detectRum () {
8+
const isRumInstrumented = !!window.DD_RUM
9+
const isRumActive = window.DD_RUM && window.DD_RUM.getInternalContext
10+
? !!window.DD_RUM.getInternalContext()
11+
: false
12+
const rumSamplingRate = window.DD_RUM && window.DD_RUM.getInitConfiguration
13+
? window.DD_RUM.getInitConfiguration().sessionSampleRate
14+
: null
15+
return { isRumInstrumented, isRumActive, rumSamplingRate }
16+
}
17+
18+
/** @returns {boolean} */
19+
function stopRumSession () {
20+
if (window.DD_RUM && window.DD_RUM.stopSession) {
21+
window.DD_RUM.stopSession()
22+
return true
23+
}
24+
return false
25+
}
26+
27+
module.exports = { detectRum, stopRumSession }

packages/datadog-instrumentations/src/playwright.js

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ let applyRepeatEachIndex = null
5252

5353
let startedSuites = []
5454

55+
// Browser-side callbacks live in a coverage-excluded file so coverage counters can't reach chromium.
56+
const { detectRum, stopRumSession } = require('./playwright-browser-scripts')
57+
5558
const STATUS_TO_TEST_STATUS = {
5659
passed: 'pass',
5760
failed: 'fail',
@@ -1117,16 +1120,7 @@ addHook({
11171120

11181121
try {
11191122
if (page) {
1120-
const { isRumInstrumented, isRumActive, rumSamplingRate } = await page.evaluate(() => {
1121-
const isRumInstrumented = !!window.DD_RUM
1122-
const isRumActive = window.DD_RUM && window.DD_RUM.getInternalContext
1123-
? !!window.DD_RUM.getInternalContext()
1124-
: false
1125-
const rumSamplingRate = window.DD_RUM && window.DD_RUM.getInitConfiguration
1126-
? window.DD_RUM.getInitConfiguration().sessionSampleRate
1127-
: null
1128-
return { isRumInstrumented, isRumActive, rumSamplingRate }
1129-
})
1123+
const { isRumInstrumented, isRumActive, rumSamplingRate } = await page.evaluate(detectRum)
11301124
if (isRumInstrumented && rumSamplingRate < 100 && !isRumActive) {
11311125
log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
11321126
}
@@ -1209,13 +1203,7 @@ addHook({
12091203
fn: async function ({ page }) {
12101204
try {
12111205
if (page) {
1212-
const isRumActive = await page.evaluate(() => {
1213-
if (window.DD_RUM && window.DD_RUM.stopSession) {
1214-
window.DD_RUM.stopSession()
1215-
return true
1216-
}
1217-
return false
1218-
})
1206+
const isRumActive = await page.evaluate(stopRumSession)
12191207

12201208
if (isRumActive) {
12211209
// Give some time RUM to flush data, similar to what we do in selenium

0 commit comments

Comments
 (0)