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
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:
- Navigate the running Bookhoard server
- Perform key user/admin workflows
- Capture screenshots at each step
- Save to
docs/images/organized by documentation section - 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:
- Finding section headers
- Inserting screenshot references after relevant steps
- 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

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:
- Selectors: Update CSS selectors to match your actual HTML structure
- Routes: Ensure all routes match your router configuration
- Wait conditions: Adjust timeouts based on your app's performance
- Modal IDs: Update modal selectors to match your implementation
Tips for Better Screenshots
- Wait for animations: Add
page.waitForTimeout(500)after clicking to let animations complete - Full page vs viewport: Use
fullPage: truefor complete page screenshots - Hide scrollbars: Use
page.evaluate(() => document.body.style.overflow = 'hidden')for cleaner screenshots - Dark theme: Most screenshots will use the default Tokyo Night theme
- 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
- Playwright Screenshots API
- Bookhoard Frontend Templates
- Bookhoard API Documentation
Created by: GLM (OpenCode)
Version: 1.0
Ready for Implementation: When frontend development is complete