[Feature]: [playwright-vscode] Add means to wrap/change test run command
🚀 Feature Request
Our app we're testing with Playwright has an environment that is set using a wrapping script. We're using a Worker-scoped fixture that needs to grab some of the app config from the environment. All things Playwright work fine at the CLI where we wrap the playwright test process, but this doesn't seem possible to do with the VSCode Playwright extension.
It is possible to set the environment using other ways, like explicitly defining them in VSCode settings.json, but that would mean maintaining two copies for each of our envs in test. Also possible is to use something like dotenv to pull in a target .env file, but in our case they aren't available/usable without the wrapper script. The script doesn't mutate files, only sets the environment for child processes.
I think this request re-raises the issue in https://github.com/microsoft/playwright/issues/22932. It should be possible to provide some better mechanism here even if it means it doesn't work for all variants of process wrappers.
Example
Ideally there would be a setting we can use to set a "parent" command, which receives the run command as final arguments.
Example: we need something like this at the CLI to get our env set for tests
APP_ENV=test pnpm with-env pnpm exec playwright test
For us, with-env lays down the environment (~100 vars) based on APP_ENV. Works great at the CLI!
I'm imagining a settings.json config to add a command prefix or override the current command used to launch tests.
Motivation
We love the vscode extension's quick test runner, being able to run specific tests from the UI in our monorepo app would be a dream.
Hi! The reasoning why we closed https://github.com/microsoft/playwright/issues/22932 is still true. Let's try to think of a different solution for your usecase.
You mention that the wrapper script only outputs the environment variables to its child process. One idea I have is that you can make that child process echo those environment variables to STDOUT, and then read them programatically from playwright.config.ts. Here's an example script for that:
// playwright.config.ts
import { execSync } from "node:child_process"
const env = JSON.parse(execSync("./wrapper.sh node -p 'JSON.stringify(process.env)'", { encoding: 'utf8' }))
process.env.WRAPPER_SET_VARIABLE = env.WRAPPER_SET_VARIABLE
...
Does that help solve your problem?
Ended up getting there somewhat following your suggestion, avoiding adding another package, and after modifying the script to optionally spew to stdout to avoid dumping to a file:
// playwright.config.ts
import { loadEnvFromProcessStdout } from 'playwright/readEnv'
await loadEnvFromProcessStdout('pnpm', ['with-env', '--stdout'])
...
// playwright/readEnv.ts
import { spawn } from 'child_process'
import readline from 'readline'
export function loadEnvFromProcessStdout(command: string, args: Array<string>): Promise<void> {
return new Promise((resolve, reject) => {
const childProcess = spawn(command, args)
const rl = readline.createInterface({
input: childProcess.stdout,
crlfDelay: Infinity,
})
rl.on('line', (line) => {
const [key, ...valueParts] = line.split('=')
if (key && valueParts.length > 0) {
const value = valueParts.join('=')
// eslint-disable-next-line no-restricted-properties
process.env[key.trim()] = value
}
})
childProcess.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Child process exited with code ${code}`))
}
})
childProcess.on('error', (err) => {
reject(err)
})
})
}
Will probably refactor, it's quite unsexy but gets us past this limitation for now.
If it gets the job done, i'd say its beautiful! Happy to hear that you've found a solution. Gonna go ahead and close the issue :)