|
| 1 | +# VRTs WordPress Plugin |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +WordPress plugin for visual regression testing. Connects to the VRTs Service API to manage test schedules, trigger screenshot comparisons, and display visual change alerts in the WordPress admin. |
| 6 | + |
| 7 | +**Tech Stack:** PHP 7.0+, WordPress APIs, @wordpress/scripts (React/webpack), SCSS |
| 8 | + |
| 9 | +## Bootstrap Sequence |
| 10 | + |
| 11 | +Entry point: `visual-regression-tests.php` |
| 12 | + |
| 13 | +1. Defines `VRTS_PLUGIN_FILE` and `VRTS_SERVICE_ENDPOINT` (env-overridable, default: `https://bleech-vrts-app.blee.ch/api/v1/`) |
| 14 | +2. Loads Composer autoloader (`vendor/autoload.php`) if present |
| 15 | +3. Loads custom autoloader (`includes/autoload.php`) |
| 16 | +4. Creates global `vrts()` function returning the `Plugin` singleton |
| 17 | +5. Calls `vrts()->setup('vrts', [...])` with three namespace-to-directory mappings that **auto-instantiate all classes** in: |
| 18 | + - `Vrts\Features\` -> `includes/features/` (20 feature classes) |
| 19 | + - `Vrts\Tables\` -> `includes/tables/` (3 table schema classes) |
| 20 | + - `Vrts\Rest_Api\` -> `includes/rest-api/` (4 REST controllers) |
| 21 | + |
| 22 | +## Custom Autoloader |
| 23 | + |
| 24 | +**File:** `includes/autoload.php` - WordPress-flavored PSR-4 via `spl_autoload_register`. |
| 25 | + |
| 26 | +- **Namespace prefix:** `Vrts\` |
| 27 | +- **Base directory:** `includes/` |
| 28 | +- **Mapping:** Namespace segments are lowercased, underscores become hyphens, class files get a `class-` prefix |
| 29 | + |
| 30 | +Examples: |
| 31 | +- `Vrts\Core\Plugin` -> `includes/core/class-plugin.php` |
| 32 | +- `Vrts\Models\Test` -> `includes/models/class-test.php` |
| 33 | +- `Vrts\Core\Utilities\Date_Time_Helpers` -> `includes/core/utilities/class-date-time-helpers.php` |
| 34 | + |
| 35 | +This is NOT standard PSR-4 - it follows WordPress naming conventions. |
| 36 | + |
| 37 | +## Namespace / Class Structure |
| 38 | + |
| 39 | +``` |
| 40 | +Vrts\ |
| 41 | +├── Core\ |
| 42 | +│ ├── Plugin # Singleton, orchestrates setup, component rendering |
| 43 | +│ ├── Traits\Singleton # Singleton pattern trait |
| 44 | +│ ├── Traits\Macroable # Dynamic method binding trait |
| 45 | +│ ├── Settings\Manager # WordPress Settings API abstraction |
| 46 | +│ └── Utilities\ # Url_Helpers, Image_Helpers, Date_Time_Helpers, |
| 47 | +│ # String_Helpers, Color_Helpers, Array_Helpers, |
| 48 | +│ # Sanitization, Async_Request, Background_Process |
| 49 | +├── Features\ # Auto-instantiated on setup() -- each hooks into WP |
| 50 | +│ ├── Admin # Menu registration, plugin action links |
| 51 | +│ ├── Admin_Columns # Custom columns in post list tables |
| 52 | +│ ├── Admin_Header # Shared admin page header component |
| 53 | +│ ├── Admin_Notices # Dismissible admin notifications |
| 54 | +│ ├── Bulk_Actions # Bulk "add test" from post list |
| 55 | +│ ├── Cron_Jobs # WP cron scheduling (hourly fetch, retry polling) |
| 56 | +│ ├── Deactivate # Plugin deactivation cleanup |
| 57 | +│ ├── Enqueue_Scripts # CSS/JS registration for admin + block editor |
| 58 | +│ ├── Install # Activation: table creation, service connection |
| 59 | +│ ├── Metaboxes # Classic editor sidebar (VRTs toggle per post) |
| 60 | +│ ├── Onboarding # Guided tour (driver.js) |
| 61 | +│ ├── Post_Update_Actions # Hooks into save_post, trash, slug changes |
| 62 | +│ ├── Service # HTTP client for Laravel API communication |
| 63 | +│ ├── Settings_Page # Click selectors, license, triggers, notifications |
| 64 | +│ ├── Subscription # Credit/tier management via WP options |
| 65 | +│ ├── Test_Runs_Page # Test runs admin page |
| 66 | +│ ├── Tests_Page # Tests admin page (add/run/bulk actions) |
| 67 | +│ ├── Tests # Additional test logic |
| 68 | +│ ├── Translations # i18n loading |
| 69 | +│ └── Upgrade_Page # Pricing/upgrade page |
| 70 | +├── Models\ # Data access layer (static methods, direct $wpdb) |
| 71 | +│ ├── Test # CRUD for vrts_tests, calculated status logic |
| 72 | +│ ├── Alert # CRUD for vrts_alerts, state management |
| 73 | +│ └── Test_Run # CRUD for vrts_test_runs, trigger metadata |
| 74 | +├── Services\ # Business logic layer |
| 75 | +│ ├── Test_Service # Create/delete/resume tests (local + remote) |
| 76 | +│ ├── Test_Run_Service # Process run webhooks, create alerts from comparisons |
| 77 | +│ ├── Alert_Service # Create alert records from comparison data |
| 78 | +│ ├── Email_Service # Send HTML test run notification emails |
| 79 | +│ └── Manual_Test_Service # Trigger manual test runs (subscription required) |
| 80 | +├── Tables\ # DB schema definitions (dbDelta) |
| 81 | +│ ├── Tests_Table |
| 82 | +│ ├── Alerts_Table |
| 83 | +│ └── Test_Runs_Table |
| 84 | +├── Rest_Api\ # REST endpoint controllers |
| 85 | +│ ├── Rest_Service_Controller # Webhook receiver (signature-verified) |
| 86 | +│ ├── Rest_Tests_Controller # Test CRUD via REST |
| 87 | +│ ├── Rest_Alerts_Controller # False positive + read status |
| 88 | +│ └── Rest_Test_Runs_Controller # Read status for runs |
| 89 | +└── List_Tables\ # WP_List_Table implementations |
| 90 | + ├── Tests_List_Table |
| 91 | + ├── Test_Runs_List_Table |
| 92 | + └── Test_Runs_Queue_List_Table |
| 93 | +``` |
| 94 | + |
| 95 | +## Custom Database Tables |
| 96 | + |
| 97 | +### `{prefix}vrts_tests` (DB_VERSION 1.5) |
| 98 | + |
| 99 | +| Column | Type | Purpose | |
| 100 | +|------------------------|---------------|-----------------------------------------| |
| 101 | +| `id` | bigint(20) PK | Auto-increment ID | |
| 102 | +| `status` | boolean | 0=paused, 1=active | |
| 103 | +| `post_id` | bigint(20) | WP post ID being tested | |
| 104 | +| `service_test_id` | varchar(40) | Remote test ID on Laravel service | |
| 105 | +| `base_screenshot_url` | varchar(2048) | S3 URL of baseline screenshot | |
| 106 | +| `base_screenshot_date` | datetime | When baseline was captured | |
| 107 | +| `last_comparison_date` | datetime | Last comparison timestamp | |
| 108 | +| `next_run_date` | datetime | Next scheduled run | |
| 109 | +| `is_running` | boolean | Whether comparison is in progress | |
| 110 | +| `hide_css_selectors` | longtext | CSS selectors to hide during screenshot | |
| 111 | + |
| 112 | +**Calculated statuses** (in `Test` model): `disconnected`, `no-credit-left`, `post-not-published`, `waiting`, `running`, `scheduled`, `has-alert`, `passed` |
| 113 | + |
| 114 | +### `{prefix}vrts_alerts` (DB_VERSION 1.2) |
| 115 | + |
| 116 | +| Column | Type | Purpose | |
| 117 | +|---------------------------------|---------------|-------------------------------| |
| 118 | +| `id` | bigint(20) PK | Auto-increment ID | |
| 119 | +| `title` | text | "Alert #N" | |
| 120 | +| `post_id` | bigint(20) | WP post that had changes | |
| 121 | +| `test_run_id` | bigint(20) | FK to test_runs table | |
| 122 | +| `screenshot_test_id` | varchar(40) | Remote test ID | |
| 123 | +| `target_screenshot_url` | varchar(2048) | New screenshot URL | |
| 124 | +| `target_screenshot_finish_date` | datetime | When new screenshot was taken | |
| 125 | +| `base_screenshot_url` | varchar(2048) | Baseline screenshot URL | |
| 126 | +| `base_screenshot_finish_date` | datetime | When baseline was taken | |
| 127 | +| `comparison_screenshot_url` | varchar(2048) | Diff image URL | |
| 128 | +| `comparison_id` | varchar(40) | Remote comparison ID | |
| 129 | +| `differences` | int(4) | Pixel diff count | |
| 130 | +| `alert_state` | tinyint | 0=Open, 1=Archived | |
| 131 | +| `is_false_positive` | tinyint | 1=marked as false positive | |
| 132 | +| `meta` | text | Serialized metadata | |
| 133 | + |
| 134 | +### `{prefix}vrts_test_runs` (DB_VERSION 1.1) |
| 135 | + |
| 136 | +| Column | Type | Purpose | |
| 137 | +|-----------------------|---------------|--------------------------------------------------| |
| 138 | +| `id` | bigint(20) PK | Auto-increment ID | |
| 139 | +| `service_test_run_id` | varchar(40) | Remote run ID | |
| 140 | +| `tests` | text | Serialized array of test info | |
| 141 | +| `trigger` | varchar(20) | `manual`, `scheduled`, `api`, `update`, `legacy` | |
| 142 | +| `trigger_notes` | text | Human-readable trigger description | |
| 143 | +| `trigger_meta` | text | Serialized metadata (user_id, update info) | |
| 144 | +| `started_at` | datetime | Run start time | |
| 145 | +| `scheduled_at` | datetime | Scheduled time | |
| 146 | +| `finished_at` | datetime | Completion time | |
| 147 | + |
| 148 | +## Service Communication |
| 149 | + |
| 150 | +All API calls go through `Vrts\Features\Service` using `wp_remote_post()` / `wp_remote_get()`. |
| 151 | + |
| 152 | +**Authentication:** Bearer token (`vrts_project_token` WP option), custom User-Agent: `VRTs/{version};{wp-user-agent}` |
| 153 | + |
| 154 | +### Outbound (Plugin -> Laravel Service) |
| 155 | + |
| 156 | +| Route | Method | Purpose | |
| 157 | +|------------------------------------|--------|------------------------------------| |
| 158 | +| `sites` | POST | Register site (initial connection) | |
| 159 | +| `sites/{id}` | PUT | Update site settings | |
| 160 | +| `sites/{id}` | DELETE | Disconnect site | |
| 161 | +| `sites/{id}/resume` | POST | Resume all tests | |
| 162 | +| `sites/{id}/trigger` | POST | Trigger manual test run | |
| 163 | +| `sites/{id}/updates` | GET | Poll for test/run updates | |
| 164 | +| `sites/{id}/runs` | GET | Fetch specific test runs | |
| 165 | +| `sites/{id}/secret` | POST | Create webhook signing secret | |
| 166 | +| `sites/{id}/register` | POST | Register license key | |
| 167 | +| `sites/{id}/unregister` | POST | Remove license key | |
| 168 | +| `tests` | POST | Create test(s) | |
| 169 | +| `tests/{id}` | PUT | Update test (URL, hide selectors) | |
| 170 | +| `tests/{id}` | DELETE | Delete test | |
| 171 | +| `tests/{id}/resume` | POST | Resume individual test | |
| 172 | +| `tests/{id}/false-positives` | POST | Mark comparison as false positive | |
| 173 | +| `tests/{id}/false-positives/{cid}` | DELETE | Unmark false positive | |
| 174 | + |
| 175 | +### Inbound Webhooks (Laravel Service -> Plugin) |
| 176 | + |
| 177 | +Endpoint: `wp-json/vrts/v1/service` (+ `admin-ajax.php` fallback for WPML compat) |
| 178 | + |
| 179 | +Signature verification: HMAC-SHA256 of JSON payload using `vrts_project_secret`. |
| 180 | + |
| 181 | +| Action | Purpose | |
| 182 | +|------------------------|----------------------------------------------------| |
| 183 | +| `test_updated` | Test screenshot/comparison ready | |
| 184 | +| `run_updated` | Test run completed, creates alerts for diffs > 1px | |
| 185 | +| `run_deleted` | Run deleted remotely | |
| 186 | +| `subscription_changed` | Subscription tier changed | |
| 187 | + |
| 188 | +### Connection Flow |
| 189 | + |
| 190 | +1. On activation, `Service::connect_service()` POSTs to `sites` with `create_token`, `rest_url`, `admin_ajax_url` |
| 191 | +2. Service responds with `id`, `token`, `secret`, credit info |
| 192 | +3. Plugin stores as WP options: `vrts_project_id`, `vrts_project_token`, `vrts_project_secret` |
| 193 | +4. Homepage is auto-added as the first test |
| 194 | + |
| 195 | +## REST API Endpoints |
| 196 | + |
| 197 | +All registered under `wp-json/vrts/v1/`: |
| 198 | + |
| 199 | +| Endpoint | Method | Auth | Purpose | |
| 200 | +|-------------------------------|-------------|------------------|-------------------------------| |
| 201 | +| `/service` | POST | Signature | Webhook receiver from service | |
| 202 | +| `/tests` | GET | Public | Get remaining/total credits | |
| 203 | +| `/tests/post/{post_id}` | GET | Public | Get test data for a post | |
| 204 | +| `/tests/post/{post_id}` | POST | `manage_options` | Create test for a post | |
| 205 | +| `/tests/post/{post_id}` | DELETE | `manage_options` | Delete test for a post | |
| 206 | +| `/tests/post/{post_id}` | PUT/PATCH | `manage_options` | Update test (CSS selectors) | |
| 207 | +| `/alerts/{id}/false-positive` | POST/DELETE | `manage_options` | Toggle false positive | |
| 208 | +| `/alerts/{id}/read-status` | POST/DELETE | `manage_options` | Toggle read status | |
| 209 | +| `/test-runs/{id}/read-status` | POST/DELETE | `manage_options` | Toggle run read status | |
| 210 | + |
| 211 | +## WordPress Hooks |
| 212 | + |
| 213 | +### Activation / Deactivation |
| 214 | +- `register_activation_hook` -> Creates DB tables, connects to service, adds homepage test |
| 215 | +- `register_deactivation_hook` -> Deletes unfinished test runs, disconnects from service |
| 216 | +- `upgrader_process_complete` -> Reinstalls tables, reconnects on plugin update |
| 217 | + |
| 218 | +### Post Integration |
| 219 | +- `save_post` -> Saves test toggle state from classic editor |
| 220 | +- `rest_after_insert_{post_type}` -> Updates hide CSS selectors from block editor |
| 221 | +- `wp_after_insert_post` -> Resumes test (retakes screenshot) after post update |
| 222 | +- `trashed_post` -> Deletes test, archives alerts when post is trashed |
| 223 | +- `transition_post_status` -> Creates/deletes remote test on publish/unpublish |
| 224 | +- `post_updated` -> Updates test URL on service when slug changes |
| 225 | + |
| 226 | +### Cron Jobs |
| 227 | +- `vrts_fetch_updates_cron` (hourly) -- Polls service for all test/run updates |
| 228 | +- `vrts_fetch_test_updates` (single-fire, exponential backoff, 10 retries at `20s * 2 * try`) -- Polls after creating a new test until `base_screenshot_date` is set |
| 229 | +- `vrts_fetch_test_run_updates` (same backoff pattern) -- Polls after triggering a run until `finished_at` is set |
| 230 | + |
| 231 | +## WordPress Options |
| 232 | + |
| 233 | +| Option | Purpose | |
| 234 | +|-------------------------|-----------------------------| |
| 235 | +| `vrts_project_id` | Service project UUID | |
| 236 | +| `vrts_project_token` | API authentication token | |
| 237 | +| `vrts_project_secret` | Webhook HMAC signing secret | |
| 238 | +| `vrts_remaining_tests` | Credits remaining | |
| 239 | +| `vrts_total_tests` | Total test quota | |
| 240 | +| `vrts_has_subscription` | Premium subscription flag | |
| 241 | +| `vrts_tier_id` | Subscription tier | |
| 242 | + |
| 243 | +## Frontend / JavaScript |
| 244 | + |
| 245 | +### Build System |
| 246 | + |
| 247 | +Webpack via `@wordpress/scripts` with two entry points: |
| 248 | +- `assets/admin.js` -> `build/admin.js` (admin pages) |
| 249 | +- `assets/editor.js` -> `build/editor.js` (Gutenberg block editor) |
| 250 | + |
| 251 | +### Admin JS |
| 252 | +- Auto-imports all `script.js` from `components/` directories |
| 253 | +- Auto-imports all `_style.scss` from `components/` |
| 254 | +- `components/` contains PHP template components rendered via `vrts()->component()` with co-located JS/SCSS |
| 255 | + |
| 256 | +### Editor JS |
| 257 | +- Uses `@wordpress/plugins` to register `PluginDocumentSettingPanel` + `PluginSidebar` |
| 258 | +- Renders React `<Metabox />` component (`editor/components/metabox/`) |
| 259 | + |
| 260 | +### Key JS Libraries |
| 261 | +- `driver.js` -- Guided onboarding tours |
| 262 | +- `a11y-dialog` -- Accessible modals (comparison dialog) |
| 263 | +- `lottie-web` -- Animations |
| 264 | +- `dompurify` -- HTML sanitization |
| 265 | +- `iframe-resizer` -- Resizable iframes (upgrade page) |
| 266 | + |
| 267 | +### Localized Data |
| 268 | +- `vrts_admin_vars`: `rest_url`, `rest_nonce`, `pluginUrl`, `currentUserId`, `onboarding` |
| 269 | +- `vrts_editor_vars`: `plugin_name`, `rest_url`, `has_post_alert`, `base_screenshot_url`, `remaining_tests`, `total_tests`, `test_status`, `test_settings`, etc. |
| 270 | + |
| 271 | +## Admin Pages |
| 272 | + |
| 273 | +Registered under the main "VRTs" menu (position 80): |
| 274 | + |
| 275 | +| Slug | Class | Purpose | |
| 276 | +|-----------------|------------------|---------------------------------------------------| |
| 277 | +| `vrts` | `Tests_Page` | Test list with add/run/bulk actions | |
| 278 | +| `vrts-runs` | `Test_Runs_Page` | Test run history, alert list, comparison modal | |
| 279 | +| `vrts-settings` | `Settings_Page` | Click selectors, license, triggers, notifications | |
| 280 | +| `vrts-upgrade` | `Upgrade_Page` | Pricing/upgrade (iframe to external) | |
| 281 | + |
| 282 | +## Linting |
| 283 | + |
| 284 | +```bash |
| 285 | +npm run lint # Run all linters |
| 286 | +npm run lint:js # ESLint (@wordpress/eslint-plugin) |
| 287 | +npm run lint:css # Stylelint (@wordpress/stylelint-config) |
| 288 | +npm run lint:php # PHPCS (WordPress Coding Standards) |
| 289 | + |
| 290 | +npm run lint-fix # Auto-fix all |
| 291 | +npm run lint-fix:js # Auto-fix JS |
| 292 | +npm run lint-fix:css # Auto-fix CSS |
| 293 | +npm run lint-fix:php # Auto-fix PHP (phpcbf) |
| 294 | +``` |
| 295 | + |
| 296 | +Always try `lint-fix` before fixing manually. |
| 297 | + |
| 298 | +## Code Style |
| 299 | + |
| 300 | +- **PHP:** WordPress Coding Standards (WPCS), `Vrts\` namespace, DocBlocks required |
| 301 | +- **JS:** ESLint with `@wordpress/eslint-plugin`, ES6+, React components |
| 302 | +- **CSS/SCSS:** Stylelint with `@wordpress/stylelint-config`, BEM naming, `.vrts-` prefix |
0 commit comments