From 243019103bbeade83cce25626276136182c45595 Mon Sep 17 00:00:00 2001 From: Hug0-Drelon Date: Tue, 2 Jun 2026 17:24:04 +0200 Subject: [PATCH 1/2] Create build and publish process. --- .github/workflows/publish-npm.yml | 58 ++++++++++++++++++++++++ .github/workflows/static-analysis.yml | 26 ++++++++--- .gitignore | 1 + README.md | 65 ++++++++++++++++++++++----- build.mjs | 38 ++++++++++++++++ package.json | 31 +++++++++---- src/config/index.js | 6 +-- src/index.js | 42 ++++------------- src/setup/global.setup.js | 6 ++- src/users/index.js | 10 ++--- src/xliff/index.js | 3 +- 11 files changed, 214 insertions(+), 72 deletions(-) create mode 100644 .github/workflows/publish-npm.yml create mode 100644 build.mjs diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..64edf62 --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,58 @@ +name: Publish to npm + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + static-analysis: + uses: ./.github/workflows/static-analysis.yml + + publish: + name: Publish @wpsyntex/e2e-test-utils + + needs: static-analysis + + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write + + steps: + - name: Check out the source code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name || github.ref }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: npm install --omit=peer + + - name: Set bleeding edge version + if: github.event_name == 'workflow_dispatch' + # Derive a unique prerelease from package.json + commit SHA so manual + # publishes never overwrite `latest` and do not require a git version bump. + run: | + BASE_VERSION=$( node -p "require('./package.json').version" ) + DEV_VERSION="${BASE_VERSION}-dev.$( git rev-parse --short HEAD )" + npm version "${DEV_VERSION}" --no-git-tag-version + echo "Publishing bleeding edge version: ${DEV_VERSION}" + + - name: Publish stable release to npm + if: github.event_name == 'release' + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish bleeding edge to npm + if: github.event_name == 'workflow_dispatch' + run: npm publish --provenance --access public --tag next + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 9b85653..a1817f9 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -6,10 +6,11 @@ on: pull_request: branches: master workflow_dispatch: + workflow_call: jobs: js: - name: ESLint + name: Build and Test runs-on: ubuntu-latest @@ -17,9 +18,22 @@ jobs: - name: Check out the source code uses: actions/checkout@v4 - - name: Run ESLint - uses: polylang/actions/eslint@main + - name: Setup Node.js + uses: actions/setup-node@v4 with: - do-js: true - js-path: './src' - do-style: false + node-version: 22 + + - name: Install dependencies + run: npm install + + - name: Install peer dependencies + run: npm install @playwright/test @wordpress/e2e-test-utils-playwright + + - name: Build package + run: npm run build + + - name: Run ESLint + run: npm run lint:js + + - name: Verify package entry point (smoke test) + run: node -e "require('@wpsyntex/e2e-test-utils')" diff --git a/.gitignore b/.gitignore index 32e599d..02e4d81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Folders, allowing symbolic links for tmp/ node_modules/ +build/ # Files .DS_Store diff --git a/README.md b/README.md index 08e9685..e33d195 100644 --- a/README.md +++ b/README.md @@ -5,30 +5,47 @@ This package provides shared E2E testing utilities for Polylang projects. ## 📦 Installation ```bash -npm install --save-dev polylang/e2e-test-utils +npm install --save-dev @wpsyntex/e2e-test-utils @playwright/test @wordpress/e2e-test-utils-playwright ``` +Peer dependencies (required in consuming projects): + +- `@playwright/test` +- `@wordpress/e2e-test-utils-playwright` + +When installing from the GitHub repository, the `prepare` script builds the package automatically. When installing from npm, the published tarball already includes the compiled `build/` output. + +## 🛠 Development + +When working on this package locally: + +```bash +npm install --omit=peer +npm run build +npm install @playwright/test @wordpress/e2e-test-utils-playwright +npm run lint:js +node -e "require('@wpsyntex/e2e-test-utils')" +``` + +Run `npm run build` after changing files in `src/`. The `build/` directory is not committed; it is generated by `prepare` on install and by `prepublishOnly` before publishing. + ## 🚀 Usage ### ⚙️ Playwright configuration -Create a `playwright.config.js` file in your project's root: +Create a `playwright.config.mjs` file in your E2E test directory: ```javascript import { getPlaywrightConfig } from '@wpsyntex/e2e-test-utils'; export default getPlaywrightConfig( { - // Override any default configuration here - use: { - // Custom use options - globalSetup: require.resolve( 'path-to-your/global.setup.js' ), // Falls back to the packaged one. - }, - webServer: { - // Custom webServer options - } + globalSetup: './global.setup.mjs', + globalTeardown: './global.teardown.mjs', } ); ``` +`getPlaywrightConfig()` merges your options with sensible defaults (Chromium, HTML reporter, `wp-env` web server, storage state path, etc.). The default `globalSetup` points to the packaged `build/setup/global.setup.cjs` when not overridden. + #### Configuration Options The `getPlaywrightConfig` function accepts an options object that can override any of the default configuration: @@ -214,3 +231,31 @@ const terms = await getAllTerms( requestUtils, 'category' ); ``` All these functions are designed to work with the Gutenberg request utils object and follow REST API patterns for interacting with Polylang's functionality. They provide a comprehensive set of tools for managing languages, settings, and taxonomies in E2E tests. + +## 📤 Publishing + +The **Publish to npm** workflow reuses **Static Analysis** (build, lint, smoke test) before publishing. + +Repository secret required: `NPM_TOKEN` (npm access token with publish rights for `@wpsyntex`). + +### Stable releases (`latest`) + +Triggered when a GitHub release is published: + +1. Bump the version in `package.json`. +2. Create a git tag matching the version (e.g. `v0.2.0`). +3. Publish a GitHub release from that tag. + +The workflow checks out the release tag and publishes that exact version to the `latest` dist-tag. + +### Bleeding edge (`next`) + +Triggered manually via `workflow_dispatch`. The workflow checks out the selected branch (usually `master`), appends `-dev.` to the version in `package.json` (e.g. `0.2.0-dev.a1b2c3d`), and publishes to the `next` dist-tag without modifying git. + +Install the bleeding edge build in a consumer project: + +```bash +npm install --save-dev @wpsyntex/e2e-test-utils@next +``` + +Stable installs (`npm install @wpsyntex/e2e-test-utils`) are unaffected. diff --git a/build.mjs b/build.mjs new file mode 100644 index 0000000..f7f9a03 --- /dev/null +++ b/build.mjs @@ -0,0 +1,38 @@ +/** + * Builds the package for publication and consumption by Node / Playwright. + * + * Source files in `src/` are compiled to CommonJS (`.cjs`) in `build/`, mirroring + * the source directory layout. Peer dependencies (`@playwright/test`, + * `@wordpress/e2e-test-utils-playwright`) and Node built-ins are not bundled. + * + * @see package.json `main` and `exports` fields. + */ +import * as esbuild from 'esbuild'; +import { globSync } from 'glob'; +import fs from 'fs'; + +const entryPoints = globSync( 'src/**/*.js' ); + +await esbuild.build( { + entryPoints, + outdir: 'build', + outbase: 'src', + // Compile each file individually so paths like `build/setup/global.setup.cjs` + // remain addressable (e.g. the default `globalSetup` path in getPlaywrightConfig). + bundle: false, + platform: 'node', + format: 'cjs', + outExtension: { '.js': '.cjs' }, +} ); + +// esbuild keeps `.js` extensions in relative require paths; rewrite them to `.cjs`. +for ( const file of globSync( 'build/**/*.cjs' ) ) { + let content = fs.readFileSync( file, 'utf8' ); + + content = content.replace( + /require\((['"])(\.\.?\/[^'"]+)\.js\1\)/g, + 'require($1$2.cjs$1)' + ); + + fs.writeFileSync( file, content ); +} diff --git a/package.json b/package.json index f697e77..0451b5a 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,21 @@ { "name": "@wpsyntex/e2e-test-utils", - "version": "0.1.0", + "version": "0.2.0", "description": "End-to-end test utilities for Polylang projects.", - "type": "module", - "main": "./src/index.js", + "main": "./build/index.cjs", "exports": { ".": { - "require": "./src/index.js", - "import": "./src/index.js", - "default": "./src/index.js" + "default": "./build/index.cjs" } }, - "module": "./src/index.js", + "files": [ + "build" + ], "scripts": { + "build": "node build.mjs", + "clean": "rimraf build", + "prepublishOnly": "npm run clean && npm run build", + "prepare": "npm run build", "lint:js": "wp-scripts lint-js" }, "keywords": [ @@ -31,10 +34,20 @@ "bugs": { "url": "https://github.com/polylang/e2e-test-utils/issues" }, + "engines": { + "node": ">=18.12.0" + }, + "publishConfig": { + "access": "public" + }, "peerDependencies": { - "@playwright/test": ">=1" + "@playwright/test": ">=1", + "@wordpress/e2e-test-utils-playwright": ">=1" }, "devDependencies": { - "@wordpress/scripts": "^30.14.1" + "@wordpress/scripts": "^30.14.1", + "esbuild": "^0.25.0", + "glob": "^11.0.0", + "rimraf": "^6.0.1" } } diff --git a/src/config/index.js b/src/config/index.js index faff929..7ea3107 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,9 +1,5 @@ import { defineConfig, devices } from '@playwright/test'; import path from 'path'; -import { fileURLToPath } from 'url'; - -const __filename = fileURLToPath( import.meta.url ); -const __dirname = path.dirname( __filename ); const STORAGE_STATE_PATH = process.env.STORAGE_STATE_PATH || @@ -20,7 +16,7 @@ const STORAGE_STATE_PATH = export function getPlaywrightConfig( options = {} ) { return defineConfig( { testDir: './specs', - globalSetup: path.resolve( __dirname, '../setup/global.setup.js' ), + globalSetup: path.resolve( __dirname, '../setup/global.setup.cjs' ), fullyParallel: false, forbidOnly: !! process.env.CI, retries: process.env.CI ? 2 : 0, diff --git a/src/index.js b/src/index.js index 602fbb8..35fdac6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,44 +1,20 @@ -import { +export { getPlaywrightConfig } from './config/index.js'; +export { default as globalSetup } from './setup/global.setup.js'; +export { getAllLanguages, deleteAllLanguages, getLanguage, createLanguage, deleteLanguage, } from './languages/index.js'; -import { saveTranslations } from './translations/index.js'; -import { resetAllSettings, setSetting, getSettings } from './settings/index.js'; -import { - getAllTerms, - deleteAllTerms, - getTermBySlug, - createTerm, -} from './taxonomies/index.js'; -import { fillInXliffExportForm, getXliffRegex } from './xliff/index.js'; -import { createTranslator, switchToUser } from './users/index.js'; -import { getDownload, getStringFromFile } from './downloads/index.js'; -import { getPlaywrightConfig } from './config/index.js'; -import globalSetup from './setup/global.setup.js'; - +export { saveTranslations } from './translations/index.js'; +export { resetAllSettings, setSetting, getSettings } from './settings/index.js'; export { - getAllLanguages, - deleteAllLanguages, - getLanguage, - createLanguage, - deleteLanguage, - saveTranslations, - resetAllSettings, - setSetting, - getSettings, getAllTerms, deleteAllTerms, getTermBySlug, createTerm, - fillInXliffExportForm, - getDownload, - getXliffRegex, - createTranslator, - switchToUser, - getStringFromFile, - getPlaywrightConfig, - globalSetup, -}; +} from './taxonomies/index.js'; +export { fillInXliffExportForm, getXliffRegex } from './xliff/index.js'; +export { createTranslator, switchToUser } from './users/index.js'; +export { getDownload, getStringFromFile } from './downloads/index.js'; diff --git a/src/setup/global.setup.js b/src/setup/global.setup.js index c55214c..da5efbf 100644 --- a/src/setup/global.setup.js +++ b/src/setup/global.setup.js @@ -1,5 +1,3 @@ -import { request } from '@playwright/test'; -import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'; import { deleteAllLanguages } from '../languages/index.js'; import { resetAllSettings } from '../settings/index.js'; @@ -10,6 +8,10 @@ import { resetAllSettings } from '../settings/index.js'; * @param {Object} config Playwright config object. */ export default async function globalSetup( config ) { + const { RequestUtils } = await import( + '@wordpress/e2e-test-utils-playwright' + ); + const { request } = await import( '@playwright/test' ); const { storageState, baseURL } = config.projects[ 0 ].use; const storageStatePath = typeof storageState === 'string' ? storageState : undefined; diff --git a/src/users/index.js b/src/users/index.js index c96bd61..8d99e4e 100644 --- a/src/users/index.js +++ b/src/users/index.js @@ -1,13 +1,10 @@ // @ts-check -import { - expect, - Admin, - RequestUtils, -} from '@wordpress/e2e-test-utils-playwright'; import { execSync } from 'child_process'; /** * @typedef {import('@playwright/test').Page} Page + * @typedef {import('@wordpress/e2e-test-utils-playwright').Admin} Admin + * @typedef {import('@wordpress/e2e-test-utils-playwright').RequestUtils} RequestUtils * @typedef {Object} User * @property {string} username The user name. * @property {string} password The user's password. @@ -55,6 +52,7 @@ export async function createTranslator( langSlugs, userName = '' ) { * @return {Promise} Promise resolving to the `Page` object. */ export async function switchToUser( user, admin, requestUtils ) { + const { expect } = await import( '@wordpress/e2e-test-utils-playwright' ); const translatorContext = await admin.browser.newContext( { baseURL: requestUtils.baseURL, } ); @@ -66,7 +64,7 @@ export async function switchToUser( user, admin, requestUtils ) { await page.getByRole( 'button', { name: 'Log In' } ).click(); await page.waitForURL( '**/wp-admin/**' ); - expect( + await expect( page.getByRole( 'menuitem', { name: `Howdy, ${ user.username }`, } ) diff --git a/src/xliff/index.js b/src/xliff/index.js index fadd659..4dfc96a 100644 --- a/src/xliff/index.js +++ b/src/xliff/index.js @@ -1,5 +1,4 @@ // @ts-check -import { expect } from '@wordpress/e2e-test-utils-playwright'; /** * Fills in the XLIFF export form. @@ -15,6 +14,8 @@ export const fillInXliffExportForm = async ( page, { postId, postTitle, languageName } ) => { + const { expect } = await import( '@wordpress/e2e-test-utils-playwright' ); + await expect( page.locator( `#post-${ postId }` ) ).toBeVisible(); await page.getByRole( 'checkbox', { name: postTitle } ).check(); From 57b89ce67e5ecf5df860f5b802745a0970c488fe Mon Sep 17 00:00:00 2001 From: Hug0-Drelon Date: Wed, 3 Jun 2026 06:41:10 +0200 Subject: [PATCH 2/2] Rollback package.json project version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0451b5a..9ba78fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@wpsyntex/e2e-test-utils", - "version": "0.2.0", + "version": "0.1.0", "description": "End-to-end test utilities for Polylang projects.", "main": "./build/index.cjs", "exports": {