import { test, expect } from '../../fixtures/multi-client'; import { RegisterPage } from '../../pages/register.page'; import { ServerSearchPage } from '../../pages/server-search.page'; import { ChatMessagesPage, type ChatDropFilePayload } from '../../pages/chat-messages.page'; import { MULTI_DEVICE_PASSWORD, loginSecondDeviceIntoServer, uniqueMultiDeviceName } from '../../helpers/multi-device-session'; const SHARED_FROM_DEVICE_TEXT = 'Shared from your device'; test.describe('Multi-device attachment sharing', () => { test.describe.configure({ timeout: 300_000, retries: 1 }); test('only the uploading device claims "Shared from your device"; the second same-user device can request it', async ({ createClient }) => { const suffix = uniqueMultiDeviceName('attach-share'); const credentials = { username: `share_${suffix}`, displayName: 'Multi Device User', password: MULTI_DEVICE_PASSWORD }; const serverName = `Attachment Sharing ${suffix}`; const fileName = `${suffix}-handoff.bin`; const caption = `Uploaded from device A ${suffix}`; const fileAttachment = createBinaryFilePayload(fileName, 'application/octet-stream', `binary-body-${suffix}`); const clientA = await createClient(); const messagesA = new ChatMessagesPage(clientA.page); await test.step('device A registers, creates a server, and uploads a generic file', async () => { const registerPage = new RegisterPage(clientA.page); await registerPage.goto(); await registerPage.register(credentials.username, credentials.displayName, credentials.password); await expect(clientA.page).toHaveURL(/\/dashboard/, { timeout: 15_000 }); const search = new ServerSearchPage(clientA.page); await search.createServer(serverName, { description: 'Multi-device attachment sharing regression coverage' }); await expect(clientA.page).toHaveURL(/\/room\//, { timeout: 15_000 }); await messagesA.waitForReady(); await messagesA.attachFiles([fileAttachment]); await messagesA.sendMessage(caption); await expect(messagesA.getMessageItemByText(caption)).toBeVisible({ timeout: 30_000 }); }); await test.step('device A (the uploader) shows "Shared from your device"', async () => { const bubbleA = messagesA.getMessageItemByText(caption); await expect(bubbleA.getByText(fileName, { exact: false })).toBeVisible({ timeout: 20_000 }); await expect(bubbleA.getByText(SHARED_FROM_DEVICE_TEXT, { exact: false })).toBeVisible({ timeout: 20_000 }); }); const clientB = await createClient(); const messagesB = new ChatMessagesPage(clientB.page); await test.step('device B (same user) logs into the same server after the upload', async () => { await loginSecondDeviceIntoServer(clientB.page, credentials, serverName); // Keep device A active so it answers device B's account_sync_peer_online push. await clientA.page.bringToFront(); await messagesA.waitForReady(); await clientB.page.bringToFront(); await messagesB.waitForReady(); }); await test.step('device B receives the message and its attachment via same-user account sync', async () => { await expect(messagesB.getMessageItemByText(caption)).toBeVisible({ timeout: 90_000 }); await expect(messagesB.getMessageItemByText(caption).getByText(fileName, { exact: false })) .toBeVisible({ timeout: 90_000 }); }); await test.step('device B does NOT claim to share it and can request/download the file', async () => { const bubbleB = messagesB.getMessageItemByText(caption); // The regression: device B used to render "Shared from your device" and hide the // download affordance because the synced metadata carried the uploader's user id. await expect(bubbleB.getByText(SHARED_FROM_DEVICE_TEXT, { exact: false })).toHaveCount(0); // Device B must instead be able to fetch the file as any recipient would. const getButton = bubbleB.getByRole('button', { name: /request|download/i }); await expect(getButton.first()).toBeVisible({ timeout: 20_000 }); }); }); }); function createBinaryFilePayload(name: string, mimeType: string, content: string): ChatDropFilePayload { return { name, mimeType, base64: Buffer.from(content, 'utf8').toString('base64') }; }