Skip to content

Commit 22480b5

Browse files
authored
Merge pull request #1171 from PatelUtkarsh/feature/wizard-e2e
Add Playwright e2e test for Cloudinary setup wizard
2 parents b56db46 + 450afc6 commit 22480b5

8 files changed

Lines changed: 366 additions & 2 deletions

File tree

.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copy to `.env` and fill in real values. `.env` is gitignored.
2+
#
3+
# Used by Playwright (tests/e2e/playwright.config.js) at e2e startup.
4+
5+
# Cloudinary connection string consumed by the wizard e2e spec
6+
# (tests/e2e/wizard-setup.spec.js). Use a dedicated test account —
7+
# never production credentials. See README "End-to-end testing" for
8+
# why this is named CLOUDINARY_E2E_URL rather than CLOUDINARY_URL.
9+
CLOUDINARY_E2E_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ jobs:
6969
run: npm run env:start
7070

7171
- name: Run E2E tests
72+
env:
73+
CLOUDINARY_E2E_URL: ${{ secrets.CLOUDINARY_E2E_URL }}
7274
run: npm run test:e2e
7375

7476
- name: Stop wp-env

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,59 @@ Files included in the release package are defined in the `gruntfile.js` under th
117117

118118
3. Run `npm run deploy-assets` to deploy just the WP.org plugin assets such as screenshots, icons and banners.
119119

