Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copy to `.env` and fill in real values. `.env` is gitignored.
#
# Used by Playwright (tests/e2e/playwright.config.js) at e2e startup.

# Cloudinary connection string consumed by the wizard e2e spec
# (tests/e2e/wizard-setup.spec.js). Use a dedicated test account —
# never production credentials. See README "End-to-end testing" for
# why this is named CLOUDINARY_E2E_URL rather than CLOUDINARY_URL.
CLOUDINARY_E2E_URL=cloudinary://API_KEY:API_SECRET@CLOUD_NAME
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ jobs:
run: npm run env:start

- name: Run E2E tests
env:
CLOUDINARY_E2E_URL: ${{ secrets.CLOUDINARY_E2E_URL }}
run: npm run test:e2e

- name: Stop wp-env
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,59 @@ Files included in the release package are defined in the `gruntfile.js` under th

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

## End-to-end testing

E2E tests run against a wp-env site using Playwright.

### One-time setup

```bash
npm install
npx playwright install --with-deps chromium
npm run env:start
```

### Running the tests

```bash
npm run test:e2e
```

### Wizard test credentials

`tests/e2e/wizard-setup.spec.js` exercises the live Cloudinary connection flow, so it needs a real connection string. Provide one of two ways:

**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.

```bash
cp .env.example .env
# edit .env, set CLOUDINARY_E2E_URL=cloudinary://...
npm run test:e2e
```

**Option 2 — shell export (good for one-off runs and CI).**

```bash
export CLOUDINARY_E2E_URL='cloudinary://API_KEY:API_SECRET@CLOUD_NAME'
npm run test:e2e
```

A real shell env var takes precedence over the `.env` file.

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.

> **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.

