mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Search Console flagged four reasons on 2026-07-28: soft 404, both duplicate-canonical variants, and noindex. All four sat on docs.snapotter.com; the landing site was clean. The cause was boilerplate dominance rather than a broken tag. /tools/video/crop-video carries 1.3 KB of unique body against 3.5 KB of identical chrome (nav, sidebar, 21-language switcher), so unrelated tool docs measured 52-60% full-page similarity. Across 20 locales that was 3,640 of 3,822 submitted URLs. Google read the lot as one duplicate cluster and began electing arbitrary representatives: /changelog became the canonical for /tools/image/favicon, and /uk/guide/getting-started for /nl/tools/image/resize. English tool docs indexed 2 of 10 and localized 4 of 10, while the landing page for those same five tools indexed 5 of 5. Translated pages now emit `noindex, follow` with a self-canonical, and sitemap.transformItems drops them from the sitemap. Self-canonical rather than pointing at English, since noindex paired with a cross-canonical sends two conflicting instructions. hreflang is removed outright: the annotation only means something between pages that can all be indexed. Readers see no change. The language switcher and every in-page link behave exactly as before. Verified against a real build: sitemap 3,822 to 182 URLs with zero translated entries, all 3,640 translated files carrying the noindex and no English file doing so, docs e2e 100 passed.
65 lines
2.9 KiB
TypeScript
65 lines
2.9 KiB
TypeScript
// tests/e2e-docs/i18n.spec.ts
|
|
import { expect, test } from "@playwright/test";
|
|
import { openDocsSearch } from "./helpers";
|
|
|
|
test.describe("docs i18n (English + committed German locale)", () => {
|
|
test("English home still renders at root", async ({ page }) => {
|
|
await page.goto("/");
|
|
await expect(page).toHaveTitle(/SnapOtter/);
|
|
});
|
|
|
|
test("German locale subtree renders under /de/", async ({ page }) => {
|
|
await page.goto("/de/guide/getting-started");
|
|
await expect(page.getByRole("heading", { name: "Erste Schritte" }).first()).toBeVisible();
|
|
await expect(page.locator("html")).toHaveAttribute("lang", "de");
|
|
});
|
|
|
|
test("machine-translation banner shows on a non-English page, not on English", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto("/de/guide/low-resource");
|
|
await expect(page.locator(".mt-banner")).toBeVisible();
|
|
await page.goto("/guide/getting-started");
|
|
await expect(page.locator(".mt-banner")).toHaveCount(0);
|
|
});
|
|
|
|
test("locale nav/sidebar links keep the /de prefix", async ({ page }) => {
|
|
await page.goto("/de/guide/getting-started");
|
|
const sidebar = page.locator(".VPSidebar, aside");
|
|
const firstLink = sidebar.locator('a[href^="/de/"]').first();
|
|
await expect(firstLink).toBeVisible();
|
|
});
|
|
|
|
test("an in-content #anchor deep link resolves in the German locale", async ({ page }) => {
|
|
// Anchors are stable across locales; #quick-start exists on the German page too.
|
|
await page.goto("/de/guide/getting-started#quick-start");
|
|
const target = page.locator("#quick-start");
|
|
await expect(target).toBeAttached();
|
|
await expect(target).toBeInViewport();
|
|
});
|
|
|
|
test("English pages are indexable and self-canonical", async ({ page }) => {
|
|
await page.goto("/guide/getting-started");
|
|
const canonical = await page.locator('link[rel="canonical"]').getAttribute("href");
|
|
expect(canonical).toBe("https://docs.snapotter.com/guide/getting-started");
|
|
await expect(page.locator('meta[name="robots"]')).toHaveCount(0);
|
|
});
|
|
|
|
// Only English is indexable, so there is no hreflang cluster left to annotate.
|
|
// See the transformHead comment in apps/docs/.vitepress/config.mts.
|
|
test("translated pages are noindex, self-canonical, and carry no hreflang", async ({ page }) => {
|
|
await page.goto("/de/guide/getting-started");
|
|
const canonical = await page.locator('link[rel="canonical"]').getAttribute("href");
|
|
expect(canonical).toBe("https://docs.snapotter.com/de/guide/getting-started");
|
|
await expect(page.locator('meta[name="robots"]')).toHaveAttribute("content", "noindex, follow");
|
|
await expect(page.locator("link[hreflang]")).toHaveCount(0);
|
|
});
|
|
|
|
test("pagefind returns results within the German locale", async ({ page }) => {
|
|
await page.goto("/de/guide/getting-started");
|
|
const input = await openDocsSearch(page);
|
|
await input.fill("docker");
|
|
await expect(page.locator('[role="option"]').first()).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
});
|