From a35a067f86c456488d844b58855c820215df64bc Mon Sep 17 00:00:00 2001 From: Xavier Solau Date: Sun, 6 Oct 2024 18:17:05 +0200 Subject: [PATCH] update network port detection for E2E tests --- .../IPlaywrightTest.cs | 7 ++ .../PlaywrightDriver.cs | 81 +++++++++++++++---- .../PlaywrightTestBuilder.cs | 55 +++++++------ .../PlaywrightTestBuilderLocalTest.cs | 2 +- 4 files changed, 106 insertions(+), 39 deletions(-) diff --git a/src/libs/SoloX.CodeQuality.Playwright/IPlaywrightTest.cs b/src/libs/SoloX.CodeQuality.Playwright/IPlaywrightTest.cs index e7406f5..64b7f4c 100644 --- a/src/libs/SoloX.CodeQuality.Playwright/IPlaywrightTest.cs +++ b/src/libs/SoloX.CodeQuality.Playwright/IPlaywrightTest.cs @@ -24,5 +24,12 @@ public interface IPlaywrightTest : IAsyncDisposable /// Page setup handler to initialize page environment before it is display (or null). /// async task. Task GotoPageAsync(string relativePath, Func testHandler, string? traceName = null, Func? pageSetupHandler = null); + + /// + /// Get Url used to bind Playwright and host to test. + /// +#pragma warning disable CA1056 // URI-like properties should not be strings + string Url { get; } +#pragma warning restore CA1056 // URI-like properties should not be strings } } diff --git a/src/libs/SoloX.CodeQuality.Playwright/PlaywrightDriver.cs b/src/libs/SoloX.CodeQuality.Playwright/PlaywrightDriver.cs index d8f07da..dd6444d 100644 --- a/src/libs/SoloX.CodeQuality.Playwright/PlaywrightDriver.cs +++ b/src/libs/SoloX.CodeQuality.Playwright/PlaywrightDriver.cs @@ -20,7 +20,8 @@ public class PlaywrightDriver : IAsyncDisposable private static readonly string[] INSTALL_ARGUMENTS = new[] { "install" }; - private static readonly object InstallLock = new object(); + private static readonly object LockSync = new object(); + private static bool playwrightInstalled; private readonly BrowserNewContextOptions? browserNewContextOptions; private readonly TracingStartOptions? tracingStartOptions; @@ -86,31 +87,78 @@ public async Task InitializeAsync(BrowserTypeLaunchOptions? browserTypeLaunchOpt /// private static void InstallPlaywright() { - InstallPlaywrightDeps(); - InstallPlaywrightBin(); + lock (LockSync) + { + if (playwrightInstalled) + { + return; + } + + var localApplicationData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PlaywrightDriver"); + + if (!Directory.Exists(localApplicationData)) + { + Directory.CreateDirectory(localApplicationData); + } + + var lockFile = Path.Combine(localApplicationData, "PlaywrightInstallLock"); + + var timeout = 60; + + while (!TryWriteLockFile(lockFile)) + { + Thread.Sleep(1000); + timeout--; + if (timeout < 0) + { + throw new PlaywrightException($"Unable to lock for Playwright install (Timeout)"); + } + } + + try + { + InstallPlaywrightDeps(); + InstallPlaywrightBin(); + } + finally + { + playwrightInstalled = true; + File.Delete(lockFile); + } + } + } + + private static bool TryWriteLockFile(string lockFile) + { + try + { + using var file = new FileStream(lockFile, FileMode.CreateNew, FileAccess.Write); + file.WriteByte(0); + file.Flush(); + file.Close(); + } + catch (IOException) + { + return false; + } + return true; } private static void InstallPlaywrightDeps() { - lock (InstallLock) + var exitCode = Microsoft.Playwright.Program.Main(INSTALL_DEPS_ARGUMENTS); + if (exitCode != 0) { - var exitCode = Microsoft.Playwright.Program.Main(INSTALL_DEPS_ARGUMENTS); - if (exitCode != 0) - { - throw new PlaywrightException($"Playwright exited with code {exitCode} on install-deps"); - } + throw new PlaywrightException($"Playwright exited with code {exitCode} on install-deps"); } } private static void InstallPlaywrightBin() { - lock (InstallLock) + var exitCode = Microsoft.Playwright.Program.Main(INSTALL_ARGUMENTS); + if (exitCode != 0) { - var exitCode2 = Microsoft.Playwright.Program.Main(INSTALL_ARGUMENTS); - if (exitCode2 != 0) - { - throw new PlaywrightException($"Playwright exited with code {exitCode2} on install"); - } + throw new PlaywrightException($"Playwright exited with code {exitCode} on install"); } } @@ -140,6 +188,9 @@ public async Task GotoPageAsync(string url, Func testHandler, Brows catch (PlaywrightException) { retry--; + + await Task.Delay(1000).ConfigureAwait(false); + if (retry == 0) { throw; diff --git a/src/libs/SoloX.CodeQuality.Playwright/PlaywrightTestBuilder.cs b/src/libs/SoloX.CodeQuality.Playwright/PlaywrightTestBuilder.cs index 68ac04d..de7365d 100644 --- a/src/libs/SoloX.CodeQuality.Playwright/PlaywrightTestBuilder.cs +++ b/src/libs/SoloX.CodeQuality.Playwright/PlaywrightTestBuilder.cs @@ -254,6 +254,8 @@ private sealed class PlaywrightTest : IPlaywrightTest private bool isDisposed; + public string Url => this.url; + internal PlaywrightTest( Browser browser, string url, @@ -358,28 +360,6 @@ private sealed class PortStore { private readonly HashSet usedPorts = []; - /// - /// Setup port store and prob all TCP port already in use. - /// - public PortStore() - { - // Get used port with Netstat like command. - var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); - var tcpListeners = ipGlobalProperties.GetActiveTcpListeners(); - - foreach (var tcpEndPoint in tcpListeners) - { - this.usedPorts.Add(tcpEndPoint.Port); - } - - var tcpConnections = ipGlobalProperties.GetActiveTcpConnections(); - - foreach (var tcpConnection in tcpConnections) - { - this.usedPorts.Add(tcpConnection.LocalEndPoint.Port); - } - } - public int GetPort(PortRange portRange) { #pragma warning disable CA5394 // Do not use insecure randomness @@ -388,7 +368,11 @@ public int GetPort(PortRange portRange) lock (this.usedPorts) { - while (!this.usedPorts.Add(port)) + var systemPort = ProbUsedPorts(); + + var allUsedPorts = new HashSet(systemPort.Union(this.usedPorts)); + + while (allUsedPorts.Contains(port)) { port++; if (port >= portRange.EndPort) @@ -396,6 +380,8 @@ public int GetPort(PortRange portRange) port = portRange.StartPort; } } + + this.usedPorts.Add(port); } return port; } @@ -407,6 +393,29 @@ public void Release(int port) this.usedPorts.Remove(port); } } + + public static HashSet ProbUsedPorts() + { + HashSet usedSystemPorts = []; + + // Get used port with Netstat like command. + var ipGlobalProperties = IPGlobalProperties.GetIPGlobalProperties(); + var tcpListeners = ipGlobalProperties.GetActiveTcpListeners(); + + foreach (var tcpEndPoint in tcpListeners) + { + usedSystemPorts.Add(tcpEndPoint.Port); + } + + var tcpConnections = ipGlobalProperties.GetActiveTcpConnections(); + + foreach (var tcpConnection in tcpConnections) + { + usedSystemPorts.Add(tcpConnection.LocalEndPoint.Port); + } + + return usedSystemPorts; + } } } } diff --git a/src/tests/SoloX.CodeQuality.Playwright.E2ETest/Resources/PackageNugetTestProject/PackageNugetTestProject/PlaywrightTestBuilderLocalTest.cs b/src/tests/SoloX.CodeQuality.Playwright.E2ETest/Resources/PackageNugetTestProject/PackageNugetTestProject/PlaywrightTestBuilderLocalTest.cs index c7c88c7..d8b70bc 100644 --- a/src/tests/SoloX.CodeQuality.Playwright.E2ETest/Resources/PackageNugetTestProject/PackageNugetTestProject/PlaywrightTestBuilderLocalTest.cs +++ b/src/tests/SoloX.CodeQuality.Playwright.E2ETest/Resources/PackageNugetTestProject/PackageNugetTestProject/PlaywrightTestBuilderLocalTest.cs @@ -15,7 +15,7 @@ public PlaywrightTestBuilderLocalTest() .WithLocalHost(localHostBuilder => { localHostBuilder - .UsePortRange(new PortRange(5000, 6000)) + .UsePortRange(new PortRange(6000, 7000)) .UseWebHostWithWwwRoot(path, "home.html") .UseWebHostBuilder(builder => {