From 50808d8e5c11478ebaf16218da08ec7042dc2cd7 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:35:22 +0530 Subject: [PATCH 1/9] update: improve the speed! fix: reports endpoint issue. --- src/schemas/screenshot.schema.ts | 2 +- src/utils/clean-modules.ts | 4 ---- tests/unit/screenshot.schema.test.ts | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) 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/utils/clean-modules.ts b/src/utils/clean-modules.ts index e3216b4..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,12 +170,8 @@ async function removeUnnecessaryFiles(): Promise { ); await deletePath(`${NODE_MODULES}/@sentry`); await deletePath(`${NODE_MODULES}/@opentelemetry`); - // Remove unminified axe-core (we have axe.min.js) await deletePath(`${NODE_MODULES}/axe-core/axe.js`); - // Remove Lighthouse CLI (we use it programmatically) await deletePath(`${NODE_MODULES}/lighthouse/cli`); - await deletePath(`${NODE_MODULES}/lighthouse/third-party`); - // Remove config files from lighthouse root await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/eslint.config.mjs`); 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", () => { From 28e8118b6c5025eca0623b7c86a6378d0d1fad70 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:37:38 +0530 Subject: [PATCH 2/9] use: specific image version! --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a970f02..6f1f518 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ RUN bun install --frozen-lockfile --production && \ rm -rf ~/.bun/install/cache /tmp/* # well-known OSS docker image -FROM chromedp/headless-shell AS final +FROM chromedp/headless-shell:143.0.7445.3 AS final # install fonts only RUN apt-get update && \ From 4e6b20d97b8b0571d69527bc2f1cd8ec1b9370d6 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 12:50:43 +0530 Subject: [PATCH 3/9] add: proper, nicer router support. --- src/server.ts | 32 ++++++++------------------------ src/utils/router.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 24 deletions(-) create mode 100644 src/utils/router.ts 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/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 }); + } +} From f8f05c805d924b6d261be46f12cb53f66ef6b760 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:46:22 +0530 Subject: [PATCH 4/9] add: test stats to post on PR. --- .github/workflows/test.yml | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1ba51aa..e00788a 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 @@ -42,6 +45,69 @@ jobs: docker compose logs exit 1 + - name: Collect Docker stats + if: github.event_name == 'pull_request' + id: docker-stats + run: | + # Get image size + IMAGE_SIZE=$(docker images appwrite-browser --format "{{.Size}}") + IMAGE_SIZE_BYTES=$(docker inspect appwrite-browser --format='{{.Size}}') + IMAGE_SIZE_MB=$(echo "scale=2; $IMAGE_SIZE_BYTES / 1024 / 1024" | bc) + + # Get container stats + CONTAINER_ID=$(docker compose ps -q browser) + MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}") + + # 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=$((TOTAL / 3)) + + # Measure fresh startup time + START_TIME=$(date +%s%3N) + docker compose restart browser + for i in {1..30}; do + if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then + END_TIME=$(date +%s%3N) + STARTUP_TIME=$((END_TIME - START_TIME)) + break + fi + sleep 0.1 + done + + # Store in GitHub output + echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT + echo "image_size_mb=$IMAGE_SIZE_MB" >> $GITHUB_OUTPUT + echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT + echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT + echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT + + - name: Comment PR with stats + if: github.event_name == 'pull_request' + uses: peter-evans/create-or-update-comment@v5 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Docker Image Stats + + | Metric | Value | + |--------|-------| + | Image Size | ${{ steps.docker-stats.outputs.image_size }} (${{ steps.docker-stats.outputs.image_size_mb }} MB) | + | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | + | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}ms | + | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}ms | + + Benchmark: Average of 3 screenshot runs on https://appwrite.io + - name: Run e2e tests run: bun test:e2e From 7cee01de8c8c488bb12f7ce90f66b6d3516b9e77 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:50:41 +0530 Subject: [PATCH 5/9] fix: ci. --- .github/workflows/test.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e00788a..8599fee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,15 +47,16 @@ jobs: - 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 --format "{{.Size}}") - IMAGE_SIZE_BYTES=$(docker inspect appwrite-browser --format='{{.Size}}') + IMAGE_SIZE=$(docker images appwrite/browser:local --format "{{.Size}}") + IMAGE_SIZE_BYTES=$(docker inspect appwrite/browser:local --format='{{.Size}}') IMAGE_SIZE_MB=$(echo "scale=2; $IMAGE_SIZE_BYTES / 1024 / 1024" | bc) # Get container stats - CONTAINER_ID=$(docker compose ps -q browser) + CONTAINER_ID=$(docker compose ps -q appwrite-browser) MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}") # Quick screenshot benchmark (3 runs, average) @@ -92,7 +93,8 @@ jobs: echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT - name: Comment PR with stats - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request' && steps.docker-stats.outcome == 'success' + continue-on-error: true uses: peter-evans/create-or-update-comment@v5 with: issue-number: ${{ github.event.pull_request.number }} From 8575c8dc7854c2c514277e8bb9a16fc4d3afaf98 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:54:53 +0530 Subject: [PATCH 6/9] fix: ci. --- .github/workflows/test.yml | 2 +- src/utils/clean-modules.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8599fee..8a8d424 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,7 @@ jobs: # Measure fresh startup time START_TIME=$(date +%s%3N) - docker compose restart browser + docker compose restart appwrite-browser for i in {1..30}; do if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then END_TIME=$(date +%s%3N) diff --git a/src/utils/clean-modules.ts b/src/utils/clean-modules.ts index 36e14e2..76b14d5 100644 --- a/src/utils/clean-modules.ts +++ b/src/utils/clean-modules.ts @@ -170,6 +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}/lighthouse/cli`); await deletePath(`${NODE_MODULES}/lighthouse/build-tracker.config.js`); await deletePath(`${NODE_MODULES}/lighthouse/commitlint.config.js`); From 4204a3c14cef822d08ef0f14c37922950d2d3c87 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 13:59:35 +0530 Subject: [PATCH 7/9] update: ci. --- .github/workflows/test.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a8d424..d919739 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,12 +52,10 @@ jobs: run: | # Get image size IMAGE_SIZE=$(docker images appwrite/browser:local --format "{{.Size}}") - IMAGE_SIZE_BYTES=$(docker inspect appwrite/browser:local --format='{{.Size}}') - IMAGE_SIZE_MB=$(echo "scale=2; $IMAGE_SIZE_BYTES / 1024 / 1024" | bc) # Get container stats CONTAINER_ID=$(docker compose ps -q appwrite-browser) - MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}") + MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}" | cut -d'/' -f1 | xargs) # Quick screenshot benchmark (3 runs, average) TOTAL=0 @@ -71,7 +69,8 @@ jobs: DURATION=$((END - START)) TOTAL=$((TOTAL + DURATION)) done - SCREENSHOT_AVG=$((TOTAL / 3)) + SCREENSHOT_AVG_MS=$((TOTAL / 3)) + SCREENSHOT_AVG=$(echo "scale=2; $SCREENSHOT_AVG_MS / 1000" | bc) # Measure fresh startup time START_TIME=$(date +%s%3N) @@ -79,15 +78,15 @@ jobs: for i in {1..30}; do if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then END_TIME=$(date +%s%3N) - STARTUP_TIME=$((END_TIME - START_TIME)) + STARTUP_TIME_MS=$((END_TIME - START_TIME)) break fi sleep 0.1 done + STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc) # Store in GitHub output echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT - echo "image_size_mb=$IMAGE_SIZE_MB" >> $GITHUB_OUTPUT echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT @@ -103,12 +102,12 @@ jobs: | Metric | Value | |--------|-------| - | Image Size | ${{ steps.docker-stats.outputs.image_size }} (${{ steps.docker-stats.outputs.image_size_mb }} MB) | + | Image Size | ${{ steps.docker-stats.outputs.image_size }} | | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | - | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}ms | - | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}ms | + | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}s | + | Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}s | - Benchmark: Average of 3 screenshot runs on https://appwrite.io + Screenshot benchmark: Average of 3 runs on https://appwrite.io - name: Run e2e tests run: bun test:e2e From 231a696ac30677d7ef7fe3391234ed38a57f6f6b Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 14:13:46 +0530 Subject: [PATCH 8/9] update: ci for comment spam. --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d919739..7ba0aab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -94,10 +94,11 @@ jobs: - name: Comment PR with stats if: github.event_name == 'pull_request' && steps.docker-stats.outcome == 'success' continue-on-error: true - uses: peter-evans/create-or-update-comment@v5 + uses: marocchino/sticky-pull-request-comment@v2 with: - issue-number: ${{ github.event.pull_request.number }} - body: | + header: docker-image-stats + skip_unchanged: true + message: | ## Docker Image Stats | Metric | Value | From 0580dacdd35f1bd0c47500309941b14f8ac0ff5d Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 16 Nov 2025 14:21:44 +0530 Subject: [PATCH 9/9] fix: startup time. --- .github/workflows/test.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7ba0aab..f63286c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,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)" @@ -72,23 +78,9 @@ jobs: SCREENSHOT_AVG_MS=$((TOTAL / 3)) SCREENSHOT_AVG=$(echo "scale=2; $SCREENSHOT_AVG_MS / 1000" | bc) - # Measure fresh startup time - START_TIME=$(date +%s%3N) - docker compose restart appwrite-browser - for i in {1..30}; do - if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then - END_TIME=$(date +%s%3N) - STARTUP_TIME_MS=$((END_TIME - START_TIME)) - break - fi - sleep 0.1 - done - STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc) - # Store in GitHub output echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT - echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT - name: Comment PR with stats @@ -105,7 +97,7 @@ jobs: |--------|-------| | Image Size | ${{ steps.docker-stats.outputs.image_size }} | | Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} | - | Startup Time | ${{ steps.docker-stats.outputs.startup_time }}s | + | 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