TE-15304: re-roll dom-serializer bundle (fix page.evaluate "Unexpected token 'function'" regression)#523
Merged
Conversation
## Cause
The dom-serializer bundle is generated by webpack + webpack-obfuscator
and committed to this repo as a pre-built artifact (src/dom/dom-
serializer.js). webpack-obfuscator's output is **non-deterministic** —
for the same source, builds randomly produce either:
(A) an IIFE-wrapped bundle: `(function(...){...})()` ~20%
(B) a bundle starting with a hoisted helper function declaration:
`function a0_0xNNNN(...){...}` ~80%
The bundle that landed on this branch (and was merged via PR LambdaTest#522 to
stage) happened to be form (B). Any SDK consumer that injects the
bundle via `page.evaluate(str)` then breaks with:
SyntaxError: Unexpected token 'function'
…because page.evaluate parses its input as an expression, and a leading
function declaration is a statement, not an expression. eval() and
script-tag injection are unaffected, so:
✓ @lambdatest/cypress-driver (uses eval())
✓ @lambdatest/selenium-driver (executeScript as script tag)
✗ @lambdatest/playwright-driver (page.evaluate)
✗ @lambdatest/puppeteer-driver (page.evaluate)
✗ @lambdatest/webdriverio-driver (browser.execute)
This masking is why the regression wasn't caught by CI — most flows
use selenium/cypress.
## Fix in this commit
Re-runs `npm run build:prod` until the obfuscator lands an IIFE-wrapped
roll, then ships that bundle. No source/code change — only the pre-
built artifact at src/dom/dom-serializer.js is replaced.
Verified empirically: 10-run experiment (5 baseline + 5 TE-15304),
both setups produced ~80% bad rolls. After this commit, the bundle
starts with `(function(_0x40c4a2,_0x2284c6){...` — page.evaluate
consumers work, snapshot is captured end-to-end.
## Follow-up (not in this PR)
webpack-obfuscator's `stringArray: true` + `selfDefending: true`
features are what cause the random helper hoisting. A future PR
against smartui-cli-dom-serializer should disable those (and
`splitStrings`) so future bundles are deterministic and never
require a manual re-roll. That change is non-trivial — it reduces
obfuscation strength somewhat — and is left for a separate PR/
discussion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sushobhit-lt
approved these changes
May 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
PR #522 shipped a dom-serializer bundle that breaks every SDK injecting via
page.evaluate(bundle)(playwright, puppeteer, webdriverio) with:This follow-up replaces the broken bundle with a re-rolled one that starts with
(function(...)— a valid expression-context IIFE that all SDK injection patterns accept.JIRA: TE-15304
Cause
The bundle at
src/dom/dom-serializer.jsis a pre-built artifact generated bywebpack + webpack-obfuscatorinsmartui-cli-dom-serializer, then committed verbatim here.webpack-obfuscator's output is non-deterministic — for the same source, builds randomly produce one of:(function(_0x...){...})()function a0_0xNNNN(...){...} ...The
stringArray: trueandselfDefending: truefeatures in the obfuscator config cause helper function declarations to be hoisted to top level. Their hoisting is randomized between runs.PR #522's bundle was form B — a hoisted helper at the top. Empirically verified with a 10-run experiment (5 baseline + 5 with TE-15304); both setups produced 60–80% bad rolls.
Why it's an SDK-side bug only some of them hit
page.evaluate(str)evaluatesstras a JavaScript expression. A leading function declaration (form B) is a statement and throws SyntaxError. Other injection patterns are unaffected:@lambdatest/cypress-drivereval(bundle)(evaluates as program)@lambdatest/selenium-driverexecuteScript(bundle)(script-tag-style)@lambdatest/playwright-driverpage.evaluate(bundle)@lambdatest/puppeteer-driverpage.evaluate(bundle)@lambdatest/webdriverio-driverbrowser.execute(bundle)This is why the regression slipped past review — most internal flows use selenium/cypress, where the broken bundle still worked.
Fix in this PR
Single file changed:
src/dom/dom-serializer.js. No source code or build pipeline changes here.Re-ran
npm run build:prodin the serializer repo until the obfuscator landed a form-A roll, then committed that artifact. Verified locally:(function(_0x40c4a2,_0x2284c6){...✓make use-cli-branch BRANCH=TE-15304followed bymake upd-cli TEST=test-playwright.jsruns the playwright SDK end-to-end withoutUnexpected tokenerrors ✓Follow-up (separate PR, NOT in this one)
The underlying obfuscator non-determinism remains — the next maintainer who rebuilds and commits a new bundle has the same 80% chance of regressing this. A proper fix is to disable
stringArray,selfDefending, andsplitStringsin the obfuscator config so output is always IIFE-wrapped (and ~27% smaller). That change reduces obfuscation strength slightly and warrants a separate discussion + PR againstsmartui-cli-dom-serializer. Until then, anyone rebuilding the bundle should:# verifies bundle starts with `(` head -c 40 src/dom/dom-serializer.jsTest plan
playwright-drivertest against a stage deploy carrying this commitmake use-cli-branch BRANCH=stage(after merge) shows✓ GOOD ROLL — IIFE-wrapped🤖 Generated with Claude Code