From 560318a30cbde7ca77b4db02b226803a16092a3a Mon Sep 17 00:00:00 2001 From: Utkarsh Patel Date: Fri, 24 Apr 2026 15:42:01 +0530 Subject: [PATCH] Add Playwright e2e hello-world test with CI job Introduces @wordpress/e2e-test-utils-playwright (the same utilities Gutenberg uses) so future e2e coverage can build on a familiar, battle-tested foundation. - tests/e2e: playwright config, global auth setup via RequestUtils, hello-world spec covering the front page and authenticated wp-admin - package.json: test:e2e / test:e2e:debug scripts and devDeps - ci.yml: dedicated e2e job that boots wp-env (using .wp-env.json) and uploads Playwright artifacts on failure - .gitignore: ignore local Playwright output directories --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++++++++++++ .gitignore | 3 +++ package-lock.json | 34 +++++++++++++-------------- package.json | 6 ++++- tests/e2e/global-setup.js | 42 ++++++++++++++++++++++++++++++++++ tests/e2e/hello-world.spec.js | 31 +++++++++++++++++++++++++ tests/e2e/playwright.config.js | 38 ++++++++++++++++++++++++++++++ 7 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 tests/e2e/global-setup.js create mode 100644 tests/e2e/hello-world.spec.js create mode 100644 tests/e2e/playwright.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d585a97..84d8b54c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.gitignore b/.gitignore index 109a11b6..0a408a57 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,9 @@ js/*.map tests/data/ tests/includes/ coverage/html/ +/artifacts/ +/playwright-report/ +/test-results/ # ENV files .env diff --git a/package-lock.json b/package-lock.json index 65ef53f0..c2cad5c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,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", @@ -33,6 +34,7 @@ "@wordpress/browserslist-config": "^6.34.0", "@wordpress/components": "^30.7.0", "@wordpress/data": "^10.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", @@ -7390,14 +7392,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", - "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "playwright": "1.58.2" + "playwright": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -10815,9 +10816,9 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.40.0.tgz", - "integrity": "sha512-7EMx/5R0l9mlR4s01I06x8bw7qq30VlU98T/tvYJa+ycFQK3oetkoPyiNfki2Y2SILQGjI3Mu4MSV1NPCa/mEw==", + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.44.0.tgz", + "integrity": "sha512-iUKHGH8TjW1s0cpkcHF6y/APOmy4YnwBfzdBNCITK4+4fuSZnTV7vZyzBU3adthGcBSMGQ9w8MTE2AzGLtlG3w==", "dev": true, "license": "GPL-2.0-or-later", "dependencies": { @@ -29736,14 +29737,13 @@ } }, "node_modules/playwright": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", - "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { - "playwright-core": "1.58.2" + "playwright-core": "1.59.1" }, "bin": { "playwright": "cli.js" @@ -29756,12 +29756,11 @@ } }, "node_modules/playwright-core": { - "version": "1.58.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", - "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -29780,7 +29779,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } diff --git a/package.json b/package.json index fc5a7ad0..2a610788 100644 --- a/package.json +++ b/package.json @@ -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": [ @@ -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", diff --git a/tests/e2e/global-setup.js b/tests/e2e/global-setup.js new file mode 100644 index 00000000..9a241ce9 --- /dev/null +++ b/tests/e2e/global-setup.js @@ -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(); +}; diff --git a/tests/e2e/hello-world.spec.js b/tests/e2e/hello-world.spec.js new file mode 100644 index 00000000..d7082d49 --- /dev/null +++ b/tests/e2e/hello-world.spec.js @@ -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(); + } ); +} ); diff --git a/tests/e2e/playwright.config.js b/tests/e2e/playwright.config.js new file mode 100644 index 00000000..db56306e --- /dev/null +++ b/tests/e2e/playwright.config.js @@ -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' ] }, + }, + ], +} );