Files
bookhoard/SCREENSHOT_AUTOMATION.md
T
john-okeefe d9c6be1429 docs: rename DEVELOPMENT.md to development.md and update links
- Rename docs/contributing/DEVELOPMENT.md to development.md (lowercase)
- Update all references from DEVELOPMENT.md to Development.md (titlecase links)
- Update docs/contributing/contributing.md
- Update docs/index.md
2026-02-03 13:54:11 -05:00

23 KiB

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

# 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:

# 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

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

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

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

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

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

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

npx playwright test

Run specific test file

npx playwright test auth.spec.ts

Run in headed mode (see browser)

npx playwright test --headed

Run with debug mode

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:

### 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

# Add sample books, collections, devices
# Create test admin and user accounts

Step 3: Set Up Playwright

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

# 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


Created by: GLM (OpenCode)
Version: 1.0
Ready for Implementation: When frontend development is complete