From 2a2138622f67d3cf8cd1d7655d8af9cb9efc575d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Thu, 3 Apr 2025 21:07:30 +0200 Subject: [PATCH 1/2] Add a Program.Main() override with a throwOnError parameter Ensuring that playwright is properly installed can be done by running the following code: ```csharp var playwright = new Microsoft.Playwright.Program(); var exitCode = playwright.Run(["install", "--with-deps"]); ``` This is fine when the installation succeeds and the exit code is 0. But when running under xUnit.net (which [does **not** capture the console output](https://xunit.net/docs/capturing-output)) and the installation fails with exit code 1, then it becomes impossibly hard to diagnose the root cause of the failure. For example, a failure can be triggered by deleting the installed browers in the `ms-playwright` cache directory and setting the `https_proxy` environment variable to an invalid proxy: `http://127.0.0.1:33333`. Under these conditions, running with the new `throwOnError` parameter becomes diagnosable. ```csharp var playwright = new Microsoft.Playwright.Program(); var exitCode = playwright.Run(["install", "--with-deps", "firefox"], throwOnError: true); ``` This throws a `PlaywrightException` with the exact command executed and its output, making it clear what the problem is. > Failed to run ~/playwright-dotnet/src/playwright-xunit/bin/Debug/net8.0/.playwright/node/darwin-x64/node "~/playwright-dotnet/src/playwright-xunit/bin/Debug/net8.0/.playwright/package/cli.js" "install" "--with-deps" "firefox" > Downloading Firefox 132.0 (playwright build v1466) from https://playwright.azureedge.net/builds/firefox/1466/firefox-mac.zip > Error: connect ECONNREFUSED 127.0.0.1:33333 > at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1610:16) { > errno: -61, > code: 'ECONNREFUSED', > syscall: 'connect', > address: '127.0.0.1', > port: 33333 > } > Downloading Firefox 132.0 (playwright build v1466) from https://playwright-akamai.azureedge.net/builds/firefox/1466/firefox-mac.zip > Error: connect ECONNREFUSED 127.0.0.1:33333 > at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1610:16) { > errno: -61, > code: 'ECONNREFUSED', > syscall: 'connect', > address: '127.0.0.1', > port: 33333 > } > Downloading Firefox 132.0 (playwright build v1466) from https://playwright-verizon.azureedge.net/builds/firefox/1466/firefox-mac.zip > Error: connect ECONNREFUSED 127.0.0.1:33333 > at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1610:16) { > errno: -61, > code: 'ECONNREFUSED', > syscall: 'connect', > address: '127.0.0.1', > port: 33333 > } > Failed to install browsers > Error: Failed to download Firefox 132.0 (playwright build v1466), caused by > Error: Download failure, code=1 --- src/Playwright/Program.cs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Playwright/Program.cs b/src/Playwright/Program.cs index f57723a8d..f33b62222 100644 --- a/src/Playwright/Program.cs +++ b/src/Playwright/Program.cs @@ -24,6 +24,7 @@ using System; using System.Diagnostics; +using System.IO; using Microsoft.Playwright.Helpers; namespace Microsoft.Playwright; @@ -37,6 +38,11 @@ public static int Main(string[] args) } public int Run(string[] args) + { + return Run(args, throwOnError: false); + } + + public int Run(string[] args, bool throwOnError) { Func getArgs; string executablePath; @@ -44,7 +50,7 @@ public int Run(string[] args) { (executablePath, getArgs) = Driver.GetExecutablePath(); } - catch + catch when (!throwOnError) { return PrintError("Microsoft.Playwright assembly was found, but is missing required assets. Please ensure to build your project before running Playwright tool."); } @@ -55,6 +61,8 @@ public int Run(string[] args) // This works after net8.0-preview-4 // https://github.com/dotnet/runtime/pull/82662 WindowStyle = ProcessWindowStyle.Hidden, + RedirectStandardOutput = throwOnError, + RedirectStandardError = throwOnError, }; foreach (var pair in Driver.EnvironmentVariables) { @@ -66,9 +74,26 @@ public int Run(string[] args) StartInfo = playwrightStartInfo, }; + using var pwOutput = new StringWriter(); + if (throwOnError) + { + pwProcess.OutputDataReceived += (_, eventArgs) => pwOutput.WriteLine(eventArgs.Data); + pwProcess.ErrorDataReceived += (_, eventArgs) => pwOutput.WriteLine(eventArgs.Data); + } pwProcess.Start(); + if (throwOnError) + { + pwProcess.BeginOutputReadLine(); + pwProcess.BeginErrorReadLine(); + } pwProcess.WaitForExit(); + + if (pwProcess.ExitCode != 0 && throwOnError) + { + throw new PlaywrightException($"Failed to run {playwrightStartInfo.FileName} {playwrightStartInfo.Arguments}{Environment.NewLine}{pwOutput.ToString().Trim()}"); + } + return pwProcess.ExitCode; } From 5717749c203e1ad569f9c2542a76b38e8499a3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ce=CC=81dric=20Luthi?= Date: Sun, 8 Dec 2024 16:47:16 +0100 Subject: [PATCH 2/2] Automatically install required browser files for Playwright MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will prevent this exception: > Microsoft.Playwright.PlaywrightException > Executable doesn't exist at ~/Library/Caches/ms-playwright/chromium-1148/chrome-mac/Chromium.app/Contents/MacOS/Chromium > ╔════════════════════════════════════════════════════════════╗ > ║ Looks like Playwright was just installed or updated. ║ > ║ Please run the following command to download new browsers: ║ > ║ ║ > ║ pwsh bin/Debug/netX/playwright.ps1 install ║ > ║ ║ > ║ <3 Playwright Team ║ > ╚════════════════════════════════════════════════════════════╝ > at Microsoft.Playwright.Transport.Connection.InnerSendMessageToServerAsync[T](ChannelOwner object, String method, Dictionary`2 dictionary, Boolean keepNulls) in /_/src/Playwright/Transport/Connection.cs:line 206 > at Microsoft.Playwright.Transport.Connection.WrapApiCallAsync[T](Func`1 action, Boolean isInternal) in /_/src/Playwright/Transport/Connection.cs:line 535 > at Microsoft.Playwright.Core.BrowserType.LaunchAsync(BrowserTypeLaunchOptions options) in /_/src/Playwright/Core/BrowserType.cs:line 56 > at Microsoft.Playwright.Xunit.BrowserService.CreateBrowser(IBrowserType browserType) in /_/src/Playwright.Xunit/BrowserService.cs:line 56 > at Microsoft.Playwright.Xunit.BrowserService.<>c__DisplayClass5_0.<b__0>d.MoveNext() in /_/src/Playwright.Xunit/BrowserService.cs:line 46 > --- End of stack trace from previous location --- > at Microsoft.Playwright.Xunit.WorkerAwareTest.RegisterService[T](String name, Func`1 factory) in /_/src/Playwright.Xunit/WorkerAwareTest.cs:line 54 > at Microsoft.Playwright.Xunit.BrowserTest.InitializeAsync() in /_/src/Playwright.Xunit/BrowserTest.cs:line 45 > at Microsoft.Playwright.Xunit.ContextTest.InitializeAsync() in /_/src/Playwright.Xunit/ContextTest.cs:line 35 > at Microsoft.Playwright.Xunit.PageTest.InitializeAsync() in /_/src/Playwright.Xunit/PageTest.cs:line 35 This helps with #2286. --- src/Playwright.MSTest/WorkerAwareTest.cs | 2 ++ src/Playwright.NUnit/WorkerAwareTest.cs | 2 ++ src/Playwright.Xunit/WorkerAwareTest.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Playwright.MSTest/WorkerAwareTest.cs b/src/Playwright.MSTest/WorkerAwareTest.cs index 0bea2243c..2559fb110 100644 --- a/src/Playwright.MSTest/WorkerAwareTest.cs +++ b/src/Playwright.MSTest/WorkerAwareTest.cs @@ -46,6 +46,8 @@ public void WorkerSetup() { _currentWorker = new(); } + + new Program().Run(["install", "--with-deps", PlaywrightSettingsProvider.BrowserName], throwOnError: true); } [TestCleanup] diff --git a/src/Playwright.NUnit/WorkerAwareTest.cs b/src/Playwright.NUnit/WorkerAwareTest.cs index 0cee929b9..f3ead401c 100644 --- a/src/Playwright.NUnit/WorkerAwareTest.cs +++ b/src/Playwright.NUnit/WorkerAwareTest.cs @@ -70,6 +70,8 @@ public void WorkerSetup() { AssertionsBase.SetDefaultTimeout(PlaywrightSettingsProvider.ExpectTimeout.Value); } + + new Program().Run(["install", "--with-deps", PlaywrightSettingsProvider.BrowserName], throwOnError: true); } [TearDown] diff --git a/src/Playwright.Xunit/WorkerAwareTest.cs b/src/Playwright.Xunit/WorkerAwareTest.cs index 1cce6b65e..d0e7cf355 100644 --- a/src/Playwright.Xunit/WorkerAwareTest.cs +++ b/src/Playwright.Xunit/WorkerAwareTest.cs @@ -69,6 +69,8 @@ async public override Task InitializeAsync() { AssertionsBase.SetDefaultTimeout(PlaywrightSettingsProvider.ExpectTimeout.Value); } + + new Program().Run(["install", "--with-deps", PlaywrightSettingsProvider.BrowserName], throwOnError: true); } public async override Task DisposeAsync()