Files
Toju/e2e/tests/chat/multi-client-chat-sync.spec.ts

177 lines
6.7 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';
import {
MULTI_DEVICE_PASSWORD,
closeClient,
expectCrossDeviceMessage,
expectSyncedMessage,
expectSyncedMessageWithResync,
expectServerPeerVisible,
loginSecondDeviceIntoServer,
reopenClientInServer,
uniqueMultiDeviceName
} from '../../helpers/multi-device-session';
test.describe('Multi-client chat sync', () => {
test.describe.configure({ timeout: 360_000, retries: 1 });
test('syncs messages between same-user devices and late-joining users after offline gaps', async ({ createClient }) => {
const suffix = uniqueMultiDeviceName('multi-chat-sync');
const hostCredentials = {
username: `ludde_${suffix}`,
displayName: 'Ludde',
password: MULTI_DEVICE_PASSWORD
};
const guestCredentials = {
username: `azaaxin_${suffix}`,
displayName: 'Azaaxin',
password: MULTI_DEVICE_PASSWORD
};
const serverName = `Multi Client Chat Sync ${suffix}`;
const sharedBaselineMessage = `Shared baseline ${suffix}`;
const soloHostMessage = `Solo host message ${suffix}`;
const liveGuestProbeMessage = `Live guest probe ${suffix}`;
const offlineGapMessage = `Offline gap message ${suffix}`;
const client1 = await createClient();
const client2 = await createClient();
const client3 = await createClient();
await test.step('client 1: host registers and creates the shared server', async () => {
const registerPage = new RegisterPage(client1.page);
await registerPage.goto();
await registerPage.register(
hostCredentials.username,
hostCredentials.displayName,
hostCredentials.password
);
await expect(client1.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
const search = new ServerSearchPage(client1.page);
await search.createServer(serverName, {
description: 'Multi-client chat sync regression coverage'
});
await expect(client1.page).toHaveURL(/\/room\//, { timeout: 15_000 });
});
const messages1 = new ChatMessagesPage(client1.page);
await messages1.waitForReady();
await test.step('client 2: second host device joins the same server', async () => {
await loginSecondDeviceIntoServer(client2.page, hostCredentials, serverName);
});
const messages2 = new ChatMessagesPage(client2.page);
await messages2.waitForReady();
await test.step('both host devices exchange chat while online together', async () => {
await expectCrossDeviceMessage(messages1, messages2, sharedBaselineMessage);
});
await test.step('close the second host browser (client 2)', async () => {
await closeClient(client2);
});
await test.step('client 1 sends chat while the second host device is offline', async () => {
await client1.page.bringToFront();
await messages1.sendMessage(soloHostMessage);
await expect(messages1.getMessageItemByText(soloHostMessage)).toBeVisible({ timeout: 20_000 });
});
await test.step('guest account registers ahead of joining the server', async () => {
const registerPage = new RegisterPage(client3.page);
await registerPage.goto();
await registerPage.register(
guestCredentials.username,
guestCredentials.displayName,
guestCredentials.password
);
await expect(client3.page).toHaveURL(/\/dashboard/, { timeout: 15_000 });
});
let messages3 = new ChatMessagesPage(client3.page);
await test.step('client 3: guest joins and receives existing chat history', async () => {
// Keep the host tab active so its websocket + peer negotiation stay alive.
await client1.page.bringToFront();
await messages1.waitForReady();
const search = new ServerSearchPage(client3.page);
await search.joinServerFromSearch(serverName);
await expect(client3.page).toHaveURL(/\/room\//, { timeout: 20_000 });
messages3 = new ChatMessagesPage(client3.page);
await messages3.waitForReady();
// Presence gate: both users must see each other in the members panel
// before cross-user chat delivery can be expected.
await client1.page.bringToFront();
await expectServerPeerVisible(client1.page, guestCredentials.displayName);
await client3.page.bringToFront();
await expectServerPeerVisible(client3.page, hostCredentials.displayName);
// Live delivery first - proves host <-> guest transport is actually up.
await expectCrossDeviceMessage(messages1, messages3, liveGuestProbeMessage);
// History only replicates over P2P inventory once the peer link exists.
await client1.page.bringToFront();
await expectSyncedMessageWithResync(client3.page, messages3, sharedBaselineMessage);
await expectSyncedMessageWithResync(client3.page, messages3, soloHostMessage);
});
await test.step('close the guest browser (client 3)', async () => {
await closeClient(client3);
});
await test.step('reopen client 2 and send a message while client 1 stays online', async () => {
await client1.page.bringToFront();
const reopened = await reopenClientInServer(createClient, hostCredentials, serverName);
// Same-user catch-up uses account_sync, not P2P between own devices.
await expectSyncedMessageWithResync(
reopened.client.page,
reopened.messages,
soloHostMessage
);
await reopened.messages.sendMessage(offlineGapMessage);
await expect(reopened.messages.getMessageItemByText(offlineGapMessage)).toBeVisible({ timeout: 20_000 });
});
await test.step('reopened guest client receives the offline-gap message from host device 2', async () => {
await client1.page.bringToFront();
await messages1.waitForReady();
const reopenedGuest = await reopenClientInServer(createClient, guestCredentials, serverName);
// Presence gate before relying on cross-user delivery again.
await client1.page.bringToFront();
await expectServerPeerVisible(client1.page, guestCredentials.displayName);
await reopenedGuest.client.page.bringToFront();
await expectServerPeerVisible(reopenedGuest.client.page, hostCredentials.displayName);
await expectCrossDeviceMessage(messages1, reopenedGuest.messages, `Guest wake ${suffix}`);
await expectSyncedMessageWithResync(
reopenedGuest.client.page,
reopenedGuest.messages,
offlineGapMessage
);
});
await test.step('primary host device still receives the message from its second device', async () => {
await expectSyncedMessage(messages1, offlineGapMessage);
});
});
});