From 0077fd1aaef5d62b03360e013394f4b708768e2f Mon Sep 17 00:00:00 2001 From: hiyouga Date: Wed, 20 May 2026 15:13:16 +0800 Subject: [PATCH] ci: add pdf export check --- .github/workflows/ci.yml | 15 ++++++++++ .gitignore | 5 ++++ package-lock.json | 64 ++++++++++++++++++++++++++++++++++++++++ package.json | 4 ++- playwright.config.js | 26 ++++++++++++++++ tests/pdf-export.spec.js | 30 +++++++++++++++++++ 6 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 playwright.config.js create mode 100644 tests/pdf-export.spec.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc476db..7d8dff0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,21 @@ jobs: - name: Build default app run: npm run build + - name: Install Playwright browser + run: npx playwright install --with-deps chromium + + - name: Export PDF + run: npm run test:pdf + env: + PDF_EXPORT_DIR: pdf-export + + - name: Upload PDF export + uses: actions/upload-artifact@v4 + with: + name: pdf-export + path: pdf-export/*.pdf + if-no-files-found: error + - name: Build GitHub Pages app run: npm run build env: diff --git a/.gitignore b/.gitignore index 02310e2..33a2ac6 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,8 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +# Playwright and generated PDF export artifacts +playwright-report/ +test-results/ +pdf-export/ diff --git a/package-lock.json b/package-lock.json index 222ec99..9898ca4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { + "@playwright/test": "^1.60.0", "@vitejs/plugin-react": "^5.0.0", "vite": "^6.0.0" } @@ -803,6 +804,22 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@playwright/test": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz", + "integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -1681,6 +1698,53 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz", + "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.60.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.60.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz", + "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", diff --git a/package.json b/package.json index 4c91af1..6fa2d8c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite --host 0.0.0.0", "build": "vite build", - "preview": "vite preview --host 0.0.0.0" + "preview": "vite preview --host 0.0.0.0", + "test:pdf": "playwright test tests/pdf-export.spec.js" }, "dependencies": { "html2canvas": "^1.4.1", @@ -16,6 +17,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { + "@playwright/test": "^1.60.0", "@vitejs/plugin-react": "^5.0.0", "vite": "^6.0.0" } diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..bf58ad2 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,26 @@ +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + timeout: 120_000, + expect: { + timeout: 10_000, + }, + use: { + acceptDownloads: true, + baseURL: "http://127.0.0.1:4173", + trace: "retain-on-failure", + }, + webServer: { + command: "npm run preview -- --host 127.0.0.1 --port 4173", + reuseExistingServer: !process.env.CI, + timeout: 120_000, + url: "http://127.0.0.1:4173", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/tests/pdf-export.spec.js b/tests/pdf-export.spec.js new file mode 100644 index 0000000..f2a662d --- /dev/null +++ b/tests/pdf-export.spec.js @@ -0,0 +1,30 @@ +import { expect, test } from "@playwright/test"; +import { mkdir, readFile } from "node:fs/promises"; +import path from "node:path"; + +const PDF_HEADER = "%PDF-"; +const PDF_EXPORT_DIR = process.env.PDF_EXPORT_DIR || "pdf-export"; +const EXPECTED_FILENAME = "minimal-web-slides-zh.pdf"; + +test("exports the default deck as a PDF", async ({ page }) => { + await page.goto("/"); + + const exportButton = page.getByRole("button", { name: "Export PDF" }); + await expect(exportButton).toBeEnabled(); + + const [download] = await Promise.all([ + page.waitForEvent("download", { timeout: 120_000 }), + exportButton.click(), + ]); + + expect(download.suggestedFilename()).toBe(EXPECTED_FILENAME); + + const outputDir = path.resolve(PDF_EXPORT_DIR); + const outputPath = path.join(outputDir, EXPECTED_FILENAME); + await mkdir(outputDir, { recursive: true }); + await download.saveAs(outputPath); + + const pdf = await readFile(outputPath); + expect(pdf.subarray(0, PDF_HEADER.length).toString()).toBe(PDF_HEADER); + expect(pdf.length).toBeGreaterThan(10_000); +});