claude-plugin-wordpress-manager 1.4.0 → 1.7.0
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 +7 -3
- package/CHANGELOG.md +111 -0
- package/README.md +10 -3
- 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/GUIDE.md +68 -15
- 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-local-dev-tools-assessment.md +332 -0
- package/docs/plans/2026-02-27-local-env-design.md +179 -0
- package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
- package/package.json +7 -3
- package/skills/wordpress-router/SKILL.md +25 -5
- package/skills/wordpress-router/references/decision-tree.md +59 -3
- 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-deploy/SKILL.md +12 -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-local-env/SKILL.md +233 -0
- package/skills/wp-local-env/references/localwp-adapter.md +156 -0
- package/skills/wp-local-env/references/mcp-adapter-setup.md +153 -0
- package/skills/wp-local-env/references/studio-adapter.md +127 -0
- package/skills/wp-local-env/references/wpenv-adapter.md +121 -0
- package/skills/wp-local-env/scripts/detect_local_env.mjs +404 -0
- package/skills/wp-playground/SKILL.md +13 -1
- 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
- package/skills/wp-wpcli-and-ops/SKILL.md +6 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# CI Pipeline Integration for WordPress Tests
|
|
2
|
+
|
|
3
|
+
Use this file when setting up GitHub Actions (or similar CI) for WordPress test automation.
|
|
4
|
+
|
|
5
|
+
## GitHub Actions workflow
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
# .github/workflows/tests.yml
|
|
9
|
+
name: Tests
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches: [main]
|
|
14
|
+
pull_request:
|
|
15
|
+
branches: [main]
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
php-tests:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
php: ['8.1', '8.2', '8.3']
|
|
23
|
+
wp: ['6.8', '6.9']
|
|
24
|
+
services:
|
|
25
|
+
mysql:
|
|
26
|
+
image: mysql:8.0
|
|
27
|
+
env:
|
|
28
|
+
MYSQL_ROOT_PASSWORD: root
|
|
29
|
+
MYSQL_DATABASE: wordpress_test
|
|
30
|
+
ports: ['3306:3306']
|
|
31
|
+
options: >-
|
|
32
|
+
--health-cmd="mysqladmin ping"
|
|
33
|
+
--health-interval=10s
|
|
34
|
+
--health-timeout=5s
|
|
35
|
+
--health-retries=5
|
|
36
|
+
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/checkout@v4
|
|
39
|
+
|
|
40
|
+
- name: Setup PHP
|
|
41
|
+
uses: shivammathur/setup-php@v2
|
|
42
|
+
with:
|
|
43
|
+
php-version: ${{ matrix.php }}
|
|
44
|
+
extensions: mysqli
|
|
45
|
+
coverage: xdebug
|
|
46
|
+
|
|
47
|
+
- name: Cache Composer
|
|
48
|
+
uses: actions/cache@v4
|
|
49
|
+
with:
|
|
50
|
+
path: vendor
|
|
51
|
+
key: composer-${{ hashFiles('composer.lock') }}
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: composer install --no-interaction
|
|
55
|
+
|
|
56
|
+
- name: Install WP test suite
|
|
57
|
+
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wp }}
|
|
58
|
+
|
|
59
|
+
- name: Run PHPUnit
|
|
60
|
+
run: vendor/bin/phpunit
|
|
61
|
+
|
|
62
|
+
js-tests:
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
|
|
67
|
+
- name: Setup Node.js
|
|
68
|
+
uses: actions/setup-node@v4
|
|
69
|
+
with:
|
|
70
|
+
node-version: 20
|
|
71
|
+
cache: npm
|
|
72
|
+
|
|
73
|
+
- name: Install dependencies
|
|
74
|
+
run: npm ci
|
|
75
|
+
|
|
76
|
+
- name: Run Jest
|
|
77
|
+
run: npx wp-scripts test-unit-js --ci --coverage
|
|
78
|
+
|
|
79
|
+
e2e-tests:
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
steps:
|
|
82
|
+
- uses: actions/checkout@v4
|
|
83
|
+
|
|
84
|
+
- name: Setup Node.js
|
|
85
|
+
uses: actions/setup-node@v4
|
|
86
|
+
with:
|
|
87
|
+
node-version: 20
|
|
88
|
+
cache: npm
|
|
89
|
+
|
|
90
|
+
- name: Install dependencies
|
|
91
|
+
run: npm ci
|
|
92
|
+
|
|
93
|
+
- name: Install Playwright browsers
|
|
94
|
+
run: npx playwright install chromium --with-deps
|
|
95
|
+
|
|
96
|
+
- name: Start wp-env
|
|
97
|
+
run: npx wp-env start
|
|
98
|
+
|
|
99
|
+
- name: Run Playwright tests
|
|
100
|
+
run: npx wp-scripts test-playwright
|
|
101
|
+
|
|
102
|
+
- name: Upload artifacts on failure
|
|
103
|
+
uses: actions/upload-artifact@v4
|
|
104
|
+
if: failure()
|
|
105
|
+
with:
|
|
106
|
+
name: playwright-artifacts
|
|
107
|
+
path: artifacts/
|
|
108
|
+
retention-days: 7
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Caching strategies
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
# Node modules
|
|
115
|
+
- uses: actions/cache@v4
|
|
116
|
+
with:
|
|
117
|
+
path: ~/.npm
|
|
118
|
+
key: npm-${{ hashFiles('package-lock.json') }}
|
|
119
|
+
|
|
120
|
+
# Playwright browsers
|
|
121
|
+
- uses: actions/cache@v4
|
|
122
|
+
with:
|
|
123
|
+
path: ~/.cache/ms-playwright
|
|
124
|
+
key: playwright-${{ hashFiles('package-lock.json') }}
|
|
125
|
+
|
|
126
|
+
# Docker images for wp-env
|
|
127
|
+
- uses: ScribeMD/docker-cache@0.5.0
|
|
128
|
+
with:
|
|
129
|
+
key: docker-${{ hashFiles('.wp-env.json') }}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Parallel test execution
|
|
133
|
+
|
|
134
|
+
Split Playwright tests across multiple workers:
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
e2e-tests:
|
|
138
|
+
strategy:
|
|
139
|
+
matrix:
|
|
140
|
+
shard: [1, 2, 3]
|
|
141
|
+
steps:
|
|
142
|
+
# ...
|
|
143
|
+
- run: npx playwright test --shard=${{ matrix.shard }}/3
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## PHPUnit without wp-env (standalone)
|
|
147
|
+
|
|
148
|
+
For projects that don't use wp-env, the `install-wp-tests.sh` script sets up the WP test suite:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 latest true
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Arguments: `db_name db_user db_pass db_host wp_version skip_db_create`
|
|
155
|
+
|
|
156
|
+
## Conditional jobs
|
|
157
|
+
|
|
158
|
+
Run expensive E2E tests only when relevant files change:
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
e2e-tests:
|
|
162
|
+
if: |
|
|
163
|
+
contains(github.event.pull_request.labels.*.name, 'e2e') ||
|
|
164
|
+
github.ref == 'refs/heads/main'
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Best practices
|
|
168
|
+
|
|
169
|
+
- Run PHPUnit with matrix strategy for PHP/WP version coverage
|
|
170
|
+
- Cache aggressively (npm, Composer, Playwright browsers, Docker)
|
|
171
|
+
- Upload test artifacts (traces, screenshots) on failure for debugging
|
|
172
|
+
- Use `--ci` flag for Jest to disable interactive mode
|
|
173
|
+
- Set reasonable timeouts to catch hanging tests early
|
|
174
|
+
- Run lint/format checks as a separate job (fast fail)
|
|
@@ -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
|