mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
ci: gate demo deploy
This commit is contained in:
@@ -13,6 +13,11 @@ on:
|
|||||||
- "turbo.json"
|
- "turbo.json"
|
||||||
- "tsconfig.base.json"
|
- "tsconfig.base.json"
|
||||||
- ".github/actions/setup/**"
|
- ".github/actions/setup/**"
|
||||||
|
- "playwright.demo.config.ts"
|
||||||
|
- "tests/e2e-demo/**"
|
||||||
|
- "tests/unit/infra/demo-theme.test.ts"
|
||||||
|
- "tests/unit/infra/demo-mock-api.test.ts"
|
||||||
|
- "tests/unit/infra/deploy-demo-workflow.test.ts"
|
||||||
- ".github/workflows/deploy-demo.yml"
|
- ".github/workflows/deploy-demo.yml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -27,13 +32,26 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
name: Deploy
|
name: Deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
- uses: ./.github/actions/setup
|
- uses: ./.github/actions/setup
|
||||||
|
|
||||||
|
- name: Run focused demo unit tests
|
||||||
|
run: pnpm vitest run --config vitest.config.ts tests/unit/infra/demo-theme.test.ts tests/unit/infra/deploy-demo-workflow.test.ts tests/unit/infra/demo-mock-api.test.ts
|
||||||
|
|
||||||
|
- name: Typecheck Demo
|
||||||
|
run: pnpm --filter @snapotter/demo exec tsc --noEmit
|
||||||
|
|
||||||
- name: Build Demo
|
- name: Build Demo
|
||||||
run: pnpm --filter @snapotter/demo build
|
run: pnpm --filter @snapotter/demo build
|
||||||
|
|
||||||
|
- name: Install Playwright Chromium
|
||||||
|
run: pnpm playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- name: Smoke Test Demo
|
||||||
|
run: pnpm playwright test --config playwright.demo.config.ts
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Pages
|
- name: Deploy to Cloudflare Pages
|
||||||
run: npx wrangler pages deploy apps/demo/dist --project-name snapotter-demo --branch main --commit-dirty=true
|
run: npx wrangler pages deploy apps/demo/dist --project-name snapotter-demo --branch main --commit-dirty=true
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineConfig, devices } from "@playwright/test";
|
||||||
|
|
||||||
|
const demoPort = 4174;
|
||||||
|
const demoBaseUrl = `http://127.0.0.1:${demoPort}`;
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: "./tests/e2e-demo",
|
||||||
|
timeout: 30_000,
|
||||||
|
expect: { timeout: 10_000 },
|
||||||
|
fullyParallel: false,
|
||||||
|
retries: process.env.CI ? 1 : 0,
|
||||||
|
workers: 1,
|
||||||
|
reporter: process.env.CI ? "github" : "list",
|
||||||
|
use: {
|
||||||
|
baseURL: demoBaseUrl,
|
||||||
|
...devices["Desktop Chrome"],
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
trace: "retain-on-failure",
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: `pnpm --filter @snapotter/demo exec vite preview --host 127.0.0.1 --port ${demoPort} --strictPort`,
|
||||||
|
url: demoBaseUrl,
|
||||||
|
reuseExistingServer: false,
|
||||||
|
timeout: 60_000,
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("demo preview uses the real app theme and reaches a tool page", async ({ page }) => {
|
||||||
|
const consoleErrors: string[] = [];
|
||||||
|
const pageErrors: string[] = [];
|
||||||
|
|
||||||
|
page.on("console", (message) => {
|
||||||
|
if (message.type() === "error") consoleErrors.push(message.text());
|
||||||
|
});
|
||||||
|
page.on("pageerror", (error) => {
|
||||||
|
pageErrors.push(error.message);
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto("/login");
|
||||||
|
await expect(page.getByText("This is a live demo. Processing is disabled.")).toBeVisible();
|
||||||
|
await expect(page.getByRole("heading", { name: "Login" })).toBeVisible();
|
||||||
|
|
||||||
|
const theme = await page.evaluate(() => {
|
||||||
|
const bannerLink = Array.from(document.querySelectorAll("a")).find((link) =>
|
||||||
|
link.textContent?.includes("Self-host SnapOtter"),
|
||||||
|
);
|
||||||
|
const banner = bannerLink?.closest("div");
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue("--color-primary")
|
||||||
|
.trim(),
|
||||||
|
themeColor: document.querySelector('meta[name="theme-color"]')?.getAttribute("content"),
|
||||||
|
bannerBackground: banner ? getComputedStyle(banner).backgroundColor : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(theme.primary.toLowerCase()).toBe("#e07832");
|
||||||
|
expect(theme.themeColor?.toLowerCase()).toBe("#e07832");
|
||||||
|
expect(theme.bannerBackground).toBe("rgb(224, 120, 50)");
|
||||||
|
|
||||||
|
await page.getByLabel("Username").fill("demo");
|
||||||
|
await page.getByLabel("Password").fill("demo");
|
||||||
|
await page.getByRole("button", { name: /^login$/i }).click();
|
||||||
|
|
||||||
|
await page.waitForURL(/\/change-password$/);
|
||||||
|
await expect(page.getByRole("heading", { name: "Change your password" })).toBeVisible();
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
localStorage.setItem("snapotter-demo-state", JSON.stringify({ passwordChanged: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
const allTab = page.getByRole("button", { name: /^All\s*\d+$/ });
|
||||||
|
await expect(allTab).toBeVisible();
|
||||||
|
const allCount = Number((await allTab.textContent())?.match(/\d+$/)?.[0] ?? 0);
|
||||||
|
expect(allCount).toBeGreaterThan(100);
|
||||||
|
|
||||||
|
await page.goto("/image/compress");
|
||||||
|
await expect(page.getByRole("heading", { name: "Compress" })).toBeVisible();
|
||||||
|
await expect(page.getByText("Drop your files here")).toBeVisible();
|
||||||
|
await expect(page.getByRole("button", { name: "Upload from computer" })).toBeVisible();
|
||||||
|
|
||||||
|
expect(consoleErrors).toEqual([]);
|
||||||
|
expect(pageErrors).toEqual([]);
|
||||||
|
});
|
||||||
@@ -9,6 +9,15 @@ function workflowText(): string {
|
|||||||
return readFileSync(workflowPath, "utf8");
|
return readFileSync(workflowPath, "utf8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expectBefore(workflow: string, earlier: string, later: string) {
|
||||||
|
const earlierIndex = workflow.indexOf(earlier);
|
||||||
|
const laterIndex = workflow.indexOf(later);
|
||||||
|
|
||||||
|
expect(earlierIndex, `${earlier} should exist in the workflow`).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(laterIndex, `${later} should exist in the workflow`).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(earlierIndex, `${earlier} should run before ${later}`).toBeLessThan(laterIndex);
|
||||||
|
}
|
||||||
|
|
||||||
describe("demo deployment workflow", () => {
|
describe("demo deployment workflow", () => {
|
||||||
it("deploys the demo app to the Cloudflare Pages demo project", () => {
|
it("deploys the demo app to the Cloudflare Pages demo project", () => {
|
||||||
const workflow = workflowText();
|
const workflow = workflowText();
|
||||||
@@ -22,6 +31,22 @@ describe("demo deployment workflow", () => {
|
|||||||
expect(workflow).toContain("CLOUDFLARE_ACCOUNT_ID: $" + "{{ secrets.CLOUDFLARE_ACCOUNT_ID }}");
|
expect(workflow).toContain("CLOUDFLARE_ACCOUNT_ID: $" + "{{ secrets.CLOUDFLARE_ACCOUNT_ID }}");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("runs focused demo gates before publishing to Cloudflare", () => {
|
||||||
|
const workflow = workflowText();
|
||||||
|
const deployCommand =
|
||||||
|
"npx wrangler pages deploy apps/demo/dist --project-name snapotter-demo --branch main --commit-dirty=true";
|
||||||
|
|
||||||
|
for (const gateCommand of [
|
||||||
|
"pnpm vitest run --config vitest.config.ts tests/unit/infra/demo-theme.test.ts tests/unit/infra/deploy-demo-workflow.test.ts tests/unit/infra/demo-mock-api.test.ts",
|
||||||
|
"pnpm --filter @snapotter/demo exec tsc --noEmit",
|
||||||
|
"pnpm --filter @snapotter/demo build",
|
||||||
|
"pnpm playwright install --with-deps chromium",
|
||||||
|
"pnpm playwright test --config playwright.demo.config.ts",
|
||||||
|
]) {
|
||||||
|
expectBefore(workflow, gateCommand, deployCommand);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("rebuilds when the demo shell, real app UI, shared code, or build inputs change", () => {
|
it("rebuilds when the demo shell, real app UI, shared code, or build inputs change", () => {
|
||||||
const workflow = workflowText();
|
const workflow = workflowText();
|
||||||
|
|
||||||
@@ -32,7 +57,14 @@ describe("demo deployment workflow", () => {
|
|||||||
'"package.json"',
|
'"package.json"',
|
||||||
'"pnpm-lock.yaml"',
|
'"pnpm-lock.yaml"',
|
||||||
'"pnpm-workspace.yaml"',
|
'"pnpm-workspace.yaml"',
|
||||||
|
'"turbo.json"',
|
||||||
|
'"tsconfig.base.json"',
|
||||||
'".github/actions/setup/**"',
|
'".github/actions/setup/**"',
|
||||||
|
'"playwright.demo.config.ts"',
|
||||||
|
'"tests/e2e-demo/**"',
|
||||||
|
'"tests/unit/infra/demo-theme.test.ts"',
|
||||||
|
'"tests/unit/infra/demo-mock-api.test.ts"',
|
||||||
|
'"tests/unit/infra/deploy-demo-workflow.test.ts"',
|
||||||
'".github/workflows/deploy-demo.yml"',
|
'".github/workflows/deploy-demo.yml"',
|
||||||
]) {
|
]) {
|
||||||
expect(workflow).toContain(pathFilter);
|
expect(workflow).toContain(pathFilter);
|
||||||
|
|||||||
Reference in New Issue
Block a user