Skip to content

Commit 7d92513

Browse files
Test: Playwright E2E
1 parent a2b73ab commit 7d92513

12 files changed

Lines changed: 3605 additions & 1540 deletions

File tree

.github/workflows/ci.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,16 @@ jobs:
3535

3636
- name: Test
3737
run: npm run test
38+
39+
- name: Install Playwright Chromium
40+
run: npx playwright install --with-deps chromium
41+
42+
- name: E2E (Playwright)
43+
run: npm run test:e2e
44+
env:
45+
CI: true
46+
GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY || 'sk-dummy' }}
47+
UPSTASH_REDIS_REST_URL: ${{ secrets.UPSTASH_REDIS_REST_URL || 'https://dummy.upstash.io' }}
48+
UPSTASH_REDIS_REST_TOKEN: ${{ secrets.UPSTASH_REDIS_REST_TOKEN || 'dummy' }}
49+
UPSTASH_VECTOR_REST_URL: ${{ secrets.UPSTASH_VECTOR_REST_URL || 'https://dummy.upstash.io' }}
50+
UPSTASH_VECTOR_REST_TOKEN: ${{ secrets.UPSTASH_VECTOR_REST_TOKEN || 'dummy' }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212

1313
# testing
1414
/coverage
15+
/test-results/
16+
/playwright-report/
17+
/blob-report/
18+
/playwright/.cache/
1519

1620
# next.js
1721
/.next/

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
- **Streaming Citations**: Citations stream before the answer for instant transparency
2828
- **Hybrid Runtime**: Edge for chat, Node.js for ingestion (optimal performance)
2929
- **Observability**: OpenTelemetry (ingest/retrieval spans), Langfuse (LLM traces, token usage)
30-
- **CI/CD & Testing**: GitHub Actions (lint, build, test), Vitest unit tests
30+
- **CI/CD & Testing**: GitHub Actions (lint, build, unit + E2E), Vitest, Playwright
3131
- **Docker**: Standalone image + docker-compose for local/self-hosted run
3232
- **Modern UI**: Beautiful, responsive chat interface with real-time streaming
3333
- **Optional gRPC Gateway**: Binary-efficient vector upsert/query sidecar (Proto + Node server)
@@ -106,6 +106,13 @@ graph TB
106106

107107
6. **Open [http://localhost:3000](http://localhost:3000)**
108108

109+
### E2E tests (Playwright)
110+
111+
- **Scripts**: `npm run test:e2e` (starts the app on **port 3100** by default so it does not clash with `next dev` on 3000), `npm run test:e2e:ui` for the Playwright UI mode. Override with `PLAYWRIGHT_E2E_PORT` / `PLAYWRIGHT_BASE_URL`.
112+
- **CI**: After `npm run build`, the workflow installs Chromium and runs `npm run test:e2e`, which starts the **standalone** server (`node .next/standalone/server.js` on port 3100) because this app uses `output: "standalone"`. Only **smoke** tests run by default (no real Gemini/Upstash calls required for the UI checks).
113+
- **Full RAG E2E**: Set `E2E_FULL=1` and real API keys in the environment used by the Next server, add `e2e/fixtures/sample.pdf` (or `E2E_FIXTURE_PDF`), then run `npx playwright test e2e/optional-full-flow.spec.ts`.
114+
- **Preview deployments**: Run with `PLAYWRIGHT_SKIP_WEB_SERVER=1` and `PLAYWRIGHT_BASE_URL=https://your-preview.vercel.app`.
115+
109116
## How It Works
110117

111118
### 1. PDF Ingestion (Client-Side)
@@ -146,14 +153,16 @@ graph TB
146153
| **PDF Parsing** | `pdfjs-dist` | Client-side to avoid Edge limits |
147154
| **UI** | React + `@ai-sdk/react` | Streaming chat with citation support |
148155
| **Observability** | OpenTelemetry, Langfuse | Traces (ingest/retrieval), LLM generations & token usage |
149-
| **CI/CD** | GitHub Actions | Lint, build, test on push/PR |
150-
| **Testing** | Vitest | Unit tests (chunking, observability) |
156+
| **CI/CD** | GitHub Actions | Lint, build, unit + E2E on push/PR |
157+
| **Testing** | Vitest, Playwright | Unit tests; browser E2E (smoke + optional full RAG) |
151158
| **Containers** | Docker, docker-compose | Local/self-hosted run |
152159

153160
## Project Structure
154161

155162
```
156163
serverless-rag/
164+
├── e2e/ # Playwright tests (smoke + optional full flow)
165+
├── playwright.config.ts # Playwright + webServer (dev local / start in CI)
157166
├── src/
158167
│ ├── app/
159168
│ │ ├── api/

e2e/fixtures/.gitkeep

Whitespace-only changes.

e2e/optional-full-flow.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as fs from "node:fs";
2+
import * as path from "node:path";
3+
4+
import { expect, test } from "@playwright/test";
5+
6+
/**
7+
* End-to-end tests that call real `/api/ingest` and `/api/chat`.
8+
* Not run in default CI (no production secrets). Enable locally or in a
9+
* protected workflow with repository secrets.
10+
*
11+
* E2E_FULL=1 npx playwright test e2e/optional-full-flow.spec.ts
12+
*
13+
* For ingest, place a small PDF at `e2e/fixtures/sample.pdf` or set E2E_FIXTURE_PDF to its path.
14+
*/
15+
const fullEnabled = process.env.E2E_FULL === "1";
16+
const fixturePath =
17+
process.env.E2E_FIXTURE_PDF?.trim() ||
18+
path.join(process.cwd(), "e2e", "fixtures", "sample.pdf");
19+
const hasFixture = fs.existsSync(fixturePath);
20+
21+
test.describe("optional full RAG flow", () => {
22+
if (!fullEnabled) {
23+
return;
24+
}
25+
26+
test("PDF upload shows completion or a clear error from the API", async ({ page }) => {
27+
test.skip(
28+
!hasFixture,
29+
`Add a PDF at ${fixturePath} or set E2E_FIXTURE_PDF to a PDF path.`,
30+
);
31+
32+
await page.goto("/");
33+
await page.getByTestId("pdf-file-input").setInputFiles(fixturePath);
34+
35+
const uploader = page.getByTestId("pdf-uploader");
36+
await expect(uploader).toContainText(
37+
/Done\. Upserted|Upload failed|Ingestion failed|Rate limit|error|Error/i,
38+
{ timeout: 90_000 },
39+
);
40+
});
41+
42+
test("chat completes a turn when Gemini and Upstash are configured", async ({ page }) => {
43+
await page.goto("/");
44+
await page.getByTestId("chat-input").fill("Reply with a single word: ok");
45+
await page.getByTestId("chat-send").click();
46+
47+
// `useChat` disables Send while streaming; re-enabled when the turn finishes
48+
await expect(page.getByTestId("chat-send")).toBeEnabled({ timeout: 120_000 });
49+
await expect(page.getByTestId("chat-messages")).toBeVisible();
50+
});
51+
});

e2e/smoke.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test.describe("smoke", () => {
4+
test("home page renders core RAG UI", async ({ page }) => {
5+
await page.goto("/");
6+
7+
await expect(page.getByTestId("app-title")).toHaveText("Serverless RAG Chatbot");
8+
9+
await expect(page.getByTestId("pdf-uploader")).toBeVisible();
10+
await expect(page.getByTestId("pdf-file-input")).toBeAttached();
11+
12+
await expect(page.getByTestId("chat-input")).toBeVisible();
13+
await expect(page.getByTestId("chat-send")).toBeEnabled();
14+
});
15+
16+
test("user can type in chat and submit is wired", async ({ page }) => {
17+
await page.goto("/");
18+
const input = page.getByTestId("chat-input");
19+
await input.fill("Hello from Playwright");
20+
await expect(input).toHaveValue("Hello from Playwright");
21+
});
22+
});

env.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,15 @@ UPSTASH_VECTOR_REST_TOKEN=
2525
# LANGFUSE_SECRET_KEY=
2626
# LANGFUSE_BASE_URL=https://cloud.langfuse.com
2727

28+
## Playwright E2E (optional)
29+
# CI runs smoke tests against a local `next start` server (see playwright.config.ts).
30+
# Full-stack tests: set E2E_FULL=1 and use real keys in the env for the Next server.
31+
# E2E_FULL=1
32+
# Optional path to a PDF for ingest E2E (default: e2e/fixtures/sample.pdf)
33+
# E2E_FIXTURE_PDF=
34+
# Point Playwright at an already-running app (skip auto webServer)
35+
# PLAYWRIGHT_SKIP_WEB_SERVER=1
36+
# Default E2E app URL is http://127.0.0.1:3100 (see playwright.config.ts)
37+
# PLAYWRIGHT_E2E_PORT=3100
38+
# PLAYWRIGHT_BASE_URL=http://127.0.0.1:3100
39+

0 commit comments

Comments
 (0)