diff --git a/bin/get-playwright-version.js b/bin/get-playwright-version.js new file mode 100644 index 0000000..df2967f --- /dev/null +++ b/bin/get-playwright-version.js @@ -0,0 +1,46 @@ +'use strict'; + +/** + * Resolves the installed @playwright/test version for CI caching. + * + * Used by polylang/actions/e2e to build the Playwright browser cache key. Must be + * run from the consumer repository root (where npm ci has installed dependencies). + * + * Resolution order: + * 1. node_modules/@playwright/test/package.json (preferred — matches installed binaries) + * 2. package-lock.json (npm lockfile v2/v3 packages path, or legacy dependencies) + * + * @example + * PLAYWRIGHT_VERSION=$(node "${{ github.action_path }}/../bin/get-playwright-version.js") + * + * Output: + * - Prints the semver string to stdout on success. + * - Exits with code 1 and writes an error message to stderr when the version cannot be determined. + */ + +const tryRequire = ( id ) => { + try { + return require( id ); + } catch { + return null; + } +}; + +const fromPackage = tryRequire( '@playwright/test/package.json' ); + +if ( fromPackage?.version ) { + process.stdout.write( fromPackage.version ); + process.exit( 0 ); +} + +const lock = tryRequire( './package-lock.json' ); +const fromLock = lock?.packages?.['node_modules/@playwright/test']?.version + || lock?.dependencies?.['@playwright/test']?.version + || ''; + +if ( ! fromLock ) { + process.stderr.write( 'Could not determine @playwright/test version.' ); + process.exit( 1 ); +} + +process.stdout.write( fromLock ); diff --git a/bin/save-wp-env-docker-images.js b/bin/save-wp-env-docker-images.js new file mode 100644 index 0000000..64e195f --- /dev/null +++ b/bin/save-wp-env-docker-images.js @@ -0,0 +1,128 @@ +'use strict'; + +/** + * Save wp-env Docker images to a tarball for GitHub Actions caching. + * + * Used by polylang/actions/e2e after `wp-env start`. Resolves the wp-env work + * directory via `wp-env status --json`, then saves images from its docker-compose.yml. + * + * @example + * node "${{ github.action_path }}/../bin/save-wp-env-docker-images.js" + * node "${{ github.action_path }}/../bin/save-wp-env-docker-images.js" --config=.wp-env.json + * node "${{ github.action_path }}/../bin/save-wp-env-docker-images.js" wp-env-image.tar + * + * Options: + * - --config= Custom wp-env config file (same as wp-env --config). + * - First non-flag argument: output tarball path (default: wp-env-image.tar). + * + * Output: + * - Writes the tarball to the output path. + * - Exits with code 1 when the install path or images cannot be resolved. + */ + +const { execFileSync } = require( 'child_process' ); +const fs = require( 'fs' ); +const path = require( 'path' ); + +const DEFAULT_OUTPUT = 'wp-env-image.tar'; +const COMPOSE_FILENAME = 'docker-compose.yml'; + +/** + * @param {string} configPath + * @return {string} + */ +const getInstallPath = ( configPath ) => { + let output; + + try { + output = execFileSync( + 'npm', + [ 'run', 'wp-env', '--', 'status', '--json', ...( configPath ? [ `--config=${ configPath }` ] : [] ) ], + { encoding: 'utf8' } + ); + } catch { + process.stderr.write( 'Could not resolve wp-env install path.\n' ); + process.exit( 1 ); + } + + let status; + + try { + status = JSON.parse( output.trim() ); + } catch { + process.stderr.write( 'Could not parse wp-env status output.\n' ); + process.exit( 1 ); + } + + if ( ! status?.installPath ) { + process.stderr.write( 'wp-env status did not return an install path.\n' ); + process.exit( 1 ); + } + + return status.installPath; +}; + +/** + * @param {string} composeFile + * @return {string[]} + */ +const getComposeImageIds = ( composeFile ) => { + try { + const output = execFileSync( + 'docker', + [ 'compose', '-f', composeFile, 'images', '-q' ], + { encoding: 'utf8' } + ); + + return output + .split( '\n' ) + .map( ( line ) => line.trim() ) + .filter( Boolean ); + } catch { + return []; + } +}; + +/** + * @param {string[]} argv + * @return {{ configPath: string, outputTar: string }} + */ +const parseArgs = ( argv ) => { + let configPath = process.env.WP_ENV_CONFIG_PATH || ''; + let outputTar = DEFAULT_OUTPUT; + + for ( const arg of argv ) { + if ( arg.startsWith( '--config=' ) ) { + configPath = arg.slice( '--config='.length ); + continue; + } + + if ( ! arg.startsWith( '--' ) ) { + outputTar = arg; + } + } + + return { configPath, outputTar }; +}; + +const { configPath, outputTar } = parseArgs( process.argv.slice( 2 ) ); +const installPath = getInstallPath( configPath ); +const composeFile = path.join( installPath, COMPOSE_FILENAME ); + +if ( ! fs.existsSync( composeFile ) ) { + process.stderr.write( `No docker-compose.yml found at ${ composeFile }.\n` ); + process.exit( 1 ); +} + +const imageIds = [ ...new Set( getComposeImageIds( composeFile ) ) ]; + +if ( imageIds.length === 0 ) { + process.stderr.write( 'No wp-env Docker images found to cache.\n' ); + process.exit( 1 ); +} + +execFileSync( + 'docker', + [ 'save', ...imageIds, '-o', outputTar ], + { stdio: 'inherit' } + ); diff --git a/e2e/action.yml b/e2e/action.yml index 5c1cbf5..e143a1d 100644 --- a/e2e/action.yml +++ b/e2e/action.yml @@ -8,6 +8,16 @@ inputs: required: false type: string default: 'no-external-cache-key' + wp-env-config-path: + description: Path to a custom wp-env config file passed to wp-env --config. Default is '' (wp-env default discovery). + required: false + type: string + default: '' + playwright-cmd: + description: CLI command to run playwright tests. Default is 'npm run test:e2e'. + required: false + type: string + default: 'npm run test:e2e' runs: using: 'composite' @@ -18,6 +28,11 @@ runs: with: node-version: 22 + - name: Get Node.js version + id: node-version + run: echo "NODE_VERSION=$(node -v)" >> "$GITHUB_OUTPUT" + shell: bash + - name: Set up package-lock.json run: | npm install --package-lock-only --no-audit @@ -28,7 +43,7 @@ runs: uses: actions/cache@v4 with: path: '**/node_modules' - key: node_modules-${{ runner.os }}-${{ runner.arch }}-${{ steps.node-version.outputs.NODE_VERSION }}-${{ hashFiles('package-lock.json') }} + key: node_modules-${{ runner.os }}-${{ runner.arch }}-${{ steps.node-version.outputs.NODE_VERSION }}-${{ hashFiles('package.json', 'package-lock.json') }} - name: Install dependencies if: ${{ steps.cache-node_modules.outputs.cache-hit != 'true' }} @@ -56,7 +71,8 @@ runs: - name: Get installed Playwright version id: playwright-version run: | - echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package-lock.json').packages['node_modules/@playwright/test'].version)")" >> $GITHUB_ENV + PLAYWRIGHT_VERSION=$(node "${{ github.action_path }}/../bin/get-playwright-version.js") + echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> "$GITHUB_ENV" shell: bash - name: Cache Playwright binaries @@ -66,6 +82,8 @@ runs: path: | ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} + restore-keys: | + ${{ runner.os }}-playwright- - name: Install Playwright binaries # Only Chromium for the moment. @@ -85,7 +103,7 @@ runs: uses: actions/cache@v4 with: path: wp-env-image.tar - key: ${{ runner.os }}-wp-env-${{ env.WP_VERSION }}-${{ inputs.container-cache-key }} + key: ${{ runner.os }}-wp-env-${{ env.WP_VERSION }}-${{ hashFiles('.wp-env.json', '.wp-env.override.json', inputs.wp-env-config-path) }}-${{ inputs.container-cache-key }} - name: Load cached Docker image (if any) if: steps.docker-cache.outputs.cache-hit == 'true' @@ -95,21 +113,25 @@ runs: - name: Install WordPress and start the server run: | - npm run wp-env start + if [ -n "${{ inputs.wp-env-config-path }}" ]; then + npm run wp-env start -- --config="${{ inputs.wp-env-config-path }}" + else + npm run wp-env start + fi shell: bash - env: - WP_ENV_PHP_VERSION: '8.0' - name: Save Docker image to cache if: steps.docker-cache.outputs.cache-hit != 'true' run: | - docker image ls - docker save $(docker images --format '{{.Repository}}:{{.Tag}}') -o wp-env-image.tar + if [ -n "${{ inputs.wp-env-config-path }}" ]; then + node "${{ github.action_path }}/../bin/save-wp-env-docker-images.js" --config="${{ inputs.wp-env-config-path }}" + else + node "${{ github.action_path }}/../bin/save-wp-env-docker-images.js" + fi shell: bash - - name: Run Playwright tests - run: | - npm run test:e2e + - name: Run Playwright tests with WordPress ${{ env.WP_VERSION }} + run: ${{ inputs.playwright-cmd }} shell: bash - name: Upload Playwright report