import { expect, test, type Page } from "playwright/test"; 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 & code review"); 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 & code review"); 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("region", { name: category }) .getByRole("link", { name: /^View all/ }) .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("https://paseo.sh/plugins/omercnet"); expect(await plugins.text()).toContain( "https://paseo.sh/plugins/omercnet/fresh-worktrees", ); }); }); 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(); });