120+
## End-to-end testing
121+
122+
E2E tests run against a wp-env site using Playwright.
123+
124+
### One-time setup
125+
126+
```bash
127+
npm install
128+
npx playwright install --with-deps chromium
129+
npm run env:start
130+
```
131+
132+
### Running the tests
133+
134+
```bash
135+
npm run test:e2e
136+
```
137+
138+
### Wizard test credentials
139+
140+
`tests/e2e/wizard-setup.spec.js` exercises the live Cloudinary connection flow, so it needs a real connection string. Provide one of two ways:
141+
142+
**Option 1 — `.env` file (recommended for sustained local development).** Copy `.env.example` to `.env` and fill in the value. `.env` is gitignored. Playwright loads it automatically at startup.
143+
144+
```bash
145+
cp .env.example .env
146+
# edit .env, set CLOUDINARY_E2E_URL=cloudinary://...
147+
npm run test:e2e
148+
```
149+
150+
**Option 2 — shell export (good for one-off runs and CI).**
151+
152+
```bash
153+
export CLOUDINARY_E2E_URL='cloudinary://API_KEY:API_SECRET@CLOUD_NAME'
154+
npm run test:e2e
155+
```
156+
157+
A real shell env var takes precedence over the `.env` file.
158+
159+
The variable is intentionally named `CLOUDINARY_E2E_URL` (not `CLOUDINARY_URL`) so it cannot be confused with the Cloudinary SDK convention or with anything you might define in `.wp-env.override.json` for local dev. Use a dedicated test Cloudinary account — never production credentials.
160+
161+
> **Note:** Do **not** set `CLOUDINARY_URL` or `CLOUDINARY_CONNECTION_STRING` as PHP constants via `.wp-env.override.json` while running this spec. The plugin treats a constant-defined connection string as already-configured and hides the wizard's connection input, which makes the test impossible.
162+
163+
CI will provide `CLOUDINARY_E2E_URL` via a GitHub Actions secret (wired separately under WPP-1195's CI subtask).
164+
165+
### Debugging a failing e2e test
166+
167+
```bash
168+
npm run test:e2e:debug -- wizard-setup
169+
```
170+
171+
This opens Playwright's UI runner where you can step through actions, inspect the DOM, and view the network panel.
172+
120173
## License
121174
122175
Released under the GPL license.

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"tippy.js": "^6.3.1"
7070
},
7171
"devDependencies": {
72+
"@playwright/test": "^1.59.1",
7273
"@release-it/bumper": "^7.0.5",
7374
"@typescript-eslint/eslint-plugin": "^8.46.3",
7475
"@wordpress/api-fetch": "^7.34.0",
@@ -77,18 +78,18 @@
7778
"@wordpress/browserslist-config": "^6.34.0",
7879
"@wordpress/components": "^30.7.0",
7980
"@wordpress/data": "^10.34.0",
80-
"@wordpress/element": "^6.34.0",
8181
"@wordpress/e2e-test-utils-playwright": "^1.44.0",
82+
"@wordpress/element": "^6.34.0",
8283
"@wordpress/env": "^10.12.0",
8384
"@wordpress/eslint-plugin": "^22.20.0",
84-
"@playwright/test": "^1.59.1",
8585
"@wordpress/i18n": "^6.7.0",
8686
"@wordpress/scripts": "^31.0.0",
8787
"copy-webpack-plugin": "^13.0.1",
8888
"css-loader": "^7.1.2",
8989
"css-minimizer-webpack-plugin": "^7.0.2",
9090
"css-unicode-loader": "^1.0.3",
9191
"cssnano": "^7.1.2",
92+
"dotenv": "^17.3.1",
9293
"eslint": "^8.57.1",
9394
"eslint-plugin-jest": "^29.0.1",
9495
"eslint-plugin-react-hooks": "^7.0.1",

tests/e2e/playwright.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
const { defineConfig, devices } = require( '@playwright/test' );
55
const path = require( 'path' );
66

7+
// Load env vars from a project-root .env file so devs don't have to
8+
// re-export CLOUDINARY_E2E_URL in every shell. The file is gitignored.
9+
// Real shell env vars take precedence (override: false). `quiet: true`
10+
// suppresses dotenv's promotional banner.
11+
require( 'dotenv' ).config( {
12+
path: path.join( process.cwd(), '.env' ),
13+
override: false,
14+
quiet: true,
15+
} );
16+
717
const STORAGE_STATE_PATH =
818
process.env.STORAGE_STATE_PATH ||
919
path.join( process.cwd(), 'artifacts/storage-states/admin.json' );

tests/e2e/utils/wizard.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* Helpers for the Cloudinary wizard e2e spec.
3+
*
4+
* State changes (deleting the connection options) bypass the WP REST
5+
* API so they don't trigger `pre_update_option_cloudinary_connect`,
6+
* which would make a live Cloudinary API call. Direct DB access via
7+
* docker + wp-cli is the right tool here.
8+
*
9+
* We use `docker exec` rather than `npx wp-env run cli` because the
10+
* latter routes through `got` → `api.wordpress.org` at startup and
11+
* times out on macOS due to an IPv6 resolution issue. `docker exec`
12+
* goes straight to the running container.
13+
*/
14+
15+
const { execSync } = require( 'child_process' );
16+
17+
const CONNECT_OPTION = 'cloudinary_connect';
18+
const SIGNATURE_OPTION = 'cloudinary_connection_signature';
19+
const STATUS_OPTION = 'cloudinary_status';
20+
21+
let cachedCliContainer = null;
22+
23+
/**
24+
* Find the wp-env CLI container name dynamically.
25+
*
26+
* Playwright drives the `tests-wordpress` site (port 8889) by default,
27+
* so we target the matching `*-tests-cli-1` container. The container
28+
* name embeds a project hash that varies between machines; we discover
29+
* it by listing running containers and filtering for the suffix.
30+
*
31+
* @return {string} Container name.
32+
* @throws If no matching container is running.
33+
*/
34+
function getCliContainer() {
35+
if ( cachedCliContainer ) {
36+
return cachedCliContainer;
37+
}
38+
39+
const out = execSync( "docker ps --format '{{.Names}}'", {
40+
encoding: 'utf8',
41+
} );
42+
const lines = out.split( '\n' ).filter( Boolean );
43+
44+
const cli = lines.find( ( name ) => /-tests-cli-1$/.test( name ) );
45+
46+
if ( ! cli ) {
47+
throw new Error(
48+
'Could not find a running wp-env tests-cli container. Run `docker ps` and confirm a `*-tests-cli-1` container is up.'
49+
);
50+
}
51+
52+
cachedCliContainer = cli;
53+
return cli;
54+
}
55+
56+
/**
57+
* Run a WP-CLI command inside the wp-env cli container.
58+
*
59+
* @param {string[]} args wp-cli arguments after the leading `wp`.
60+
* @return {string} stdout, trimmed.
61+
*/
62+
function wpCli( args ) {
63+
const container = getCliContainer();
64+
const cmd = [
65+
'docker',
66+
'exec',
67+
container,
68+
'wp',
69+
...args,
70+
'--allow-root',
71+
].join( ' ' );
72+
73+
return execSync( cmd, {
74+
encoding: 'utf8',
75+
stdio: [ 'ignore', 'pipe', 'pipe' ],
76+
} ).trim();
77+
}
78+
79+
/**
80+
* Wipe any existing Cloudinary connection so the wizard reappears.
81+
*
82+
* Each option may or may not exist. We attempt all three and
83+
* silently swallow "Could not get/delete option" errors.
84+
*/
85+
function resetCloudinaryConnection() {
86+
for ( const opt of [ CONNECT_OPTION, SIGNATURE_OPTION, STATUS_OPTION ] ) {
87+
try {
88+
wpCli( [ 'option', 'delete', opt ] );
89+
} catch ( e ) {
90+
// Option not present; that's fine.
91+
}
92+
}
93+
}
94+
95+
/**
96+
* Read the Cloudinary connection string from the environment.
97+
*
98+
* We use a dedicated `CLOUDINARY_E2E_URL` env var rather than the
99+
* Cloudinary SDK's conventional `CLOUDINARY_URL` to make it explicit
100+
* that this is test-only credentials and to avoid colliding with any
101+
* SDK auto-bootstrap behaviour developers may rely on locally.
102+
*
103+
* Throws if not set so the test fails loudly rather than silently
104+
* producing a meaningless pass/fail.
105+
*
106+
* @return {string} The cloudinary:// URL.
107+
*/
108+
function getCloudinaryUrlFromEnv() {
109+
const url = process.env.CLOUDINARY_E2E_URL;
110+
if ( ! url || ! url.startsWith( 'cloudinary://' ) ) {
111+
throw new Error(
112+
'CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec.'
113+
);
114+
}
115+
return url;
116+
}
117+
118+
/**
119+
* Navigate the admin browser to the wizard screen.
120+
*
121+
* We hit the wizard URL directly so the test does not depend on the
122+
* "not connected → wizard" redirect.
123+
*
124+
* @param {Object} admin Admin fixture from @wordpress/e2e-test-utils-playwright.
125+
*/
126+
async function visitWizard( admin ) {
127+
await admin.visitAdminPage( 'admin.php', 'page=cloudinary&section=wizard' );
128+
}
129+
130+
module.exports = {
131+
getCliContainer,
132+
getCloudinaryUrlFromEnv,
133+
resetCloudinaryConnection,
134+
visitWizard,
135+
wpCli,
136+
};

0 commit comments

Comments
 (0)