Files
Toju/e2e/tests/plugins/plugin-api-two-users.spec.ts
2026-04-29 01:14:14 +02:00

180 lines
8.7 KiB
TypeScript

import { type Page } from '@playwright/test';
import {
expect,
test,
type Client
} from '../../fixtures/multi-client';
import { ChatMessagesPage } from '../../pages/chat-messages.page';
import { ChatRoomPage } from '../../pages/chat-room.page';
import { RegisterPage } from '../../pages/register.page';
import { ServerSearchPage } from '../../pages/server-search.page';
const PLUGIN_SOURCE_URL = 'http://localhost:4200/plugins/e2e-plugin-source.json';
const PLUGIN_TITLE = 'E2E All API Plugin';
const EDITED_MESSAGE = 'Plugin API edited message';
const ORIGINAL_MESSAGE = 'Plugin API original message';
const DELETED_MESSAGE = 'Plugin API deleted message';
const DELETED_MESSAGE_CONTENT = '[Message deleted]';
const PLUGIN_BOT_MESSAGE = 'Plugin bot message from all-api fixture';
const CUSTOM_EMBED_TEXT = 'E2E custom embed: Plugin API custom embed';
const SOUND_BOARD_TEXT = 'E2E soundboard ready';
const SOUND_BOARD_LABEL = 'E2E Soundboard';
const SOUND_BOARD_PLAYED_MESSAGE = 'E2E soundboard played Airhorn to voice channel';
const VOICE_CHANNEL = 'Plugin Voice';
test.describe('Plugin API multi-user runtime', () => {
test.describe.configure({ timeout: 180_000 });
test('runs chat, embed, soundboard, and profile APIs between two users', async ({ createClient }) => {
const scenario = await createPluginApiScenario(createClient);
await test.step('Install and activate the plugin for Bob as the embed/soundboard receiver', async () => {
await installGrantAndActivatePlugin(scenario.bob.page);
await closeSettingsModal(scenario.bob.page);
await expect(soundboardComposerButton(scenario.bob.page)).toBeVisible({ timeout: 20_000 });
await expect(scenario.bob.page.getByText(SOUND_BOARD_TEXT, { exact: true })).toBeVisible({ timeout: 20_000 });
await expect(scenario.bob.page.getByTestId('e2e-plugin-owned-dom')).toHaveAttribute('data-plugin-owner', 'e2e.all-api-plugin');
});
await test.step('Install and activate the plugin for Alice as the API driver', async () => {
await installGrantAndActivatePlugin(scenario.alice.page);
await closeSettingsModal(scenario.alice.page);
await expect(soundboardComposerButton(scenario.alice.page)).toBeVisible({ timeout: 20_000 });
await expect(scenario.alice.page.getByText(SOUND_BOARD_TEXT, { exact: true })).toBeVisible({ timeout: 20_000 });
await expect(scenario.alice.page.getByTestId('e2e-plugin-owned-dom')).toHaveAttribute('data-plugin-owner', 'e2e.all-api-plugin');
});
await test.step('Alice opens the plugin soundboard modal and plays a sound to voice', async () => {
await soundboardComposerButton(scenario.alice.page).click();
await expect(scenario.alice.page.getByRole('dialog', { name: SOUND_BOARD_LABEL })).toBeVisible({ timeout: 20_000 });
await expect(scenario.alice.page.getByTestId('e2e-soundboard-modal')).toHaveAttribute('data-plugin-owner', 'e2e.all-api-plugin');
await scenario.alice.page.getByRole('button', { name: 'Play airhorn to voice' }).click();
await expect(scenario.alice.page.getByTestId('e2e-soundboard-status')).toHaveText(SOUND_BOARD_PLAYED_MESSAGE, { timeout: 20_000 });
});
await test.step('Bob receives messages sent and edited by Alice through the plugin API', async () => {
await expect(scenario.bobMessages.getMessageItemByText(EDITED_MESSAGE)).toBeVisible({ timeout: 30_000 });
await expect(scenario.bobMessages.getMessageItemByText(ORIGINAL_MESSAGE)).toHaveCount(0);
await expect(scenario.bob.page.getByText('(edited)')).toBeVisible({ timeout: 20_000 });
});
await test.step('Bob sees plugin API deletion state and plugin-user messages', async () => {
await expect(scenario.bobMessages.getMessageItemByText(DELETED_MESSAGE_CONTENT)).toBeVisible({ timeout: 30_000 });
await expect(scenario.bobMessages.getMessageItemByText(DELETED_MESSAGE)).toHaveCount(0);
await expect(scenario.bobMessages.getMessageItemByText(PLUGIN_BOT_MESSAGE)).toBeVisible({ timeout: 30_000 });
await expect(scenario.bobMessages.getMessageItemByText(SOUND_BOARD_PLAYED_MESSAGE)).toBeVisible({ timeout: 30_000 });
});
await test.step('Bob renders Alice custom embed through the plugin embed API', async () => {
await expect(scenario.bob.page.getByTestId('plugin-message-embeds')).toContainText(CUSTOM_EMBED_TEXT, { timeout: 30_000 });
});
await test.step('Bob sees Alice profile name changed by the plugin API', async () => {
await expect(scenario.bobMessages.getMessageItemByText(EDITED_MESSAGE)).toContainText('Alice Plugin Renamed', { timeout: 30_000 });
});
});
});
interface PluginApiScenario {
alice: Client;
aliceRoom: ChatRoomPage;
bob: Client;
bobRoom: ChatRoomPage;
aliceMessages: ChatMessagesPage;
bobMessages: ChatMessagesPage;
}
async function createPluginApiScenario(createClient: () => Promise<Client>): Promise<PluginApiScenario> {
const suffix = uniqueName('plugin-api');
const serverName = `Plugin API Server ${suffix}`;
const alice = await createClient();
const bob = await createClient();
await registerUser(alice.page, `alice_${suffix}`, 'Alice');
await registerUser(bob.page, `bob_${suffix}`, 'Bob');
const aliceSearch = new ServerSearchPage(alice.page);
await aliceSearch.createServer(serverName, { description: 'Two-user plugin API E2E coverage' });
await expect(alice.page).toHaveURL(/\/room\//, { timeout: 30_000 });
const aliceRoom = new ChatRoomPage(alice.page);
await aliceRoom.ensureVoiceChannelExists(VOICE_CHANNEL);
const bobSearch = new ServerSearchPage(bob.page);
await bobSearch.joinServerFromSearch(serverName);
await expect(bob.page).toHaveURL(/\/room\//, { timeout: 30_000 });
const bobRoom = new ChatRoomPage(bob.page);
await aliceRoom.joinVoiceChannel(VOICE_CHANNEL);
await bobRoom.joinVoiceChannel(VOICE_CHANNEL);
await expect(aliceRoom.voiceControls).toBeVisible({ timeout: 30_000 });
await expect(bobRoom.voiceControls).toBeVisible({ timeout: 30_000 });
const aliceMessages = new ChatMessagesPage(alice.page);
const bobMessages = new ChatMessagesPage(bob.page);
await aliceMessages.waitForReady();
await bobMessages.waitForReady();
await expect(alice.page.locator('[data-testid^="room-user-card-"]', { hasText: 'Bob' })).toBeVisible({ timeout: 30_000 });
await expect(bob.page.locator('[data-testid^="room-user-card-"]', { hasText: 'Alice' })).toBeVisible({ timeout: 30_000 });
return {
alice,
aliceRoom,
bob,
bobRoom,
aliceMessages,
bobMessages
};
}
async function registerUser(page: Page, username: string, displayName: string): Promise<void> {
const registerPage = new RegisterPage(page);
await registerPage.goto();
await registerPage.register(username, displayName, 'TestPass123!');
await expect(page).toHaveURL(/\/search/, { timeout: 30_000 });
}
async function installGrantAndActivatePlugin(page: Page): Promise<void> {
await page.getByRole('button', { name: 'Plugins' }).click();
await expect(page).toHaveURL(/\/plugin-store/, { timeout: 20_000 });
await expect(page.getByTestId('plugin-store-page')).toBeVisible({ timeout: 20_000 });
await page.getByPlaceholder('https://example.com/plugins.json').fill(PLUGIN_SOURCE_URL);
await page.getByRole('button', { name: 'Add Source' }).click();
await expect(page.getByRole('heading', { name: PLUGIN_TITLE })).toBeVisible({ timeout: 20_000 });
await page.getByRole('button', { exact: true, name: 'Install' }).click();
await expect(page.locator('article', { hasText: PLUGIN_TITLE }).getByText('Installed')).toBeVisible({ timeout: 20_000 });
await page.getByRole('button', { name: 'Manage Plugins' }).click();
await expect(page.getByTestId('plugin-manager')).toBeVisible({ timeout: 20_000 });
await expect(page.locator('article', { hasText: PLUGIN_TITLE })).toBeVisible({ timeout: 20_000 });
await page.locator('article', { hasText: PLUGIN_TITLE })
.getByRole('button', { name: 'Select' })
.click();
await page.getByRole('button', { name: 'Grant all requested' }).click();
await page.getByRole('button', { name: 'Activate ready plugins' }).click();
await expect(page.locator('article', { hasText: PLUGIN_TITLE }).getByText('ready', { exact: true })).toBeVisible({ timeout: 30_000 });
await page.getByRole('button', { name: 'Logs' }).click();
await expect(page.getByText('all-api plugin completed')).toBeVisible({ timeout: 30_000 });
}
async function closeSettingsModal(page: Page): Promise<void> {
await page.keyboard.press('Escape');
await expect(page.getByTestId('plugin-manager')).toHaveCount(0);
}
function uniqueName(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.random().toString(36)
.slice(2, 8)}`;
}
function soundboardComposerButton(page: Page) {
return page.locator('app-chat-message-composer')
.getByRole('button', { exact: true, name: SOUND_BOARD_LABEL });
}