Skip to content
Open
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
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,43 @@ jobs:

- name: Run build
run: npm run build

e2e:
name: E2E (Playwright)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Build assets
run: npm run build

- name: Start wp-env
run: npm run env:start

- name: Run E2E tests
run: npm run test:e2e

- name: Stop wp-env
if: always()
run: npm run env:stop

- name: Upload Playwright artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: artifacts/
retention-days: 7
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ js/*.map
tests/data/
tests/includes/
coverage/html/
/artifacts/
/playwright-report/
/test-results/

# ENV files
.env
Expand Down
34 changes: 16 additions & 18 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"release": "release-it --no-increment",
"release:ci": "release-it --ci",
"release:dry": "release-it --dry-run",
"prepare": "husky"
"prepare": "husky",
"test:e2e": "playwright test --config tests/e2e/playwright.config.js",
"test:e2e:debug": "playwright test --config tests/e2e/playwright.config.js --ui"
},
"lint-staged": {
"*.php": [
Expand Down Expand Up @@ -76,8 +78,10 @@
"@wordpress/components": "^30.7.0",
"@wordpress/data": "^10.34.0",
"@wordpress/element": "^6.34.0",
"@wordpress/e2e-test-utils-playwright": "^1.44.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",
Expand Down
42 changes: 42 additions & 0 deletions tests/e2e/global-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* External dependencies
*/
const { request } = require( '@playwright/test' );
const { RequestUtils } = require( '@wordpress/e2e-test-utils-playwright' );
const path = require( 'path' );
const fs = require( 'fs' );

/**
* Global setup: authenticate admin and persist storage state for tests.
*
* Uses the RequestUtils helper from @wordpress/e2e-test-utils-playwright
* which is the same utility used by Gutenberg's e2e test suite.
*
* @param {import('@playwright/test').FullConfig} config Resolved Playwright config.
*/
module.exports = async function globalSetup( config ) {
const { storageState, baseURL } = config.projects[ 0 ].use;
const storageStatePath =
typeof storageState === 'string' ? storageState : undefined;

if ( ! storageStatePath ) {
throw new Error( 'storageState path must be a string.' );
}

fs.mkdirSync( path.dirname( storageStatePath ), { recursive: true } );

const requestContext = await request.newContext( {
baseURL: baseURL || 'http://localhost:8889',
} );

const requestUtils = new RequestUtils( requestContext, {
storageStatePath,
user: {
username: 'admin',
password: 'password',
},
} );

await requestUtils.setupRest();
await requestContext.dispose();
};
31 changes: 31 additions & 0 deletions tests/e2e/hello-world.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* External dependencies
*/
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );

test.describe( 'Hello World', () => {
test( 'front page loads with a non-empty title', async ( { page } ) => {
const response = await page.goto( '/' );

expect(
response,
'Expected a response from the home page.'
).not.toBeNull();
expect( response.status() ).toBeLessThan( 400 );

const title = await page.title();
expect( title.trim().length ).toBeGreaterThan( 0 );
} );

test( 'admin dashboard is reachable for logged-in admin', async ( {
admin,
page,
} ) => {
await admin.visitAdminPage( 'index.php' );

await expect(
page.locator( '#wpadminbar' ),
'Admin bar should render on wp-admin.'
).toBeVisible();
} );
} );
38 changes: 38 additions & 0 deletions tests/e2e/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* External dependencies
*/
const { defineConfig, devices } = require( '@playwright/test' );
const path = require( 'path' );

const STORAGE_STATE_PATH =
process.env.STORAGE_STATE_PATH ||
path.join( process.cwd(), 'artifacts/storage-states/admin.json' );

module.exports = defineConfig( {
testDir: '.',
reporter: process.env.CI ? [ [ 'github' ], [ 'list' ] ] : 'list',
forbidOnly: !! process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1,
timeout: 60_000,
expect: {
timeout: 10_000,
},
outputDir: path.join( process.cwd(), 'artifacts/test-results' ),
globalSetup: require.resolve( './global-setup.js' ),
use: {
baseURL: process.env.WP_BASE_URL || 'http://localhost:8889',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
storageState: STORAGE_STATE_PATH,
actionTimeout: 10_000,
navigationTimeout: 15_000,
},
projects: [
{
name: 'chromium',
use: { ...devices[ 'Desktop Chrome' ] },
},
],
} );