← back to Open Seo
e2e/keyword-research-navigation.spec.ts
125 lines
import { expect, test, type Page } from "@playwright/test";
async function getProjectId(page: Page) {
await page.goto("/");
await page.waitForURL(/\/p\/([^/]+)\/keywords(?:\?.*)?$/, {
timeout: 30_000,
});
const match = page.url().match(/\/p\/([^/]+)\/keywords/);
if (!match) throw new Error(`Could not read project id from ${page.url()}`);
return match[1];
}
test.describe("Keyword Research navigation", () => {
test("Back to Recent searches clears the active keyword tab query", async ({
page,
}) => {
const projectId = await getProjectId(page);
await page.goto(
`/p/${projectId}/keywords?q=keyword%20research&loc=2840&kLimit=150&mode=auto`,
);
await expect(
page.getByRole("heading", { name: "Keyword Research", exact: true }),
).toBeVisible();
await expect(
page.getByRole("row", { name: /keyword research/i }).first(),
).toBeVisible({ timeout: 30_000 });
const recentSearchesButton = page.locator(
'[data-testid="keyword-research-recent-searches"]:visible',
);
await expect(recentSearchesButton).toBeVisible();
await recentSearchesButton.click();
await expect
.poll(() => new URL(page.url()).searchParams.get("q"))
.toBe(null);
await expect(
page.getByRole("link", { name: "keyword research US" }),
).toBeVisible();
await expect(recentSearchesButton).toBeHidden();
});
test("clicking keyword tabs keeps the clicked tab and URL in sync", async ({
page,
}) => {
const projectId = await getProjectId(page);
await page.goto(
`/p/${projectId}/keywords?q=keyword%20research&loc=2840&kLimit=150&mode=auto`,
);
await expect(
page.getByRole("row", { name: /keyword research/i }).first(),
).toBeVisible({ timeout: 30_000 });
await page.goto(
`/p/${projectId}/keywords?q=backlinks&loc=2840&kLimit=150&mode=auto`,
);
await expect(
page.getByRole("row", { name: /backlinks/i }).first(),
).toBeVisible({ timeout: 30_000 });
await page.getByRole("tab", { name: /keyword research/i }).click();
await expect
.poll(() => new URL(page.url()).searchParams.get("q"))
.toBe("keyword research");
await expect(
page.getByRole("row", { name: /keyword research/i }).first(),
).toBeVisible();
await page.getByRole("tab", { name: /^backlinks/i }).click();
await expect
.poll(() => new URL(page.url()).searchParams.get("q"))
.toBe("backlinks");
await expect(
page.getByRole("row", { name: /backlinks/i }).first(),
).toBeVisible();
});
test("closing the active middle keyword tab removes it and selects the next tab", async ({
page,
}) => {
const projectId = await getProjectId(page);
for (const keyword of ["ai seo", "backlinks", "open seo"]) {
await page.goto(
`/p/${projectId}/keywords?q=${encodeURIComponent(keyword)}&loc=2840&kLimit=150&mode=auto`,
);
await expect(
page.getByRole("row", { name: new RegExp(keyword, "i") }).first(),
).toBeVisible({ timeout: 30_000 });
}
await page.getByRole("tab", { name: /^backlinks/i }).click();
await expect
.poll(() => new URL(page.url()).searchParams.get("q"))
.toBe("backlinks");
const closeButton = page.getByRole("button", {
name: "Close backlinks tab",
});
const closedTabId = await closeButton.getAttribute("data-search-tab-id");
expect(closedTabId).toBeTruthy();
await closeButton.click();
await expect
.poll(() => new URL(page.url()).searchParams.get("q"))
.toBe("open seo");
await expect(page.getByRole("tab", { name: /^open seo/i })).toHaveAttribute(
"aria-selected",
"true",
);
await expect(
page.locator(`[data-search-tab-id="${closedTabId}"]`),
).toHaveCount(0);
// Count only search tabs: the app shell has grown other tablists
// (Browse/Chat), so a bare role=tab count would include them.
await expect(
page.getByRole("tablist", { name: "Search tabs" }).getByRole("tab"),
).toHaveCount(2);
});
});