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 { 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)}`; }