# Screenshot Automation Plan for Bookhoard Documentation > **Status**: Ready to implement when frontend is complete > **Last Updated**: 2026-02-03 This document outlines the complete plan for automatically generating screenshots for Bookhoard documentation using Playwright. ## Overview Playwright will be used to: 1. Navigate the running Bookhoard server 2. Perform key user/admin workflows 3. Capture screenshots at each step 4. Save to `docs/images/` organized by documentation section 5. Generate/update markdown files with proper image references ## Prerequisites (To Verify When Ready) ### Frontend Pages Complete Verify these pages are fully functional before starting: - [ ] `/` - Login page - [ ] `/register` - Registration page - [ ] `/dashboard` - Main dashboard with library browsing - [ ] `/collections` - Collections management - [ ] `/devices` - Device management and registration - [ ] `/conflicts` - Sync conflict resolution - [ ] `/progress` - Reading progress tracking - [ ] `/analytics` - Usage analytics - [ ] `/queue` - Sync queue monitoring - [ ] `/admin` - Admin dashboard - [ ] `/admin/profile` - Profile settings - [ ] `/admin/library` - Library management - [ ] `/api-explorer` - API testing interface ### Test Environment Ready - [ ] Bookhoard server running on `http://localhost:8765` - [ ] Test database seeded with sample data (books, collections, devices) - [ ] Test admin account ready (username, password, role=admin) - [ ] Test regular user account ready (username, password, role=user) ## Installation & Setup ```bash # Install Playwright (run in project root) npm init -y npm install -D @playwright/test npx playwright install chromium ``` Project structure after setup: ``` bookhoard/ ├── docs/ │ ├── images/ # Screenshots will be stored here │ │ ├── user/ │ │ ├── admin/ │ │ ├── devices/ │ │ └── sync/ │ └── *.md # Existing markdown files ├── screenshots/ # Playwright test files │ ├── auth.spec.ts │ ├── user-workflows.spec.ts │ ├── admin-workflows.spec.ts │ ├── device-workflows.spec.ts │ ├── sync-workflows.spec.ts │ └── config.ts ├── .env.screenshots # Environment configuration └── ... ``` ## Environment Setup Create `.env.screenshots` in the project root: ```env # Bookhoard Server BASE_URL=http://localhost:8765 # Admin Account (for admin guide screenshots) ADMIN_EMAIL=admin@example.com ADMIN_USERNAME=admin ADMIN_PASSWORD=SecureAdminPass123! # Regular User Account (for user guide screenshots) USER_EMAIL=user@example.com USER_USERNAME=user USER_PASSWORD=SecureUserPass456! ``` ## Questions to Ask When Ready Before running the screenshot automation, ask the user: ### 1. Server Access **Q**: Where is the Bookhoard server running? - [ ] `localhost:8765` (default) - [ ] Custom port: `________` - [ ] Remote URL: `________` ### 2. Test Credentials **Q**: What credentials should Playwright use? **Admin Account** (for admin guide screenshots): - Username: `________` - Password: `________` **Regular User Account** (for user guide screenshots): - Username: `________` - Password: `________` ### 3. Screenshot Format **Q**: What format for screenshots? - [ ] **WebP** (recommended - modern, good compression) - [ ] PNG (highest quality, larger files) - [ ] JPEG (smaller files, compression artifacts) ### 4. Screenshot Dimensions **Q**: What viewport sizes for screenshots? - [ ] **Desktop**: 1920x1080 (full-width screenshots) - [ ] **Tablet**: 768x1024 (responsive documentation) - [ ] **Mobile**: 375x667 (mobile documentation) - [ ] All three sizes (comprehensive coverage) ### 5. Theme **Q**: What theme should screenshots use? - [ ] **Default** (Tokyo Night theme as seen in templates) - [ ] Light theme (if implemented) - [ ] Multiple themes (document theme switching) ### 6. Language **Q**: What language/region for the UI? - [ ] **English** (default) - [ ] Other: `________` ## Screenshot Plan by Documentation Section ### 1. User Guide Screenshots **File**: `docs/user/user-guide.md` **Screenshots Needed**: | Screenshot Name | Description | Page/Action | |-----------------|-------------|-------------| | `login-page.webp` | Login form with filled credentials | `/` - Login page | | `dashboard-overview.webp` | Main dashboard showing libraries | `/dashboard` | | `library-grid.webp` | Media items grid view | `/dashboard` → Click library | | `book-detail.webp` | Book detail view with metadata | `/dashboard` → Click book | | `search-results.webp` | Search in action | `/dashboard` → Type in search | | `filter-panel.webp` | Filter options expanded | `/dashboard` → Open filters | | `collections-list.webp` | Collections grid view | `/collections` | | `create-collection.webp` | New collection modal | `/collections` → Click "New Collection" | | `reading-progress.webp` | Progress tracking view | `/progress` | | `analytics-view.webp` | User analytics dashboard | `/analytics` | **Estimated Screenshots**: ~10 ### 2. Admin Guide Screenshots **File**: `docs/user/admin-guide.md` **Screenshots Needed**: | Screenshot Name | Description | Page/Action | |-----------------|-------------|-------------| | `admin-dashboard.webp` | Admin overview panel | `/admin` | | `user-management.webp` | User list with actions | `/admin` → Users section | | `add-user.webp` | Add new user form | `/admin` → Click "Add User" | | `library-settings.webp` | Library configuration | `/admin/library` | | `add-library.webp` | Create new library form | `/admin/library` → Click "Add Library" | | `analytics-admin.webp` | Admin analytics view | `/analytics` (admin view) | | `sync-queue.webp` | Sync queue monitoring | `/queue` | | `theme-settings.webp` | Theme selection interface | `/admin/profile` → Theme section | | `user-profile-edit.webp` | Edit user profile | `/admin/profile` | **Estimated Screenshots**: ~9 ### 3. Device Setup Screenshots **File**: `docs/user/devices/kobo-setup.md` and `koreader-setup.md` **Screenshots Needed**: | Screenshot Name | Description | Page/Action | |-----------------|-------------|-------------| | `device-list.webp` | Device management page | `/devices` | | `add-device-modal.webp` | Add new device modal | `/devices` → Click "Add New Device" | | `device-form-kobo.webp` | Kobo device registration form | `/devices` → Select Kobo type | | `device-form-koreader.webp` | KOReader device registration form | `/devices` → Select KOReader type | | `device-qr-code.webp` | QR code for device approval | After device registration | | `device-approved.webp` | Device approved confirmation | After approving device | | `device-sync-settings.webp` | Sync configuration for device | `/devices` → Click device settings | | `shelf-mapping.webp` | Collection to shelf mapping | `/devices` → Click shelf mapping | | `sync-queue-item.webp` | Device sync in queue | `/queue` (device specific) | **Estimated Screenshots**: ~9 ### 4. Sync Guide Screenshots **File**: `docs/user/sync-guide.md` **Screenshots Needed**: | Screenshot Name | Description | Page/Action | |-----------------|-------------|-------------| | `sync-conflicts.webp` | Conflicts list view | `/conflicts` | | `conflict-resolution.webp` | Resolve conflict dialog | `/conflicts` → Click resolve | | `unlinked-books.webp` | Unlinked books list | `/unlinked-books` | | `book-linking.webp` | Link book to metadata | `/unlinked-books` → Click link | | `sync-success.webp` | Successful sync indicator | Any page after sync | **Estimated Screenshots**: ~5 ### 5. Settings Guide Screenshots **File**: `docs/user/settings-guide.md` **Screenshots Needed**: | Screenshot Name | Description | Page/Action | |-----------------|-------------|-------------| | `profile-overview.webp` | Profile settings page | `/admin/profile` | | `update-username.webp` | Username change form | `/admin/profile` | | `update-email.webp` | Email change form | `/admin/profile` | | `change-password.webp` | Password change form | `/admin/profile` | | `theme-selector.webp` | Theme selection dropdown | `/admin/profile` (if implemented) | **Estimated Screenshots**: ~5 ## Playwright Test Files ### `screenshots/config.ts` - Playwright Configuration ```typescript import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './', fullyParallel: false, retries: 1, reporter: 'list', use: { baseURL: process.env.BASE_URL || 'http://localhost:8765', trace: 'on-first-retry', screenshot: 'only-on-failure', }, projects: [ { name: 'chromium-desktop', use: { ...devices['Desktop Chrome'], viewport: { width: 1920, height: 1080 } }, }, ], }); ``` ### `screenshots/auth.spec.ts` - Authentication Screenshots ```typescript import { test, expect } from '@playwright/test'; test.describe('Authentication Screenshots', () => { test('Login page', async ({ page }) => { await page.goto('/'); await page.screenshot({ path: 'docs/images/user/login-page.webp', fullPage: true }); }); test('User login flow', async ({ page }) => { await page.goto('/'); await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); await page.screenshot({ path: 'docs/images/user/login-form-filled.webp', fullPage: true }); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard', { timeout: 5000 }); await page.screenshot({ path: 'docs/images/user/dashboard-after-login.webp', fullPage: true }); }); test('Admin login flow', async ({ page }) => { await page.goto('/'); await page.fill('input[name="login"]', process.env.ADMIN_USERNAME || 'admin'); await page.fill('input[name="password"]', process.env.ADMIN_PASSWORD || 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard', { timeout: 5000 }); await page.goto('/admin'); await page.screenshot({ path: 'docs/images/admin/admin-dashboard.webp', fullPage: true }); }); test('Registration page', async ({ page }) => { await page.goto('/register'); await page.screenshot({ path: 'docs/images/user/registration-page.webp', fullPage: true }); await page.fill('input[name="email"]', 'newuser@example.com'); await page.fill('input[name="username"]', 'newuser'); await page.fill('input[name="password"]', 'SecurePass123!'); await page.screenshot({ path: 'docs/images/user/registration-form-filled.webp', fullPage: true }); }); }); ``` ### `screenshots/user-workflows.spec.ts` - User Guide Screenshots ```typescript import { test, expect } from '@playwright/test'; test.describe('User Guide Screenshots', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard', { timeout: 5000 }); }); test('Dashboard overview', async ({ page }) => { await page.goto('/dashboard'); await page.screenshot({ path: 'docs/images/user/dashboard-overview.webp', fullPage: true }); }); test('Library grid view', async ({ page }) => { await page.goto('/dashboard'); await page.waitForSelector('#libraries-container', { timeout: 5000 }); const firstLibrary = page.locator('[data-library]').first(); if (await firstLibrary.isVisible()) { await firstLibrary.click(); await page.waitForURL(/\/dashboard/, { timeout: 5000 }); await page.screenshot({ path: 'docs/images/user/library-grid.webp', fullPage: true }); } }); test('Search functionality', async ({ page }) => { await page.goto('/dashboard'); await page.waitForSelector('#search-input', { timeout: 5000 }); await page.fill('#search-input', 'science'); await page.waitForTimeout(1000); await page.screenshot({ path: 'docs/images/user/search-results.webp', fullPage: true }); }); test('Filter panel', async ({ page }) => { await page.goto('/dashboard'); const filterPanel = page.locator('.filter-panel details'); if (await filterPanel.isVisible()) { await filterPanel.click(); await page.waitForTimeout(500); await page.screenshot({ path: 'docs/images/user/filter-panel-open.webp', fullPage: true }); } }); test('Collections list', async ({ page }) => { await page.goto('/collections'); await page.screenshot({ path: 'docs/images/user/collections-list.webp', fullPage: true }); }); test('Create collection modal', async ({ page }) => { await page.goto('/collections'); await page.click('button:has-text("New Collection"), button:has-text("Create Your First Collection")'); await page.waitForSelector('#create-modal', { state: 'visible', timeout: 5000 }); await page.fill('#collection-name', 'My Reading List'); await page.fill('#collection-description', 'Books I want to read'); await page.screenshot({ path: 'docs/images/user/create-collection-modal.webp', fullPage: true }); }); test('Reading progress view', async ({ page }) => { await page.goto('/progress'); await page.screenshot({ path: 'docs/images/user/reading-progress.webp', fullPage: true }); }); test('Analytics view', async ({ page }) => { await page.goto('/analytics'); await page.screenshot({ path: 'docs/images/user/analytics-view.webp', fullPage: true }); }); }); ``` ### `screenshots/admin-workflows.spec.ts` - Admin Guide Screenshots ```typescript import { test, expect } from '@playwright/test'; test.describe('Admin Guide Screenshots', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.fill('input[name="login"]', process.env.ADMIN_USERNAME || 'admin'); await page.fill('input[name="password"]', process.env.ADMIN_PASSWORD || 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard', { timeout: 5000 }); }); test('Admin dashboard', async ({ page }) => { await page.goto('/admin'); await page.screenshot({ path: 'docs/images/admin/admin-dashboard.webp', fullPage: true }); }); test('User management', async ({ page }) => { await page.goto('/admin'); const userSection = page.locator('[data-section="users"], text="Users"'); if (await userSection.isVisible()) { await page.screenshot({ path: 'docs/images/admin/user-management.webp', fullPage: true }); } }); test('Library management', async ({ page }) => { await page.goto('/admin/library'); await page.screenshot({ path: 'docs/images/admin/library-management.webp', fullPage: true }); }); test('Add library form', async ({ page }) => { await page.goto('/admin/library'); const addLibraryBtn = page.locator('button:has-text("Add Library"), button:has-text("Create Library")'); if (await addLibraryBtn.isVisible()) { await addLibraryBtn.click(); await page.waitForTimeout(500); await page.screenshot({ path: 'docs/images/admin/add-library-form.webp', fullPage: true }); } }); test('Profile settings', async ({ page }) => { await page.goto('/admin/profile'); await page.screenshot({ path: 'docs/images/admin/profile-settings.webp', fullPage: true }); }); test('Theme selector', async ({ page }) => { await page.goto('/admin/profile'); const themeSelect = page.locator('select[name="theme"], [data-theme-selector]'); if (await themeSelect.isVisible()) { await themeSelect.click(); await page.waitForTimeout(500); await page.screenshot({ path: 'docs/images/admin/theme-selector.webp', fullPage: true }); } }); }); ``` ### `screenshots/device-workflows.spec.ts` - Device Setup Screenshots ```typescript import { test, expect } from '@playwright/test'; test.describe('Device Setup Screenshots', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard', { timeout: 5000 }); }); test('Device list page', async ({ page }) => { await page.goto('/devices'); await page.screenshot({ path: 'docs/images/devices/device-list.webp', fullPage: true }); }); test('Add device modal', async ({ page }) => { await page.goto('/devices'); await page.click('button:has-text("Add New Device"), button:has-text("Add Your First Device")'); await page.waitForSelector('[data-modal="add-device"], #add-device-modal', { state: 'visible', timeout: 5000 }); await page.screenshot({ path: 'docs/images/devices/add-device-modal.webp', fullPage: true }); }); test('Kobo device form', async ({ page }) => { await page.goto('/devices'); await page.click('button:has-text("Add New Device")'); await page.waitForSelector('[data-modal="add-device"]', { state: 'visible', timeout: 5000 }); const deviceTypeSelect = page.locator('select[name="device_type"]'); if (await deviceTypeSelect.isVisible()) { await deviceTypeSelect.selectOption('kobo'); await page.waitForTimeout(500); await page.screenshot({ path: 'docs/images/devices/device-form-kobo.webp', fullPage: true }); } }); test('KOReader device form', async ({ page }) => { await page.goto('/devices'); await page.click('button:has-text("Add New Device")'); await page.waitForSelector('[data-modal="add-device"]', { state: 'visible', timeout: 5000 }); const deviceTypeSelect = page.locator('select[name="device_type"]'); if (await deviceTypeSelect.isVisible()) { await deviceTypeSelect.selectOption('koreader'); await page.waitForTimeout(500); await page.screenshot({ path: 'docs/images/devices/device-form-koreader.webp', fullPage: true }); } }); test('Sync queue', async ({ page }) => { await page.goto('/queue'); await page.screenshot({ path: 'docs/images/devices/sync-queue.webp', fullPage: true }); }); }); ``` ### `screenshots/sync-workflows.spec.ts` - Sync Guide Screenshots ```typescript import { test, expect } from '@playwright/test'; test.describe('Sync Guide Screenshots', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.fill('input[name="login"]', process.env.USER_USERNAME || 'user'); await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password'); await page.click('button[type="submit"]'); await page.waitForURL('/dashboard', { timeout: 5000 }); }); test('Sync conflicts page', async ({ page }) => { await page.goto('/conflicts'); await page.screenshot({ path: 'docs/images/sync/sync-conflicts.webp', fullPage: true }); }); test('Unlinked books page', async ({ page }) => { await page.goto('/unlinked-books'); await page.screenshot({ path: 'docs/images/sync/unlinked-books.webp', fullPage: true }); }); }); ``` ## Running the Tests ### Run all tests ```bash npx playwright test ``` ### Run specific test file ```bash npx playwright test auth.spec.ts ``` ### Run in headed mode (see browser) ```bash npx playwright test --headed ``` ### Run with debug mode ```bash npx playwright test --debug ``` ## Markdown Update Strategy ### Option 1: Create New Markdown Generate fresh markdown files with embedded screenshots. ### Option 2: Update Existing Markdown Update existing markdown files by: 1. Finding section headers 2. Inserting screenshot references after relevant steps 3. Using alt text to describe what's shown **Example Markdown Update**: ```markdown ### Step 1: Log In to Bookhoard 1. Open your browser and navigate to `http://localhost:8765` 2. Enter your username and password 3. Click the **Login** button ![Login page showing username and password fields](../images/user/login-page.webp) ``` ## Workflow When Ready ### Step 1: Verify Frontend Complete - Check all pages listed in "Prerequisites" are working - Confirm no TODO placeholders in templates ### Step 2: Seed Test Data ```bash # Add sample books, collections, devices # Create test admin and user accounts ``` ### Step 3: Set Up Playwright ```bash npm install -D @playwright/test npx playwright install chromium ``` ### Step 4: Create Environment File Create `.env.screenshots` with the test credentials ### Step 5: Run Screenshot Scripts ```bash # Run all screenshot workflows npx playwright test # Or run specific suites npx playwright test auth.spec.ts npx playwright test user-workflows.spec.ts ``` ### Step 6: Review and Adjust - Manually review screenshots - Retake any that need adjustment - Update markdown files if needed ## Customizing for Your Implementation When you're ready to run these, you may need to update: 1. **Selectors**: Update CSS selectors to match your actual HTML structure 2. **Routes**: Ensure all routes match your router configuration 3. **Wait conditions**: Adjust timeouts based on your app's performance 4. **Modal IDs**: Update modal selectors to match your implementation ## Tips for Better Screenshots 1. **Wait for animations**: Add `page.waitForTimeout(500)` after clicking to let animations complete 2. **Full page vs viewport**: Use `fullPage: true` for complete page screenshots 3. **Hide scrollbars**: Use `page.evaluate(() => document.body.style.overflow = 'hidden')` for cleaner screenshots 4. **Dark theme**: Most screenshots will use the default Tokyo Night theme 5. **Clean test data**: Use consistent test data for reproducible screenshots ## Estimated Time Investment | Task | Time | |------|------| | Install & configure Playwright | 15 min | | Seed test database | 30 min | | Write Playwright scripts | 2-3 hours | | Run screenshot automation | 10 min | | Review & retake screenshots | 30-60 min | | Update markdown files | 30-60 min | | **Total** | **4-6 hours** | ## Future Enhancements - **Visual Regression Testing**: Use Playwright to detect UI changes - **Multi-theme Screenshots**: Automatically capture all theme variations - **Multi-language Screenshots**: Capture screenshots in different locales - **Auto-update on Deploy**: Run screenshots as part of CI/CD on documentation updates ## References - [Playwright Documentation](https://playwright.dev/) - [Playwright Screenshots API](https://playwright.dev/docs/screenshots) - [Bookhoard Frontend Templates](templates/) - [Bookhoard API Documentation](docs/developer/api/) --- **Created by**: GLM (OpenCode) **Version**: 1.0 **Ready for Implementation**: When frontend development is complete