fix: improve plugins functionality with server management

This commit is contained in:
2026-04-29 20:33:54 +02:00
parent b8f6d58d99
commit fa2cca6fa4
82 changed files with 1708 additions and 303 deletions

View File

@@ -1,7 +1,13 @@
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { chromium, type BrowserContext, type Locator, type Page, type Route } from '@playwright/test';
import {
chromium,
type BrowserContext,
type Locator,
type Page,
type Route
} from '@playwright/test';
import { test, expect } from '../../fixtures/multi-client';
import { installTestServerEndpoint } from '../../helpers/seed-test-endpoint';
import { installWebRTCTracking } from '../../helpers/webrtc-helpers';
@@ -31,7 +37,11 @@ interface PersistentClient {
}
const STATIC_GIF_BASE64 = 'R0lGODlhAQABAPAAAP///wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==';
const GIF_FRAME_MARKER = Buffer.from([0x21, 0xf9, 0x04]);
const GIF_FRAME_MARKER = Buffer.from([
0x21,
0xf9,
0x04
]);
const CLIENT_LAUNCH_ARGS = ['--use-fake-device-for-media-stream', '--use-fake-ui-for-media-stream'];
const SERVER_ICON_SYNC_TIMEOUT_MS = 45_000;
@@ -77,6 +87,7 @@ test.describe('Server icon sync', () => {
await new ServerSearchPage(alice.page).createServer(serverName, {
description: 'Server icon synchronization E2E coverage'
});
await expect(alice.page).toHaveURL(/\/room\//, { timeout: 15_000 });
await joinServerFromSearch(bob.page, serverName);
@@ -263,15 +274,15 @@ async function openServerSettings(page: Page, serverName: string): Promise<void>
async function openSettingsModalThroughAngularDevMode(page: Page): Promise<void> {
await page.evaluate(() => {
type SettingsModalComponentHandle = {
interface SettingsModalComponentHandle {
modal?: {
open: (page: string) => void;
};
};
type AngularDebugApi = {
}
interface AngularDebugApi {
getComponent: (element: Element) => SettingsModalComponentHandle;
applyChanges?: (component: SettingsModalComponentHandle) => void;
};
}
const host = document.querySelector('app-settings-modal');
const debugApi = (window as Window & { ng?: AngularDebugApi }).ng;
@@ -373,33 +384,33 @@ async function retryTransientNavigation<T>(navigate: () => Promise<T>, attempts
async function expectServerSettingsIcon(page: Page, serverName: string, expectedDataUrl: string): Promise<void> {
const settingsPanel = page.locator('app-server-settings');
const image = settingsPanel.locator(`img[alt="${serverName} icon"]`).first();
const image = settingsPanel.locator('[style*="background-image"]').first();
await expectImageLoadedWithSrc(image, expectedDataUrl, 'settings server icon');
await expectBackgroundImageLoadedWithUrl(image, expectedDataUrl, 'settings server icon');
}
async function expectRoomHeaderIcon(page: Page, serverName: string, expectedDataUrl: string): Promise<void> {
const channelsPanel = page.locator('app-rooms-side-panel').first();
const image = channelsPanel.locator(`img[alt="${serverName} icon"]`).first();
const image = channelsPanel.locator('[style*="background-image"]').first();
await expectImageLoadedWithSrc(image, expectedDataUrl, 'room header server icon');
await expectBackgroundImageLoadedWithUrl(image, expectedDataUrl, 'room header server icon');
}
async function expectRailIcon(page: Page, serverName: string, expectedDataUrl: string): Promise<void> {
const image = page.locator(`app-servers-rail img[alt="${serverName} icon"]`).first();
const image = page.locator(`app-servers-rail button[title="${serverName}"] [style*="background-image"]`).first();
await expectImageLoadedWithSrc(image, expectedDataUrl, 'servers rail icon');
await expectBackgroundImageLoadedWithUrl(image, expectedDataUrl, 'servers rail icon');
}
async function expectSearchResultIcon(page: Page, serverName: string, expectedDataUrl: string): Promise<void> {
const serverCard = page.locator('app-server-search div[title]', { hasText: serverName }).first();
const image = serverCard.locator(`img[alt="${serverName} icon"]`).first();
const image = serverCard.locator('[style*="background-image"]').first();
await expect(serverCard).toBeVisible({ timeout: 20_000 });
await expectImageLoadedWithSrc(image, expectedDataUrl, 'search result server icon');
await expectBackgroundImageLoadedWithUrl(image, expectedDataUrl, 'search result server icon');
}
async function expectImageLoadedWithSrc(image: Locator, expectedDataUrl: string, label: string): Promise<void> {
async function expectBackgroundImageLoadedWithUrl(image: Locator, expectedDataUrl: string, label: string): Promise<void> {
await expect
.poll(
async () => {
@@ -407,14 +418,14 @@ async function expectImageLoadedWithSrc(image: Locator, expectedDataUrl: string,
return null;
}
return image.getAttribute('src');
return image.evaluate((element) => getComputedStyle(element).backgroundImage);
},
{
timeout: SERVER_ICON_SYNC_TIMEOUT_MS,
message: `${label} src should update`
message: `${label} background should update`
}
)
.toBe(expectedDataUrl);
.toContain(expectedDataUrl);
await expect
.poll(
@@ -423,11 +434,23 @@ async function expectImageLoadedWithSrc(image: Locator, expectedDataUrl: string,
return false;
}
return image.evaluate((element) => {
const img = element as HTMLImageElement;
return image.evaluate(
(element) =>
new Promise<boolean>((resolve) => {
const backgroundImage = getComputedStyle(element).backgroundImage;
const match = /^url\("?(.*?)"?\)$/.exec(backgroundImage);
const img = new Image();
return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;
});
if (!match?.[1]) {
resolve(false);
return;
}
img.onload = () => resolve(img.naturalWidth > 0 && img.naturalHeight > 0);
img.onerror = () => resolve(false);
img.src = match[1];
})
);
},
{
timeout: SERVER_ICON_SYNC_TIMEOUT_MS,
@@ -448,8 +471,22 @@ function buildGifUpload(label: string): ImageUploadPayload {
const header = baseGif.subarray(0, frameStart);
const frame = baseGif.subarray(frameStart, baseGif.length - 1);
const commentData = Buffer.from(label, 'ascii');
const commentExtension = Buffer.concat([Buffer.from([0x21, 0xfe, commentData.length]), commentData, Buffer.from([0x00])]);
const buffer = Buffer.concat([header, commentExtension, frame, Buffer.from([0x3b])]);
const commentExtension = Buffer.concat([
Buffer.from([
0x21,
0xfe,
commentData.length
]),
commentData,
Buffer.from([0x00])
]);
const buffer = Buffer.concat([
header,
commentExtension,
frame,
frame,
Buffer.from([0x3b])
]);
const base64 = buffer.toString('base64');
return {
@@ -461,5 +498,6 @@ function buildGifUpload(label: string): ImageUploadPayload {
}
function uniqueName(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
return `${prefix}-${Date.now()}-${Math.random().toString(36)
.slice(2, 8)}`;
}