Skip to content

Commit b56db46

Browse files
authored
Merge pull request #1164 from PatelUtkarsh/chore/e2e-hello-world
Add Playwright e2e hello-world test with CI job
2 parents 232e369 + 558797b commit b56db46

8 files changed

Lines changed: 211 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ name: CI
22

33
on:
44
push:
5+
pull_request:
56

67
jobs:
78
build:
@@ -41,3 +42,43 @@ jobs:
4142

4243
- name: Run build
4344
run: npm run build
45+
46+
e2e:
47+
name: E2E (Playwright)
48+
runs-on: ubuntu-latest
49+
steps:
50+
- name: Checkout repository
51+
uses: actions/checkout@v4
52+
53+
- name: Setup Node.js
54+
uses: actions/setup-node@v4
55+
with:
56+
node-version: '22'
57+
cache: 'npm'
58+
59+
- name: Install dependencies
60+
run: npm ci
61+
62+
- name: Install Playwright browsers
63+
run: npx playwright install --with-deps chromium
64+
65+
- name: Build assets
66+
run: npm run build
67+
68+
- name: Start wp-env
69+
run: npm run env:start
70+
71+
- name: Run E2E tests
72+
run: npm run test:e2e
73+
74+
- name: Stop wp-env
75+
if: always()
76+
run: npm run env:stop
77+
78+
- name: Upload Playwright artifacts
79+
if: failure()
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: playwright-artifacts
83+
path: artifacts/
84+
retention-days: 7

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ js/*.map
3636
tests/data/
3737
tests/includes/
3838
coverage/html/
39+
/artifacts/
40+
/playwright-report/
41+
/test-results/
3942

4043
# ENV files
4144
.env

package-lock.json

Lines changed: 2 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
"release": "release-it --no-increment",
3939
"release:ci": "release-it --ci",
4040
"release:dry": "release-it --dry-run",
41-
"prepare": "husky"
41+
"prepare": "husky",
42+
"test:e2e": "playwright test --config tests/e2e/playwright.config.js",
43+
"test:e2e:debug": "playwright test --config tests/e2e/playwright.config.js --ui"
4244
},
4345
"lint-staged": {
4446
"*.php": [
@@ -76,8 +78,10 @@
7678
"@wordpress/components": "^30.7.0",
7779
"@wordpress/data": "^10.34.0",
7880
"@wordpress/element": "^6.34.0",
81+
"@wordpress/e2e-test-utils-playwright": "^1.44.0",
7982
"@wordpress/env": "^10.12.0",
8083
"@wordpress/eslint-plugin": "^22.20.0",
84+
"@playwright/test": "^1.59.1",
8185
"@wordpress/i18n": "^6.7.0",
8286
"@wordpress/scripts": "^31.0.0",
8387
"copy-webpack-plugin": "^13.0.1",

tests/e2e/global-setup.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* External dependencies
3+
*/
4+
const { request } = require( '@playwright/test' );
5+
const { RequestUtils } = require( '@wordpress/e2e-test-utils-playwright' );
6+
const path = require( 'path' );
7+
const fs = require( 'fs' );
8+
9+
/**
10+
* Global setup: authenticate admin and persist storage state for tests.
11+
*
12+
* Uses the RequestUtils helper from @wordpress/e2e-test-utils-playwright
13+
* which is the same utility used by Gutenberg's e2e test suite.
14+
*
15+
* @param {import('@playwright/test').FullConfig} config Resolved Playwright config.
16+
*/
17+
module.exports = async function globalSetup( config ) {
18+
const { storageState, baseURL } = config.projects[ 0 ].use;
19+
const storageStatePath =
20+
typeof storageState === 'string' ? storageState : undefined;
21+
22+
if ( ! storageStatePath ) {
23+
throw new Error( 'storageState path must be a string.' );
24+
}
25+
26+
fs.mkdirSync( path.dirname( storageStatePath ), { recursive: true } );
27+
28+
const requestContext = await request.newContext( {
29+
baseURL: baseURL || 'http://localhost:8889',
30+
} );
31+
32+
const requestUtils = new RequestUtils( requestContext, {
33+
storageStatePath,
34+
user: {
35+
username: 'admin',
36+
password: 'password',
37+
},
38+
} );
39+
40+
await requestUtils.setupRest();
41+
await requestContext.dispose();
42+
};

