| 1 |
# Imagify — End-to-End Testing |
| 2 |
|
| 3 |
This document is the canonical reference for the Imagify Playwright E2E test suite. **Read this before writing any new tests.** |
| 4 |
|
| 5 |
--- |
| 6 |
|
| 7 |
## Architecture overview |
| 8 |
|
| 9 |
| Layer | Path | Purpose | |
| 10 |
|-------|------|---------| |
| 11 |
| Config | `Tests/e2e/playwright.config.ts` | Playwright base config (baseURL, reporters, timeouts) | |
| 12 |
| Fixtures | `Tests/e2e/fixtures/` | Shared helpers: login, WP-CLI wrapper, API key guard | |
| 13 |
| Page objects | `Tests/e2e/pages/` | One class per major admin surface | |
| 14 |
| Specs | `Tests/e2e/specs/` | Test files, one per feature area | |
| 15 |
| Dev scripts | `bin/dev-up.sh`, `bin/dev-down.sh`, `bin/dev-seed.sh` | Local environment lifecycle | |
| 16 |
| CI | `.github/workflows/e2e.yml` | Automated runs on pull requests | |
| 17 |
|
| 18 |
|
| 19 |
All tests run against a local WordPress environment managed by `@wordpress/env` (Docker). The environment maps the plugin root directly into the container at `wp-content/plugins/imagify`. |
| 20 |
|
| 21 |
--- |
| 22 |
|
| 23 |
## Admin URLs |
| 24 |
|
| 25 |
| Surface | URL | |
| 26 |
|---------|-----| |
| 27 |
| Settings | `/wp-admin/options-general.php?page=imagify` | |
| 28 |
| Bulk optimization | `/wp-admin/upload.php?page=imagify-bulk-optimization` | |
| 29 |
| Custom folders | `/wp-admin/upload.php?page=imagify-files` | |
| 30 |
| Media library (list) | `/wp-admin/upload.php?mode=list` | |
| 31 |
| Plugins list | `/wp-admin/plugins.php` | |
| 32 |
|
| 33 |
--- |
| 34 |
|
| 35 |
## Running tests locally |
| 36 |
|
| 37 |
### One-time setup |
| 38 |
|
| 39 |
```bash |
| 40 |
# From the repository root |
| 41 |
bash bin/dev-up.sh # Start wp-env + activate plugin + seed test data |
| 42 |
cd Tests/e2e |
| 43 |
npm install |
| 44 |
npx playwright install chromium |
| 45 |
``` |
| 46 |
|
| 47 |
### Running |
| 48 |
|
| 49 |
```bash |
| 50 |
cd Tests/e2e |
| 51 |
npm test # Headless, list reporter |
| 52 |
npm run test:headed # With browser UI visible |
| 53 |
npm run test:ui # Playwright interactive UI mode |
| 54 |
npm run report # Open the last HTML report |
| 55 |
``` |
| 56 |
|
| 57 |
### Environment variables |
| 58 |
|
| 59 |
| Variable | Default | Purpose | |
| 60 |
|----------|---------|---------| |
| 61 |
| `IMAGIFY_BASE_URL` | `http://localhost:8888` | Override the WordPress base URL | |
| 62 |
| `IMAGIFY_ADMIN_USER` | `admin` | WP admin username | |
| 63 |
| `IMAGIFY_ADMIN_PASS` | `password` | WP admin password | |
| 64 |
| `IMAGIFY_TESTS_API_KEY` | _(unset)_ | Real Imagify API key — required for optimization tests | |
| 65 |
|
| 66 |
Set `IMAGIFY_TESTS_API_KEY` to run tests that call the Imagify API. Without it, those tests are automatically skipped. |
| 67 |
|
| 68 |
--- |
| 69 |
|
| 70 |
## Page objects |
| 71 |
|
| 72 |
Each major admin surface has a Page Object class. Use them instead of raw selectors — they centralize selector maintenance. |
| 73 |
|
| 74 |
### `SettingsPage` (`Tests/e2e/pages/settings.ts`) |
| 75 |
|
| 76 |
```typescript |
| 77 |
import { SettingsPage } from '../pages/settings'; |
| 78 |
const settings = new SettingsPage( page ); |
| 79 |
await settings.goto(); |
| 80 |
await settings.setApiKey( process.env.IMAGIFY_TESTS_API_KEY! ); |
| 81 |
``` |
| 82 |
|
| 83 |
Key members: `apiKeyInput`, `saveButton`, `successNotice`, `goto()`, `setApiKey()`, `getApiKey()`, `expectNoFatalError()`. |
| 84 |
|
| 85 |
### `BulkOptimizationPage` (`Tests/e2e/pages/bulk-optimization.ts`) |
| 86 |
|
| 87 |
```typescript |
| 88 |
import { BulkOptimizationPage } from '../pages/bulk-optimization'; |
| 89 |
const bulk = new BulkOptimizationPage( page ); |
| 90 |
await bulk.goto(); |
| 91 |
await expect( bulk.optimizeButton ).toBeVisible(); |
| 92 |
``` |
| 93 |
|
| 94 |
Key members: `optimizeButton`, `progressBar`, `statsTable`, `goto()`, `expectNoFatalError()`. |
| 95 |
|
| 96 |
### `MediaLibraryPage` (`Tests/e2e/pages/media-library.ts`) |
| 97 |
|
| 98 |
```typescript |
| 99 |
import { MediaLibraryPage } from '../pages/media-library'; |
| 100 |
const library = new MediaLibraryPage( page ); |
| 101 |
await library.goto(); |
| 102 |
await expect( library.imagifyColumn ).toBeVisible(); |
| 103 |
``` |
| 104 |
|
| 105 |
Key members: `imagifyColumn`, `goto()`, `hasImagifyColumn()`, `getFirstAttachmentStatus()`, `expectNoFatalError()`. |
| 106 |
|
| 107 |
--- |
| 108 |
|
| 109 |
## Fixtures |
| 110 |
|
| 111 |
### `loginAsAdmin( page )` — `Tests/e2e/fixtures/auth.ts` |
| 112 |
|
| 113 |
Logs in as the WordPress administrator. Idempotent: skips the login form if a session cookie is already active. |
| 114 |
|
| 115 |
```typescript |
| 116 |
import { loginAsAdmin } from '../fixtures/auth'; |
| 117 |
test.beforeEach( async ( { page } ) => { |
| 118 |
await loginAsAdmin( page ); |
| 119 |
} ); |
| 120 |
``` |
| 121 |
|
| 122 |
### `wpCli( command )` — `Tests/e2e/fixtures/wp-cli.ts` |
| 123 |
|
| 124 |
Runs a WP-CLI command inside the wp-env `cli` container. Returns stdout as a string. |
| 125 |
|
| 126 |
```typescript |
| 127 |
import { wpCli } from '../fixtures/wp-cli'; |
| 128 |
const value = wpCli( 'option get imagify_settings --format=json' ); |
| 129 |
``` |
| 130 |
|
| 131 |
### `hasApiKey()` — `Tests/e2e/fixtures/wp-cli.ts` |
| 132 |
|
| 133 |
Returns `true` if `IMAGIFY_TESTS_API_KEY` is set. Use with `test.skip` to gate API-dependent tests: |
| 134 |
|
| 135 |
```typescript |
| 136 |
import { hasApiKey } from '../fixtures/wp-cli'; |
| 137 |
test.skip( ! hasApiKey(), 'IMAGIFY_TESTS_API_KEY not set' ); |
| 138 |
``` |
| 139 |
|
| 140 |
--- |
| 141 |
|
| 142 |
## Writing new tests |
| 143 |
|
| 144 |
### Determinism rules |
| 145 |
|
| 146 |
- **Never** use `setTimeout` or `waitForTimeout`. Use web-first assertions (`toBeVisible`, `toHaveValue`, `toHaveURL`, etc.) which have built-in retry. |
| 147 |
- **Never** assert on volatile values (timestamps, auto-increment IDs) without normalization. |
| 148 |
- **Always** seed state before tests that depend on specific DB content. |
| 149 |
|
| 150 |
### API key guard |
| 151 |
|
| 152 |
Any test that triggers image optimization through the Imagify API must be guarded: |
| 153 |
|
| 154 |
```typescript |
| 155 |
test( 'Optimizing an image succeeds', async ( { page } ) => { |
| 156 |
test.skip( ! process.env.IMAGIFY_TESTS_API_KEY, 'IMAGIFY_TESTS_API_KEY not set — skipping live optimization test' ); |
| 157 |
// ... test body |
| 158 |
} ); |
| 159 |
``` |
| 160 |
|
| 161 |
### Adding a new page object |
| 162 |
|
| 163 |
Create `Tests/e2e/pages/<feature>.ts`: |
| 164 |
|
| 165 |
```typescript |
| 166 |
import { Page, Locator, expect } from '@playwright/test'; |
| 167 |
|
| 168 |
export class FeaturePage { |
| 169 |
readonly page: Page; |
| 170 |
// locators... |
| 171 |
|
| 172 |
constructor( page: Page ) { |
| 173 |
this.page = page; |
| 174 |
// initialize locators |
| 175 |
} |
| 176 |
|
| 177 |
async goto(): Promise<void> { |
| 178 |
await this.page.goto( '/wp-admin/...' ); |
| 179 |
await this.page.waitForLoadState( 'networkidle' ); |
| 180 |
} |
| 181 |
|
| 182 |
async expectNoFatalError(): Promise<void> { |
| 183 |
await expect( this.page.locator( '.wp-die-message, #error-page' ) ).toHaveCount( 0 ); |
| 184 |
} |
| 185 |
} |
| 186 |
``` |
| 187 |
|
| 188 |
### Adding a new spec |
| 189 |
|
| 190 |
Create `Tests/e2e/specs/<feature>.spec.ts`. Follow this template: |
| 191 |
|
| 192 |
```typescript |
| 193 |
import { test, expect } from '@playwright/test'; |
| 194 |
import { loginAsAdmin } from '../fixtures/auth'; |
| 195 |
import { FeaturePage } from '../pages/<feature>'; |
| 196 |
|
| 197 |
test.describe( 'Feature area', () => { |
| 198 |
test.beforeEach( async ( { page } ) => { |
| 199 |
await loginAsAdmin( page ); |
| 200 |
} ); |
| 201 |
|
| 202 |
test( 'Something works', async ( { page } ) => { |
| 203 |
const featurePage = new FeaturePage( page ); |
| 204 |
await featurePage.goto(); |
| 205 |
await featurePage.expectNoFatalError(); |
| 206 |
// assertions... |
| 207 |
} ); |
| 208 |
} ); |
| 209 |
``` |
| 210 |
|
| 211 |
--- |
| 212 |
|
| 213 |
## CI |
| 214 |
|
| 215 |
The E2E workflow (`.github/workflows/e2e.yml`) runs on pull requests that touch: |
| 216 |
- `classes/**`, `inc/**`, `assets/**`, `views/**` — PHP/JS plugin code |
| 217 |
- `Tests/e2e/**` — test files themselves |
| 218 |
- `.wp-env.json`, `bin/dev-up.sh`, `bin/dev-seed.sh` — environment config |
| 219 |
- `composer.json`, `.github/workflows/e2e.yml` |
| 220 |
|
| 221 |
The `IMAGIFY_TESTS_API_KEY` secret must be set in the GitHub repository settings for API-dependent tests to run. Without it, those tests are automatically skipped and the suite still passes. |
| 222 |
|
| 223 |
--- |
| 224 |
|
| 225 |
## Known coverage gaps |
| 226 |
|
| 227 |
The following areas are not yet covered by automated E2E tests (good places to contribute): |
| 228 |
|
| 229 |
- Custom folders optimization workflow |
| 230 |
- WP-CLI bulk-optimize and bulk-restore commands (use `wpCli()` fixture) |
| 231 |
- Admin notices (API quota reached, outdated plugin) |
| 232 |
- Network mode (proxy URL, login/password settings) |
| 233 |
- Multisite network activation |
| 234 |
|