mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
feat: reposition to self-hosted file-processing infrastructure (#469)
Leads public copy with self-hosted file-processing infrastructure and demotes tool count to a proof point across README, docs, llms.txt, DockerHub, and the landing site. Adds a /self-hosted hub plus 7 job-intent SEO pages with a build-time validator, deepens the flagship /alternatives pages, adds a remove.bg page, three shared components, and landing e2e coverage.
This commit is contained in:
@@ -6,9 +6,7 @@ test.describe("Landing Homepage", () => {
|
||||
});
|
||||
|
||||
test("page loads with self-hosted title", async ({ page }) => {
|
||||
await expect(page).toHaveTitle(
|
||||
/SnapOtter \| Self-Hosted File Processing for Privacy-Sensitive Teams/,
|
||||
);
|
||||
await expect(page).toHaveTitle(/SnapOtter \| Self-Hosted File-Processing Infrastructure/);
|
||||
});
|
||||
|
||||
test("navbar renders brand and navigation links", async ({ page }) => {
|
||||
@@ -23,12 +21,12 @@ test.describe("Landing Homepage", () => {
|
||||
});
|
||||
|
||||
test("hero renders the headline", async ({ page }) => {
|
||||
await expect(page.locator("h1")).toContainText("File processing for privacy-sensitive teams.");
|
||||
await expect(page.locator("h1")).toContainText("Private file processing for data that");
|
||||
});
|
||||
|
||||
test("hero renders the subtitle", async ({ page }) => {
|
||||
await expect(
|
||||
page.getByText("Run image, video, audio, PDF, and file tools with local AI"),
|
||||
page.getByText("run local AI across image, video, audio, PDF, and documents"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const SLUGS = [
|
||||
"image-compressor",
|
||||
"pdf-ocr",
|
||||
"video-converter",
|
||||
"background-removal",
|
||||
"transcription",
|
||||
"file-conversion-api",
|
||||
"metadata-removal",
|
||||
];
|
||||
|
||||
test.describe("Self-hosted hub", () => {
|
||||
test("renders the hub heading and lists use-case cards", async ({ page }) => {
|
||||
const res = await page.goto("/self-hosted");
|
||||
expect(res?.status()).toBeLessThan(400);
|
||||
await expect(page.getByRole("heading", { name: "Self-hosted file processing" })).toBeVisible();
|
||||
await expect(page.locator('a[href^="/self-hosted/"]')).not.toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Self-hosted spokes", () => {
|
||||
for (const slug of SLUGS) {
|
||||
test(`/self-hosted/${slug} renders and sets a unique canonical`, async ({ page }) => {
|
||||
const res = await page.goto(`/self-hosted/${slug}`);
|
||||
expect(res?.status()).toBeLessThan(400);
|
||||
await expect(page.locator("main h1")).toBeVisible();
|
||||
await expect(page.locator('link[rel="canonical"]')).toHaveAttribute(
|
||||
"href",
|
||||
`https://snapotter.com/self-hosted/${slug}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test.describe("pdf-ocr spoke details", () => {
|
||||
test("emits FAQ JSON-LD and links to a real tool page", async ({ page }) => {
|
||||
await page.goto("/self-hosted/pdf-ocr");
|
||||
await expect(page.locator("main h1")).toContainText("Self-hosted PDF OCR");
|
||||
|
||||
const ld = await page.locator('script[type="application/ld+json"]').allTextContents();
|
||||
expect(ld.some((t) => t.includes('"FAQPage"'))).toBe(true);
|
||||
|
||||
const toolLink = page.locator('a[href="/tools/pdf/ocr-pdf"]').first();
|
||||
await expect(toolLink).toBeVisible();
|
||||
const toolRes = await page.request.get("/tools/pdf/ocr-pdf");
|
||||
expect(toolRes.status()).toBeLessThan(400);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user