π₯ Chunked Upload Nightmare in Playwright
Can you test a 5β―GB CSV upload without loading the whole file into memory?
π Problem Statement
Enterprises let users drop massive CSVs (5β―GB+) that the UI splits into 10β―MB chunks. The browser sends each chunk with XβChunkβIndex and XβTotalβChunks headers. Traditional page.setInputFiles() canβt emulate this because it streams the whole file and hides the HTTP calls.
π‘ Solution & Code Walkthrough
1οΈβ£ Create onβtheβfly chunks β use a generator that yields Buffer.alloc(chunkSize, 0x61) (or random data) without persisting a file.
2οΈβ£ Drive the upload via Playwrightβs API request context β page.request lets you fire raw HTTP calls, set custom headers, and inspect responses.
3οΈβ£ Maintain state β keep totalChunks, increment index, and reuse the same auth cookies/session from the page.
import { test, expect } from '@playwright/test';
const CHUNK_SIZE = 10 * 1024 * 1024; // 10β―MB
const TOTAL_SIZE = 5 * 1024 * 1024 * 1024; // 5β―GB
const TOTAL_CHUNKS = Math.ceil(TOTAL_SIZE / CHUNK_SIZE);
// Simple async generator for synthetic chunks
async function* chunkGenerator() {
for (let i = 0; i < TOTAL_CHUNKS; i++) {
// Fill with repeatable data to keep memory cheap
const buf = Buffer.alloc(Math.min(CHUNK_SIZE, TOTAL_SIZE - i * CHUNK_SIZE), 0x61);
yield buf;
}
}
test('reliable chunked upload', async ({ page }) => {
// 1οΈβ£ Navigate & capture auth cookies
await page.goto('https://app.example.com/upload');
const cookies = await page.context().cookies();
// 2οΈβ£ Prepare request context with same cookies
const request = page.request;
await request.setExtraHTTPHeaders({ cookie: cookies.map(c => `${c.name}=${c.value}`).join('; ') });
// 3οΈβ£ Stream chunks sequentially
let index = 0;
for await (const chunk of chunkGenerator()) {
const response = await request.post('https://api.example.com/upload/chunk', {
headers: {
'Content-Type': 'application/octet-stream',
'X-Chunk-Index': String(index),
'X-Total-Chunks': String(TOTAL_CHUNKS),
},
data: chunk,
});
expect(response.ok()).toBeTruthy();
index++;
}
// 4οΈβ£ Verify final assembly endpoint
const finish = await request.post('https://api.example.com/upload/complete', {
json: { fileName: 'big-data.csv', totalChunks: TOTAL_CHUNKS },
});
expect(await finish.json()).toMatchObject({ status: 'SUCCESS' });
});
β Why this works
- No full file in RAM β each chunk is generated on demand.
- Full control of headers β mimics the UIβs exact protocol.
- Playwrightβnative session β reuses cookies, CSRF tokens, and can still run UI steps (e.g., dragβandβdrop) before the API loop.
π Key Takeaways
- Use
page.requestfor lowβlevel HTTP when UI helpers fall short. -
Chunk generators keep memory footprint constant (
O(chunkSize)). - Preserve session state by copying cookies or auth tokens from the page context.
β Quick Summary Q&A
β Can I still use setInputFiles? β No, it hides the chunk traffic.
β
Do I need a real 5β―GB file? β No, synthetic buffers are enough.
β
How to assert serverβside assembly? β Call the final βcompleteβ endpoint and check its JSON response.
TAGS: playwright, typescript, e2e-testing, file-upload, automation
ββββββββββββββββββββββββββββββββββββββββ
π² π
πππ ππππππ πππ β πππ+ ππππ π&ππ¬
Practice real-world interview scenarios offline on the free QA Automation & SDET Prep app:
π€ ππ¨π¨π π₯π ππ₯ππ² (ππ§ππ«π¨π’π):
https://play.google.com/store/apps/details?id=com.app.seleniuminterviewquestions&referrer=utm_source%3Ddevto%26utm_medium%3Darticle%26utm_campaign%3Dselenium_20260911
π ππ©π© πππ¨π«π (π’ππ):
https://apps.apple.com/app/id6786760948?pt=128640464&ct=devto_selenium_20260911&mt=8
ββββββββββββββββββββββββββββββββββββββββ
Top comments (0)