Skip to content
Merged
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
58 changes: 58 additions & 0 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
@@ -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 }}
26 changes: 20 additions & 6 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,34 @@ on:
pull_request:
branches: master
workflow_dispatch:
workflow_call:

jobs:
js:
name: ESLint
name: Build and Test

runs-on: ubuntu-latest

steps:
- 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')"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Folders, allowing symbolic links for tmp/
node_modules/
build/

# Files
.DS_Store
Expand Down
65 changes: 55 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.<git-sha>` 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.
38 changes: 38 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -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 );
}
29 changes: 21 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
"name": "@wpsyntex/e2e-test-utils",
"version": "0.1.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": [
Expand All @@ -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"
}
}
6 changes: 1 addition & 5 deletions src/config/index.js
Original file line number Diff line number Diff line change
@@ -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 ||
Expand All @@ -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,
Expand Down
42 changes: 9 additions & 33 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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';
6 changes: 4 additions & 2 deletions src/setup/global.setup.js
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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;
Expand Down
Loading
Loading