CI will provide `CLOUDINARY_E2E_URL` via a GitHub Actions secret (wired separately under WPP-1195's CI subtask).

### Debugging a failing e2e test

```bash
npm run test:e2e:debug -- wizard-setup
```

This opens Playwright's UI runner where you can step through actions, inspect the DOM, and view the network panel.

## License

Released under the GPL license.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"tippy.js": "^6.3.1"
},
"devDependencies": {
"@playwright/test": "^1.59.1",
"@release-it/bumper": "^7.0.5",
"@typescript-eslint/eslint-plugin": "^8.46.3",
"@wordpress/api-fetch": "^7.34.0",
Expand All @@ -77,18 +78,18 @@
"@wordpress/browserslist-config": "^6.34.0",
"@wordpress/components": "^30.7.0",
"@wordpress/data": "^10.34.0",
"@wordpress/element": "^6.34.0",
"@wordpress/e2e-test-utils-playwright": "^1.44.0",
"@wordpress/element": "^6.34.0",
"@wordpress/env": "^10.12.0",
"@wordpress/eslint-plugin": "^22.20.0",
"@playwright/test": "^1.59.1",
"@wordpress/i18n": "^6.7.0",
"@wordpress/scripts": "^31.0.0",
"copy-webpack-plugin": "^13.0.1",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.2",
"css-unicode-loader": "^1.0.3",
"cssnano": "^7.1.2",
"dotenv": "^17.3.1",
"eslint": "^8.57.1",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-react-hooks": "^7.0.1",
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/playwright.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
const { defineConfig, devices } = require( '@playwright/test' );
const path = require( 'path' );

// Load env vars from a project-root .env file so devs don't have to
// re-export CLOUDINARY_E2E_URL in every shell. The file is gitignored.
// Real shell env vars take precedence (override: false). `quiet: true`
// suppresses dotenv's promotional banner.
require( 'dotenv' ).config( {
path: path.join( process.cwd(), '.env' ),
override: false,
quiet: true,
} );

const STORAGE_STATE_PATH =
process.env.STORAGE_STATE_PATH ||
path.join( process.cwd(), 'artifacts/storage-states/admin.json' );
Expand Down
136 changes: 136 additions & 0 deletions tests/e2e/utils/wizard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/**
* Helpers for the Cloudinary wizard e2e spec.
*
* State changes (deleting the connection options) bypass the WP REST
* API so they don't trigger `pre_update_option_cloudinary_connect`,
* which would make a live Cloudinary API call. Direct DB access via
* docker + wp-cli is the right tool here.
*
* We use `docker exec` rather than `npx wp-env run cli` because the
* latter routes through `got` → `api.wordpress.org` at startup and
* times out on macOS due to an IPv6 resolution issue. `docker exec`
* goes straight to the running container.
*/

const { execSync } = require( 'child_process' );

const CONNECT_OPTION = 'cloudinary_connect';
const SIGNATURE_OPTION = 'cloudinary_connection_signature';
const STATUS_OPTION = 'cloudinary_status';

let cachedCliContainer = null;

/**
* Find the wp-env CLI container name dynamically.
*
* Playwright drives the `tests-wordpress` site (port 8889) by default,
* so we target the matching `*-tests-cli-1` container. The container
* name embeds a project hash that varies between machines; we discover
* it by listing running containers and filtering for the suffix.
*
* @return {string} Container name.
* @throws If no matching container is running.
*/
function getCliContainer() {
if ( cachedCliContainer ) {
return cachedCliContainer;
}

const out = execSync( "docker ps --format '{{.Names}}'", {
encoding: 'utf8',
} );
const lines = out.split( '\n' ).filter( Boolean );

const cli = lines.find( ( name ) => /-tests-cli-1$/.test( name ) );

if ( ! cli ) {
throw new Error(
'Could not find a running wp-env tests-cli container. Run `docker ps` and confirm a `*-tests-cli-1` container is up.'
);
}

cachedCliContainer = cli;
return cli;
}

/**
* Run a WP-CLI command inside the wp-env cli container.
*
* @param {string[]} args wp-cli arguments after the leading `wp`.
* @return {string} stdout, trimmed.
*/
function wpCli( args ) {
const container = getCliContainer();
const cmd = [
'docker',
'exec',
container,
'wp',
...args,
'--allow-root',
].join( ' ' );

return execSync( cmd, {
encoding: 'utf8',
stdio: [ 'ignore', 'pipe', 'pipe' ],
} ).trim();
}

/**
* Wipe any existing Cloudinary connection so the wizard reappears.
*
* Each option may or may not exist. We attempt all three and
* silently swallow "Could not get/delete option" errors.
*/
function resetCloudinaryConnection() {
for ( const opt of [ CONNECT_OPTION, SIGNATURE_OPTION, STATUS_OPTION ] ) {
try {
wpCli( [ 'option', 'delete', opt ] );
} catch ( e ) {
// Option not present; that's fine.
}
}
}

/**
* Read the Cloudinary connection string from the environment.
*
* We use a dedicated `CLOUDINARY_E2E_URL` env var rather than the
* Cloudinary SDK's conventional `CLOUDINARY_URL` to make it explicit
* that this is test-only credentials and to avoid colliding with any
* SDK auto-bootstrap behaviour developers may rely on locally.
*
* Throws if not set so the test fails loudly rather than silently
* producing a meaningless pass/fail.
*
* @return {string} The cloudinary:// URL.
*/
function getCloudinaryUrlFromEnv() {
const url = process.env.CLOUDINARY_E2E_URL;
if ( ! url || ! url.startsWith( 'cloudinary://' ) ) {
throw new Error(

Check failure on line 111 in tests/e2e/utils/wizard.js

View workflow job for this annotation

GitHub Actions / E2E (Playwright)

[chromium] › tests/e2e/wizard-setup.spec.js:119:2 › Cloudinary wizard setup › persists connection state so the wizard does not reappear

2) [chromium] › tests/e2e/wizard-setup.spec.js:119:2 › Cloudinary wizard setup › persists connection state so the wizard does not reappear Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec. at utils/wizard.js:111 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 | } at getCloudinaryUrlFromEnv (/home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/utils/wizard.js:111:9) at /home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/wizard-setup.spec.js:123:25

Check failure on line 111 in tests/e2e/utils/wizard.js

View workflow job for this annotation

GitHub Actions / E2E (Playwright)

[chromium] › tests/e2e/wizard-setup.spec.js:119:2 › Cloudinary wizard setup › persists connection state so the wizard does not reappear

2) [chromium] › tests/e2e/wizard-setup.spec.js:119:2 › Cloudinary wizard setup › persists connection state so the wizard does not reappear Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec. at utils/wizard.js:111 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 | } at getCloudinaryUrlFromEnv (/home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/utils/wizard.js:111:9) at /home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/wizard-setup.spec.js:123:25

