Files
bookhoard/SCREENSHOT_AUTOMATION.md
T
john-okeefe 4d321528b2 docs: update comprehensive API documentation and project guides
This commit updates all documentation files throughout the project:

- Updated IMPLEMENTATION_PLAN.md with new implementation details
- Updated PROJECT_GUIDELINES.md with coding standards and practices
- Updated README.md with current project information
- Updated SCREENSHOT_AUTOMATION.md with new automation details
- Added TEST_DATA.md with test fixtures data
- Updated cover_image_serving_plan.md with static URL patterns

Documentation API updates:
- Updated API reference documentation for all endpoints including:
  - Authentication (login, logout, register, refresh_token)
  - Book matching (auto_link, bulk_link, link_book, search)
  - Collections (CRUD operations, shelf mappings, auto-assign rules)
  - Conflicts (bulk operations, resolve/dismiss)
  - Devices (registration, approval, shelf management)
  - Highlights (create, update, delete, get)
  - Kobo sync (bookmark, markup, initialization, sync)
  - KOReader sync (library, metadata, bookmarks, progress)
  - Libraries (CRUD, folders, media items, stats)
  - Media items (bulk operations, CRUD)
  - Notes (CRUD operations)
  - OPDS (acquisition, feeds, publication)
  - Progress (reading progress tracking)
  - Queue (device queue management)
  - Ratings (star ratings)
  - Scanner (watch mode, scan operations)
  - Sync protocols (Kobo, KOReader)
  - Users (profile, password, admin operations)
  - WebSocket protocols

- Updated user guides (admin, dashboard, settings, sync)
- Updated device setup guides (Kobo, KOReader)
- Updated developer guides (testing, contributing, operations)
- Updated scripts/README.md
2026-02-27 17:06:22 -05:00

25 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