# Getting started (/docs/getting-started) ## 1. Install [#1-install] ```bash npm install @sargonpiraev/seokit @playwright/test ``` ## 2. Extend expect [#2-extend-expect] Create `src/test/seokit.ts`: ```typescript import { expect as baseExpect, test } from "@playwright/test"; import { extendSeokitExpect } from "@sargonpiraev/seokit"; const expect = extendSeokitExpect(baseExpect); export { test, expect }; ``` Import `test` / `expect` from this file in SEO specs — not from `@playwright/test` directly. ## 3. Playwright project [#3-playwright-project] ```typescript import { defineConfig } from "@playwright/test"; export default defineConfig({ testDir: "./src", testMatch: "**/*.seokit.spec.ts", use: { baseURL: process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:3000", }, }); ``` ```bash npx playwright test ``` ## 4. First spec [#4-first-spec] Colocate next to the page, e.g. `src/app/[locale]/page.seokit.spec.ts`. Recommended pattern: known locales × known URLs, with expectations in a test-only fixture file (not imported by the app): ```typescript import { test, expect } from "@/test/seokit"; const LOCALES = ["en", "de", "fr"] as const; for (const locale of LOCALES) { test(`/${locale}`, async ({ page }) => { const response = await page.goto(`/${locale}`); expect(response?.ok()).toBeTruthy(); await expect(page).toHaveMetadata({ lang: locale, title: /.+/, description: /.+/, alternates: { canonical: `https://example.com/${locale}`, languages: { en: "https://example.com/en", de: "https://example.com/de", fr: "https://example.com/fr", "x-default": "https://example.com/en", }, }, }); await expect(page).toHaveJsonLd([{ "@type": "Organization" }]); }); } ``` See the [example app](https://github.com/sargonpiraev/seokit/tree/main/apps/example) (`src/test/seo-fixtures.ts` + colocated `*.seokit.spec.ts`). Full matcher reference: [Matchers](/docs/matchers). ## Optional: Next route helpers [#optional-next-route-helpers] `@sargonpiraev/seokit/next` also exports `createSeokitPageRoutes` / `assertSeokitRouteBasics` for next-intl route matrices driven by the Next build manifest. They need a prior `next build`. The example app uses the simpler fixture loops above; helpers are covered by package unit tests under `packages/seokit/src/next/`. # Overview (/docs) **seokit** adds two Playwright matchers for page SEO contracts: | Matcher | What it does | | ---------------- | ------------------------------------------ | | `toHaveMetadata` | HTML → Next-like metadata object → compare | | `toHaveJsonLd` | JSON-LD scripts → compare | ```typescript await expect(page).toHaveMetadata({ lang: "en", title: /Acme/, description: /.+/, alternates: { canonical: "https://example.com/en", languages: { en: "https://example.com/en", "x-default": "https://example.com/en", }, }, robots: { index: true, follow: true }, }); await expect(page).toHaveJsonLd([{ "@type": "Organization" }]); ``` ## In scope / out of scope [#in-scope--out-of-scope] | In seokit | Use Playwright directly | | -------------------------------------------------------------------- | ------------------------------------------------------------- | | Page metadata (title, description, canonical, hreflang, robots meta) | `response?.ok()`, status, headers | | JSON-LD on HTML pages | `robots.txt` / sitemap XML (see example `app.seokit.spec.ts`) | ## Next [#next] * [Getting started](/docs/getting-started) * [Matchers](/docs/matchers) * [Skill](/docs/skill) # Matchers (/docs/matchers) Both matchers take an `expected` value. seokit extracts the actual value from the page and deep-partial compares. Strings may be `RegExp`. ## `toHaveMetadata` [#tohavemetadata] Extracts a Next.js-like metadata object from HTML and compares. ```typescript await expect(page).toHaveMetadata({ lang: "en", title: "Acme", description: /tools/, alternates: { canonical: "https://example.com/en", languages: { en: "https://example.com/en", eo: "https://example.com/eo", "x-default": "https://example.com/en", }, }, robots: { index: true, follow: true }, }); ``` | Field | Source | | ---------------------- | -------------------------------------------- | | `lang` | `` | | `title` | `