diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index ea5f17f3fa..83e4ee4303 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -133,7 +133,7 @@ jobs: run: pnpm exec playwright install --with-deps - name: Run Playwright tests - run: pnpm exec playwright test playwright/global.spec.ts --workers 6 + run: pnpm exec playwright test playwright/smoke --workers 6 - uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 0aece47314..1c6ac51786 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,7 +1,5 @@ name: Playwright Tests -on: - push: - branches: [main] +on: pull_request env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} @@ -11,25 +9,43 @@ env: WORKOS_CLIENT_ID: ${{ secrets.WORKOS_CLIENT_ID }} # HUME_API_KEY: ${{ secrets.HUME_API_KEY }} -jobs: {} - # test: - # timeout-minutes: 60 - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v4 +jobs: + test: + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - # - name: Install - # uses: ./.github/actions/install + - name: Install + uses: ./.github/actions/install - # - name: Install Playwright Browsers - # run: pnpm exec playwright install --with-deps + - name: Compile and build + run: pnpm turbo compile codegen build + env: + FERN_TOKEN: ${{ secrets.FERN_TOKEN }} + WORKOS_API_KEY: ${{ secrets.WORKOS_API_KEY }} + WORKOS_CLIENT_ID: ${{ secrets.WORKOS_CLIENT_ID }} - # - name: Run Playwright tests - # run: pnpm exec playwright test --debug + - name: Build next bundle + run: cd packages/ui/local-preview-bundle; pnpm next build - # - uses: actions/upload-artifact@v4 - # if: always() - # with: - # name: playwright-report - # path: playwright-report/ - # retention-days: 30 + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + + - name: Install fern-dev API + env: + NPM_TOKEN: ${{ secrets.FERN_NPM_TOKEN }} + FERN_TOKEN: ${{ secrets.FERN_ORG_TOKEN_DEV }} + run: | + npm config set //registry.npmjs.org/:_authToken $NPM_TOKEN + npm install -g @fern-api/fern-api-dev + + - name: Run Playwright tests + run: pnpm exec playwright test playwright/fixtures + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 6d49e1a88b..318a0949f4 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -131,10 +131,7 @@ jobs: run: pnpm exec playwright install --with-deps - name: Run Playwright tests - run: pnpm exec playwright test playwright/global.spec.ts --workers 6 - - - name: Run Playwright tests - run: pnpm exec playwright test playwright/versioned-docs.spec.ts + run: pnpm exec playwright test playwright/smoke --workers 6 - uses: actions/upload-artifact@v4 if: always() diff --git a/playwright/fixtures/stylesheet/fern/docs.yml b/playwright/fixtures/stylesheet/fern/docs.yml new file mode 100644 index 0000000000..7926bc42f8 --- /dev/null +++ b/playwright/fixtures/stylesheet/fern/docs.yml @@ -0,0 +1,9 @@ +instances: + - url: https://stylesheet-fixture.docs.buildwithfern.com +title: Sample Styled Docs +navigation: + - page: Test Page + path: ./page.mdx +colors: + accentPrimary: "#ffffff" + background: "#330000" diff --git a/playwright/fixtures/stylesheet/fern/fern.config.json b/playwright/fixtures/stylesheet/fern/fern.config.json new file mode 100644 index 0000000000..b8404da414 --- /dev/null +++ b/playwright/fixtures/stylesheet/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "stylesheet-fixture", + "version": "*" +} diff --git a/playwright/fixtures/stylesheet/fern/page.mdx b/playwright/fixtures/stylesheet/fern/page.mdx new file mode 100644 index 0000000000..22041cbdbc --- /dev/null +++ b/playwright/fixtures/stylesheet/fern/page.mdx @@ -0,0 +1,4 @@ +--- +title: Welcome to a demo site +slug: page +--- diff --git a/playwright/fixtures/stylesheet/stylesheet.spec.ts b/playwright/fixtures/stylesheet/stylesheet.spec.ts new file mode 100644 index 0000000000..3b75b46912 --- /dev/null +++ b/playwright/fixtures/stylesheet/stylesheet.spec.ts @@ -0,0 +1,18 @@ +import test, { expect } from "@playwright/test"; +import { runFixture } from "../../test-runner"; + +const port = "6983"; +test("Check CSS variable values", async ({ page }) => { + const computedStyles = await runFixture("stylesheet", port, async () => { + await page.goto(`localhost:${port}`); + + const element = page.locator("body"); + return await element.evaluate((el) => { + const styles = window.getComputedStyle(el); + return { + backgroundColor: styles.getPropertyValue("--background"), + }; + }); + }); + expect(computedStyles.backgroundColor.trim()).toBe("51, 0, 0"); +}); diff --git a/playwright/global.spec.ts b/playwright/global.spec.ts deleted file mode 100644 index 821471dcec..0000000000 --- a/playwright/global.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { expect, test } from "@playwright/test"; -import * as fs from "fs"; -import * as yaml from "js-yaml"; - -const testUrlConfigs: { - url: string; - isFaviconIncluded: boolean; -}[] = []; - -const playwrightConfig = yaml.load(fs.readFileSync("playwright/global-inclusions.yml", "utf-8")); - -const globalTestInclusions = new Set(playwrightConfig["global-inclusions"]); -const faviconInclusions = new Set(playwrightConfig["favicon-inclusions"]); - -function processLineByLineSync(filePath: string): void { - const fileContent = fs.readFileSync(filePath, "utf-8"); - - const lines = fileContent.split(/\r?\n/); - - lines.forEach((line) => { - const urlPattern = /\(([^)]+\?host=([^&]+))\)/; - const match = line.match(urlPattern); - if (match) { - const fullUrl = match[1]; - const isIncludedUrl = match[2]; - if (fullUrl && globalTestInclusions.has(isIncludedUrl ?? "")) { - testUrlConfigs.push({ - url: fullUrl, - isFaviconIncluded: faviconInclusions.has(isIncludedUrl ?? ""), - }); - } - } - }); -} - -processLineByLineSync("preview.txt"); - -if (testUrlConfigs.length === 0) { - throw new Error("No URLs found in preview.txt"); -} - -testUrlConfigs.forEach((testUrlConfig) => { - test(`Check if ${testUrlConfig.url} is online`, async ({ page }) => { - const response = await page.goto(testUrlConfig.url); - expect(response?.status()).toBe(200); - }); - - test(`Check if favicon exists and URL does not return 404 for ${testUrlConfig.url}`, async ({ page }) => { - if (!testUrlConfig.isFaviconIncluded) { - return; - } - - await page.goto(testUrlConfig.url); - - const faviconUrl = await page.getAttribute('link[rel="icon"]', "href", { - timeout: 5000, - }); - - expect(faviconUrl).not.toBeNull(); - - if (faviconUrl) { - const response = await page.goto(faviconUrl); - expect(response?.status()).toBe(200); - } else { - throw new Error("Favicon link not found"); - } - }); -}); diff --git a/playwright/global-inclusions.yml b/playwright/inclusions.yml similarity index 93% rename from playwright/global-inclusions.yml rename to playwright/inclusions.yml index 4c3601d880..daba0cdcdb 100644 --- a/playwright/global-inclusions.yml +++ b/playwright/inclusions.yml @@ -1,4 +1,4 @@ -global-inclusions: +existence: - docs.trykeet.com - docs.argolabs.ai - docs.conductorquantum.com @@ -45,7 +45,7 @@ global-inclusions: - dev.documentation.sayari.com - fern.assemblyai.com -favicon-inclusions: +favicon: - docs.argolabs.ai - docs.conductorquantum.com - developers.gappify.com @@ -57,14 +57,10 @@ favicon-inclusions: - www.intern.mavenagi.com - docs.schematichq.com - plantstore.dev - - www.plantstore.dev - docs.boundaryml.com - - docs.qlty.ai - docs.rightbrain.ai - docs.getkard.com - docs.syndicate.io - - docs.qlty.us - - hume.ferndocs.com - docs.stack-auth.com - gemini2.ferndocs.com - docs.darwincloud.xyz @@ -77,7 +73,6 @@ favicon-inclusions: - docs.reka.ai - docs.aiaplatform.com.au - docs.anterior.com - - developers.beehiiv.com - docs.scorecard.io - fern.humanloop.com - docs.goldenbasis.com diff --git a/playwright/run-fixture.sh b/playwright/run-fixture.sh new file mode 100755 index 0000000000..50d6049e12 --- /dev/null +++ b/playwright/run-fixture.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd playwright/fixtures/$1 +fern-dev docs dev --port $2 --bundle-path ../../../packages/ui/local-preview-bundle/out \ No newline at end of file diff --git a/playwright/smoke/existence.spec.ts b/playwright/smoke/existence.spec.ts new file mode 100644 index 0000000000..71d88e8390 --- /dev/null +++ b/playwright/smoke/existence.spec.ts @@ -0,0 +1,10 @@ +import { expect, test } from "@playwright/test"; +import { getPlaywrightTestUrls } from "../test-runner"; + +const existenceUrls = getPlaywrightTestUrls("existence"); +existenceUrls.forEach((testUrl) => { + test(`Check if ${testUrl} is online`, async ({ page }) => { + const response = await page.goto(testUrl); + expect(response?.status()).toBe(200); + }); +}); diff --git a/playwright/smoke/favicon.spec.ts b/playwright/smoke/favicon.spec.ts new file mode 100644 index 0000000000..91abd0a99c --- /dev/null +++ b/playwright/smoke/favicon.spec.ts @@ -0,0 +1,22 @@ +import test, { expect } from "@playwright/test"; +import { getPlaywrightTestUrls } from "../test-runner"; + +const faviconUrls = getPlaywrightTestUrls("favicon"); +faviconUrls.forEach((testUrl) => { + test(`Check if favicon exists and URL does not return 404 for ${testUrl}`, async ({ page }) => { + await page.goto(testUrl); + + const faviconUrl = await page.getAttribute('link[rel="icon"]', "href", { + timeout: 5000, + }); + + expect(faviconUrl).not.toBeNull(); + + if (faviconUrl) { + const response = await page.goto(faviconUrl); + expect(response?.status()).toBe(200); + } else { + throw new Error("Favicon link not found"); + } + }); +}); diff --git a/playwright/versioned-docs.spec.ts b/playwright/smoke/versioned-docs.spec.ts similarity index 98% rename from playwright/versioned-docs.spec.ts rename to playwright/smoke/versioned-docs.spec.ts index ff801b9a8b..05ff68599a 100644 --- a/playwright/versioned-docs.spec.ts +++ b/playwright/smoke/versioned-docs.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "@playwright/test"; -import { generatePreviewContext } from "./utils"; +import { generatePreviewContext } from "../utils"; /** * This test will run on a sample of urls from customer docs using versions. diff --git a/playwright/test-runner.ts b/playwright/test-runner.ts new file mode 100644 index 0000000000..6eef57778b --- /dev/null +++ b/playwright/test-runner.ts @@ -0,0 +1,67 @@ +import { spawn } from "child_process"; +import * as fs from "fs"; +import * as yaml from "js-yaml"; + +export function getPlaywrightTestUrls(type: string): string[] { + const testUrls: string[] = []; + const playwrightConfig = yaml.load(fs.readFileSync("playwright/inclusions.yml", "utf-8")); + + if (!(type in playwrightConfig)) { + throw new Error(`Test type ${type} not found in playwright/inclusions.yml`); + } + const testInclusions = new Set(playwrightConfig[type]); + + function processLineByLineSync(filePath: string): void { + const fileContent = fs.readFileSync(filePath, "utf-8"); + + const lines = fileContent.split(/\r?\n/); + + lines.forEach((line) => { + const urlPattern = /\(([^)]+\?host=([^&]+))\)/; + const match = line.match(urlPattern); + if (match) { + const fullUrl = match[1]; + const isIncludedUrl = match[2]; + if (fullUrl && testInclusions.has(isIncludedUrl ?? "")) { + testUrls.push(fullUrl); + } + } + }); + } + + processLineByLineSync("preview.txt"); + + if (testUrls.length === 0) { + throw new Error("No URLs found in preview.txt"); + } + + return testUrls; +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export async function runFixture(fixturePath: string, port: string, test: () => Promise): Promise { + const l = spawn("playwright/run-fixture.sh", [fixturePath, port]); + l.stdout.pipe(process.stdout); + l.stderr.pipe(process.stderr); + await sleep(7500); + + const result = await test(); + + await sleep(500); + const livePort = spawn("lsof", ["-i", `:${port}`, "-t"]); + livePort.stdout.on("data", (data) => { + data.toString() + .split("\n") + .forEach((pid: string) => { + if (pid.length > 0 && Number(pid) !== process.pid) { + process.kill(Number(pid)); + } + }); + }); + await sleep(500); + + return result; +}