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