// Playwright (and the image helpers pngjs / pixelmatch) for the SW browser tests. Playwright is not a repository // dependency, so the first location that has it is used: // 1. SW_PLAYWRIGHT_PATH the playwright package directory (or its entry file) // 2. SW_RUNTIME_MODULES a node_modules directory that contains playwright // 3. HM_PLAYWRIGHT_DIR a project directory whose node_modules contains playwright (scripts/verify-quality-runtime.mjs) // 4. this repository node_modules/playwright (npm i -D playwright, not committed) // 5. the Codex runtime /.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules // 6. global npm `npm root -g` // A location set by an environment variable must exist: a wrong value fails instead of falling back silently. import { createRequire } from 'node:module' import { execFileSync } from 'node:child_process' import { existsSync } from 'node:fs' import os from 'node:os' import path from 'node:path' const nodeModulesOf = file => { const parts = path.resolve(file).split(path.sep) const index = parts.lastIndexOf('node_modules') return index < 0 ? null : parts.slice(0, index + 1).join(path.sep) } const fromNodeModules = dir => createRequire(path.join(dir, 'index.js')) const globalRoot = () => { try { return execFileSync('npm', ['root', '-g'], { encoding: 'utf8', shell: process.platform === 'win32', stdio: ['ignore', 'pipe', 'ignore'] }).trim() } catch { return null } } function candidates() { const list = [] const { SW_PLAYWRIGHT_PATH: entry, SW_RUNTIME_MODULES: modules, HM_PLAYWRIGHT_DIR: project } = process.env if (entry) { const modulesDir = nodeModulesOf(entry) list.push({ source: `SW_PLAYWRIGHT_PATH=${entry}`, explicit: true, entry: path.resolve(entry), require: modulesDir ? fromNodeModules(modulesDir) : createRequire(import.meta.url) }) } if (modules) list.push({ source: `SW_RUNTIME_MODULES=${modules}`, explicit: true, require: fromNodeModules(path.resolve(modules)) }) if (project) list.push({ source: `HM_PLAYWRIGHT_DIR=${project}`, explicit: true, require: createRequire(path.join(path.resolve(project), 'package.json')) }) list.push({ source: 'repository node_modules', require: createRequire(import.meta.url) }) const codex = path.join(os.homedir(), '.cache', 'codex-runtimes', 'codex-primary-runtime', 'dependencies', 'node', 'node_modules') if (existsSync(codex)) list.push({ source: `Codex runtime ${codex}`, require: fromNodeModules(codex) }) const global = globalRoot() if (global && existsSync(global)) list.push({ source: `global npm ${global}`, require: fromNodeModules(global) }) return list } let resolved function resolve() { if (resolved) return resolved const tried = [] for (const candidate of candidates()) { try { const entry = candidate.entry ?? candidate.require.resolve('playwright') if (candidate.entry && !existsSync(candidate.entry)) throw new Error('path does not exist') resolved = { ...candidate, entry } return resolved } catch (error) { tried.push(`${candidate.source}: ${error.message.split('\n')[0]}`) if (candidate.explicit) break } } throw new Error(`Playwright not found for the SW browser tests. Set SW_RUNTIME_MODULES to a node_modules directory that contains playwright (or SW_PLAYWRIGHT_PATH to the playwright package).\n tried:\n - ${tried.join('\n - ')}`) } /** The playwright module (`{ chromium, firefox, ... }`). */ export const loadPlaywright = () => resolve().require(resolve().entry) /** * A `require` rooted where Playwright was found, for the other runtime modules the tests use (pngjs, pixelmatch). * `require('playwright')` through it returns the same module as `loadPlaywright()`. */ export function runtimeRequire() { const { require, entry } = resolve() const wrapped = id => (id === 'playwright' ? require(entry) : require(id)) wrapped.resolve = (id, options) => (id === 'playwright' ? entry : require.resolve(id, options)) return wrapped } /** Where Playwright came from (for reports). */ export const playwrightSource = () => resolve().source