Files
Toju/e2e/tests/chat/large-generic-file-transfer.spec.ts
T
myxeliumandCursor 590e487250 fix: Bug - Sending files between users doesn't really work (chunk-time size re-gate)
Remove the leftover MAX_AUTO_SAVE_SIZE_BYTES guard from handleFileChunk's
in-memory path. The request gate (canReceiveAttachment) already admits 10-50 MB
generic files for in-memory receive on stores without disk streaming (browser),
but the chunk handler silently dropped every chunk of such files: no ack was
sent, the sender's waitForAck timed out, and the receiver's GUI never changed.
Receive admission is now decided once, at request time.

Adds a two-browser regression e2e that sends an 11 MB generic file and asserts
Request -> progress -> Download.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 18:48:30 +02:00

115 lines
4.6 KiB
TypeScript

import { test, expect } from '../../fixtures/multi-client';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
import { ChatMessagesPage } from '../../pages/chat-messages.page';
/**
* Regression coverage for "Sending files between users doesn't really work":
* a generic (non-media) file above the 10 MB auto-save cap sent to a browser
* receiver. The receiver clicks Request; previously the chunk handler dropped
* every incoming chunk with a silent file-too-large error, the sender's ack
* wait timed out, and the GUI never changed.
*/
const LARGE_FILE_SIZE_BYTES = 11 * 1024 * 1024;
test.describe('Large generic file transfer', () => {
test.describe.configure({ timeout: 420_000, retries: 1 });
test('browser receiver can request and download a generic file above the auto-save cap', async ({ createClient }) => {
const suffix = uniqueName('largefile');
const serverName = `Large File Server ${suffix}`;
const fileName = `${suffix}-dataset.bin`;
const caption = `Large file upload ${suffix}`;
const alice = await createClient();
const bob = await createClient();
const aliceMessages = new ChatMessagesPage(alice.page);
const bobMessages = new ChatMessagesPage(bob.page);
await test.step('Alice and Bob register and meet in a server', async () => {
const aliceRegister = new RegisterPage(alice.page);
await aliceRegister.goto();
await aliceRegister.register(`alice_${suffix}`, 'Alice', 'TestPass123!');
await expect(alice.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
const bobRegister = new RegisterPage(bob.page);
await bobRegister.goto();
await bobRegister.register(`bob_${suffix}`, 'Bob', 'TestPass123!');
await expect(bob.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
const aliceSearch = new ServerSearchPage(alice.page);
await aliceSearch.createServer(serverName, { description: 'Large generic file transfer coverage' });
await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });
const bobSearch = new ServerSearchPage(bob.page);
await bobSearch.joinServerFromSearch(serverName);
await expect(bob.page).toHaveURL(/\/room\//, { timeout: 15_000 });
await aliceMessages.waitForReady();
await bobMessages.waitForReady();
});
await test.step('Alice sends an 11 MB generic file', async () => {
await attachGeneratedBinaryFile(aliceMessages, fileName, LARGE_FILE_SIZE_BYTES);
await aliceMessages.sendMessage(caption);
await expect(aliceMessages.getMessageItemByText(caption)).toBeVisible({ timeout: 30_000 });
});
const bobBubble = bobMessages.getMessageItemByText(caption);
await test.step('Bob sees the attachment card with a Request button', async () => {
await expect(bobBubble).toBeVisible({ timeout: 30_000 });
await expect(bobBubble.getByText(fileName, { exact: false })).toBeVisible({ timeout: 30_000 });
await expect(bobBubble.getByRole('button', { name: /request/i })).toBeVisible({ timeout: 20_000 });
});
await test.step('Bob requests the file and it downloads to completion', async () => {
await bobBubble.getByRole('button', { name: /request/i }).click();
// The transfer must visibly progress (Cancel replaces Request) instead of
// silently stalling at 0 bytes like the original bug.
await expect(bobBubble.getByRole('button', { name: /cancel/i })).toBeVisible({ timeout: 30_000 });
await expect(bobBubble.getByRole('button', { name: /download/i })).toBeVisible({ timeout: 300_000 });
await expect(bobBubble.getByText(/too large/i)).toHaveCount(0);
});
});
});
/**
* Builds the file inside the page so the multi-megabyte payload never crosses
* the CDP protocol as a base64 string.
*/
async function attachGeneratedBinaryFile(
messages: ChatMessagesPage,
fileName: string,
sizeBytes: number
): Promise<void> {
await messages.waitForReady();
await messages.composerInput.evaluate((element, { name, size }) => {
const bytes = new Uint8Array(size);
for (let index = 0; index < size; index++) {
bytes[index] = (index * 31 + 7) & 0xff;
}
const dataTransfer = new DataTransfer();
dataTransfer.items.add(new File([bytes], name, { type: 'application/octet-stream' }));
element.dispatchEvent(new DragEvent('drop', {
bubbles: true,
cancelable: true,
dataTransfer
}));
}, { name: fileName, size: sizeBytes });
}
function uniqueName(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.random().toString(36)
.slice(2, 8)}`;
}