tests/e2e/hello-world.spec.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* External dependencies
3+
*/
4+
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
5+
6+
test.describe( 'Hello World', () => {
7+
test( 'front page loads with a non-empty title', async ( { page } ) => {
8+
const response = await page.goto( '/' );
9+
10+
expect(
11+
response,
12+
'Expected a response from the home page.'
13+
).not.toBeNull();
14+
expect( response.status() ).toBeLessThan( 400 );
15+
16+
const title = await page.title();
17+
expect( title.trim().length ).toBeGreaterThan( 0 );
18+
} );
19+
20+
test( 'admin dashboard is reachable for logged-in admin', async ( {
21+
admin,
22+
page,
23+
} ) => {
24+
await admin.visitAdminPage( 'index.php' );
25+
26+
await expect(
27+
page.locator( '#wpadminbar' ),
28+
'Admin bar should render on wp-admin.'
29+
).toBeVisible();
30+
} );
31+
} );

tests/e2e/playwright.config.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* External dependencies
3+
*/
4+
const { defineConfig, devices } = require( '@playwright/test' );
5+
const path = require( 'path' );
6+
7+
const STORAGE_STATE_PATH =
8+
process.env.STORAGE_STATE_PATH ||
9+
path.join( process.cwd(), 'artifacts/storage-states/admin.json' );
10+
11+
module.exports = defineConfig( {
12+
testDir: '.',
13+
reporter: process.env.CI ? [ [ 'github' ], [ 'list' ] ] : 'list',
14+
forbidOnly: !! process.env.CI,
15+
retries: process.env.CI ? 2 : 0,
16+
workers: 1,
17+
timeout: 60_000,
18+
expect: {
19+
timeout: 10_000,
20+
},
21+
outputDir: path.join( process.cwd(), 'artifacts/test-results' ),
22+
globalSetup: require.resolve( './global-setup.js' ),
23+
use: {
24+
baseURL: process.env.WP_BASE_URL || 'http://localhost:8889',
25+
trace: 'retain-on-failure',
26+
screenshot: 'only-on-failure',
27+
video: 'retain-on-failure',
28+
storageState: STORAGE_STATE_PATH,
29+
actionTimeout: 10_000,
30+
navigationTimeout: 15_000,
31+
},
32+
projects: [
33+
{
34+
name: 'chromium',
35+
use: { ...devices[ 'Desktop Chrome' ] },
36+
},
37+
],
38+
} );

tests/e2e/plugin.spec.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* External dependencies
3+
*/
4+
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
5+
6+
test.describe( 'Cloudinary plugin', () => {
7+
test( 'is listed and active on the Plugins screen', async ( {
8+
admin,
9+
page,
10+
} ) => {
11+
await admin.visitAdminPage( 'plugins.php' );
12+
13+
const pluginRow = page
14+
.locator(
15+
'tr[data-slug="cloudinary-image-management-and-manipulation-in-the-cloud-cdn"], tr[data-plugin*="cloudinary"]'
16+
)
17+
.first();
18+
19+
await expect(
20+
pluginRow,
21+
'Cloudinary plugin row should appear on the Plugins screen.'
22+
).toBeVisible();
23+
24+
await expect(
25+
pluginRow,
26+
'Cloudinary plugin row should be marked active.'
27+
).toHaveClass( /active/ );
28+
} );
29+
30+
test( 'registers a top-level Cloudinary admin menu', async ( {
31+
admin,
32+
page,
33+
} ) => {
34+
await admin.visitAdminPage( 'index.php' );
35+
36+
const menuLink = page.locator(
37+
'#adminmenu a.toplevel_page_cloudinary'
38+
);
39+
40+
await expect(
41+
menuLink,
42+
'Cloudinary top-level menu should be registered.'
43+
).toBeVisible();
44+
45+
await menuLink.click();
46+
47+
await expect( page ).toHaveURL( /admin\.php\?page=cloudinary/ );
48+
} );
49+
} );

0 commit comments

Comments
 (0)