diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ba51aa..f63286c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,9 @@ jobs: tests: name: "Unit and E2E Tests" runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write steps: - name: Checkout code uses: actions/checkout@v4 @@ -28,10 +31,16 @@ jobs: run: docker compose up -d - name: Wait for container to be ready + id: container-startup run: | + START_TIME=$(date +%s%3N) for i in {1..30}; do if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then - echo "Container is ready!" + END_TIME=$(date +%s%3N) + STARTUP_TIME_MS=$((END_TIME - START_TIME)) + STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc) + echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT + echo "Container is ready in ${STARTUP_TIME}s!" exit 0 fi echo "Waiting for container... ($i/30)" @@ -42,6 +51,57 @@ jobs: docker compose logs exit 1 + - name: Collect Docker stats + if: github.event_name == 'pull_request' + continue-on-error: true + id: docker-stats + run: | + # Get image size + IMAGE_SIZE=$(docker images appwrite/browser:local --format "{{.Size}}") + + # Get container stats + CONTAINER_ID=$(docker compose ps -q appwrite-browser) + MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}" | cut -d'/' -f1 | xargs) + + # Quick screenshot benchmark (3 runs, average) + TOTAL=0 + for i in {1..3}; do + START=$(date +%s%3N) + curl -s -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{"url":"https://appwrite.io"}' \ + -o /dev/null + END=$(date +%s%3N) + DURATION=$((END - START)) + TOTAL=$((TOTAL + DURATION)) + done + SCREENSHOT_AVG_MS=$((TOTAL / 3)) + SCREENSHOT_AVG=$(echo "scale=2; $SCREENSHOT_AVG_MS / 1000" | bc) + + # Store in GitHub output + echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT + echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT + echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT + + - name: Comment PR with stats + if: github.event_name == 'pull_request' && steps.docker-stats.outcome == 'success' + continue-on-error: true + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: docker-image-stats + skip_unchanged: true + message: | + ## Docker Image Stats + + | Metric | Value | + |--------|-------| + | Image Size | ${{ steps.docker-stats.outputs.image_size }} | + | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | + | Cold Start Time | ${{ steps.container-startup.outputs.startup_time }}s | + | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}s | + + Screenshot benchmark: Average of 3 runs on https://appwrite.io + - name: Run e2e tests run: bun test:e2e diff --git a/src/schemas/screenshot.schema.ts b/src/schemas/screenshot.schema.ts index ca50351..3e5b156 100644 --- a/src/schemas/screenshot.schema.ts +++ b/src/schemas/screenshot.schema.ts @@ -4,7 +4,7 @@ export const screenshotSchema = z.object({ url: z.string().url(), theme: z.enum(["light", "dark"]).default("light"), headers: z.record(z.string(), z.any()).optional(), - sleep: z.number().min(0).max(60000).default(3000), + sleep: z.number().min(0).max(60000).default(0), // Viewport options viewport: z .object({ diff --git a/src/server.ts b/src/server.ts index 6abb755..11c0872 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,33 +5,17 @@ import { handleScreenshotsRequest, handleTestRequest, } from "./routes"; +import { Router } from "./utils/router"; + +const router = new Router(); +router.add("POST", "/v1/screenshots", handleScreenshotsRequest); +router.add("POST", "/v1/reports", handleReportsRequest); +router.add("GET", "/v1/health", handleHealthRequest); +router.add("GET", "/v1/test", handleTestRequest); const server = Bun.serve({ port, - async fetch(req) { - const url = new URL(req.url); - const path = url.pathname; - - // Route matching - if (path === "/v1/screenshots" && req.method === "POST") { - return await handleScreenshotsRequest(req); - } - - if (path === "/v1/reports" && req.method === "POST") { - return await handleReportsRequest(req); - } - - if (path === "/v1/health" && req.method === "GET") { - return await handleHealthRequest(req); - } - - if (path === "/v1/test" && req.method === "GET") { - return await handleTestRequest(req); - } - - // 404 Not Found - return new Response("Not Found", { status: 404 }); - }, + fetch: (request) => router.handle(request), }); console.log(`Server running on http://0.0.0.0:${server.port}`); diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index e47bd7e..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,7 +170,7 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); - await deletePath(`${NODE_MODULES}/axe-core/axe.js`); + await deletePath(`${NODE_MODULES}/axe-core/axe.js`); await deletePath(`${NODE_MODULES}/lighthouse/cli`); await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); diff --git a/src/utils/router.ts b/src/utils/router.ts new file mode 100644 index 0000000..1825a32 --- /dev/null +++ b/src/utils/router.ts @@ -0,0 +1,40 @@ +type HTTPMethod = + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE" + | "OPTIONS" + | "HEAD"; + +type RouteHandler = (req: Request) => Promise; + +type Route = { + method: HTTPMethod; + pattern: RegExp; + handler: RouteHandler; +}; + +export class Router { + private routes: Route[] = []; + + add(method: HTTPMethod, path: string, handler: RouteHandler): void { + this.routes.push({ + method, + pattern: new RegExp(`^${path}$`), + handler, + }); + } + + async handle(req: Request): Promise { + const url = new URL(req.url); + + for (const route of this.routes) { + if (route.method === req.method && route.pattern.test(url.pathname)) { + return await route.handler(req); + } + } + + return new Response("Not Found", { status: 404 }); + } +} diff --git a/tests/unit/screenshot.schema.test.ts b/tests/unit/screenshot.schema.test.ts index b6082ac..68a9901 100644 --- a/tests/unit/screenshot.schema.test.ts +++ b/tests/unit/screenshot.schema.test.ts @@ -23,7 +23,7 @@ describe("screenshotSchema", () => { expect(result.quality).toBe(90); expect(result.waitUntil).toBe("domcontentloaded"); expect(result.timeout).toBe(30000); - expect(result.sleep).toBe(3000); + expect(result.sleep).toBe(0); }); test("should validate custom viewport", () => {