claude-plugin-wordpress-manager 1.5.0 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +97 -0
- package/README.md +27 -13
- package/agents/wp-accessibility-auditor.md +206 -0
- package/agents/wp-content-strategist.md +18 -0
- package/agents/wp-deployment-engineer.md +34 -2
- package/agents/wp-performance-optimizer.md +12 -0
- package/agents/wp-security-auditor.md +20 -0
- package/agents/wp-security-hardener.md +266 -0
- package/agents/wp-site-manager.md +14 -0
- package/agents/wp-test-engineer.md +207 -0
- package/docs/guides/INDEX.md +46 -0
- package/docs/guides/wp-blog.md +590 -0
- package/docs/guides/wp-design-system.md +976 -0
- package/docs/guides/wp-ecommerce.md +786 -0
- package/docs/guides/wp-landing-page.md +762 -0
- package/docs/guides/wp-portfolio.md +713 -0
- package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
- package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
- package/package.json +2 -2
- package/skills/wordpress-router/references/decision-tree.md +12 -2
- package/skills/wp-accessibility/SKILL.md +170 -0
- package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
- package/skills/wp-accessibility/references/a11y-testing.md +222 -0
- package/skills/wp-accessibility/references/block-a11y.md +247 -0
- package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
- package/skills/wp-accessibility/references/media-a11y.md +254 -0
- package/skills/wp-accessibility/references/theme-a11y.md +309 -0
- package/skills/wp-audit/SKILL.md +4 -0
- package/skills/wp-block-development/SKILL.md +5 -0
- package/skills/wp-block-themes/SKILL.md +4 -0
- package/skills/wp-e2e-testing/SKILL.md +186 -0
- package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
- package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
- package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
- package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
- package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
- package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
- package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
- package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
- package/skills/wp-headless/SKILL.md +168 -0
- package/skills/wp-headless/references/api-layer-choice.md +160 -0
- package/skills/wp-headless/references/cors-config.md +245 -0
- package/skills/wp-headless/references/frontend-integration.md +331 -0
- package/skills/wp-headless/references/headless-auth.md +286 -0
- package/skills/wp-headless/references/webhooks.md +277 -0
- package/skills/wp-headless/references/wpgraphql.md +331 -0
- package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
- package/skills/wp-i18n/SKILL.md +170 -0
- package/skills/wp-i18n/references/js-i18n.md +201 -0
- package/skills/wp-i18n/references/multilingual-setup.md +219 -0
- package/skills/wp-i18n/references/php-i18n.md +196 -0
- package/skills/wp-i18n/references/rtl-support.md +206 -0
- package/skills/wp-i18n/references/translation-workflow.md +178 -0
- package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
- package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
- package/skills/wp-interactivity-api/SKILL.md +4 -0
- package/skills/wp-plugin-development/SKILL.md +6 -0
- package/skills/wp-rest-api/SKILL.md +4 -0
- package/skills/wp-security/SKILL.md +179 -0
- package/skills/wp-security/references/api-restriction.md +147 -0
- package/skills/wp-security/references/authentication-hardening.md +105 -0
- package/skills/wp-security/references/filesystem-hardening.md +105 -0
- package/skills/wp-security/references/http-headers.md +105 -0
- package/skills/wp-security/references/incident-response.md +144 -0
- package/skills/wp-security/references/user-capabilities.md +115 -0
- package/skills/wp-security/references/wp-config-security.md +129 -0
- package/skills/wp-security/scripts/security_inspect.mjs +393 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Jest for WordPress JavaScript Testing
|
|
2
|
+
|
|
3
|
+
Use this file when writing or configuring Jest unit tests for WordPress JavaScript code.
|
|
4
|
+
|
|
5
|
+
## Setup with @wordpress/scripts
|
|
6
|
+
|
|
7
|
+
`@wordpress/scripts` includes a preconfigured Jest setup. No separate Jest config needed:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -D @wordpress/scripts
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Add to `package.json`:
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test:unit": "wp-scripts test-unit-js",
|
|
18
|
+
"test:unit:watch": "wp-scripts test-unit-js --watch"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Test file location
|
|
24
|
+
|
|
25
|
+
By convention, tests go in:
|
|
26
|
+
- `src/component/__tests__/component.test.js` — colocated with source
|
|
27
|
+
- `src/component/test/component.test.js` — `test/` subdirectory
|
|
28
|
+
- `tests/js/` — separate test directory (configure in `jest.config.js`)
|
|
29
|
+
|
|
30
|
+
## Writing tests
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { render, screen } from '@testing-library/react';
|
|
34
|
+
import MyBlock from '../edit';
|
|
35
|
+
|
|
36
|
+
describe('MyBlock', () => {
|
|
37
|
+
it('renders the block content', () => {
|
|
38
|
+
const attributes = { content: 'Hello World' };
|
|
39
|
+
render(<MyBlock attributes={attributes} />);
|
|
40
|
+
expect(screen.getByText('Hello World')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Mocking WordPress globals
|
|
46
|
+
|
|
47
|
+
Create `tests/js/setup-globals.js`:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// Mock wp global
|
|
51
|
+
global.wp = {
|
|
52
|
+
element: require('@wordpress/element'),
|
|
53
|
+
data: require('@wordpress/data'),
|
|
54
|
+
i18n: { __: (str) => str, _n: (s, p, n) => (n === 1 ? s : p) },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Mock jQuery if needed
|
|
58
|
+
global.jQuery = jest.fn(() => ({
|
|
59
|
+
on: jest.fn(),
|
|
60
|
+
off: jest.fn(),
|
|
61
|
+
trigger: jest.fn(),
|
|
62
|
+
ready: jest.fn((fn) => fn()),
|
|
63
|
+
}));
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Reference in `jest.config.js`:
|
|
67
|
+
```js
|
|
68
|
+
module.exports = {
|
|
69
|
+
...require('@wordpress/scripts/config/jest-unit.config'),
|
|
70
|
+
setupFiles: ['./tests/js/setup-globals.js'],
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Testing @wordpress/data stores
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
import { createRegistry } from '@wordpress/data';
|
|
78
|
+
import { store as myStore } from '../store';
|
|
79
|
+
|
|
80
|
+
describe('My Store', () => {
|
|
81
|
+
let registry;
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
registry = createRegistry();
|
|
85
|
+
registry.register(myStore);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('returns default state', () => {
|
|
89
|
+
const result = registry.select(myStore).getItems();
|
|
90
|
+
expect(result).toEqual([]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('adds an item via action', () => {
|
|
94
|
+
registry.dispatch(myStore).addItem({ id: 1, title: 'Test' });
|
|
95
|
+
const result = registry.select(myStore).getItems();
|
|
96
|
+
expect(result).toHaveLength(1);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Running
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx wp-scripts test-unit-js # Run all tests
|
|
105
|
+
npx wp-scripts test-unit-js --watch # Watch mode
|
|
106
|
+
npx wp-scripts test-unit-js --coverage # With coverage report
|
|
107
|
+
npx wp-scripts test-unit-js -- path/to/test # Run specific test
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Common issues
|
|
111
|
+
|
|
112
|
+
- **"Cannot find module @wordpress/..."**: ensure the package is in dependencies; `@wordpress/scripts` provides many but not all
|
|
113
|
+
- **JSX transform errors**: `@wordpress/scripts` handles this; don't add a separate Babel config unless needed
|
|
114
|
+
- **Snapshot mismatches after update**: review changes, then `npx wp-scripts test-unit-js --updateSnapshot`
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# PHPUnit for WordPress
|
|
2
|
+
|
|
3
|
+
Use this file when writing or configuring PHPUnit tests for WordPress PHP code.
|
|
4
|
+
|
|
5
|
+
## Scaffolding with WP-CLI
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
wp scaffold plugin-tests my-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This creates:
|
|
12
|
+
- `phpunit.xml.dist` — PHPUnit configuration
|
|
13
|
+
- `tests/bootstrap.php` — WordPress test library loader
|
|
14
|
+
- `tests/test-sample.php` — Example test
|
|
15
|
+
- `bin/install-wp-tests.sh` — Script to install WP test library
|
|
16
|
+
|
|
17
|
+
## Running with wp-env (recommended)
|
|
18
|
+
|
|
19
|
+
wp-env includes the WordPress test suite out of the box:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx wp-env run tests-cli --env-cwd=wp-content/plugins/my-plugin phpunit
|
|
23
|
+
npx wp-env run tests-cli --env-cwd=wp-content/plugins/my-plugin phpunit -- --filter=test_activation
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Writing tests
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
class Test_My_Plugin extends WP_UnitTestCase {
|
|
30
|
+
|
|
31
|
+
public function set_up(): void {
|
|
32
|
+
parent::set_up();
|
|
33
|
+
// Runs before each test
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public function tear_down(): void {
|
|
37
|
+
// Runs after each test
|
|
38
|
+
parent::tear_down();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public function test_plugin_activates(): void {
|
|
42
|
+
$this->assertTrue( is_plugin_active( 'my-plugin/my-plugin.php' ) );
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public function test_custom_post_type_registered(): void {
|
|
46
|
+
$this->assertTrue( post_type_exists( 'book' ) );
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public function test_hook_fires(): void {
|
|
50
|
+
$fired = false;
|
|
51
|
+
add_action( 'my_plugin_init', function() use ( &$fired ) {
|
|
52
|
+
$fired = true;
|
|
53
|
+
});
|
|
54
|
+
do_action( 'my_plugin_init' );
|
|
55
|
+
$this->assertTrue( $fired );
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public function test_filter_modifies_value(): void {
|
|
59
|
+
add_filter( 'my_plugin_title', function( $title ) {
|
|
60
|
+
return 'Modified: ' . $title;
|
|
61
|
+
});
|
|
62
|
+
$result = apply_filters( 'my_plugin_title', 'Original' );
|
|
63
|
+
$this->assertSame( 'Modified: Original', $result );
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Testing REST API endpoints
|
|
69
|
+
|
|
70
|
+
```php
|
|
71
|
+
class Test_REST_API extends WP_UnitTestCase {
|
|
72
|
+
|
|
73
|
+
public function set_up(): void {
|
|
74
|
+
parent::set_up();
|
|
75
|
+
do_action( 'rest_api_init' );
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public function test_endpoint_registered(): void {
|
|
79
|
+
$routes = rest_get_server()->get_routes();
|
|
80
|
+
$this->assertArrayHasKey( '/my-plugin/v1/items', $routes );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public function test_get_items(): void {
|
|
84
|
+
$request = new WP_REST_Request( 'GET', '/my-plugin/v1/items' );
|
|
85
|
+
$response = rest_get_server()->dispatch( $request );
|
|
86
|
+
$this->assertSame( 200, $response->get_status() );
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public function test_unauthorized_access(): void {
|
|
90
|
+
$request = new WP_REST_Request( 'POST', '/my-plugin/v1/items' );
|
|
91
|
+
$response = rest_get_server()->dispatch( $request );
|
|
92
|
+
$this->assertSame( 401, $response->get_status() );
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Test factories
|
|
98
|
+
|
|
99
|
+
```php
|
|
100
|
+
public function test_post_with_meta(): void {
|
|
101
|
+
$post_id = self::factory()->post->create([
|
|
102
|
+
'post_title' => 'Test Post',
|
|
103
|
+
'post_status' => 'publish',
|
|
104
|
+
]);
|
|
105
|
+
update_post_meta( $post_id, 'my_key', 'my_value' );
|
|
106
|
+
|
|
107
|
+
$this->assertSame( 'my_value', get_post_meta( $post_id, 'my_key', true ) );
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public function test_user_with_role(): void {
|
|
111
|
+
$user_id = self::factory()->user->create([ 'role' => 'editor' ]);
|
|
112
|
+
wp_set_current_user( $user_id );
|
|
113
|
+
|
|
114
|
+
$this->assertTrue( current_user_can( 'edit_posts' ) );
|
|
115
|
+
$this->assertFalse( current_user_can( 'manage_options' ) );
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## phpunit.xml.dist configuration
|
|
120
|
+
|
|
121
|
+
```xml
|
|
122
|
+
<phpunit bootstrap="tests/bootstrap.php" colors="true">
|
|
123
|
+
<testsuites>
|
|
124
|
+
<testsuite name="My Plugin">
|
|
125
|
+
<directory suffix=".php">./tests/</directory>
|
|
126
|
+
</testsuite>
|
|
127
|
+
</testsuites>
|
|
128
|
+
<coverage>
|
|
129
|
+
<include>
|
|
130
|
+
<directory suffix=".php">./includes/</directory>
|
|
131
|
+
</include>
|
|
132
|
+
</coverage>
|
|
133
|
+
</phpunit>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Common issues
|
|
137
|
+
|
|
138
|
+
- **"Class WP_UnitTestCase not found"**: bootstrap not loading; use wp-env or re-run `bin/install-wp-tests.sh`
|
|
139
|
+
- **"No tests executed"**: test methods must start with `test_`; class must extend `WP_UnitTestCase`
|
|
140
|
+
- **Database errors**: each test runs in a transaction that rolls back; avoid `dbDelta()` in tests
|
|
141
|
+
- **`set_up()` not `setUp()`**: WordPress renamed the method in WP 5.9 for snake_case consistency
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Playwright for WordPress E2E Testing
|
|
2
|
+
|
|
3
|
+
Use this file when writing or configuring Playwright E2E tests for WordPress projects.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D @playwright/test @wordpress/e2e-test-utils-playwright
|
|
9
|
+
npx playwright install chromium
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Configuration (`playwright.config.ts`)
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { defineConfig } from '@playwright/test';
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
testDir: './tests/e2e',
|
|
19
|
+
outputDir: './tests/e2e/artifacts',
|
|
20
|
+
fullyParallel: false, // WordPress state can conflict in parallel
|
|
21
|
+
retries: process.env.CI ? 2 : 0,
|
|
22
|
+
use: {
|
|
23
|
+
baseURL: process.env.WP_BASE_URL || 'http://localhost:8888',
|
|
24
|
+
storageState: process.env.STORAGE_STATE_PATH,
|
|
25
|
+
trace: 'retain-on-failure',
|
|
26
|
+
screenshot: 'only-on-failure',
|
|
27
|
+
},
|
|
28
|
+
webServer: {
|
|
29
|
+
command: 'npx wp-env start',
|
|
30
|
+
url: 'http://localhost:8888',
|
|
31
|
+
reuseExistingServer: true,
|
|
32
|
+
timeout: 120000,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## WordPress-specific fixtures
|
|
38
|
+
|
|
39
|
+
`@wordpress/e2e-test-utils-playwright` provides fixtures:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
|
43
|
+
|
|
44
|
+
test('can create a post', async ({ admin, editor, page }) => {
|
|
45
|
+
await admin.visitAdminPage('post-new.php');
|
|
46
|
+
await editor.canvas.locator('[data-type="core/paragraph"]').click();
|
|
47
|
+
await page.keyboard.type('Hello from Playwright');
|
|
48
|
+
await editor.publishPost();
|
|
49
|
+
await expect(page.locator('.components-snackbar')).toContainText('published');
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Key fixtures:
|
|
54
|
+
- `admin` — navigate to admin pages, authenticated as admin
|
|
55
|
+
- `editor` — block editor helpers (canvas, inserter, publish)
|
|
56
|
+
- `requestUtils` — REST API helpers for test data setup
|
|
57
|
+
- `page` — standard Playwright page object
|
|
58
|
+
|
|
59
|
+
## Authentication setup
|
|
60
|
+
|
|
61
|
+
Create a global setup that stores auth state:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// tests/e2e/global-setup.ts
|
|
65
|
+
import { request } from '@playwright/test';
|
|
66
|
+
|
|
67
|
+
export default async function globalSetup() {
|
|
68
|
+
const api = await request.newContext({ baseURL: 'http://localhost:8888' });
|
|
69
|
+
await api.post('/wp-login.php', {
|
|
70
|
+
form: { log: 'admin', pwd: 'password', rememberme: 'forever' },
|
|
71
|
+
});
|
|
72
|
+
await api.storageState({ path: './tests/e2e/.auth/admin.json' });
|
|
73
|
+
await api.dispose();
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Seeding test data via REST
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
test.beforeAll(async ({ requestUtils }) => {
|
|
81
|
+
await requestUtils.createPost({
|
|
82
|
+
title: 'Test Post',
|
|
83
|
+
content: '<!-- wp:paragraph --><p>Content</p><!-- /wp:paragraph -->',
|
|
84
|
+
status: 'publish',
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test.afterAll(async ({ requestUtils }) => {
|
|
89
|
+
await requestUtils.deleteAllPosts();
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Running tests
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx wp-scripts test-playwright # Using @wordpress/scripts
|
|
97
|
+
npx playwright test # Direct Playwright
|
|
98
|
+
npx playwright test --ui # Interactive UI mode
|
|
99
|
+
npx playwright test --grep "block editor" # Filter by title
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Best practices
|
|
103
|
+
|
|
104
|
+
- Use `test.describe.serial()` for tests that depend on order
|
|
105
|
+
- Clean up test data in `afterAll` to prevent state leakage
|
|
106
|
+
- Use `page.waitForLoadState('networkidle')` sparingly — prefer specific selectors
|
|
107
|
+
- Store authentication state to avoid login in every test
|
|
108
|
+
- Use `test.slow()` for tests involving complex editor operations
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Test Data Generation
|
|
2
|
+
|
|
3
|
+
Use this file when creating test data and fixtures for WordPress tests.
|
|
4
|
+
|
|
5
|
+
## PHPUnit: WP test factories
|
|
6
|
+
|
|
7
|
+
`WP_UnitTestCase` provides factory methods for creating test data:
|
|
8
|
+
|
|
9
|
+
```php
|
|
10
|
+
// Posts
|
|
11
|
+
$post_id = self::factory()->post->create([
|
|
12
|
+
'post_title' => 'Test Post',
|
|
13
|
+
'post_status' => 'publish',
|
|
14
|
+
'post_type' => 'post',
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
// Multiple posts
|
|
18
|
+
$post_ids = self::factory()->post->create_many(10, [
|
|
19
|
+
'post_status' => 'publish',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
// Users
|
|
23
|
+
$user_id = self::factory()->user->create([
|
|
24
|
+
'role' => 'editor',
|
|
25
|
+
'user_login' => 'testeditor',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
// Terms
|
|
29
|
+
$term_id = self::factory()->term->create([
|
|
30
|
+
'taxonomy' => 'category',
|
|
31
|
+
'name' => 'Test Category',
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// Comments
|
|
35
|
+
$comment_id = self::factory()->comment->create([
|
|
36
|
+
'comment_post_ID' => $post_id,
|
|
37
|
+
'comment_content' => 'Test comment',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
// Attachments
|
|
41
|
+
$attachment_id = self::factory()->attachment->create_upload_object(
|
|
42
|
+
DIR_TESTDATA . '/images/canola.jpg',
|
|
43
|
+
$post_id
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Playwright E2E: requestUtils
|
|
48
|
+
|
|
49
|
+
`@wordpress/e2e-test-utils-playwright` provides REST API-based data creation:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
test.beforeAll(async ({ requestUtils }) => {
|
|
53
|
+
await requestUtils.createPost({
|
|
54
|
+
title: 'E2E Test Post',
|
|
55
|
+
content: '<!-- wp:paragraph --><p>Test content</p><!-- /wp:paragraph -->',
|
|
56
|
+
status: 'publish',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await requestUtils.createPage({
|
|
60
|
+
title: 'Test Page',
|
|
61
|
+
status: 'publish',
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## WP-CLI bulk data generation
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Generate posts
|
|
70
|
+
npx wp-env run cli wp post generate --count=50 --post_type=post --post_status=publish
|
|
71
|
+
|
|
72
|
+
# Generate users
|
|
73
|
+
npx wp-env run cli wp user generate --count=10 --role=subscriber
|
|
74
|
+
|
|
75
|
+
# Import theme unit test data (standard WP test content)
|
|
76
|
+
npx wp-env run cli wp import /tmp/theme-unit-test-data.xml --authors=create
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Cleanup patterns
|
|
80
|
+
|
|
81
|
+
### PHPUnit (automatic)
|
|
82
|
+
|
|
83
|
+
Each PHPUnit test runs in a database transaction that rolls back automatically. Factory-created data is cleaned up without explicit teardown.
|
|
84
|
+
|
|
85
|
+
### Playwright (manual cleanup)
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
test.afterAll(async ({ requestUtils }) => {
|
|
89
|
+
await requestUtils.deleteAllPosts();
|
|
90
|
+
await requestUtils.deleteAllPages();
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Between test runs
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx wp-env clean all # Reset both databases to initial state
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Fixtures for complex scenarios
|
|
101
|
+
|
|
102
|
+
Create a reusable fixture file:
|
|
103
|
+
|
|
104
|
+
```php
|
|
105
|
+
// tests/fixtures/class-test-fixtures.php
|
|
106
|
+
class Test_Fixtures {
|
|
107
|
+
public static function create_sample_store(): array {
|
|
108
|
+
$category = self::factory()->term->create(['taxonomy' => 'product_cat', 'name' => 'Widgets']);
|
|
109
|
+
$products = self::factory()->post->create_many(5, [
|
|
110
|
+
'post_type' => 'product',
|
|
111
|
+
'post_status' => 'publish',
|
|
112
|
+
]);
|
|
113
|
+
foreach ($products as $id) {
|
|
114
|
+
wp_set_object_terms($id, $category, 'product_cat');
|
|
115
|
+
}
|
|
116
|
+
return compact('category', 'products');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Best practices
|
|
122
|
+
|
|
123
|
+
- Create the minimum data needed for each test
|
|
124
|
+
- Use factories over raw SQL or direct `wp_insert_post()` calls
|
|
125
|
+
- Name test data clearly to distinguish from real content
|
|
126
|
+
- Clean up E2E test data in `afterAll` hooks
|
|
127
|
+
- Avoid relying on auto-increment IDs; query by known attributes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Visual Regression Testing
|
|
2
|
+
|
|
3
|
+
Use this file when adding screenshot-based visual regression tests to a WordPress project.
|
|
4
|
+
|
|
5
|
+
## Playwright built-in approach (recommended)
|
|
6
|
+
|
|
7
|
+
Playwright includes `toHaveScreenshot()` for visual comparison:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
|
|
11
|
+
|
|
12
|
+
test('homepage matches visual baseline', async ({ page }) => {
|
|
13
|
+
await page.goto('/');
|
|
14
|
+
await expect(page).toHaveScreenshot('homepage.png', {
|
|
15
|
+
maxDiffPixelRatio: 0.01, // Allow 1% pixel difference
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('block renders correctly in editor', async ({ admin, editor, page }) => {
|
|
20
|
+
await admin.visitAdminPage('post-new.php');
|
|
21
|
+
await editor.insertBlock({ name: 'my-plugin/my-block' });
|
|
22
|
+
const block = editor.canvas.locator('[data-type="my-plugin/my-block"]');
|
|
23
|
+
await expect(block).toHaveScreenshot('my-block-editor.png');
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Generating baselines
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# First run: creates baseline screenshots
|
|
31
|
+
npx playwright test --update-snapshots
|
|
32
|
+
|
|
33
|
+
# Subsequent runs: compare against baselines
|
|
34
|
+
npx playwright test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Baselines are stored in `tests/e2e/__snapshots__/` by default.
|
|
38
|
+
|
|
39
|
+
## Handling dynamic content
|
|
40
|
+
|
|
41
|
+
Mask elements that change between runs:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
await expect(page).toHaveScreenshot('dashboard.png', {
|
|
45
|
+
mask: [
|
|
46
|
+
page.locator('.current-time'),
|
|
47
|
+
page.locator('.random-ad'),
|
|
48
|
+
page.locator('#wpadminbar'), // Admin bar may show user-specific data
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Disable animations
|
|
54
|
+
|
|
55
|
+
Add to `playwright.config.ts` to prevent animation-related flakiness:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
use: {
|
|
59
|
+
// ...
|
|
60
|
+
contextOptions: {
|
|
61
|
+
reducedMotion: 'reduce',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Consistent viewport
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
use: {
|
|
70
|
+
viewport: { width: 1280, height: 720 },
|
|
71
|
+
},
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## CI integration
|
|
75
|
+
|
|
76
|
+
Playwright stores screenshots as test artifacts. In GitHub Actions:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
- uses: actions/upload-artifact@v4
|
|
80
|
+
if: failure()
|
|
81
|
+
with:
|
|
82
|
+
name: visual-regression-diffs
|
|
83
|
+
path: tests/e2e/artifacts/
|
|
84
|
+
retention-days: 7
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Updating baselines after intentional changes
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx playwright test --update-snapshots
|
|
91
|
+
git add tests/e2e/__snapshots__/
|
|
92
|
+
git commit -m "Update visual regression baselines"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Threshold tuning
|
|
96
|
+
|
|
97
|
+
- `maxDiffPixelRatio: 0.01` — 1% tolerance (good default)
|
|
98
|
+
- `maxDiffPixels: 100` — absolute pixel count tolerance
|
|
99
|
+
- `threshold: 0.2` — per-pixel color sensitivity (0-1, lower = stricter)
|
|
100
|
+
|
|
101
|
+
Use higher thresholds for pages with web fonts (rendering varies across OS).
|
|
102
|
+
|
|
103
|
+
## Common issues
|
|
104
|
+
|
|
105
|
+
- **False positives on CI**: OS font rendering differs; consider running in Docker or using consistent font stacks
|
|
106
|
+
- **Flaky screenshots**: add `await page.waitForLoadState('networkidle')` before screenshots; mask dynamic elements
|
|
107
|
+
- **Large snapshot files**: use PNG compression; store in Git LFS for large projects
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# wp-env Test Environment Setup
|
|
2
|
+
|
|
3
|
+
Use this file when setting up or configuring wp-env as a test environment for WordPress development.
|
|
4
|
+
|
|
5
|
+
## Minimal `.wp-env.json` for testing
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"core": null,
|
|
10
|
+
"phpVersion": "8.2",
|
|
11
|
+
"plugins": ["./"],
|
|
12
|
+
"config": {
|
|
13
|
+
"WP_DEBUG": true,
|
|
14
|
+
"SCRIPT_DEBUG": true
|
|
15
|
+
},
|
|
16
|
+
"port": 8888,
|
|
17
|
+
"testsPort": 8889
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For a theme project, replace `"plugins": ["./"]` with `"themes": ["./"]`.
|
|
22
|
+
|
|
23
|
+
## Starting and managing
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx wp-env start # Start both dev and test environments
|
|
27
|
+
npx wp-env start --update # Start and pull latest images
|
|
28
|
+
npx wp-env stop # Stop containers (preserves data)
|
|
29
|
+
npx wp-env destroy # Remove containers and data
|
|
30
|
+
npx wp-env clean all # Reset databases only
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Running commands inside wp-env
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# WP-CLI in development environment
|
|
37
|
+
npx wp-env run cli wp plugin list
|
|
38
|
+
|
|
39
|
+
# WP-CLI in tests environment
|
|
40
|
+
npx wp-env run tests-cli wp option get siteurl
|
|
41
|
+
|
|
42
|
+
# PHPUnit in tests container
|
|
43
|
+
npx wp-env run tests-cli --env-cwd=wp-content/plugins/my-plugin phpunit
|
|
44
|
+
|
|
45
|
+
# Arbitrary bash
|
|
46
|
+
npx wp-env run cli bash -c "cat wp-config.php | grep WP_DEBUG"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Default credentials
|
|
50
|
+
|
|
51
|
+
- **Development**: `http://localhost:8888` — admin / password
|
|
52
|
+
- **Tests**: `http://localhost:8889` — admin / password
|
|
53
|
+
|
|
54
|
+
## Custom PHP and WP versions
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"core": "WordPress/WordPress#6.8",
|
|
59
|
+
"phpVersion": "8.1"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This is useful for testing compatibility matrices. Each CI job can override these values.
|
|
64
|
+
|
|
65
|
+
## Mounting additional plugins/themes
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"plugins": [
|
|
70
|
+
"./",
|
|
71
|
+
"https://downloads.wordpress.org/plugin/gutenberg.latest-stable.zip"
|
|
72
|
+
],
|
|
73
|
+
"mappings": {
|
|
74
|
+
"wp-content/mu-plugins": "./test-utils/mu-plugins"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Override file for local settings
|
|
80
|
+
|
|
81
|
+
Create `.wp-env.override.json` (gitignored) for developer-specific settings:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"port": 9999,
|
|
86
|
+
"config": {
|
|
87
|
+
"WP_DEBUG_LOG": true
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Common issues
|
|
93
|
+
|
|
94
|
+
- **Port conflict**: change `port`/`testsPort` or stop the conflicting service
|
|
95
|
+
- **Docker not running**: `docker info` must succeed; start Docker Desktop or daemon
|
|
96
|
+
- **Stale containers**: `npx wp-env destroy && npx wp-env start` for a clean slate
|
|
97
|
+
- **Plugin not activated**: run `npx wp-env run cli wp plugin activate my-plugin`
|