Check failure on line 111 in tests/e2e/utils/wizard.js

View workflow job for this annotation

GitHub Actions / E2E (Playwright)

[chromium] › tests/e2e/wizard-setup.spec.js:119:2 › Cloudinary wizard setup › persists connection state so the wizard does not reappear

2) [chromium] › tests/e2e/wizard-setup.spec.js:119:2 › Cloudinary wizard setup › persists connection state so the wizard does not reappear Error: CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec. at utils/wizard.js:111 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 | } at getCloudinaryUrlFromEnv (/home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/utils/wizard.js:111:9) at /home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/wizard-setup.spec.js:123:25

Check failure on line 111 in tests/e2e/utils/wizard.js

View workflow job for this annotation

GitHub Actions / E2E (Playwright)

[chromium] › tests/e2e/wizard-setup.spec.js:72:2 › Cloudinary wizard setup › accepts a valid connection string and completes the wizard

1) [chromium] › tests/e2e/wizard-setup.spec.js:72:2 › Cloudinary wizard setup › accepts a valid connection string and completes the wizard Retry #2 ─────────────────────────────────────────────────────────────────────────────────────── Error: CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec. at utils/wizard.js:111 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 | } at getCloudinaryUrlFromEnv (/home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/utils/wizard.js:111:9) at /home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/wizard-setup.spec.js:76:25

Check failure on line 111 in tests/e2e/utils/wizard.js

View workflow job for this annotation

GitHub Actions / E2E (Playwright)

[chromium] › tests/e2e/wizard-setup.spec.js:72:2 › Cloudinary wizard setup › accepts a valid connection string and completes the wizard

1) [chromium] › tests/e2e/wizard-setup.spec.js:72:2 › Cloudinary wizard setup › accepts a valid connection string and completes the wizard Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec. at utils/wizard.js:111 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 | } at getCloudinaryUrlFromEnv (/home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/utils/wizard.js:111:9) at /home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/wizard-setup.spec.js:76:25

Check failure on line 111 in tests/e2e/utils/wizard.js

View workflow job for this annotation

GitHub Actions / E2E (Playwright)

[chromium] › tests/e2e/wizard-setup.spec.js:72:2 › Cloudinary wizard setup › accepts a valid connection string and completes the wizard

1) [chromium] › tests/e2e/wizard-setup.spec.js:72:2 › Cloudinary wizard setup › accepts a valid connection string and completes the wizard Error: CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec. at utils/wizard.js:111 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 | } at getCloudinaryUrlFromEnv (/home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/utils/wizard.js:111:9) at /home/runner/work/cloudinary_wordpress/cloudinary_wordpress/tests/e2e/wizard-setup.spec.js:76:25
'CLOUDINARY_E2E_URL env var must be set to a valid cloudinary:// connection string before running the wizard e2e spec.'
);
}
return url;
}

/**
* Navigate the admin browser to the wizard screen.
*
* We hit the wizard URL directly so the test does not depend on the
* "not connected → wizard" redirect.
*
* @param {Object} admin Admin fixture from @wordpress/e2e-test-utils-playwright.
*/
async function visitWizard( admin ) {
await admin.visitAdminPage( 'admin.php', 'page=cloudinary&section=wizard' );
}

module.exports = {
getCliContainer,
getCloudinaryUrlFromEnv,
resetCloudinaryConnection,
visitWizard,
wpCli,
};
Loading
Loading