@duotech/shots
v1.0.4
Published
Automated screenshot capture tool for web applications. Optimized for AI agents.
Downloads
422
Maintainers
Readme
@duotech/shots
Automated screenshot capture tool for web applications. Optimized for AI agents.
Features
- Simple Configuration - Define routes in a TypeScript config file
- Automatic Compression - JPEG output with configurable quality
- Mobile Support - Capture both desktop and mobile viewports
- Gallery Generator - HTML gallery for easy viewing
- Version Management - Track multiple capture sessions
- Custom Authentication - Pluggable auth handlers
- Pattern Filtering - Capture specific routes with
--onlyand--exclude - Arbitrary URLs - Capture any URL with
--urls
Installation
npm install @duotech/shots
# or
pnpm add @duotech/shotsInstall Playwright browsers:
npx playwright install chromiumQuick Start
1. Create Configuration
npx shots initThis creates shots.config.ts:
import { defineConfig } from '@duotech/shots';
export default defineConfig({
baseUrl: 'http://localhost:3000',
routes: [
{ id: 'home', path: '/' },
{ id: 'login', path: '/login' },
{ id: 'dashboard', path: '/dashboard', auth: true },
],
});2. Capture Screenshots
npx shotsCLI Usage
# Capture all routes
npx shots
# Capture specific routes
npx shots --only home,login
# Exclude routes
npx shots --exclude admin,settings
# Capture arbitrary URLs
npx shots --urls /custom-page,/another-page
# Include mobile viewport
npx shots --mobile
# List captured versions
npx shots --list
# Compare versions
npx shots --compare 20231220-1234Options
| Flag | Short | Description |
|------|-------|-------------|
| --config <path> | -c | Config file path |
| --only <ids> | -o | Only capture matching routes |
| --exclude <ids> | -x | Exclude matching routes |
| --urls <urls> | -u | Capture arbitrary URLs |
| --mobile | -m | Include mobile viewport |
| --quiet | -q | Less verbose output |
| --list | | List available versions |
| --compare <v> | | Compare with previous version |
| --help | -h | Show help |
Configuration
Basic Config
import { defineConfig } from '@duotech/shots';
export default defineConfig({
baseUrl: 'http://localhost:3000',
outputDir: './screenshots',
routes: [
{ id: 'home', path: '/' },
{ id: 'login', path: '/login' },
],
});Full Config Options
import { defineConfig } from '@duotech/shots';
export default defineConfig({
// Required
baseUrl: 'http://localhost:3000',
routes: [...],
// Optional
outputDir: './screenshots', // Default: './screenshots'
locale: 'fa-IR', // Browser locale
timezone: 'Asia/Tehran', // Browser timezone
// Placeholder values for dynamic routes
placeholders: {
tenant: 'demo',
userId: '123',
},
// Viewport sizes
viewports: {
desktop: { width: 1440, height: 900 },
mobile: { width: 390, height: 844 },
},
// Compression settings
compression: {
quality: 70, // JPEG quality (1-100)
maxWidth: 1440, // Max width for desktop
mobileMaxWidth: 390, // Max width for mobile
},
// Gallery configuration
gallery: {
title: 'My Screenshots',
rtl: true, // RTL support
dark: true, // Dark theme
css: '', // Custom CSS
},
// Custom authentication handler
auth: async (page) => {
await page.goto('/api/dev-login?user=test');
return true;
},
});Route Config
interface RouteConfig {
id: string; // Unique identifier
path: string; // URL path (supports placeholders)
description?: string; // Human-readable description
category?: string; // Category for grouping
auth?: boolean; // Requires authentication
action?: ActionType; // Action before capture
actionSelector?: string; // CSS selector for action
waitForSelector?: string; // Wait for selector after action
waitAfterLoad?: number; // Wait time after load (ms)
waitAfterAction?: number; // Wait time after action (ms)
}
type ActionType =
| 'none'
| 'click-first-row'
| 'click-first-card'
| 'click-button'
| 'open-modal';Dynamic Routes with Placeholders
export default defineConfig({
baseUrl: 'http://localhost:3000',
placeholders: {
tenant: 'acme',
workspace: 'main',
},
routes: [
{ id: 'dashboard', path: '/{tenant}/{workspace}/dashboard' },
{ id: 'settings', path: '/{tenant}/settings' },
],
});Actions (Click, Modal, etc.)
routes: [
// Click first row in table
{
id: 'users-detail',
path: '/admin/users',
action: 'click-first-row',
actionSelector: 'table tbody tr:first-child',
waitAfterAction: 1000,
},
// Open modal
{
id: 'new-user-modal',
path: '/admin/users',
action: 'open-modal',
actionSelector: 'button:has-text("Add User")',
waitForSelector: '[role="dialog"]',
waitAfterAction: 500,
},
]Output Structure
screenshots/
├── versions.json # Index of all versions
├── 20231220-1234-abcd/ # Version folder
│ ├── index.html # Gallery view
│ ├── home_desktop.jpg
│ ├── login_desktop.jpg
│ ├── login_mobile.jpg
│ └── ...Programmatic API
import { capture, defineConfig } from 'shots';
const config = defineConfig({
baseUrl: 'http://localhost:3000',
routes: [
{ id: 'home', path: '/' },
],
});
const result = await capture(config, {
mobile: true,
only: ['home'],
});
console.log(result);
// {
// version: '20231220-1234-abcd',
// successCount: 2,
// failCount: 0,
// totalSize: 150000,
// outputDir: './screenshots/20231220-1234-abcd'
// }For AI Agents
This tool is optimized for AI agents to verify UI changes:
# After making UI changes, capture screenshots
npx shots --only affected-page
# View results
open screenshots/*/index.html
# Compare with previous version
npx shots --compare previous-version-idPattern Matching
The --only and --exclude flags match against:
- Route ID:
--only home,login - URL path:
--only /admin/users - Category:
--only admin - Partial match:
--only user(matchesusers,user-settings, etc.)
License
MIT
