From c7d558d7b7e3d3f3b983de5a341514153fc6f023 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 1 May 2026 16:04:39 +0100 Subject: [PATCH] feat(program): add RunWithResult for capturing driver output Adds Program.RunWithResult, which runs the driver without inheriting the calling process's stdio and returns a RunResult { ExitCode, StandardOutput, StandardError }. Useful under test frameworks (e.g. xUnit) that don't surface inherited console output, where a non-zero Run() exit code is otherwise undiagnosable. Run() is unchanged. References https://github.com/microsoft/playwright-dotnet/issues/3139 --- src/Playwright/Program.cs | 88 ++++++++++++++++++++++++++++++++------- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/src/Playwright/Program.cs b/src/Playwright/Program.cs index f57723a8d..f5e450419 100644 --- a/src/Playwright/Program.cs +++ b/src/Playwright/Program.cs @@ -24,6 +24,7 @@ using System; using System.Diagnostics; +using System.Text; using Microsoft.Playwright.Helpers; namespace Microsoft.Playwright; @@ -38,38 +39,76 @@ public static int Main(string[] args) public int Run(string[] args) { - Func getArgs; - string executablePath; + Process pwProcess; try { - (executablePath, getArgs) = Driver.GetExecutablePath(); + pwProcess = CreateDriverProcess(args, captureOutput: false); } catch { return PrintError("Microsoft.Playwright assembly was found, but is missing required assets. Please ensure to build your project before running Playwright tool."); } + using (pwProcess) + { + pwProcess.Start(); + pwProcess.WaitForExit(); + return pwProcess.ExitCode; + } + } + + /// + /// Runs the Playwright driver with the given arguments and returns the captured stdout, stderr, and exit code. + /// Unlike , this does not inherit the calling process's stdio, so the output is + /// available to callers running under environments (e.g. xUnit) that don't surface inherited console output. + /// + /// The arguments to pass to the Playwright driver (e.g. install, codegen). + /// The captured . + public RunResult RunWithResult(string[] args) + { + using var pwProcess = CreateDriverProcess(args, captureOutput: true); + + var stdout = new StringBuilder(); + var stderr = new StringBuilder(); + pwProcess.OutputDataReceived += (_, e) => + { + if (e.Data != null) + { + stdout.AppendLine(e.Data); + } + }; + pwProcess.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + stderr.AppendLine(e.Data); + } + }; + + pwProcess.Start(); + pwProcess.BeginOutputReadLine(); + pwProcess.BeginErrorReadLine(); + pwProcess.WaitForExit(); + + return new RunResult(pwProcess.ExitCode, stdout.ToString(), stderr.ToString()); + } - var playwrightStartInfo = new ProcessStartInfo(executablePath, getArgs(args.Length > 0 ? "\"" + string.Join("\" \"", args) + "\"" : null)) + private static Process CreateDriverProcess(string[] args, bool captureOutput) + { + var (executablePath, getArgs) = Driver.GetExecutablePath(); + var startInfo = new ProcessStartInfo(executablePath, getArgs(args.Length > 0 ? "\"" + string.Join("\" \"", args) + "\"" : null)) { UseShellExecute = false, // This works after net8.0-preview-4 // https://github.com/dotnet/runtime/pull/82662 WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardOutput = captureOutput, + RedirectStandardError = captureOutput, }; foreach (var pair in Driver.EnvironmentVariables) { - playwrightStartInfo.EnvironmentVariables[pair.Key] = pair.Value; + startInfo.EnvironmentVariables[pair.Key] = pair.Value; } - - using var pwProcess = new Process() - { - StartInfo = playwrightStartInfo, - }; - - pwProcess.Start(); - - pwProcess.WaitForExit(); - return pwProcess.ExitCode; + return new Process() { StartInfo = startInfo }; } private static int PrintError(string error) @@ -77,4 +116,23 @@ private static int PrintError(string error) Console.Error.WriteLine("\x1b[91m" + error + "\x1b[0m"); return 1; } + + /// + /// Captured result of . + /// + public class RunResult + { + internal RunResult(int exitCode, string standardOutput, string standardError) + { + ExitCode = exitCode; + StandardOutput = standardOutput; + StandardError = standardError; + } + + public int ExitCode { get; } + + public string StandardOutput { get; } + + public string StandardError { get; } + } }