diff --git a/README.md b/README.md index 98630fc..0a8ac0c 100644 --- a/README.md +++ b/README.md @@ -4,41 +4,711 @@ [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) [![appwrite.io](https://img.shields.io/badge/appwrite-.io-f02e65?style=flat-square)](https://appwrite.io) -Docker Browser is simple to use and extend REST API meant to simplify screenshot preview, reports, and analysis. +Docker Browser is a powerful REST API for taking screenshots, generating Lighthouse reports, and performing web analysis with extensive configuration options. -## Usage +## Features -Add Docker Browser to your `docker-compose.yml`. +- πŸ“Έ **Screenshots** with customizable viewport, format, and quality +- πŸ“Š **Lighthouse Reports** for performance, accessibility, SEO, and more +- 🌐 **Browser Context** configuration (user agent, locale, timezone, geolocation) +- 🎨 **Theme Support** (light/dark mode) +- ⚑ **Performance** optimized with Playwright and Chromium +- πŸ”§ **Highly Configurable** with comprehensive API options -``` +## Quick Start + +Add Docker Browser to your `docker-compose.yml`: + +```yaml services: appwrite-browser: image: appwrite/browser:0.1.0 + ports: + - "3000:3000" ``` -Start Docker Browser alongside rest of your services. +Start the service: -``` +```bash docker compose up -d ``` -Communicate with Docker Browser endpoints. +Take a simple screenshot: + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{"url":"https://example.com"}' +``` + +## API Endpoints + +### Health Check + +**GET** `/v1/health` + +Check if the browser service is running. ```bash -curl -X POST -H 'content-type: application/json' -d '{"url":"http://google.com/ping"}' http://appwrite-browser:3000/v1/screenshots +curl http://localhost:3000/v1/health +``` + +**Response:** +```json +{ + "status": "ok" +} +``` + +### Test Configuration + +**GET** `/v1/test` + +Display current browser configuration values. This endpoint returns an HTML page showing what the browser currently has set, using inline JavaScript to detect and display all browser capabilities and settings. Perfect for taking screenshots to verify browser state. + +#### Usage + +```bash +curl http://localhost:3000/v1/test +``` + +#### Response Format + +The test endpoint returns an HTML page with a comprehensive visual display of current browser configuration values. The page includes: + +- **Viewport & Display**: Screen dimensions, device pixel ratio +- **Theme & Appearance**: Color scheme detection +- **Localization**: Language, timezone settings +- **Device & Hardware**: Touch support, mobile detection +- **User Agent**: Current user agent string +- **Geolocation**: GPS coordinates and accuracy (if available) +- **Permissions**: Browser permission states +- **Page Information**: URL, title, ready state + +The HTML page automatically adapts to the browser's theme preference (light/dark) and uses inline JavaScript to display real-time browser values. + +#### Use Cases + +- **Browser State Inspection**: See what the browser currently has configured +- **Visual Verification**: Take screenshots to verify browser capabilities +- **Debugging**: Visual inspection of browser configuration issues +- **Documentation**: Generate visual examples of browser behavior +- **Screenshot Testing**: Perfect for testing the screenshot API itself +- **Development**: Quick way to check browser state during development + +### Screenshots + +**POST** `/v1/screenshots` + +Take screenshots of web pages with extensive configuration options. + +#### Basic Usage + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{"url":"https://example.com"}' +``` + +#### Advanced Configuration + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": { + "width": 1920, + "height": 1080 + }, + "format": "jpeg", + "quality": 95, + "fullPage": true, + "theme": "dark", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "locale": "en-US", + "timezoneId": "America/New_York", + "geolocation": { + "latitude": 40.7128, + "longitude": -74.0060, + "accuracy": 100 + }, + "headers": { + "Authorization": "Bearer your-token", + "X-Custom-Header": "value" + }, + "waitUntil": "networkidle", + "timeout": 60000, + "sleep": 5000, + "deviceScaleFactor": 2, + "hasTouch": true, + "isMobile": false + }' +``` + +#### Screenshot Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `url` | string | **required** | The URL to screenshot | +| `viewport` | object | `{width: 1280, height: 720}` | Viewport dimensions | +| `viewport.width` | number | 1280 | Viewport width (1-3840) | +| `viewport.height` | number | 720 | Viewport height (1-2160) | +| `format` | string | "png" | Image format: "png", "jpeg", "webp" | +| `quality` | number | 90 | Image quality 0-100 (for jpeg/webp) | +| `fullPage` | boolean | false | Capture full page scroll | +| `clip` | object | - | Crop area: `{x, y, width, height}` | +| `theme` | string | "light" | Browser theme: "light", "dark" | +| `userAgent` | string | - | Custom user agent string | +| `locale` | string | - | Browser locale (e.g., "en-US") | +| `timezoneId` | string | - | IANA timezone identifier (see [Timezone](#timezone) section) | +| `geolocation` | object | - | GPS coordinates | +| `geolocation.latitude` | number | - | Latitude (-90 to 90) | +| `geolocation.longitude` | number | - | Longitude (-180 to 180) | +| `geolocation.accuracy` | number | - | Accuracy in meters | +| `permissions` | array | - | Browser permissions (see [Permissions](#permissions) section) | +| `headers` | object | - | Custom HTTP headers | +| `waitUntil` | string | "domcontentloaded" | Wait condition: "load", "domcontentloaded", "networkidle", "commit" | +| `timeout` | number | 30000 | Navigation timeout (0-120000ms) | +| `sleep` | number | 3000 | Wait time after load (0-60000ms) | +| `deviceScaleFactor` | number | 1 | Device pixel ratio (0.1-3) | +| `hasTouch` | boolean | false | Touch support | +| `isMobile` | boolean | false | Mobile device emulation | + +### Lighthouse Reports + +**POST** `/v1/reports` + +Generate Lighthouse performance, accessibility, SEO, and PWA reports. + +#### Basic Usage + +```bash +curl -X POST http://localhost:3000/v1/reports \ + -H "Content-Type: application/json" \ + -d '{"url":"https://example.com"}' +``` + +#### Advanced Configuration + +```bash +curl -X POST http://localhost:3000/v1/reports \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": "desktop", + "json": true, + "html": true, + "csv": false, + "theme": "dark", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + "locale": "en-US", + "timezoneId": "America/New_York", + "headers": { + "Authorization": "Bearer your-token" + }, + "thresholds": { + "performance": 90, + "accessibility": 95, + "best-practices": 85, + "seo": 80, + "pwa": 70 + }, + "waitUntil": "networkidle", + "timeout": 60000 + }' +``` + +#### Report Parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `url` | string | **required** | The URL to analyze | +| `viewport` | string | "mobile" | Device type: "mobile", "desktop" | +| `json` | boolean | true | Include JSON report | +| `html` | boolean | false | Include HTML report | +| `csv` | boolean | false | Include CSV report | +| `theme` | string | "light" | Browser theme: "light", "dark" | +| `userAgent` | string | - | Custom user agent string | +| `locale` | string | - | Browser locale | +| `timezoneId` | string | - | IANA timezone identifier (see [Timezone](#timezone) section) | +| `permissions` | array | - | Browser permissions (see [Permissions](#permissions) section) | +| `headers` | object | - | Custom HTTP headers | +| `thresholds` | object | - | Performance thresholds (0-100) | +| `thresholds.performance` | number | 0 | Performance score threshold | +| `thresholds.accessibility` | number | 0 | Accessibility score threshold | +| `thresholds.best-practices` | number | 0 | Best practices score threshold | +| `thresholds.seo` | number | 0 | SEO score threshold | +| `thresholds.pwa` | number | 0 | PWA score threshold | +| `waitUntil` | string | "domcontentloaded" | Wait condition | +| `timeout` | number | 30000 | Navigation timeout (0-120000ms) | + +## Timezone + +The `timezoneId` parameter allows you to set the browser's timezone for accurate time-based testing and content capture. This is particularly useful for testing applications that display time-sensitive content or have timezone-dependent features. + +### Format + +The `timezoneId` must be a valid **IANA timezone identifier** in the format `Region/City`. This is the same format used by JavaScript's `Intl.DateTimeFormat` and is the standard for timezone identification. + +### Valid Timezone Examples + +#### Americas +- `America/New_York` - Eastern Time (US) +- `America/Chicago` - Central Time (US) +- `America/Denver` - Mountain Time (US) +- `America/Los_Angeles` - Pacific Time (US) +- `America/Toronto` - Eastern Time (Canada) +- `America/Vancouver` - Pacific Time (Canada) +- `America/Sao_Paulo` - BrasΓ­lia Time (Brazil) +- `America/Mexico_City` - Central Time (Mexico) +- `America/Argentina/Buenos_Aires` - Argentina Time + +#### Europe +- `Europe/London` - Greenwich Mean Time / British Summer Time +- `Europe/Paris` - Central European Time +- `Europe/Berlin` - Central European Time +- `Europe/Rome` - Central European Time +- `Europe/Madrid` - Central European Time +- `Europe/Amsterdam` - Central European Time +- `Europe/Moscow` - Moscow Time +- `Europe/Istanbul` - Turkey Time + +#### Asia +- `Asia/Tokyo` - Japan Standard Time +- `Asia/Shanghai` - China Standard Time +- `Asia/Hong_Kong` - Hong Kong Time +- `Asia/Singapore` - Singapore Time +- `Asia/Seoul` - Korea Standard Time +- `Asia/Dubai` - Gulf Standard Time +- `Asia/Kolkata` - India Standard Time +- `Asia/Bangkok` - Indochina Time + +#### Australia & Pacific +- `Australia/Sydney` - Australian Eastern Time +- `Australia/Melbourne` - Australian Eastern Time +- `Australia/Perth` - Australian Western Time +- `Pacific/Auckland` - New Zealand Time +- `Pacific/Honolulu` - Hawaii-Aleutian Time +- `Pacific/Fiji` - Fiji Time + +#### Africa +- `Africa/Cairo` - Eastern European Time +- `Africa/Johannesburg` - South Africa Standard Time +- `Africa/Lagos` - West Africa Time +- `Africa/Nairobi` - East Africa Time + +#### Special Cases +- `UTC` - Coordinated Universal Time +- `GMT` - Greenwich Mean Time + +### Timezone Examples + +#### Basic Timezone Setting +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "timezoneId": "America/New_York" + }' +``` + +#### International Testing +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://ecommerce-site.com", + "timezoneId": "Asia/Tokyo", + "locale": "ja-JP" + }' +``` + +#### UTC for Consistent Testing +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://api-dashboard.com", + "timezoneId": "UTC" + }' +``` + +#### Comprehensive Test Command + +Here's a complete test command that showcases all available parameters: + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": { + "width": 1920, + "height": 1080 + }, + "format": "jpeg", + "quality": 95, + "fullPage": true, + "theme": "dark", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "locale": "en-US", + "timezoneId": "America/New_York", + "geolocation": { + "latitude": 40.7128, + "longitude": -74.0060, + "accuracy": 100 + }, + "permissions": [ + "geolocation", + "notifications", + "camera", + "microphone", + "clipboard-read", + "clipboard-write" + ], + "headers": { + "Authorization": "Bearer test-token-12345", + "X-Custom-Header": "test-value", + "X-Request-ID": "screenshot-test-001" + }, + "waitUntil": "networkidle", + "timeout": 60000, + "sleep": 5000, + "deviceScaleFactor": 2, + "hasTouch": true, + "isMobile": false + }' \ + --output "comprehensive-test-screenshot.jpg" +``` + +#### Mobile Device Test Command + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": { + "width": 375, + "height": 667 + }, + "format": "png", + "quality": 90, + "fullPage": true, + "theme": "light", + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", + "locale": "en-US", + "timezoneId": "America/Los_Angeles", + "geolocation": { + "latitude": 37.7749, + "longitude": -122.4194, + "accuracy": 50 + }, + "permissions": [ + "geolocation", + "camera", + "microphone", + "notifications" + ], + "headers": { + "X-Mobile-App": "true", + "X-Device-Type": "mobile" + }, + "waitUntil": "domcontentloaded", + "timeout": 30000, + "sleep": 3000, + "deviceScaleFactor": 3, + "hasTouch": true, + "isMobile": true + }' \ + --output "mobile-test-screenshot.png" +``` + +#### High-Quality Desktop Test Command + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": { + "width": 2560, + "height": 1440 + }, + "format": "jpeg", + "quality": 100, + "fullPage": true, + "theme": "dark", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "locale": "en-US", + "timezoneId": "Europe/London", + "geolocation": { + "latitude": 51.5074, + "longitude": -0.1278, + "accuracy": 20 + }, + "permissions": [ + "geolocation", + "notifications", + "camera", + "microphone", + "clipboard-read", + "clipboard-write", + "payment-handler", + "usb", + "bluetooth" + ], + "headers": { + "Authorization": "Bearer premium-token", + "X-Premium-User": "true", + "X-Request-Source": "screenshot-api" + }, + "waitUntil": "networkidle", + "timeout": 90000, + "sleep": 8000, + "deviceScaleFactor": 2, + "hasTouch": false, + "isMobile": false + }' \ + --output "high-quality-desktop-screenshot.jpg" +``` + +#### Clipped Screenshot Test Command + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": { + "width": 1920, + "height": 1080 + }, + "format": "png", + "quality": 90, + "fullPage": false, + "clip": { + "x": 100, + "y": 100, + "width": 800, + "height": 600 + }, + "theme": "light", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0", + "locale": "en-GB", + "timezoneId": "Europe/Berlin", + "permissions": [ + "geolocation", + "notifications" + ], + "headers": { + "X-Test-Mode": "clipped-screenshot", + "X-Region": "europe" + }, + "waitUntil": "load", + "timeout": 45000, + "sleep": 2000, + "deviceScaleFactor": 1.5, + "hasTouch": false, + "isMobile": false + }' \ + --output "clipped-screenshot.png" +``` + +### Common Use Cases + +- **E-commerce**: Test pricing displays in different timezones +- **Scheduling Apps**: Verify appointment times across regions +- **News Sites**: Check time-sensitive content display +- **Financial Apps**: Test market hours and trading times +- **Social Media**: Verify post timestamps and feeds +- **Analytics**: Test time-based reporting accuracy + +### Validation + +The API validates timezone identifiers using the IANA timezone database format. Invalid timezone identifiers will result in a validation error with a helpful message. + +**Valid format**: `Region/City` (e.g., `America/New_York`) +**Invalid formats**: +- `EST`, `PST` (abbreviations not supported) +- `UTC+5` (offset format not supported) +- `New York` (missing region prefix) + +## Permissions + +The `permissions` parameter allows you to grant specific browser permissions to the page during screenshot capture or report generation. This is useful for testing features that require user permission or for capturing content that depends on specific browser capabilities. + +### Available Permissions + +| Permission | Description | Use Case | +|------------|-------------|----------| +| `geolocation` | Access to device location | Location-based features, maps | +| `camera` | Access to device camera | Video calls, photo capture | +| `microphone` | Access to device microphone | Audio recording, voice calls | +| `notifications` | Display notifications | Push notifications, alerts | +| `clipboard-read` | Read from clipboard | Copy/paste functionality | +| `clipboard-write` | Write to clipboard | Copy/paste functionality | +| `payment-handler` | Handle payment requests | E-commerce, payment flows | +| `midi` | Access to MIDI devices | Music applications | +| `usb` | Access to USB devices | Hardware integration | +| `serial` | Access to serial ports | Hardware communication | +| `bluetooth` | Access to Bluetooth devices | IoT, wireless devices | +| `persistent-storage` | Persistent storage access | Offline data storage | +| `accelerometer` | Access to accelerometer | Motion detection, games | +| `gyroscope` | Access to gyroscope | Orientation, VR/AR | +| `magnetometer` | Access to magnetometer | Compass, navigation | +| `ambient-light-sensor` | Access to light sensor | Auto-brightness, themes | +| `background-sync` | Background synchronization | Offline sync | +| `background-fetch` | Background data fetching | Offline content | +| `idle-detection` | Detect user idle state | Power management | +| `periodic-background-sync` | Periodic background sync | Scheduled updates | +| `push` | Push messaging | Real-time notifications | +| `speaker-selection` | Audio output selection | Audio routing | +| `storage-access` | Cross-site storage access | Third-party cookies | +| `top-level-storage-access` | Top-level storage access | First-party storage | +| `window-management` | Window management | Multi-window apps | +| `local-fonts` | Access to local fonts | Custom typography | +| `display-capture` | Screen capture | Screen sharing | +| `nfc` | Near Field Communication | Contactless payments | +| `screen-wake-lock` | Prevent screen sleep | Always-on displays | +| `web-share` | Web Share API | Native sharing | +| `xr-spatial-tracking` | XR spatial tracking | VR/AR applications | + +### Permission Examples + +#### Basic Permissions +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "permissions": ["geolocation", "notifications"] + }' +``` + +#### Camera and Microphone Access +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://video-call-app.com", + "permissions": ["camera", "microphone", "notifications"] + }' +``` + +#### Hardware Integration +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://iot-dashboard.com", + "permissions": ["usb", "bluetooth", "serial"] + }' +``` + +#### VR/AR Applications +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://vr-app.com", + "permissions": ["xr-spatial-tracking", "accelerometer", "gyroscope", "magnetometer"] + }' +``` + +#### Payment and E-commerce +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://ecommerce-site.com", + "permissions": ["payment-handler", "clipboard-write", "nfc"] + }' +``` + +## Examples + +### Mobile Screenshot + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": {"width": 375, "height": 667}, + "isMobile": true, + "hasTouch": true, + "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)" + }' +``` + +### High-Quality Screenshot + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "format": "jpeg", + "quality": 100, + "deviceScaleFactor": 2, + "fullPage": true + }' +``` + +### Geolocation Testing + +```bash +curl -X POST http://localhost:3000/v1/screenshots \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "geolocation": { + "latitude": 37.7749, + "longitude": -122.4194, + "accuracy": 100 + }, + "timezoneId": "America/Los_Angeles" + }' +``` + +### Performance Report with Custom Thresholds + +```bash +curl -X POST http://localhost:3000/v1/reports \ + -H "Content-Type: application/json" \ + -d '{ + "url": "https://example.com", + "viewport": "desktop", + "thresholds": { + "performance": 90, + "accessibility": 95, + "best-practices": 85, + "seo": 80 + }, + "html": true + }' ``` ## Development Make sure you have [pnpm](https://pnpm.io/) installed. -To install dependencies, run the following command. +Install dependencies: ```bash pnpm i ``` -Next, start the server by running `npm start`, and visit use endpoint `http://localhost:3000` as REST API endpoint. +Start the development server: + +```bash +npm start +``` + +The API will be available at `http://localhost:3000`. ## Contributing diff --git a/src/index.js b/src/index.js index fcdf15e..eb43180 100644 --- a/src/index.js +++ b/src/index.js @@ -36,17 +36,118 @@ const defaultContext = { const screenshotSchema = z.object({ url: z.string().url(), theme: z.enum(["light", "dark"]).default("light"), - headers: z.record(z.string(), z.any()), + headers: z.record(z.string(), z.any()).optional(), sleep: z.number().min(0).max(60000).default(3000), + // Viewport options + viewport: z + .object({ + width: z.number().min(1).max(3840).default(1280), + height: z.number().min(1).max(2160).default(720), + }) + .optional(), + // Screenshot options + format: z.enum(["png", "jpeg", "webp"]).default("png"), + quality: z.number().min(0).max(100).default(90), + fullPage: z.boolean().default(false), + clip: z + .object({ + x: z.number().min(0), + y: z.number().min(0), + width: z.number().min(1), + height: z.number().min(1), + }) + .optional(), + // Browser context options + userAgent: z.string().optional(), + locale: z.string().optional(), + timezoneId: z + .string() + .regex( + /^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific|UTC)\/[A-Za-z_]+$/, + "Must be a valid IANA timezone identifier (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo')", + ) + .optional(), + geolocation: z + .object({ + latitude: z.number().min(-90).max(90), + longitude: z.number().min(-180).max(180), + accuracy: z.number().min(0).optional(), + }) + .optional(), + permissions: z + .array( + z.enum([ + "geolocation", + "camera", + "microphone", + "notifications", + "clipboard-read", + "clipboard-write", + "payment-handler", + "midi", + "usb", + "serial", + "bluetooth", + "persistent-storage", + "accelerometer", + "gyroscope", + "magnetometer", + "ambient-light-sensor", + "background-sync", + "background-fetch", + "idle-detection", + "periodic-background-sync", + "push", + "speaker-selection", + "storage-access", + "top-level-storage-access", + "window-management", + "local-fonts", + "display-capture", + "nfc", + "screen-wake-lock", + "web-share", + "xr-spatial-tracking", + ]), + ) + .optional(), + // Navigation options + waitUntil: z + .enum(["load", "domcontentloaded", "networkidle", "commit"]) + .default("domcontentloaded"), + timeout: z.number().min(0).max(120000).default(30000), + // Additional options + deviceScaleFactor: z.number().min(0.1).max(3).default(1), + hasTouch: z.boolean().default(false), + isMobile: z.boolean().default(false), }); router.post( "/v1/screenshots", defineEventHandler(async (event) => { const body = await readValidatedBody(event, screenshotSchema.parse); - const context = await browser.newContext({ + + // Build context options + const contextOptions = { ...defaultContext, colorScheme: body.theme, - }); + viewport: body.viewport || defaultContext.viewport, + deviceScaleFactor: body.deviceScaleFactor, + hasTouch: body.hasTouch, + isMobile: body.isMobile, + }; + + // Add optional context options + if (body.userAgent) contextOptions.userAgent = body.userAgent; + if (body.locale) contextOptions.locale = body.locale; + if (body.timezoneId) contextOptions.timezoneId = body.timezoneId; + if (body.geolocation) contextOptions.geolocation = body.geolocation; + + const context = await browser.newContext(contextOptions); + + // Grant permissions if specified + if (body.permissions && body.permissions.length > 0) { + await context.grantPermissions(body.permissions, { origin: body.url }); + } // await context.tracing.start({ screenshots: true, snapshots: true }); @@ -59,7 +160,7 @@ router.post( return await route.continue({ headers: { ...request.headers(), - ...body.headers, + ...(body.headers || {}), }, }); } @@ -68,14 +169,26 @@ router.post( }); await page.goto(body.url, { - waitUntil: "domcontentloaded", + waitUntil: body.waitUntil, + timeout: body.timeout, }); if (body.sleep > 0) { await page.waitForTimeout(body.sleep); // Safe addition for any extra JS } - const screen = await page.screenshot(); + // Build screenshot options + const screenshotOptions = { + type: body.format, + quality: body.quality, + fullPage: body.fullPage, + }; + + if (body.clip) { + screenshotOptions.clip = body.clip; + } + + const screen = await page.screenshot(screenshotOptions); // await context.tracing.stop({ path: '/tmp/trace' + Date.now() + '.zip' }); @@ -90,6 +203,70 @@ const lighthouseSchema = z.object({ json: z.boolean().default(true), html: z.boolean().default(false), csv: z.boolean().default(false), + // Additional lighthouse options + theme: z.enum(["light", "dark"]).default("light"), + headers: z.record(z.string(), z.any()).optional(), + userAgent: z.string().optional(), + locale: z.string().optional(), + timezoneId: z + .string() + .regex( + /^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific|UTC)\/[A-Za-z_]+$/, + "Must be a valid IANA timezone identifier (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo')", + ) + .optional(), + permissions: z + .array( + z.enum([ + "geolocation", + "camera", + "microphone", + "notifications", + "clipboard-read", + "clipboard-write", + "payment-handler", + "midi", + "usb", + "serial", + "bluetooth", + "persistent-storage", + "accelerometer", + "gyroscope", + "magnetometer", + "ambient-light-sensor", + "background-sync", + "background-fetch", + "idle-detection", + "periodic-background-sync", + "push", + "speaker-selection", + "storage-access", + "top-level-storage-access", + "window-management", + "local-fonts", + "display-capture", + "nfc", + "screen-wake-lock", + "web-share", + "xr-spatial-tracking", + ]), + ) + .optional(), + // Performance thresholds + thresholds: z + .object({ + performance: z.number().min(0).max(100).default(0), + accessibility: z.number().min(0).max(100).default(0), + "best-practices": z.number().min(0).max(100).default(0), + seo: z.number().min(0).max(100).default(0), + pwa: z.number().min(0).max(100).default(0), + }) + .optional(), + // Navigation options + waitUntil: z + .enum(["load", "domcontentloaded", "networkidle", "commit"]) + .default("domcontentloaded"), + timeout: z.number().min(0).max(120000).default(30000), }); const configs = { mobile: lighthouseMobileConfig, @@ -99,9 +276,57 @@ router.post( "/v1/reports", defineEventHandler(async (event) => { const body = await readValidatedBody(event, lighthouseSchema.parse); - const context = await browser.newContext(defaultContext); + + // Build context options + const contextOptions = { + ...defaultContext, + colorScheme: body.theme, + }; + + // Add optional context options + if (body.userAgent) contextOptions.userAgent = body.userAgent; + if (body.locale) contextOptions.locale = body.locale; + if (body.timezoneId) contextOptions.timezoneId = body.timezoneId; + + const context = await browser.newContext(contextOptions); + + // Grant permissions if specified + if (body.permissions && body.permissions.length > 0) { + await context.grantPermissions(body.permissions, { origin: body.url }); + } + const page = await context.newPage(); - await page.goto(body.url); + + // Override headers if provided + if (body.headers) { + await page.route("**/*", async (route, request) => { + const url = request.url(); + if (url.startsWith("http://appwrite/")) { + return await route.continue({ + headers: { + ...request.headers(), + ...body.headers, + }, + }); + } + return await route.continue({ headers: request.headers() }); + }); + } + + await page.goto(body.url, { + waitUntil: body.waitUntil, + timeout: body.timeout, + }); + + // Use custom thresholds if provided, otherwise use defaults + const thresholds = body.thresholds || { + "best-practices": 0, + accessibility: 0, + performance: 0, + pwa: 0, + seo: 0, + }; + const results = await playAudit({ reports: { formats: { @@ -113,13 +338,7 @@ router.post( config: configs[body.viewport], page: page, port: 9222, - thresholds: { - "best-practices": 0, - accessibility: 0, - performance: 0, - pwa: 0, - seo: 0, - }, + thresholds, }); await context.close(); return JSON.parse(results.report); @@ -135,6 +354,368 @@ router.get( }), ); +router.get( + "/v1/test", + defineEventHandler(async (event) => { + // Create a simple context with default settings + const context = await browser.newContext(defaultContext); + const page = await context.newPage(); + + // Navigate to a simple page to get browser info + await page.goto("about:blank"); + + // Generate HTML page with browser configuration info + const html = await page.evaluate(() => { + const timestamp = new Date().toISOString(); + + return ` + + + + + + Browser Configuration Test + + + + +
+

🌐 Browser Configuration Test

+
Generated: ${timestamp}
+ +
+
+

πŸ“± Viewport & Display

+
+ Viewport Width: + Loading... +
+
+ Viewport Height: + Loading... +
+
+ Screen Width: + Loading... +
+
+ Screen Height: + Loading... +
+
+ Device Pixel Ratio: + Loading... +
+
+ +
+

🎨 Theme & Appearance

+
+ Color Scheme: + Loading... +
+
+ +
+

🌍 Localization

+
+ Language: + Loading... +
+
+ Timezone: + Loading... +
+
+ +
+

πŸ”§ Device & Hardware

+
+ Touch Support: + Loading... +
+
+ Mobile Device: + Loading... +
+
+ +
+

🌐 User Agent

+
+ User Agent: + Loading... +
+
+ +
+

πŸ“ Geolocation

+
+ Location: + Loading... +
+
+ +
+

πŸ” Permissions

+
+ Permission States: + Loading... +
+
+ +
+

πŸ“„ Page Information

+
+ URL: + Loading... +
+
+ Title: + Loading... +
+
+ Ready State: + Loading... +
+
+
+
+ + + +`; + }); + + await context.close(); + + // Set content type to HTML + event.node.res.setHeader("Content-Type", "text/html"); + return html; + }), +); + createServer(toNodeListener(app)).listen(port); console.log(`Server running on port http://0.0.0.0:${port}`);