Add Playwright e2e tests for upload interception - #68
Conversation
Cover image block upload, media library drop, editor canvas drop, featured image drop, and Media Library admin upload against wp-env. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Important Review skippedToo many files! This PR contains 236 files, which is 136 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (236)
You can disable this status message by setting the 📝 WalkthroughWalkthroughThis change adds a Playwright end-to-end test setup for WordPress upload interception. It includes local and CI configuration, authenticated request utilities, shared media helpers, and JPG-to-WebP coverage across multiple upload flows. ChangesE2E upload interception
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🤖 Pull request artifacts
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@e2e/playwright.config.ts`:
- Around line 21-26: Remove ignoreHTTPSErrors from the Playwright use
configuration in e2e/playwright.config.ts and the authenticated context setup in
e2e/config/global-setup.ts, preserving TLS validation for WordPress credentials,
WP_BASE_URL, and storage-state restoration; configure trust only for the local
test CA used by HTTPS endpoints.
In `@e2e/readme.md`:
- Around line 12-18: Update the environment-variable code fence in the README
example to declare the dotenv language while preserving its existing contents.
In `@e2e/test-utils/test.ts`:
- Around line 11-18: Update the requestUtils fixture to read WP_BASE_URL,
WP_USERNAME, and WP_PASSWORD once before calling ExtendedRequestUtils.setup,
validate that all required values are present, and throw a clear configuration
error identifying the missing E2E configuration before fixture setup. Pass the
validated values into the existing setup call.
In `@e2e/tests/upload-interception.spec.ts`:
- Around line 16-25: Guard destructive setup and cleanup in
e2e/tests/upload-interception.spec.ts and e2e/test-utils/test.ts with an
explicit destructive-test opt-in before creating authenticated fixtures or
making destructive API calls. Replace requestUtils.deleteAllMedia() in the
upload-interception beforeEach/afterEach flow with deletion limited to media
created by the current test, while preserving post cleanup through deletePost
and clearing pageId.
- Around line 90-102: Update the modal selection flow after expectNewMediaIsWebp
to wait for the .attachment[data-id="${media.id}"] element to appear before
attempting selection, rather than relying on count(). Also wait for the
Select/Insert button to become enabled before clicking it, while preserving the
existing modal visibility guard and selection behavior.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e7f70a65-ac1a-46b6-a515-cfa16f5af2d4
⛔ Files ignored due to path filters (2)
e2e/fixtures/sample.jpgis excluded by!**/*.jpgpackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
.env.example.eslintignore.gitignore.wp-env.jsone2e/config/global-setup.tse2e/playwright.config.tse2e/readme.mde2e/test-utils/index.tse2e/test-utils/media.tse2e/test-utils/requestUtils.tse2e/test-utils/test.tse2e/tests/upload-interception.spec.tspackage.jsontsconfig.json
| ``` | ||
| WP_BASE_URL=http://localhost:8889 | ||
| WP_AUTH_STORAGE=wp-auth.json | ||
| WP_USERNAME=admin | ||
| WP_PASSWORD=password | ||
| CIMO_SLUG=Cimo/cimo | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add a language to the environment file fence.
The Markdown linter reports MD040. Use dotenv for this environment-variable example.
Proposed fix
-```
+```dotenv
WP_BASE_URL=http://localhost:8889
WP_AUTH_STORAGE=wp-auth.json
WP_USERNAME=admin
WP_PASSWORD=password
CIMO_SLUG=Cimo/cimo</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)
[warning] 12-12: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/readme.md` around lines 12 - 18, Update the environment-variable code
fence in the README example to declare the dotenv language while preserving its
existing contents.
Source: Linters/SAST tools
| requestUtils: async ( {}, use ) => { | ||
| const requestUtils = await ExtendedRequestUtils.setup( { | ||
| baseURL: process.env.WP_BASE_URL, | ||
| user: { | ||
| username: process.env.WP_USERNAME, | ||
| password: process.env.WP_PASSWORD, | ||
| }, | ||
| } ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject incomplete E2E configuration before fixture setup.
These environment variables can be undefined. The fixture then fails during authentication or a later REST request. Read the required values once and throw a clear configuration error.
Proposed fix
+const getRequiredEnv = ( name: string ) => {
+ const value = process.env[ name ]
+ if ( ! value ) {
+ throw new Error( `Missing required E2E environment variable: ${ name }` )
+ }
+ return value
+}
+
const test = base.extend<{
requestUtils: ExtendedRequestUtils;
}>( {
requestUtils: async ( {}, use ) => {
const requestUtils = await ExtendedRequestUtils.setup( {
- baseURL: process.env.WP_BASE_URL,
+ baseURL: getRequiredEnv( 'WP_BASE_URL' ),
user: {
- username: process.env.WP_USERNAME,
- password: process.env.WP_PASSWORD,
+ username: getRequiredEnv( 'WP_USERNAME' ),
+ password: getRequiredEnv( 'WP_PASSWORD' ),
},
} )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| requestUtils: async ( {}, use ) => { | |
| const requestUtils = await ExtendedRequestUtils.setup( { | |
| baseURL: process.env.WP_BASE_URL, | |
| user: { | |
| username: process.env.WP_USERNAME, | |
| password: process.env.WP_PASSWORD, | |
| }, | |
| } ) | |
| const getRequiredEnv = ( name: string ) => { | |
| const value = process.env[ name ] | |
| if ( ! value ) { | |
| throw new Error( `Missing required E2E environment variable: ${ name }` ) | |
| } | |
| return value | |
| } | |
| requestUtils: async ( {}, use ) => { | |
| const requestUtils = await ExtendedRequestUtils.setup( { | |
| baseURL: getRequiredEnv( 'WP_BASE_URL' ), | |
| user: { | |
| username: getRequiredEnv( 'WP_USERNAME' ), | |
| password: getRequiredEnv( 'WP_PASSWORD' ), | |
| }, | |
| } ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/test-utils/test.ts` around lines 11 - 18, Update the requestUtils fixture
to read WP_BASE_URL, WP_USERNAME, and WP_PASSWORD once before calling
ExtendedRequestUtils.setup, validate that all required values are present, and
throw a clear configuration error identifying the missing E2E configuration
before fixture setup. Pass the validated values into the existing setup call.
| test.beforeEach( async ( { requestUtils } ) => { | ||
| await requestUtils.deleteAllMedia() | ||
| } ) | ||
|
|
||
| test.afterEach( async ( { requestUtils } ) => { | ||
| if ( pageId ) { | ||
| await requestUtils.deletePost( pageId, 'pages' ) | ||
| pageId = null | ||
| } | ||
| await requestUtils.deleteAllMedia() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 5 '\bdeleteAllMedia\s*\(|\bdeletePost\s*\(|WP_BASE_URL|CIMO_E2E' e2e
fd -a -I 'playwright.config.ts' e2e --exec sed -n '1,260p' {}
fd -a -I 'global-setup.ts' e2e --exec sed -n '1,260p' {}Repository: gambitph/Cimo
Length of output: 5911
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== request-utils outline =="
ast-grep outline e2e/test-utils/requestUtils.ts || true
echo "== requestUtils relevant sections =="
sed -n '1,260p' e2e/test-utils/requestUtils.ts
echo "== test fixture section =="
sed -n '1,80p' e2e/test-utils/test.ts
echo "== upload interception relevant sections =="
sed -n '1,120p' e2e/tests/upload-interception.spec.ts
echo "== all WP_BASE_URL references =="
rg -n 'WP_BASE_URL|CIMO_E2E|WP_AUTH' .Repository: gambitph/Cimo
Length of output: 8045
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in ["e2e/test-utils/requestUtils.ts", "e2e/tests/upload-interception.spec.ts"]:
text = Path(p).read_text()
print(f"\n== {p}: deleteAllMedia definitions/usages ==")
lines = text.splitlines()
for i,l in enumerate(lines, start=1):
if "deleteAllMedia" in l or "deletePost" in l or "requestUtils.setup" in l:
start=max(1,i-3); end=min(len(lines), i+8)
print(f"-- around line {i} --")
for n in range(start,end+1):
print(f"{n:4}: {lines[n-1]}")
PYRepository: gambitph/Cimo
Length of output: 1769
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import re
files = ["e2e/test-utils/requestUtils.ts", "e2e/tests/upload-interception.spec.ts"]
for p in files:
text = Path(p).read_text()
print(f"== {p} ==")
print("contains deleteAllMedia:", "deleteAllMedia" in text)
print("contains listMedia:", "listMedia" in text)
print("contains deleteMedia:", "deleteMedia" in text or re.search(r'\bdelete\s*\([^)]*\bmedia\b', text, re.I) is not None)
for name in ["deleteAllMedia", "deleteMedia", "deletePost"]:
print(f"count {name}: {text.count(name)}")
config = Path("e2e/playwright.config.ts").read_text()
print("== e2e/playwright.config.ts ==")
print("contains CI env config references:", "CI" in config or "workers" in config or "timeout" in config)
print("contains destructive/prod env guard:", re.search(r'(CI|unsafe|destructive|prod|production|WP_BASE_URL\s*=\s*"https?://(?!localhost|127\.0\.0\.1))', config, re.I) is not None)
PYRepository: gambitph/Cimo
Length of output: 643
Guard destructive cleanup from arbitrary WordPress targets.
upload-interception.spec.ts calls requestUtils.deleteAllMedia() before and after every case, while WP_BASE_URL is used directly by the fixture and global setup without an unsafe-target guard. A mistaken remote endpoint can delete unrelated media on each run.
- Replace broad media deletion with cleanup of media created by the current test where safe.
- Require an explicit destructive-test opt-in before creating authenticated fixtures or performing destructive API calls.
📍 Affects 2 files
e2e/tests/upload-interception.spec.ts#L16-L25(this comment)e2e/test-utils/test.ts#L12-L18
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@e2e/tests/upload-interception.spec.ts` around lines 16 - 25, Guard
destructive setup and cleanup in e2e/tests/upload-interception.spec.ts and
e2e/test-utils/test.ts with an explicit destructive-test opt-in before creating
authenticated fixtures or making destructive API calls. Replace
requestUtils.deleteAllMedia() in the upload-interception beforeEach/afterEach
flow with deletion limited to media created by the current test, while
preserving post cleanup through deletePost and clearing pageId.
The PR check only built the plugin but never executed the e2e suite added in e2e/. This spins up wp-env, builds the plugin, and runs Playwright against the test WordPress instance on every push/PR to master and develop. Co-authored-by: Cursor <cursoragent@cursor.com>
wp-env logs streams and never exits unless --no-watch is passed, which caused the failure-diagnostics step to hang the CI job indefinitely instead of completing. Co-authored-by: Cursor <cursoragent@cursor.com>
- dropFile(): build the File from decoded bytes instead of fetch()-ing a data: URL, since that fetch fails inside the block editor iframe's CSP. - Featured image test: getByLabel(/Settings/i) matched 3 elements (toggle button, sidebar region, close button); target the exact "Settings" toggle button instead. - Media Library modal test: the upload drop zone no longer has the legacy .media-frame-uploader/.uploader-inline/.upload-ui classes visible in this WP version; target the "Upload files" tabpanel by role instead. Co-authored-by: Cursor <cursoragent@cursor.com>
The payload was always undefined because the callback treated the resolved element as the data argument. Also switched to passing a plain byte array instead of a base64 string/data-URL fetch, avoiding both the iframe CSP fetch failure and any string-serialization edge cases. Co-authored-by: Cursor <cursoragent@cursor.com>
…drop target - Media Library modal test: the uploaded attachment only appears as a selectable grid item under the "Media Library" tab, not "Upload files". Switch tabs before selecting, and wait for visibility/enabled state instead of racing with count()/isEnabled() checks. - Featured image test: Cimo's interceptor re-dispatches the converted file onto Gutenberg's actual DropZone element (.components-drop-zone), nested inside .editor-post-featured-image. Dropping on the outer container missed that inner target; drop on the inner zone when present. - Bump the describe-level timeout to 120s since some flows legitimately take over a minute under CI load. Co-authored-by: Cursor <cursoragent@cursor.com>
A single click plus waiting for the Select button to enable was flaky (button stayed disabled). Double-clicking the attachment selects and inserts it in one step, matching the standard WP media modal UX pattern; falls back to the click + Select button flow if the modal is still open afterwards. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…king Diagnostics showed the grid item's <li> renders (and passes visibility checks) before its thumbnail/content is filled in; clicking that early landed on an inert placeholder that never toggled Backbone's selection state, leaving the Select button permanently disabled. Waiting for .attachment-preview to render first fixes it. Also removes the temporary debug logging and restores the full e2e test run in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
… button locator This grid's Attachment view only toggles selection via its .check checkbox button — clicking elsewhere on the item doesn't register. Separately, the loose /Select|Insert/i search wasn't scoped to the toolbar, so it ambiguously matched the attachment's own checkbox once its accessible name toggled to "Deselect". Scoping to .media-toolbar fixes that. Also restores the full e2e test run in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
Playwright clicks never update wp.media's Backbone selection, so Select stays disabled after a modal drop. Select the uploaded attachment via the media frame API instead. Also skip already-active sidebar tabs so the featured-image test does not hang waiting for a stable click. Co-authored-by: Cursor <cursoragent@cursor.com>
Playwright now boots @wp-playground/cli as webServer so local and CI runs need no Docker, and specs dismiss the Gutenberg starter-pattern modal that was flaking upload flows.
Summary
@wordpress/enve2e scaffolding (mirrors Interactions): config, global setup, fixtures, and helpers.build:e2e/test/test:debugscripts,.wp-env.json, and.env.example.Test plan
cp .env.example .env(confirmCIMO_SLUGmatches checkout folder)npx @wordpress/env start(Docker required)npm run build:e2enpm run test— all 5 upload interception specs passnpm run test:debugand spot-check one failing surface if any flakeMade with Cursor
Summary by CodeRabbit
New Features
Tests