Files
HMDesign/packages/sw/tests/playwright-runtime.mjs
T

77 lines
4.0 KiB
JavaScript

// 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 <home>/.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