Skip to content

Commit 9c4e426

Browse files
authored
Merge pull request #44 from zz-plant/codex/set-up-playwright-cli-with-typescript
Add Playwright smoke tests and CI workflow
2 parents a76faeb + 482352d commit 9c4e426

8 files changed

Lines changed: 228 additions & 8 deletions

File tree

.github/workflows/playwright.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Playwright Tests
2+
on:
3+
push:
4+
branches: [ main, master ]
5+
pull_request:
6+
branches: [ main, master ]
7+
jobs:
8+
test:
9+
timeout-minutes: 60
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-node@v4
14+
with:
15+
node-version: lts/*
16+
- name: Install dependencies
17+
run: npm ci
18+
- name: Install Playwright Browsers
19+
run: npx playwright install --with-deps
20+
- name: Build site for preview
21+
run: npm run build
22+
- name: Run Playwright tests
23+
run: npm run e2e:ci
24+
- uses: actions/upload-artifact@v4
25+
if: ${{ !cancelled() }}
26+
with:
27+
name: playwright-report
28+
path: playwright-report/
29+
retention-days: 30

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ node_modules
44
/.vercel
55
.env
66
.DS_Store
7+
8+
# Playwright
9+
/test-results/
10+
/playwright-report/
11+
/blob-report/
12+
/playwright/.cache/
13+
/playwright/.auth/

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,28 @@
33
This repository powers [ethotechnics.org](https://ethotechnics.org), a content-driven site exploring ethical technology, human-centered design, and the sociotechnical systems that shape them. The project favors lean, fast-loading pages and clear storytelling.
44

55
## Getting started
6+
67
1. Install dependencies: `npm install`
78
2. Run the dev server: `npm run dev`
89
3. Build the Worker bundle: `npm run build` (optionally preview with `npm run preview`).
910

1011
## Checks before committing
12+
1113
Use `npm run check` for a full pre-commit sweep. It will run linting, tests, TypeScript checks, and Astro's checker (skipping any step that is not configured).
14+
15+
## End-to-end tests
16+
17+
- `npm run e2e` builds the Worker bundle and runs Playwright against `npm run preview`.
18+
- Override the preview target with `PLAYWRIGHT_BASE_URL` (defaults to `http://127.0.0.1:4321`).
19+
- CI uses `npm run e2e:ci` and publishes the Playwright HTML report as a workflow artifact.
20+
1221
### Environment configuration
22+
1323
- Copy `.env.example` to `.env.local` for local development. Astro automatically loads `.env`, `.env.local`, and environment-specific files (such as `.env.development`).
1424
- No environment variables are required today, but add new entries to `.env.example` if the project adopts external services.
25+
1526
## Quickstart for agents
27+
1628
- **Prerequisites:** Node.js 20+ with npm (project uses `package-lock.json`), Git, and a shell with `npm` on PATH.
1729
- **Dev server:** `npm run dev`
1830
- Expected log snippet: `[@astrojs/compiler] ready` followed by `Local http://localhost:4321/`.
@@ -23,6 +35,7 @@ Use `npm run check` for a full pre-commit sweep. It will run linting, tests, Typ
2335
- If commands are slow or fail, confirm Node version with `node -v` and reinstall dependencies via `rm -rf node_modules && npm install`.
2436

2537
## Deployment to Cloudflare Workers
38+
2639
The site uses the official Cloudflare adapter for Astro to produce a Worker-compatible server build.
2740

2841
`wrangler.toml` captures the Worker name, compatibility date, and entrypoint (`dist/_worker.js`). Session storage is not enabled by default; if you add it later, define the KV binding in `wrangler.toml` before deploying.
@@ -36,15 +49,18 @@ The site uses the official Cloudflare adapter for Astro to produce a Worker-comp
3649
5. Configure DNS for `ethotechnics.org` to point to the Cloudflare Worker route you set in Wrangler.
3750

3851
## Tech stack
52+
3953
- [Astro 5](https://astro.build) with strict TypeScript defaults
4054
- React island for the navigation, hydrated on load
4155
- Cloudflare Worker adapter for server output
4256
- Modern, responsive styling with a focus on accessibility and contrast
4357

4458
## Staying updated
59+
4560
- Subscribe to the RSS feed at [`/rss.xml`](https://ethotechnics.org/rss.xml) to catch new pages and updates as they ship.
4661

4762
## Where things live
63+
4864
- `src/pages`: Astro routes, starting with [`src/pages/index.astro`](src/pages/index.astro) for the homepage content.
4965
- `src/layouts`: Shared layouts such as [`src/layouts/BaseLayout.astro`](src/layouts/BaseLayout.astro), which wires global SEO, fonts, and the navigation shell.
5066
- `src/components`: Interactive islands like the navigation React component in [`src/components/Navigation.tsx`](src/components/Navigation.tsx).

package-lock.json

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
"typecheck": "tsc --noEmit",
1414
"check": "npm run lint --if-present && npm test --if-present && npm run typecheck --if-present && npm run astro:check",
1515
"test": "vitest",
16-
"test:ci": "vitest --run --coverage"
16+
"test:ci": "vitest --run --coverage",
17+
"e2e": "npm run build && npx playwright test",
18+
"e2e:ci": "npm run build && npx playwright test --reporter=line"
1719
},
1820
"dependencies": {
1921
"@astrojs/cloudflare": "^12.6.12",
@@ -33,7 +35,9 @@
3335
},
3436
"devDependencies": {
3537
"@astrojs/check": "^0.9.6",
38+
"@playwright/test": "^1.57.0",
3639
"@types/jsdom": "^27.0.0",
40+
"@types/node": "^24.10.1",
3741
"@vitest/coverage-v8": "^4.0.15",
3842
"jsdom": "^27.2.0",
3943
"typescript": "^5.9.3",

playwright.config.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { defineConfig, devices } from "@playwright/test";
2+
3+
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? "http://127.0.0.1:4321";
4+
5+
export default defineConfig({
6+
testDir: "./tests/e2e",
7+
fullyParallel: true,
8+
forbidOnly: !!process.env.CI,
9+
retries: process.env.CI ? 2 : 0,
10+
workers: process.env.CI ? 1 : undefined,
11+
reporter: "html",
12+
use: {
13+
baseURL,
14+
trace: "on-first-retry",
15+
},
16+
projects: [
17+
{
18+
name: "chromium",
19+
use: { ...devices["Desktop Chrome"] },
20+
},
21+
{
22+
name: "firefox",
23+
use: { ...devices["Desktop Firefox"] },
24+
},
25+
{
26+
name: "webkit",
27+
use: { ...devices["Desktop Safari"] },
28+
},
29+
],
30+
webServer: {
31+
command: "npm run preview -- --host 0.0.0.0 --port 4321",
32+
url: baseURL,
33+
reuseExistingServer: !process.env.CI,
34+
timeout: 120_000,
35+
},
36+
});

tests/e2e/smoke.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
const HERO_HEADING =
4+
"Technology should serve people — not the other way around.";
5+
6+
test.describe("Homepage smoke", () => {
7+
test("shows the primary hero content", async ({ page }) => {
8+
await page.goto("/");
9+
10+
await expect(
11+
page.getByRole("heading", { level: 1, name: HERO_HEADING }),
12+
).toBeVisible();
13+
await expect(page.getByRole("link", { name: "Get updates" })).toBeVisible();
14+
await expect(
15+
page.getByRole("link", { name: "Explore focus areas" }),
16+
).toBeVisible();
17+
});
18+
});
19+
20+
test("serves an XML RSS feed", async ({ request }) => {
21+
const rss = await request.get("/rss.xml");
22+
23+
expect(rss.ok()).toBeTruthy();
24+
expect(rss.headers()["content-type"]).toContain("xml");
25+
26+
const body = await rss.text();
27+
expect(body).toContain("<rss");
28+
});
29+
30+
test.describe("Navigation", () => {
31+
test("opens on mobile and navigates to library", async ({ page }) => {
32+
await page.setViewportSize({ width: 480, height: 900 });
33+
await page.goto("/");
34+
35+
const navContent = page.locator(".nav__content");
36+
await expect(navContent).not.toHaveClass(/is-open/);
37+
38+
await page.getByRole("button", { name: /open navigation/i }).click();
39+
await expect(navContent).toHaveClass(/is-open/);
40+
41+
await page.getByRole("link", { name: "Library" }).click();
42+
await page.waitForURL(/\/library/);
43+
44+
await expect(
45+
page.getByRole("heading", { level: 1, name: "Library" }),
46+
).toBeVisible();
47+
await expect(page.locator(".nav__content")).not.toHaveClass(/is-open/);
48+
});
49+
});

vitest.config.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,30 @@
1-
import { defineConfig, getViteConfig } from 'astro/config';
1+
import { defineConfig, getViteConfig } from "astro/config";
22

3-
const resolvedViteConfig = await getViteConfig({})({ mode: 'test', command: 'serve' });
3+
const resolvedViteConfig = await getViteConfig({})({
4+
mode: "test",
5+
command: "serve",
6+
});
7+
const resolvedTestConfig = (
8+
resolvedViteConfig as { test?: { exclude?: string[] } }
9+
).test;
410

511
export default defineConfig({
612
...resolvedViteConfig,
713
test: {
8-
environment: 'node',
14+
...resolvedTestConfig,
15+
environment: "node",
916
globals: true,
10-
globalSetup: ['src/test/global-setup.ts'],
11-
setupFiles: ['src/test/setup.ts'],
17+
globalSetup: ["src/test/global-setup.ts"],
18+
setupFiles: ["src/test/setup.ts"],
19+
exclude: [
20+
"**/node_modules/**",
21+
"**/.git/**",
22+
"**/dist/**",
23+
...(resolvedTestConfig?.exclude ?? []),
24+
"tests/e2e/**",
25+
],
1226
deps: {
13-
inline: ['astro', '@astrojs/react'],
27+
inline: ["astro", "@astrojs/react"],
1428
},
1529
},
16-
} as unknown as import('astro').AstroUserConfig);
30+
} as unknown as import("astro").AstroUserConfig);

0 commit comments

Comments
 (0)