Skip to content

chore(sdk): cap browser heap in huge.spec.ts to catch memory leaks#925

Closed
eugenioenko wants to merge 8 commits intomainfrom
test/heap-cap-huge-spec
Closed

chore(sdk): cap browser heap in huge.spec.ts to catch memory leaks#925
eugenioenko wants to merge 8 commits intomainfrom
test/heap-cap-huge-spec

Conversation

@eugenioenko
Copy link
Copy Markdown
Contributor

@eugenioenko eugenioenko commented Apr 23, 2026

Summary

  • Cap V8 old-space to 512 MB in the large file e2e test (huge.spec.ts)
  • The existing 3 GB encrypt/decrypt test always passed even with the memory leak from Make segment batch size and stream chunk size configurable on decrypt #919 because CI runners have enough RAM
  • With the heap cap, retaining all decrypted segments will OOM the browser, enforcing that peak memory scales with segment size, not total file size

Purpose

This PR is a test to see if the heap cap causes the existing huge.spec.ts to fail on main (before the fix in #922 lands). If it does fail, this validates the approach and should be merged alongside or after #922.

Test plan

  • Observe whether the huge.spec.ts test fails on CI with the 512 MB heap cap

Summary by CodeRabbit

  • Tests
    • Added a new large-file roundtrip test to validate encrypt/decrypt behavior with very large inputs and improve stability.
    • Increased test runtime memory allocation to reduce flakiness for heavy-file scenarios.
  • Chores
    • CI configuration updated to include the new large-file test in automated runs.

Limit V8 old-space to 512 MB so the 3 GB encrypt/decrypt e2e test
will OOM if decrypted segments are retained instead of released.
This enforces that peak heap scales with segment size, not file size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@eugenioenko eugenioenko requested a review from a team as a code owner April 23, 2026 17:57
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@eugenioenko has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 0 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 18 minutes and 0 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bcbcc504-d3bd-4555-ac4f-66c86729d170

📥 Commits

Reviewing files that changed from the base of the PR and between c9359d2 and 66c2e73.

📒 Files selected for processing (2)
  • web-app/tests/tests/huge.spec.ts
  • web-app/tests/tests/roundtrip.spec.ts
📝 Walkthrough

Walkthrough

Adds Playwright test adjustments and a new large-file roundtrip test: increases Chromium V8 old-space heap for test runs, adds a ~3 GiB encrypt/decrypt roundtrip with console-log assertions and file cleanup, and updates CI workflow to run both roundtrip and huge test groups.

Changes

Cohort / File(s) Summary
Huge spec config
web-app/tests/tests/huge.spec.ts
Adds test.use() to pass Chromium flag --js-flags=--max-old-space-size=512 so the browser process uses a larger V8 old-space heap during these tests.
Roundtrip large-file tests
web-app/tests/tests/roundtrip.spec.ts
Adds a Large File roundtrip test (~3 GiB): generate input, encrypt, upload/download ciphertext, decrypt, assert decrypted filename and byte size, capture/assert Using decrypt read tuning console logs, and clean up temporary files via nested finally blocks.
CI workflow
.github/workflows/reusable_build-and-test.yaml
Updates PLAYWRIGHT_TESTS_TO_RUN from roundtrip to roundtrip huge, causing the platform Docker job to run both test groups.

Sequence Diagram(s)

sequenceDiagram
  participant Tester
  participant WebApp
  participant Storage
  participant Browser as Playwright-Browser

  Tester->>Browser: launch with --js-flags=--max-old-space-size=512
  Tester->>WebApp: authorize / open app (with segment batch params)
  Tester->>WebApp: upload plaintext (~3 GiB)
  WebApp->>Storage: store ciphertext
  Tester->>Storage: download ciphertext
  Tester->>WebApp: request decrypt of downloaded ciphertext
  WebApp->>Tester: emit console logs (includes "Using decrypt read tuning")
  Tester->>Tester: assert logs, verify decrypted file exists & size, cleanup files
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • eugenioenko

Poem

🐰 I nibble bytes beneath the moon,
A giant file hums a sleepy tune,
Heap stretched wider, tests take flight,
Roundtrip’s done by dawn’s first light,
Hooray — the rabbit chews all night! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the primary change: adding a V8 heap cap to huge.spec.ts to catch memory leaks, which is the main objective of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/heap-cap-huge-spec

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@eugenioenko eugenioenko changed the title test: cap browser heap in huge.spec.ts to catch memory leaks chore(sdk): cap browser heap in huge.spec.ts to catch memory leaks Apr 23, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a memory limit for the V8 engine in the huge.spec.ts test file by configuring Playwright's launchOptions. The review feedback correctly identifies that the --js-flags argument is specific to Chromium and will not apply to Firefox or WebKit, suggesting that this limitation should be documented or handled via browser-specific test skipping.


test.use({
launchOptions: {
args: ['--js-flags=--max-old-space-size=512'],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The --js-flags argument is specific to the V8 engine (Chromium). This memory cap will not be enforced when running the test suite against Firefox or WebKit, which are both enabled in your playwright.config.ts. Consequently, memory leaks might go undetected on those platforms. Consider adding a comment to document this limitation or using test.skip for non-Chromium browsers if the test's primary purpose is to validate heap constraints.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web-app/tests/tests/huge.spec.ts`:
- Around line 5-9: The test sets Chromium-specific V8 flags via test.use({
launchOptions: { args: ['--js-flags=--max-old-space-size=512'] } }) in
huge.spec.ts but that flag is ignored by Firefox/WebKit; restrict this spec to
Chromium only by either (A) wrapping or guarding the test file so the
test.use/launchOptions args are applied only when the running project is
'chromium' (use a project-specific filter/skip around the tests referencing
test.use and args), or (B) change the Playwright config so huge.spec.ts is only
included in the chromium project (remove it from firefox/webkit test lists);
update test.use, launchOptions, args, and the Playwright config accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bf1146fa-7301-429a-8318-b70efbe5bec8

📥 Commits

Reviewing files that changed from the base of the PR and between 7c02e1f and fe115e6.

📒 Files selected for processing (1)
  • web-app/tests/tests/huge.spec.ts

Comment on lines +5 to +9
test.use({
launchOptions: {
args: ['--js-flags=--max-old-space-size=512'],
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'playwright.config' web-app/tests
fd -t f 'playwright.config' web-app/tests --exec cat {}

Repository: opentdf/web-sdk

Length of output: 2796


🏁 Script executed:

cat -n web-app/tests/tests/huge.spec.ts | head -50

Repository: opentdf/web-sdk

Length of output: 1946


🏁 Script executed:

wc -l web-app/tests/tests/huge.spec.ts

Repository: opentdf/web-sdk

Length of output: 94


🏁 Script executed:

cat -n web-app/tests/tests/huge.spec.ts

Repository: opentdf/web-sdk

Length of output: 2207


The heap cap applies only to Chromium; Firefox and WebKit will ignore --js-flags.

The Playwright config runs huge.spec.ts across all three browser projects (chromium, firefox, webkit), but --js-flags=--max-old-space-size=512 is a Chromium/V8-specific flag. Firefox and WebKit will silently ignore it, meaning the memory leak detection guarantee from this change only holds for the Chromium run — the other browsers could still pass despite the leak in #919.

Limit this test to the chromium project, either by wrapping the test.use() call with a project-specific filter or by modifying the Playwright config to exclude firefox/webkit for huge.spec.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-app/tests/tests/huge.spec.ts` around lines 5 - 9, The test sets
Chromium-specific V8 flags via test.use({ launchOptions: { args:
['--js-flags=--max-old-space-size=512'] } }) in huge.spec.ts but that flag is
ignored by Firefox/WebKit; restrict this spec to Chromium only by either (A)
wrapping or guarding the test file so the test.use/launchOptions args are
applied only when the running project is 'chromium' (use a project-specific
filter/skip around the tests referencing test.use and args), or (B) change the
Playwright config so huge.spec.ts is only included in the chromium project
(remove it from firefox/webkit test lists); update test.use, launchOptions,
args, and the Playwright config accordingly.

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

opentdf-ctl
opentdf-sdk-lib

@github-actions
Copy link
Copy Markdown

eugenioenko and others added 2 commits April 23, 2026 11:31
Temporarily add huge to PLAYWRIGHT_TESTS_TO_RUN to validate the
heap cap catches the memory leak on main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

If these changes look good, signoff on them with:

git pull && git commit --amend --signoff && git push --force-with-lease origin

If they aren't any good, please remove them with:

git pull && git reset --hard HEAD~1 && git push --force-with-lease origin

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

✅ go-v0.9.0
opentdf-ctl
opentdf-sdk-lib

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
web-app/tests/tests/roundtrip.spec.ts (1)

157-160: Minor: the foo && fs.unlinkSync(foo) guard is redundant.

At these points cipherTextPath / plainTextPath have already been narrowed/validated to truthy values by the earlier throw new Error() guards, so the short-circuit is dead code. Either drop the guard or use fs.rmSync(path, { force: true }) for robustness against files that may already be gone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-app/tests/tests/roundtrip.spec.ts` around lines 157 - 160, The two
cleanup calls using the redundant short-circuit guards (plainTextPath &&
fs.unlinkSync(plainTextPath) and cipherTextPath &&
fs.unlinkSync(cipherTextPath)) should be made deterministic: either remove the
unnecessary "&&" guard since plainTextPath and cipherTextPath are already
validated earlier, or replace fs.unlinkSync(...) with fs.rmSync(path, { force:
true }) to safely tolerate missing files; update the cleanup in the finally
block accordingly, touching the references to plainTextPath and cipherTextPath
and the fs.unlinkSync calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/reusable_build-and-test.yaml:
- Around line 218-221: The workflow currently sets the environment variable
PLAYWRIGHT_TESTS_TO_RUN but wait-and-test.sh (which calls npm test) does not
consume it; update either the workflow or the script so the variable actually
filters tests: either remove PLAYWRIGHT_TESTS_TO_RUN from the workflow, or
modify wait-and-test.sh (or the npm test invocation inside it) to read
PLAYWRIGHT_TESTS_TO_RUN and forward it to the test runner (for example pass it
as a grep/-g argument or as a Playwright project/test filter) so the value of
PLAYWRIGHT_TESTS_TO_RUN affects which tests run.

In `@web-app/tests/tests/roundtrip.spec.ts`:
- Around line 141-144: The test currently awaits page.waitForEvent('download')
before triggering the download, causing a timeout; change to start the waiter
without awaiting (use const plainDownloadPromise = page.waitForEvent('download',
{ timeout: 60000 });), then perform the actions that trigger the download (click
the '#fileSink' and '#decryptButton' locators), and finally await the promise
into download2 (const download2 = await plainDownloadPromise); update the
sequence around page.waitForEvent, the '#fileSink' and '#decryptButton' clicks,
and the plainDownloadPromise/download2 usage so the wait is non-blocking until
after the clicks.
- Around line 11-15: The global test.use({ launchOptions: { args:
['--js-flags=--max-old-space-size=512'] } }) is currently applied to the whole
file; restrict the 512MB V8 cap to only the heavy test by either wrapping the
"Large File" test in a test.describe block and calling test.describe(..., () =>
test.use({ launchOptions: { args: ['--js-flags=--max-old-space-size=512'] } }))
around that block, or move the "Large File" test into a new file (e.g.,
huge.spec.ts) and place the test.use launchOptions there so tests like "login",
"roundtrip ztdf", and "Remote Source Streaming" are unaffected.
- Around line 110-113: The test "Large File" fails because appUrl is not
defined; update the test in roundtrip.spec.ts (the test function named 'Large
File' that calls authorize(page) and
page.goto(`${appUrl}?segmentBatchSize=2&maxConcurrentSegmentBatches=1`) ) to use
a defined URL: either import the shared appUrl constant used elsewhere or
replace appUrl with Playwright's base URL/origin (e.g., use
page.context().baseURL or construct from process.env / test fixtures) and ensure
the page.goto call uses that resolved value so the test no longer throws a
ReferenceError.

---

Nitpick comments:
In `@web-app/tests/tests/roundtrip.spec.ts`:
- Around line 157-160: The two cleanup calls using the redundant short-circuit
guards (plainTextPath && fs.unlinkSync(plainTextPath) and cipherTextPath &&
fs.unlinkSync(cipherTextPath)) should be made deterministic: either remove the
unnecessary "&&" guard since plainTextPath and cipherTextPath are already
validated earlier, or replace fs.unlinkSync(...) with fs.rmSync(path, { force:
true }) to safely tolerate missing files; update the cleanup in the finally
block accordingly, touching the references to plainTextPath and cipherTextPath
and the fs.unlinkSync calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38be07b3-393b-4201-800e-96c13e24b17a

📥 Commits

Reviewing files that changed from the base of the PR and between fe115e6 and fde2b4f.

📒 Files selected for processing (3)
  • .github/workflows/reusable_build-and-test.yaml
  • web-app/tests/tests/huge.spec.ts
  • web-app/tests/tests/roundtrip.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • web-app/tests/tests/huge.spec.ts

Comment on lines 218 to 221
- env:
PLAYWRIGHT_TESTS_TO_RUN: roundtrip
PLAYWRIGHT_TESTS_TO_RUN: roundtrip huge
run: |-
./wait-and-test.sh platform
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

PLAYWRIGHT_TESTS_TO_RUN is not consumed by wait-and-test.sh.

Per .github/workflows/roundtrip/wait-and-test.sh (lines 183–198), the script invokes npm test with no grep/filter arguments and does not read PLAYWRIGHT_TESTS_TO_RUN. Setting it to roundtrip huge has no effect on which tests run — Playwright will execute the full suite regardless. Either wire the env var into the script (e.g., forward it as -g or as a positional project/test filter), or drop it here to avoid the false impression of selective targeting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/reusable_build-and-test.yaml around lines 218 - 221, The
workflow currently sets the environment variable PLAYWRIGHT_TESTS_TO_RUN but
wait-and-test.sh (which calls npm test) does not consume it; update either the
workflow or the script so the variable actually filters tests: either remove
PLAYWRIGHT_TESTS_TO_RUN from the workflow, or modify wait-and-test.sh (or the
npm test invocation inside it) to read PLAYWRIGHT_TESTS_TO_RUN and forward it to
the test runner (for example pass it as a grep/-g argument or as a Playwright
project/test filter) so the value of PLAYWRIGHT_TESTS_TO_RUN affects which tests
run.

Comment thread web-app/tests/tests/roundtrip.spec.ts Outdated
Comment thread web-app/tests/tests/roundtrip.spec.ts
Comment on lines +141 to +144
const plainDownloadPromise = await page.waitForEvent('download', { timeout: 60000 });
await page.locator('#fileSink').click();
await page.locator('#decryptButton').click();
const download2 = await plainDownloadPromise;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

waitForEvent is awaited before the action that triggers it — will time out.

const plainDownloadPromise = await page.waitForEvent('download', { timeout: 60000 }); blocks here waiting for a download event, but the clicks that initiate the download are on lines 142–143, after this await. No event can arrive, so this will hang until the 60 s timeout and the test will fail (or worse, any download actually produced is from an earlier action). Also note the variable name suggests a promise but after await it's already a Download, and line 144 then awaits it again.

Mirror the pattern used on lines 39–42 / 53–56:

Proposed fix
-    const plainDownloadPromise = await page.waitForEvent('download', { timeout: 60000 });
-    await page.locator('#fileSink').click();
-    await page.locator('#decryptButton').click();
-    const download2 = await plainDownloadPromise;
+    const plainDownloadPromise = page.waitForEvent('download', { timeout: 60000 });
+    await page.locator('#fileSink').click();
+    await page.locator('#decryptButton').click();
+    const download2 = await plainDownloadPromise;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const plainDownloadPromise = await page.waitForEvent('download', { timeout: 60000 });
await page.locator('#fileSink').click();
await page.locator('#decryptButton').click();
const download2 = await plainDownloadPromise;
const plainDownloadPromise = page.waitForEvent('download', { timeout: 60000 });
await page.locator('#fileSink').click();
await page.locator('#decryptButton').click();
const download2 = await plainDownloadPromise;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-app/tests/tests/roundtrip.spec.ts` around lines 141 - 144, The test
currently awaits page.waitForEvent('download') before triggering the download,
causing a timeout; change to start the waiter without awaiting (use const
plainDownloadPromise = page.waitForEvent('download', { timeout: 60000 });), then
perform the actions that trigger the download (click the '#fileSink' and
'#decryptButton' locators), and finally await the promise into download2 (const
download2 = await plainDownloadPromise); update the sequence around
page.waitForEvent, the '#fileSink' and '#decryptButton' clicks, and the
plainDownloadPromise/download2 usage so the wait is non-blocking until after the
clicks.

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

opentdf-ctl
opentdf-sdk-lib

@eugenioenko eugenioenko force-pushed the test/heap-cap-huge-spec branch from fde2b4f to c9359d2 Compare April 23, 2026 18:58
@github-actions
Copy link
Copy Markdown

If these changes look good, signoff on them with:

git pull && git commit --amend --signoff && git push --force-with-lease origin

If they aren't any good, please remove them with:

git pull && git reset --hard HEAD~1 && git push --force-with-lease origin

@github-actions
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

X-Test Failure Report

opentdf-ctl
opentdf-sdk-lib

@github-actions
Copy link
Copy Markdown

eugenioenko and others added 2 commits April 23, 2026 12:40
appUrl was never defined — introduced in #920. Use page.url() and
append query params to navigate to the current origin instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same fix as huge.spec.ts — appUrl was never defined.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

X-Test Failure Report

opentdf-ctl
opentdf-sdk-lib

@github-actions
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant