From fe115e6ae27ddd672265cba95acdb271c779062a Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Thu, 23 Apr 2026 10:57:09 -0700 Subject: [PATCH 1/7] test: cap browser heap in huge.spec.ts to catch memory leaks 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 --- web-app/tests/tests/huge.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web-app/tests/tests/huge.spec.ts b/web-app/tests/tests/huge.spec.ts index 764631079..2d959496b 100644 --- a/web-app/tests/tests/huge.spec.ts +++ b/web-app/tests/tests/huge.spec.ts @@ -2,6 +2,12 @@ import { test, expect, type Page } from '@playwright/test'; import fs from 'node:fs'; import { authorize, loadFile } from './acts.js'; +test.use({ + launchOptions: { + args: ['--js-flags=--max-old-space-size=512'], + }, +}); + test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => { console.error(err); From 4cdfd40a1afcd032f3792389733b889be4fd4a98 Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Thu, 23 Apr 2026 11:31:48 -0700 Subject: [PATCH 2/7] ci: include huge test in playwright run 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 --- .github/workflows/reusable_build-and-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable_build-and-test.yaml b/.github/workflows/reusable_build-and-test.yaml index bdf550d81..9e562b8f8 100644 --- a/.github/workflows/reusable_build-and-test.yaml +++ b/.github/workflows/reusable_build-and-test.yaml @@ -216,7 +216,7 @@ jobs: go env GOWORK - run: docker compose up -d --wait --wait-timeout 240 - env: - PLAYWRIGHT_TESTS_TO_RUN: roundtrip + PLAYWRIGHT_TESTS_TO_RUN: roundtrip huge run: |- ./wait-and-test.sh platform From 2859576a1b14a7b5c67d25c2cc407aae22adeb15 Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Thu, 23 Apr 2026 11:42:34 -0700 Subject: [PATCH 3/7] chore: large file added to roundtirp --- web-app/tests/tests/roundtrip.spec.ts | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/web-app/tests/tests/roundtrip.spec.ts b/web-app/tests/tests/roundtrip.spec.ts index 6b69b69d6..74c15c1b6 100644 --- a/web-app/tests/tests/roundtrip.spec.ts +++ b/web-app/tests/tests/roundtrip.spec.ts @@ -8,6 +8,13 @@ import { authorize, loadFile } from './acts.js'; // Playwright assertions: https://playwright.dev/docs/test-assertions // upload files: https://timdeschryver.dev/blog/how-to-upload-files-with-playwright + +test.use({ + launchOptions: { + args: ['--js-flags=--max-old-space-size=512'], + }, +}); + test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => { console.error(err); @@ -100,3 +107,59 @@ test('Remote Source Streaming', async ({ page }) => { server.close(); } }); + + + +test('Large File', async ({ page }) => { + await authorize(page); + await page.goto(`${appUrl}?segmentBatchSize=2&maxConcurrentSegmentBatches=1`); + await expect(page.locator('#sessionState')).toHaveText('loggedin'); + + const decryptTuningLogs: string[] = []; + page.on('console', (message) => { + const text = message.text(); + if (text.includes('Using decrypt read tuning')) { + decryptTuningLogs.push(text); + } + }); + + const threeGigs = 3 * 2 ** 30; + await page.locator('#randomSelector').fill(threeGigs.toString()); + + const downloadPromise = page.waitForEvent('download'); + await page.locator('#fileSink').click(); + await page.locator('#encryptButton').click(); + + const download = await downloadPromise; + const cipherTextPath = await download.path(); + try { + expect(download.suggestedFilename()).toContain('bytes'); + expect(cipherTextPath).toBeTruthy(); + if (!cipherTextPath) { + throw new Error(); + } + + await page.locator('#randomSelector').clear(); + await loadFile(page, cipherTextPath); + const plainDownloadPromise = await page.waitForEvent('download', { timeout: 60000 }); + await page.locator('#fileSink').click(); + await page.locator('#decryptButton').click(); + const download2 = await plainDownloadPromise; + expect(download2.suggestedFilename()).toContain('.decrypted'); + expect(decryptTuningLogs).toEqual([ + 'Using decrypt read tuning {"segmentBatchSize":2,"maxConcurrentSegmentBatches":1}', + ]); + const plainTextPath = await download2.path(); + if (!plainTextPath) { + throw new Error(); + } + try { + const stats = fs.statSync(plainTextPath); + expect(stats).toHaveProperty('size', threeGigs); + } finally { + plainTextPath && fs.unlinkSync(plainTextPath); + } + } finally { + cipherTextPath && fs.unlinkSync(cipherTextPath); + } +}); From c9359d2edb008f708abfcc1ce58182a37de9aa43 Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Thu, 23 Apr 2026 11:58:39 -0700 Subject: [PATCH 4/7] chore: huge run --- web-app/tests/tests/roundtrip.spec.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/web-app/tests/tests/roundtrip.spec.ts b/web-app/tests/tests/roundtrip.spec.ts index 74c15c1b6..41b72607b 100644 --- a/web-app/tests/tests/roundtrip.spec.ts +++ b/web-app/tests/tests/roundtrip.spec.ts @@ -9,12 +9,6 @@ import { authorize, loadFile } from './acts.js'; // upload files: https://timdeschryver.dev/blog/how-to-upload-files-with-playwright -test.use({ - launchOptions: { - args: ['--js-flags=--max-old-space-size=512'], - }, -}); - test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => { console.error(err); From 7a9ba6906ef6f7cc8ebf7341956c373265934e3c Mon Sep 17 00:00:00 2001 From: eugenioenko <17834212+eugenioenko@users.noreply.github.com> Date: Thu, 23 Apr 2026 18:59:43 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=A4=96=20=F0=9F=8E=A8=20Autoformat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-app/tests/tests/roundtrip.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/web-app/tests/tests/roundtrip.spec.ts b/web-app/tests/tests/roundtrip.spec.ts index 41b72607b..75b3592a0 100644 --- a/web-app/tests/tests/roundtrip.spec.ts +++ b/web-app/tests/tests/roundtrip.spec.ts @@ -8,7 +8,6 @@ import { authorize, loadFile } from './acts.js'; // Playwright assertions: https://playwright.dev/docs/test-assertions // upload files: https://timdeschryver.dev/blog/how-to-upload-files-with-playwright - test.beforeEach(async ({ page }) => { page.on('pageerror', (err) => { console.error(err); @@ -102,8 +101,6 @@ test('Remote Source Streaming', async ({ page }) => { } }); - - test('Large File', async ({ page }) => { await authorize(page); await page.goto(`${appUrl}?segmentBatchSize=2&maxConcurrentSegmentBatches=1`); From a64703e62fc37475c710516ddfe9580c2e870056 Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Thu, 23 Apr 2026 12:40:05 -0700 Subject: [PATCH 6/7] fix(tests): replace undefined appUrl with current page URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- web-app/tests/tests/huge.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web-app/tests/tests/huge.spec.ts b/web-app/tests/tests/huge.spec.ts index 7d9075c85..c670ce61d 100644 --- a/web-app/tests/tests/huge.spec.ts +++ b/web-app/tests/tests/huge.spec.ts @@ -19,7 +19,10 @@ test.beforeEach(async ({ page }) => { test('Large File', async ({ page }) => { await authorize(page); - await page.goto(`${appUrl}?segmentBatchSize=2&maxConcurrentSegmentBatches=1`); + const currentUrl = new URL(page.url()); + currentUrl.searchParams.set('segmentBatchSize', '2'); + currentUrl.searchParams.set('maxConcurrentSegmentBatches', '1'); + await page.goto(currentUrl.toString()); await expect(page.locator('#sessionState')).toHaveText('loggedin'); const decryptTuningLogs: string[] = []; From 66c2e736d19c902fde811f0f2bfb889b36357a7f Mon Sep 17 00:00:00 2001 From: Eugene Yakhnenko Date: Thu, 23 Apr 2026 12:40:40 -0700 Subject: [PATCH 7/7] fix(tests): replace undefined appUrl in roundtrip.spec.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same fix as huge.spec.ts — appUrl was never defined. Co-Authored-By: Claude Opus 4.6 --- web-app/tests/tests/roundtrip.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web-app/tests/tests/roundtrip.spec.ts b/web-app/tests/tests/roundtrip.spec.ts index 75b3592a0..7fc814356 100644 --- a/web-app/tests/tests/roundtrip.spec.ts +++ b/web-app/tests/tests/roundtrip.spec.ts @@ -103,7 +103,10 @@ test('Remote Source Streaming', async ({ page }) => { test('Large File', async ({ page }) => { await authorize(page); - await page.goto(`${appUrl}?segmentBatchSize=2&maxConcurrentSegmentBatches=1`); + const currentUrl = new URL(page.url()); + currentUrl.searchParams.set('segmentBatchSize', '2'); + currentUrl.searchParams.set('maxConcurrentSegmentBatches', '1'); + await page.goto(currentUrl.toString()); await expect(page.locator('#sessionState')).toHaveText('loggedin'); const decryptTuningLogs: string[] = [];