Files
paseo/packages/website/e2e/plugins.spec.ts
T
Mohamed Boudra 5aee41952e feat(website): browse plugins by nine community categories (#6068)
Organize community plugins by what Paseo does and verify the category layout with a static registry fixture.
2026-10-04 17:12:11 +01:00

240 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test, type Page } from "playwright/test";
import { CATEGORIES } from "../src/plugins/categories";
async function openPlugins(page: Page) {
// Wait for hydration so typing reaches React rather than the server-rendered input.
await page.goto("/plugins", { waitUntil: "networkidle" });
await expect(page.getByRole("heading", { level: 1, name: "Plugins" })).toBeVisible();
}
test("browses from the directory into a category, a plugin, and its author", async ({
page,
context,
}) => {
// WebKit has no clipboard permission to grant; record what the page writes instead.
await context.addInitScript(() => {
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: {
writeText: (text: string) => {
(window as unknown as { __copied?: string }).__copied = text;
return Promise.resolve();
},
},
});
});
await openPlugins(page);
const themes = page.getByRole("region", { name: "Themes" });
await expect(themes.getByRole("link", { name: /Dracula/ })).toBeVisible();
await browseCategory(page, "Git");
await expect(page).toHaveURL(/category=/);
await expect(page.getByRole("heading", { level: 2 })).toHaveCount(1);
await expect(page.getByRole("region", { name: "Themes" })).toHaveCount(0);
await openPlugin(page, /Fresh Worktrees/);
await expect(page).toHaveURL(/\/plugins\/omercnet\/fresh-worktrees$/);
await expect(
page.getByRole("heading", { level: 1, name: "Fresh Worktrees", exact: true }),
).toBeVisible();
await expect(page.getByText("paseo plugin install omercnet/fresh-worktrees")).toBeVisible();
await expect(
page.getByRole("heading", { level: 2, name: "Link to this section Behavior", exact: true }),
).toBeVisible();
await copyInstallCommand(page);
await expect
.poll(() => page.evaluate(() => (window as unknown as { __copied?: string }).__copied))
.toBe("paseo plugin install omercnet/fresh-worktrees");
const breadcrumbs = page.getByRole("navigation", { name: "Breadcrumb" });
await expect(breadcrumbs).toContainText("Plugins");
await expect(breadcrumbs).toContainText("Git");
await expect(breadcrumbs).toContainText("Fresh Worktrees");
await openAuthor(page, "Omer Cohen");
await expect(page).toHaveURL(/\/plugins\/omercnet$/);
await expect(page.getByRole("heading", { level: 1, name: "Omer Cohen" })).toBeVisible();
await expect(page.getByRole("link", { name: /Agent Monitor/ })).toBeVisible();
await expect(page.getByRole("link", { name: /Defer/ })).toHaveCount(0);
});
test("filters by search and clears to the full directory", async ({ page }) => {
await openPlugins(page);
await searchPlugins(page, "graphite");
await expect(page).toHaveURL(/q=graphite/);
await expect(page.getByRole("heading", { level: 2, name: /Results for/ })).toBeVisible();
await expect(page.getByRole("link", { name: /Graphite/ })).toBeVisible();
await expect(page.getByRole("link", { name: /Dracula/ })).toHaveCount(0);
await searchPlugins(page, "zzzz-nothing");
await expect(page.getByText("No plugins match.")).toBeVisible();
await page.getByRole("link", { name: "Clear filters" }).click();
await expect(page).toHaveURL(/\/plugins\/?$/);
await expect(page.getByRole("region", { name: "Themes" })).toBeVisible();
});
test("explains a plugin that is not listed", async ({ page }) => {
await page.goto("/plugins/acme/does-not-exist");
await expect(page.getByRole("heading", { level: 1, name: "Plugin not found" })).toBeVisible();
await page.getByRole("link", { name: "Browse all plugins" }).click();
await expect(page.getByRole("heading", { level: 1, name: "Plugins" })).toBeVisible();
});
async function copyInstallCommand(page: Page) {
await page.getByRole("button", { name: "Copy to clipboard" }).first().click();
}
async function browseCategory(page: Page, category: string) {
await page.getByRole("link", { name: category, exact: true }).click();
}
async function openPlugin(page: Page, name: RegExp) {
await page.getByRole("link", { name }).first().click();
}
async function openAuthor(page: Page, name: string) {
await page.getByRole("link", { name }).first().click();
}
async function searchPlugins(page: Page, query: string) {
await page.getByRole("searchbox", { name: "Search plugins" }).fill(query);
}
test.describe("search engine visits without JavaScript", () => {
test.use({ javaScriptEnabled: false });
test("reads directory, author, and complete plugin HTML with page metadata", async ({ page }) => {
await openPlugins(page);
await expect(page.getByRole("link", { name: /Fresh Worktrees/ }).first()).toBeVisible();
await expectPageMetadata(page, "Plugins – Extend Paseo with community plugins", "/plugins");
await page.goto("/plugins/omercnet");
await expect(page.getByRole("heading", { level: 1, name: "Omer Cohen" })).toBeVisible();
await expect(page.getByRole("link", { name: /Fresh Worktrees/ }).first()).toBeVisible();
await expectPageMetadata(page, "Omer Cohen – Paseo plugins", "/plugins/omercnet");
const response = await page.goto("/plugins/omercnet/fresh-worktrees");
expect(response?.status()).toBe(200);
expect(response?.headers()["cache-control"]).toBe("private, no-store");
expect(response?.headers()["x-robots-tag"]).toBeUndefined();
await expect(page.getByText("paseo plugin install omercnet/fresh-worktrees")).toBeVisible();
await expect(
page.getByRole("heading", { level: 2, name: "Link to this section Behavior", exact: true }),
).toBeVisible();
await expectPageMetadata(
page,
"Fresh Worktrees – Paseo plugin",
"/plugins/omercnet/fresh-worktrees",
);
await expect(page.locator('meta[property="og:image"]')).toHaveAttribute(
"content",
"https://raw.githubusercontent.com/omercnet/paseo-plugins/main/fresh-worktrees/docs/images/fresh-worktrees-behind.png",
);
await expect(page.locator('meta[name="description"]')).toHaveAttribute(
"content",
"Fast-forwards clean local base branches before Paseo creates branch-off worktrees",
);
await expect(page.locator('meta[name="robots"]')).toHaveCount(0);
await page.goto("/plugins/tomgrin10/graphite");
await expectPageMetadata(page, "Graphite – Paseo plugin", "/plugins/tomgrin10/graphite");
await expect(page.locator('meta[property="og:image"]')).toHaveAttribute(
"content",
"https://paseo.sh/og-image.png",
);
});
test("returns real 404 pages for unknown plugins and authors", async ({ page }) => {
const plugin = await page.goto("/plugins/acme/does-not-exist");
expect(plugin?.status()).toBe(404);
await expect(page.getByRole("heading", { name: "Plugin not found" })).toBeVisible();
await expect(page).toHaveTitle("Plugin not found – Paseo");
const author = await page.goto("/plugins/does-not-exist");
expect(author?.status()).toBe(404);
await expect(page.getByRole("heading", { name: "Author not found" })).toBeVisible();
await expect(page).toHaveTitle("Author not found – Paseo");
});
test("discovers plugin and author URLs through robots and the sitemap index", async ({
request,
}) => {
const robots = await request.get("/robots.txt");
expect(await robots.text()).toContain("Sitemap: https://paseo.sh/sitemap-index.xml");
const index = await request.get("/sitemap-index.xml");
expect(await index.text()).toContain("https://paseo.sh/sitemap-plugins.xml");
const plugins = await request.get("/sitemap-plugins.xml");
expect(plugins.status()).toBe(200);
expect(plugins.headers()["content-type"]).toContain("application/xml");
expect(await plugins.text()).toContain("<loc>https://paseo.sh/plugins/omercnet</loc>");
expect(await plugins.text()).toContain(
"<loc>https://paseo.sh/plugins/omercnet/fresh-worktrees</loc>",
);
});
});
async function expectPageMetadata(page: Page, title: string, path: string) {
await expect(page).toHaveTitle(title);
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
"href",
`https://paseo.sh${path}`,
);
await expect(page.locator('meta[property="og:title"]')).toHaveAttribute("content", title);
await expect(page.locator('meta[property="og:url"]')).toHaveAttribute(
"content",
`https://paseo.sh${path}`,
);
await expect(page.locator('meta[property="og:type"]')).toHaveAttribute("content", "website");
await expect(page.locator('meta[name="twitter:card"]')).toHaveAttribute(
"content",
"summary_large_image",
);
const description = await page.locator('meta[name="description"]').getAttribute("content");
expect(description).toBeTruthy();
await expect(page.locator('meta[property="og:description"]')).toHaveAttribute(
"content",
description!,
);
}
test("keeps the directory unlinked until the coordinated announcement", async ({ page }) => {
await page.goto("/");
await expect(
page.getByRole("banner").getByRole("link", { name: "Plugins", exact: true }),
).toHaveCount(0);
await expect(
page.getByRole("contentinfo").getByRole("link", { name: "Plugins", exact: true }),
).toHaveCount(0);
await expect(page.getByRole("link", { name: "Community plugins" })).toHaveAttribute(
"href",
"https://paseo.cafe",
);
await expect(page.locator('a[href="/plugins"]')).toHaveCount(0);
const response = await page.goto("/plugins");
expect(response?.status()).toBe(200);
await expect(page.getByRole("heading", { level: 1, name: "Plugins" })).toBeVisible();
});
// External deployments own their registry contents; these assertions use the local fixture.
test.describe("registry category layout", () => {
test.skip(Boolean(process.env.WEBSITE_TEST_URL), "Requires the local registry fixture");
test("orders populated categories and hides the empty Extras category", async ({ page }) => {
await openPlugins(page);
await expect(page.getByRole("heading", { level: 2 })).toHaveText([
"Daemon management",
"Themes",
"Providers",
"Orchestration",
"Git",
"Workspaces",
"Sidebar",
"Utils",
]);
await expect(page.getByRole("region", { name: "Extras", exact: true })).toHaveCount(0);
for (const category of CATEGORIES.filter((entry) => entry.slug !== "extras")) {
await expect(page.getByRole("region", { name: category.label, exact: true })).toContainText(
category.description,
);
}
});
});