Files
homarr/e2e/manage-collections.spec.ts
T

149 lines
6.2 KiB
TypeScript

import { mkdir } from "node:fs/promises";
import path from "node:path";
import { chromium } from "@playwright/test";
import type { Page } from "@playwright/test";
import { stringify as stringifySuperJSON } from "superjson";
import { describe, expect, test } from "vitest";
import * as sqliteSchema from "../packages/db/schema/sqlite";
import { createHomarrContainer } from "./shared/create-homarr-container";
import { createSqliteDbFileAsync } from "./shared/e2e-db";
import { loginAsync } from "./shared/login";
import { seedAdminUserAsync } from "./shared/seed-admin-user";
const credentials = { username: "admin", password: "Comp(exP4sswOrd" };
const captureScreenshotAsync = async (page: Page, name: string) => {
const directory = process.env.HOMARR_E2E_SCREENSHOT_DIR?.trim();
if (!directory) return;
await mkdir(directory, { recursive: true });
await page.screenshot({ path: path.join(directory, `manage-${name}.png`), fullPage: true });
};
describe("Management collections", () => {
test("renders unified collections and a compact board preview on desktop and mobile", async () => {
const { db, localMountPath } = await createSqliteDbFileAsync();
const { userId } = await seedAdminUserAsync(db, credentials);
const boardId = "manage-board";
const layoutId = "manage-layout";
const sectionId = "manage-section";
const appId = "manage-app";
await db.insert(sqliteSchema.apps).values({
id: appId,
name: "Very long service name used to verify responsive management rows",
description: "A useful service with enough detail to exercise the shared collection description slot.",
iconUrl: "/favicon.ico",
href: "https://service.example.internal/a/very/long/reverse/proxy/path/that/must/not/overflow",
pingUrl: "https://service.example.internal/health",
});
await db.insert(sqliteSchema.integrations).values({
id: "manage-integration",
name: "Living room Sonarr",
url: "https://sonarr.example.internal/a/long/reverse/proxy/path",
kind: "sonarr",
appId,
});
await db.insert(sqliteSchema.searchEngines).values({
id: "manage-search-engine",
iconUrl: "/favicon.ico",
name: "Internal documentation",
short: "docs",
description: "Search private documentation from Spotlight.",
urlTemplate: "https://docs.example.internal/search?q=%s",
type: "generic",
});
await db.insert(sqliteSchema.boards).values({
id: boardId,
name: "management-preview",
creatorId: userId,
isPublic: false,
});
await db.insert(sqliteSchema.layouts).values({
id: layoutId,
name: "Base",
boardId,
columnCount: 10,
breakpoint: 768,
role: "base",
});
await db.insert(sqliteSchema.sections).values({
id: sectionId,
boardId,
kind: "empty",
xOffset: 0,
yOffset: 0,
});
await db.insert(sqliteSchema.items).values([
{
id: "manage-clock",
boardId,
kind: "clock",
options: stringifySuperJSON({ is24HourFormat: true }),
},
{
id: "manage-app-item",
boardId,
kind: "app",
options: stringifySuperJSON({ appId, openInNewTab: true, showTitle: true }),
},
]);
await db.insert(sqliteSchema.itemLayouts).values([
{ itemId: "manage-clock", sectionId, layoutId, xOffset: 0, yOffset: 0, width: 2, height: 2 },
{ itemId: "manage-app-item", sectionId, layoutId, xOffset: 2, yOffset: 0, width: 1, height: 1 },
]);
const homarrContainer = await createHomarrContainer({
environment: { AUTH_PROVIDERS: "credentials" },
mounts: { "/appdata": localMountPath },
}).start();
const baseUrl = `http://${homarrContainer.getHost()}:${homarrContainer.getMappedPort(7575)}`;
const browser = await chromium.launch();
const context = await browser.newContext({ viewport: { width: 1280, height: 900 }, reducedMotion: "reduce" });
const page = await context.newPage();
try {
await loginAsync({ page, baseUrl, credentials, destination: "/manage/apps" });
await page.getByRole("heading", { name: "Apps", exact: true }).waitFor();
expect(await page.getByRole("list", { name: "Configured apps" }).getByRole("listitem").count()).toBe(1);
const editApp = page.getByRole("link", { name: /Edit Very long service name/ });
await editApp.focus();
expect(await editApp.evaluate((element) => element === document.activeElement)).toBe(true);
expect((await editApp.boundingBox())?.height).toBeGreaterThanOrEqual(44);
await page.goto(`${baseUrl}/manage/integrations`);
await page.getByRole("heading", { name: "Integrations", exact: true }).waitFor();
expect(await page.getByRole("list", { name: "Configured integrations" }).getByRole("listitem").count()).toBe(1);
expect(
await page
.getByRole("list", { name: "Configured integrations" })
.getByRole("listitem")
.getByText("Sonarr", { exact: true })
.isVisible(),
).toBe(true);
await page.goto(`${baseUrl}/manage/search-engines`);
await page.getByRole("heading", { name: "Search engines", exact: true }).waitFor();
expect(await page.getByRole("list", { name: "Configured search engines" }).getByRole("listitem").count()).toBe(1);
await page.goto(`${baseUrl}/manage/boards`);
await page.getByRole("heading", { name: "Your boards", exact: true }).waitFor();
const preview = page.getByRole("img", { name: "Layout preview for management-preview, with 2 items" });
await preview.waitFor();
expect(await preview.locator('[data-lane="main"]').count()).toBe(1);
await captureScreenshotAsync(page, "desktop-boards");
await page.setViewportSize({ width: 375, height: 812 });
for (const route of ["apps", "integrations", "search-engines", "boards"]) {
await page.goto(`${baseUrl}/manage/${route}`);
await page.locator("h1:visible").last().waitFor();
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true);
}
await captureScreenshotAsync(page, "mobile-boards");
} finally {
await Promise.all([browser.close(), homarrContainer.stop()]);
}
}, 90_000);
});