1515use PlaywrightPHP \Support \RingBuffer ;
1616use Psr \Log \LoggerInterface ;
1717use Psr \Log \NullLogger ;
18+ use Symfony \Component \Process \ExecutableFinder ;
1819use Symfony \Component \Process \InputStream ;
1920use Symfony \Component \Process \Process ;
2021
@@ -48,6 +49,24 @@ public function start(array $command, ?string $cwd = null, array $env = [], ?flo
4849 }
4950
5051 try {
52+ // Proactively validate that the executable exists to distinguish
53+ // between a true launch failure (unknown command) and a short-lived
54+ // process that exits quickly with a non-zero code (which callers
55+ // may want to handle via wait/exit checks).
56+ $ executable = $ command [0 ] ?? '' ;
57+ if ('' === $ executable ) {
58+ throw new ProcessLaunchException ('Command cannot be empty ' , 0 , null , ['command ' => $ command , 'cwd ' => $ cwd ]);
59+ }
60+
61+ // Only check PATH-resolved commands (no path separators)
62+ if (!str_contains ($ executable , DIRECTORY_SEPARATOR )) {
63+ $ finder = new ExecutableFinder ();
64+ $ resolved = $ finder ->find ($ executable );
65+ if (null === $ resolved ) {
66+ throw new ProcessLaunchException ('Failed to launch Node process ' , 0 , null , ['command ' => $ command , 'cwd ' => $ cwd , 'env ' => array_keys ($ env ), 'stderr ' => 'Executable not found in PATH ' ]);
67+ }
68+ }
69+
5170 $ this ->lastInputStream = new InputStream ();
5271 $ process = new Process ($ command , $ cwd , $ env , null , $ timeout );
5372 $ process ->setTimeout ($ timeout );
@@ -59,12 +78,10 @@ public function start(array $command, ?string $cwd = null, array $env = [], ?flo
5978 }
6079 });
6180
62- usleep (50_000 );
63-
64- if (!$ process ->isRunning () && !$ process ->isSuccessful ()) {
65- $ excerpt = $ this ->stderrBuf ->toString ();
66- throw new ProcessLaunchException ('Failed to launch Node process ' , 0 , null , ['command ' => $ command , 'cwd ' => $ cwd , 'env ' => array_keys ($ env ), 'stderr ' => $ excerpt , 'exitCode ' => $ process ->getExitCode (), 'exitCodeText ' => $ process ->getExitCodeText ()]);
67- }
81+ // Do not treat an immediate non-zero exit as a launch failure.
82+ // Some tests intentionally start short-lived commands. True
83+ // launch errors are detected above (executable missing), while
84+ // runtime failures should be surfaced by waitForExit().
6885
6986 $ this ->log ('Node process started successfully ' , [
7087 'pid ' => $ process ->getPid (),
0 commit comments