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", () => {