149 lines
6.2 KiB
TypeScript
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);
|
|
});
|