From 1586132b0a1b2abf6411a89370df224cb1be5c8e Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Mon, 4 Nov 2024 00:19:12 +0330 Subject: [PATCH 01/15] Add Testcontainers.Playwright library Implemented the initial version of the Testcontainers.Playwright library. This includes the PlaywrightBuilder, PlaywrightContainer, PlaywrightConfiguration, PlaywrightBrowser classes, and the project file setup. The changes also integrate the new project into the solution file. --- Testcontainers.sln | 7 ++ .../PlaywrightBrowser.cs | 52 ++++++++++++ .../PlaywrightBuilder.cs | 83 +++++++++++++++++++ .../PlaywrightConfiguration.cs | 62 ++++++++++++++ .../PlaywrightContainer.cs | 14 ++++ .../Testcontainers.Playwright.csproj | 12 +++ src/Testcontainers.Playwright/Usings.cs | 16 ++++ 7 files changed, 246 insertions(+) create mode 100644 src/Testcontainers.Playwright/PlaywrightBrowser.cs create mode 100644 src/Testcontainers.Playwright/PlaywrightBuilder.cs create mode 100644 src/Testcontainers.Playwright/PlaywrightConfiguration.cs create mode 100644 src/Testcontainers.Playwright/PlaywrightContainer.cs create mode 100644 src/Testcontainers.Playwright/Testcontainers.Playwright.csproj create mode 100644 src/Testcontainers.Playwright/Usings.cs diff --git a/Testcontainers.sln b/Testcontainers.sln index 10cae3fc2..4574859bb 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -195,6 +195,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright", "src\Testcontainers.Playwright\Testcontainers.Playwright.csproj", "{275ADBA5-498E-45FA-AD3F-0E5EE7996D99}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -568,6 +570,10 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU + {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -661,5 +667,6 @@ Global {9E8E6AA5-65D1-498F-BEAB-BA34723A0050} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {275ADBA5-498E-45FA-AD3F-0E5EE7996D99} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.Playwright/PlaywrightBrowser.cs b/src/Testcontainers.Playwright/PlaywrightBrowser.cs new file mode 100644 index 000000000..96ab688b4 --- /dev/null +++ b/src/Testcontainers.Playwright/PlaywrightBrowser.cs @@ -0,0 +1,52 @@ +namespace Testcontainers.Playwright; + +/// +/// Playwright browser configuration. +/// +[PublicAPI] +public readonly struct PlaywrightBrowser +{ + /// + /// Gets the Playwright standalone Chrome configuration. + /// + public static readonly PlaywrightBrowser Chrome = new PlaywrightBrowser("jacoblincool/playwright:chrome-server"); + + /// + /// Gets the Playwright standalone Chromium configuration. + /// + public static readonly PlaywrightBrowser Chromium = new PlaywrightBrowser("jacoblincool/playwright:chromium-server"); + + /// + /// Gets the Playwright standalone Firefox configuration. + /// + public static readonly PlaywrightBrowser Firefox = new PlaywrightBrowser("jacoblincool/playwright:firefox-server"); + + /// + /// Gets the Playwright standalone Edge configuration. + /// + public static readonly PlaywrightBrowser Edge = new PlaywrightBrowser("jacoblincool/playwright:msedge-server"); + + + /// + /// Initializes a new instance of the struct. + /// + /// The Playwright standalone Docker image. + public PlaywrightBrowser(string image) + : this(new DockerImage(image)) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The Playwright standalone Docker image. + public PlaywrightBrowser(IImage image) + { + Image = image; + } + /// + /// Gets the Playwright standalone Docker image. + /// + [NotNull] + public IImage Image { get; } +} diff --git a/src/Testcontainers.Playwright/PlaywrightBuilder.cs b/src/Testcontainers.Playwright/PlaywrightBuilder.cs new file mode 100644 index 000000000..f497efdaf --- /dev/null +++ b/src/Testcontainers.Playwright/PlaywrightBuilder.cs @@ -0,0 +1,83 @@ +namespace Testcontainers.Playwright; + +/// +/// +/// Find further information about the Playwright image, here: https://playwright.dev/dotnet/docs/docker. +/// +[PublicAPI] +public class PlaywrightBuilder : ContainerBuilder +{ + private const ushort PlaywrightPort = 53333; + private const string PlaywrightEndpointPath = "/playwright"; + + /// + /// Initializes a new instance of the class. + /// + public PlaywrightBuilder() : this(new PlaywrightConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private PlaywrightBuilder(PlaywrightConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } + + /// + protected override PlaywrightConfiguration DockerResourceConfiguration { get; } + + public override PlaywrightContainer Build() + { + Validate(); + return new PlaywrightContainer(DockerResourceConfiguration); + } + + /// + protected override PlaywrightBuilder Init() + { + return base.Init() + .WithBrowser(PlaywrightBrowser.Chrome) + .WithNetwork(new NetworkBuilder().Build()) + .WithEndpointPath(PlaywrightEndpointPath) + .WithPortBinding(PlaywrightPort, true); + } + + public PlaywrightBuilder WithBrowser(PlaywrightBrowser playwrightBrowser) + { + return WithImage(playwrightBrowser.Image); + } + + /// + /// Sets the MsSql password. + /// + /// The MsSql password. + /// A configured instance of . + public PlaywrightBuilder WithEndpointPath(string endpointPath) + { + return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(endpoint: endpointPath)) + .WithEnvironment("BROWSER_WS_ENDPOINT", endpointPath); + } + + /// + protected override PlaywrightBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(resourceConfiguration)); + } + + /// + protected override PlaywrightBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(resourceConfiguration)); + } + + /// + protected override PlaywrightBuilder Merge(PlaywrightConfiguration oldValue, PlaywrightConfiguration newValue) + { + return new PlaywrightBuilder(new PlaywrightConfiguration(oldValue, newValue)); + } +} diff --git a/src/Testcontainers.Playwright/PlaywrightConfiguration.cs b/src/Testcontainers.Playwright/PlaywrightConfiguration.cs new file mode 100644 index 000000000..18587b197 --- /dev/null +++ b/src/Testcontainers.Playwright/PlaywrightConfiguration.cs @@ -0,0 +1,62 @@ +namespace Testcontainers.Playwright; + +/// +[PublicAPI] +public class PlaywrightConfiguration : ContainerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + /// The Playwright endpoint. + public PlaywrightConfiguration(string endpoint = null) + { + Endpoint = endpoint; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public PlaywrightConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public PlaywrightConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public PlaywrightConfiguration(PlaywrightConfiguration resourceConfiguration) + : this(new PlaywrightConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public PlaywrightConfiguration(PlaywrightConfiguration oldValue, PlaywrightConfiguration newValue) + : base(oldValue, newValue) + { + Endpoint = BuildConfiguration.Combine(oldValue.Endpoint, newValue.Endpoint); + } + + + /// + /// Gets the Playwright endpoint. + /// + public string Endpoint { get; } +} diff --git a/src/Testcontainers.Playwright/PlaywrightContainer.cs b/src/Testcontainers.Playwright/PlaywrightContainer.cs new file mode 100644 index 000000000..c86007cae --- /dev/null +++ b/src/Testcontainers.Playwright/PlaywrightContainer.cs @@ -0,0 +1,14 @@ +namespace Testcontainers.Playwright; + +/// +[PublicAPI] +public class PlaywrightContainer : DockerContainer +{ + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + public PlaywrightContainer(PlaywrightConfiguration configuration) : base(configuration) + { + } +} diff --git a/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj b/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj new file mode 100644 index 000000000..2e29f8592 --- /dev/null +++ b/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj @@ -0,0 +1,12 @@ + + + net6.0;net8.0;netstandard2.0;netstandard2.1 + latest + + + + + + + + diff --git a/src/Testcontainers.Playwright/Usings.cs b/src/Testcontainers.Playwright/Usings.cs new file mode 100644 index 000000000..00e88cf71 --- /dev/null +++ b/src/Testcontainers.Playwright/Usings.cs @@ -0,0 +1,16 @@ +global using System; +global using System.IO; +global using System.Linq; +global using System.Net.Http; +global using System.Text.Json; +global using System.Threading; +global using System.Threading.Tasks; +global using Docker.DotNet.Models; +global using DotNet.Testcontainers; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using DotNet.Testcontainers.Images; +global using DotNet.Testcontainers.Networks; +global using JetBrains.Annotations; +global using Microsoft.Extensions.Logging.Abstractions; \ No newline at end of file From f7e2e31034e9eeb7f0e177281627ca9ff342d620 Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Tue, 5 Nov 2024 00:33:43 +0330 Subject: [PATCH 02/15] Add Playwright test setup and initial tests Introduce a new Playwright test project with necessary configurations and dependencies. Implement initial container setup and a test to validate the integration with a sample "hello world" application. --- Directory.Packages.props | 129 +++++++++--------- Testcontainers.sln | 7 + .../PlaywrightBuilder.cs | 25 +++- .../PlaywrightConfiguration.cs | 10 +- .../GlobalUsings.cs | 1 + .../PlaywrightContainerTest.cs | 28 ++++ .../TestInitializer.cs | 75 ++++++++++ .../Testcontainers.Playwright.Tests.csproj | 25 ++++ 8 files changed, 230 insertions(+), 70 deletions(-) create mode 100644 tests/Testcontainers.Playwright.Tests/GlobalUsings.cs create mode 100644 tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs create mode 100644 tests/Testcontainers.Playwright.Tests/TestInitializer.cs create mode 100644 tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj diff --git a/Directory.Packages.props b/Directory.Packages.props index fbc55db0a..c6386a780 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,67 +1,68 @@ - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Testcontainers.sln b/Testcontainers.sln index 4574859bb..e607c6de2 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -197,6 +197,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright", "src\Testcontainers.Playwright\Testcontainers.Playwright.csproj", "{275ADBA5-498E-45FA-AD3F-0E5EE7996D99}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright.Tests", "tests\Testcontainers.Playwright.Tests\Testcontainers.Playwright.Tests.csproj", "{A56D182C-8347-4CBE-9FC8-F2C98648F134}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -574,6 +576,10 @@ Global {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Debug|Any CPU.Build.0 = Debug|Any CPU {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Release|Any CPU.ActiveCfg = Release|Any CPU {275ADBA5-498E-45FA-AD3F-0E5EE7996D99}.Release|Any CPU.Build.0 = Release|Any CPU + {A56D182C-8347-4CBE-9FC8-F2C98648F134}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A56D182C-8347-4CBE-9FC8-F2C98648F134}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A56D182C-8347-4CBE-9FC8-F2C98648F134}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A56D182C-8347-4CBE-9FC8-F2C98648F134}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -668,5 +674,6 @@ Global {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {275ADBA5-498E-45FA-AD3F-0E5EE7996D99} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {A56D182C-8347-4CBE-9FC8-F2C98648F134} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.Playwright/PlaywrightBuilder.cs b/src/Testcontainers.Playwright/PlaywrightBuilder.cs index f497efdaf..bb181cde0 100644 --- a/src/Testcontainers.Playwright/PlaywrightBuilder.cs +++ b/src/Testcontainers.Playwright/PlaywrightBuilder.cs @@ -42,9 +42,10 @@ protected override PlaywrightBuilder Init() { return base.Init() .WithBrowser(PlaywrightBrowser.Chrome) - .WithNetwork(new NetworkBuilder().Build()) .WithEndpointPath(PlaywrightEndpointPath) - .WithPortBinding(PlaywrightPort, true); + .WithBrowserPort(PlaywrightPort) + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilMessageIsLogged($"ws://.*:{PlaywrightPort}{PlaywrightEndpointPath}")); } public PlaywrightBuilder WithBrowser(PlaywrightBrowser playwrightBrowser) @@ -53,16 +54,30 @@ public PlaywrightBuilder WithBrowser(PlaywrightBrowser playwrightBrowser) } /// - /// Sets the MsSql password. + /// Sets the BROWSER WS ENDPOINT. /// - /// The MsSql password. - /// A configured instance of . + /// The BROWSER WS ENDPOINT. + /// A configured instance of . public PlaywrightBuilder WithEndpointPath(string endpointPath) { return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(endpoint: endpointPath)) .WithEnvironment("BROWSER_WS_ENDPOINT", endpointPath); } + /// + /// Sets the BROWSER WS PORT. + /// + /// The BROWSER WS PORT. + /// A configured instance of . + public PlaywrightBuilder WithBrowserPort(int port, bool assignRandomHostPort=false) + { + return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(port: port)) + .WithEnvironment("BROWSER_PORT", port.ToString()) + .WithPortBinding(port, assignRandomHostPort); + } + + + /// protected override PlaywrightBuilder Clone(IResourceConfiguration resourceConfiguration) { diff --git a/src/Testcontainers.Playwright/PlaywrightConfiguration.cs b/src/Testcontainers.Playwright/PlaywrightConfiguration.cs index 18587b197..e7dc16a20 100644 --- a/src/Testcontainers.Playwright/PlaywrightConfiguration.cs +++ b/src/Testcontainers.Playwright/PlaywrightConfiguration.cs @@ -8,9 +8,11 @@ public class PlaywrightConfiguration : ContainerConfiguration /// Initializes a new instance of the class. /// /// The Playwright endpoint. - public PlaywrightConfiguration(string endpoint = null) + public PlaywrightConfiguration(string endpoint = null, + int? port = null) { Endpoint = endpoint; + Port = port; } /// @@ -59,4 +61,10 @@ public PlaywrightConfiguration(PlaywrightConfiguration oldValue, PlaywrightConfi /// Gets the Playwright endpoint. /// public string Endpoint { get; } + + + /// + /// Gets the Playwright port. + /// + public int? Port { get; } } diff --git a/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs b/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs new file mode 100644 index 000000000..c802f4480 --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs b/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs new file mode 100644 index 000000000..235d423c4 --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs @@ -0,0 +1,28 @@ +namespace Testcontainers.Playwright.Tests; + +public class PlaywrightContainerTest : IClassFixture +{ + private readonly Uri _helloWorldBaseAddress; + + public PlaywrightContainerTest(TestInitializer testInitializer) + { + _helloWorldBaseAddress = testInitializer._helloWorldBaseAddress; + } + + [Fact] + public async Task HeadingElementReturnsHelloWorld() + { + // Given + var playwright = await Microsoft.Playwright.Playwright.CreateAsync(); + var browser = await playwright.Chromium.ConnectAsync($"ws://localhost:63333/playwright"); + var page = await browser.NewPageAsync(); + + // When + await page.GotoAsync(_helloWorldBaseAddress.ToString()); + var headingElement = await page.QuerySelectorAsync("h1"); + var headingElementText = await headingElement.InnerTextAsync(); + + // Then + Assert.Equal("Hello world", headingElementText); + } +} diff --git a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs new file mode 100644 index 000000000..3682e438d --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs @@ -0,0 +1,75 @@ +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Configurations; +using DotNet.Testcontainers.Containers; +using DotNet.Testcontainers.Networks; + +namespace Testcontainers.Playwright.Tests +{ + public class TestInitializer : IAsyncLifetime + { + internal readonly Uri _helloWorldBaseAddress = new UriBuilder(Uri.UriSchemeHttp, "hello-world-container", 8080).Uri; + private IContainer _helloWorldContainer; + private PlaywrightContainer _playwrightContainer; + + private const string PlaywrightContainerName = "testplaywright"; + + public async Task InitializeAsync() + { + var network = CreateNetwork(); + + _helloWorldContainer = CreateHelloWorldContainer(network); + _playwrightContainer = CreatePlaywrightContainer(network); + + await StartContainersAsync(); + } + + public async Task DisposeAsync() + { + await DisposeContainersAsync(); + } + + private INetwork CreateNetwork() + { + return new NetworkBuilder() + .WithName(Guid.NewGuid().ToString("D")) + .WithDriver(NetworkDriver.Bridge) + .WithCleanUp(true) + .Build(); + } + + private IContainer CreateHelloWorldContainer(INetwork network) + { + return new ContainerBuilder() + .WithImage("testcontainers/helloworld:1.1.0") + .WithNetwork(network) + .WithNetworkAliases(_helloWorldBaseAddress.Host) + .WithPortBinding(_helloWorldBaseAddress.Port, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => + request.ForPath("/").ForPort(Convert.ToUInt16(_helloWorldBaseAddress.Port)))) + .Build(); + } + + private PlaywrightContainer CreatePlaywrightContainer(INetwork network) + { + return new PlaywrightBuilder() + .WithNetwork(network) + .WithName(PlaywrightContainerName) + .WithNetworkAliases(PlaywrightContainerName) + .WithPortBinding(63333, 53333) + .WithBrowser(PlaywrightBrowser.Chromium) + .Build(); + } + + private async Task StartContainersAsync() + { + await _helloWorldContainer.StartAsync(); + await _playwrightContainer.StartAsync(); + } + + private async Task DisposeContainersAsync() + { + await _playwrightContainer.DisposeAsync(); + await _helloWorldContainer.DisposeAsync(); + } + } +} diff --git a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj new file mode 100644 index 000000000..b39a49c37 --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + From 9a0800aa31fd14cf2e0832baa3c1a9defccc7ffe Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Tue, 5 Nov 2024 11:15:21 +0330 Subject: [PATCH 03/15] Update xunit package reference to latest version Removed the specific version number from the xunit package reference in the project file. This change allows the project to always use the latest compatible version of xunit. --- .../Testcontainers.Playwright.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj index b39a49c37..b0c92e65d 100644 --- a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj +++ b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj @@ -12,7 +12,7 @@ - + From c4bea331a58f5d213170bdea7b3d37ebacce2ebc Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Tue, 5 Nov 2024 11:24:52 +0330 Subject: [PATCH 04/15] Refactor test project settings Removed unnecessary properties and updated package order in the test project file. This cleanup improves readability and aligns the project with current standards, ensuring consistent behavior. --- .../Testcontainers.Playwright.Tests.csproj | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj index b0c92e65d..75923e008 100644 --- a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj +++ b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj @@ -1,20 +1,16 @@ - net8.0 - enable - enable - false - true + false - - - + + + From 70018cb9281b52c7dc1b75b60c9d411664aef475 Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Tue, 5 Nov 2024 11:31:14 +0330 Subject: [PATCH 05/15] Add missing System and threading imports Include System for fundamental classes and base classes, and threading for asynchronous operations. This ensures that the components used in the TestInitializer are properly referenced and compiled. --- tests/Testcontainers.Playwright.Tests/TestInitializer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs index 3682e438d..264f7f253 100644 --- a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs +++ b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs @@ -1,3 +1,5 @@ +using System; +using System.Threading.Tasks; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; From 3733a885d6c864aa0cc9ea8ccda0034628f6f464 Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Tue, 5 Nov 2024 11:38:18 +0330 Subject: [PATCH 06/15] Refactor usings in test files Move common usings to GlobalUsings.cs for better maintainability and cleaner code in TestInitializer.cs. This enhances code organization and reduces redundancy. --- tests/Testcontainers.Playwright.Tests/GlobalUsings.cs | 2 ++ tests/Testcontainers.Playwright.Tests/TestInitializer.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs b/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs index c802f4480..40c3c0c7c 100644 --- a/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs +++ b/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs @@ -1 +1,3 @@ global using Xunit; +global using System; +global using System.Threading.Tasks; diff --git a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs index 264f7f253..3682e438d 100644 --- a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs +++ b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading.Tasks; using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Configurations; using DotNet.Testcontainers.Containers; From 6c55ebf1f8a83818e0bf4359df71df3dba94ede4 Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Wed, 13 Nov 2024 23:58:18 +0330 Subject: [PATCH 07/15] Fix merge conflicts --- Directory.Packages.props | 1 + .../Testcontainers.Playwright.Tests.csproj | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Directory.Packages.props b/Directory.Packages.props index 9c650674d..521b65572 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -63,5 +63,6 @@ + diff --git a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj index 75923e008..b5cdbad07 100644 --- a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj +++ b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj @@ -18,4 +18,8 @@ + + + + From f805a4ffc3284e977482c3c4ed72c1239467fd5f Mon Sep 17 00:00:00 2001 From: Ali Mahboubi Date: Thu, 14 Nov 2024 09:50:07 +0330 Subject: [PATCH 08/15] Add Playwright test to CiCD --- .github/workflows/cicd.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 08b77d44e..c36caa0e1 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -76,7 +76,8 @@ jobs: { name: "Testcontainers.RavenDb", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.Redis", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.Redpanda", runs-on: "ubuntu-22.04" }, - { name: "Testcontainers.WebDriver", runs-on: "ubuntu-22.04" } + { name: "Testcontainers.WebDriver", runs-on: "ubuntu-22.04" }, + { name: "Testcontainers.Playwright", runs-on: "ubuntu-22.04" } ] runs-on: ${{ matrix.test-projects.runs-on }} From b877abcf217958630c3333e855b86d1303852cb5 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:34:53 +0100 Subject: [PATCH 09/15] chore: Update project configurations --- Directory.Packages.props | 2 +- src/Testcontainers.Playwright/.editorconfig | 1 + .../Testcontainers.Playwright.csproj | 22 +++++----- .../.editorconfig | 1 + .../Testcontainers.Playwright.Tests/.runs-on | 1 + .../Testcontainers.Playwright.Tests.csproj | 42 ++++++++----------- 6 files changed, 33 insertions(+), 36 deletions(-) create mode 100644 src/Testcontainers.Playwright/.editorconfig create mode 100644 tests/Testcontainers.Playwright.Tests/.editorconfig create mode 100644 tests/Testcontainers.Playwright.Tests/.runs-on diff --git a/Directory.Packages.props b/Directory.Packages.props index 4aa3dafc0..eab74b4be 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -68,6 +68,7 @@ + @@ -86,6 +87,5 @@ - diff --git a/src/Testcontainers.Playwright/.editorconfig b/src/Testcontainers.Playwright/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/src/Testcontainers.Playwright/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj b/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj index 2e29f8592..906f34018 100644 --- a/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj +++ b/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj @@ -1,12 +1,12 @@  - - net6.0;net8.0;netstandard2.0;netstandard2.1 - latest - - - - - - - - + + net8.0;net9.0;netstandard2.0;netstandard2.1 + latest + + + + + + + + \ No newline at end of file diff --git a/tests/Testcontainers.Playwright.Tests/.editorconfig b/tests/Testcontainers.Playwright.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Playwright.Tests/.runs-on b/tests/Testcontainers.Playwright.Tests/.runs-on new file mode 100644 index 000000000..d0395e498 --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/.runs-on @@ -0,0 +1 @@ +ubuntu-24.04 \ No newline at end of file diff --git a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj index b5cdbad07..0a5f575ca 100644 --- a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj +++ b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj @@ -1,25 +1,19 @@ - - net8.0 - false - false - - - - - - - - - - - - - - - - - - - - + + net9.0 + false + false + Exe + + + + + + + + + + + + + \ No newline at end of file From fc50cade799c8c846bdcb32024df7703a4f61858 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:37:20 +0100 Subject: [PATCH 10/15] fix: Implement XunitV3 interface --- tests/Testcontainers.Playwright.Tests/TestInitializer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs index 3682e438d..ce44587ec 100644 --- a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs +++ b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs @@ -13,7 +13,7 @@ public class TestInitializer : IAsyncLifetime private const string PlaywrightContainerName = "testplaywright"; - public async Task InitializeAsync() + public async ValueTask InitializeAsync() { var network = CreateNetwork(); @@ -23,7 +23,7 @@ public async Task InitializeAsync() await StartContainersAsync(); } - public async Task DisposeAsync() + public async ValueTask DisposeAsync() { await DisposeContainersAsync(); } From 2e037be5c9973e8e8b7f202a2fae5ebb418b232f Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:55:54 +0100 Subject: [PATCH 11/15] chore: Align with the WebDriver module --- Testcontainers.sln | 28 ++-- .../PlaywrightBrowser.cs | 80 +++++----- .../PlaywrightBuilder.cs | 151 ++++++++---------- .../PlaywrightConfiguration.cs | 115 ++++++------- .../PlaywrightContainer.cs | 40 +++-- src/Testcontainers.Playwright/Usings.cs | 9 +- .../GlobalUsings.cs | 3 - .../PlaywrightContainerTest.cs | 111 ++++++++++--- .../TestInitializer.cs | 75 --------- .../Testcontainers.Playwright.Tests/Usings.cs | 7 + 10 files changed, 296 insertions(+), 323 deletions(-) delete mode 100644 tests/Testcontainers.Playwright.Tests/GlobalUsings.cs delete mode 100644 tests/Testcontainers.Playwright.Tests/TestInitializer.cs create mode 100644 tests/Testcontainers.Playwright.Tests/Usings.cs diff --git a/Testcontainers.sln b/Testcontainers.sln index 8975a000a..8338ec57a 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -102,6 +102,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Papercut", "src\Testcontainers.Papercut\Testcontainers.Papercut.csproj", "{B2608563-8EE4-49AA-A9A0-B1614486AEEF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright", "src\Testcontainers.Playwright\Testcontainers.Playwright.csproj", "{7F91A202-4F07-470D-881C-9A46A700DCA5}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql", "src\Testcontainers.PostgreSql\Testcontainers.PostgreSql.csproj", "{8AB91636-9055-4900-A72A-7CFFACDFDBF0}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub", "src\Testcontainers.PubSub\Testcontainers.PubSub.csproj", "{E6642255-667D-476B-B584-089AA5E6C0B1}" @@ -230,6 +232,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Lin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Windows.Tests", "tests\Testcontainers.Platform.Windows.Tests\Testcontainers.Platform.Windows.Tests.csproj", "{3E55CBE8-AFE8-426D-9470-49D63CD1051C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright.Tests", "tests\Testcontainers.Playwright.Tests\Testcontainers.Playwright.Tests.csproj", "{DE065875-1B7B-4E20-9332-C88C8437333D}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql.Tests", "tests\Testcontainers.PostgreSql.Tests\Testcontainers.PostgreSql.Tests.csproj", "{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub.Tests", "tests\Testcontainers.PubSub.Tests\Testcontainers.PubSub.Tests.csproj", "{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}" @@ -264,10 +268,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Xunit.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.XunitV3.Tests", "tests\Testcontainers.XunitV3.Tests\Testcontainers.XunitV3.Tests.csproj", "{B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright", "src\Testcontainers.Playwright\Testcontainers.Playwright.csproj", "{7F91A202-4F07-470D-881C-9A46A700DCA5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Playwright.Tests", "tests\Testcontainers.Playwright.Tests\Testcontainers.Playwright.Tests.csproj", "{DE065875-1B7B-4E20-9332-C88C8437333D}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -438,6 +438,10 @@ Global {B2608563-8EE4-49AA-A9A0-B1614486AEEF}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2608563-8EE4-49AA-A9A0-B1614486AEEF}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2608563-8EE4-49AA-A9A0-B1614486AEEF}.Release|Any CPU.Build.0 = Release|Any CPU + {7F91A202-4F07-470D-881C-9A46A700DCA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F91A202-4F07-470D-881C-9A46A700DCA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F91A202-4F07-470D-881C-9A46A700DCA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F91A202-4F07-470D-881C-9A46A700DCA5}.Release|Any CPU.Build.0 = Release|Any CPU {8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -694,6 +698,10 @@ Global {3E55CBE8-AFE8-426D-9470-49D63CD1051C}.Debug|Any CPU.Build.0 = Debug|Any CPU {3E55CBE8-AFE8-426D-9470-49D63CD1051C}.Release|Any CPU.ActiveCfg = Release|Any CPU {3E55CBE8-AFE8-426D-9470-49D63CD1051C}.Release|Any CPU.Build.0 = Release|Any CPU + {DE065875-1B7B-4E20-9332-C88C8437333D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE065875-1B7B-4E20-9332-C88C8437333D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE065875-1B7B-4E20-9332-C88C8437333D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE065875-1B7B-4E20-9332-C88C8437333D}.Release|Any CPU.Build.0 = Release|Any CPU {56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU {56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -762,14 +770,6 @@ Global {B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB}.Release|Any CPU.Build.0 = Release|Any CPU - {7F91A202-4F07-470D-881C-9A46A700DCA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F91A202-4F07-470D-881C-9A46A700DCA5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F91A202-4F07-470D-881C-9A46A700DCA5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F91A202-4F07-470D-881C-9A46A700DCA5}.Release|Any CPU.Build.0 = Release|Any CPU - {DE065875-1B7B-4E20-9332-C88C8437333D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DE065875-1B7B-4E20-9332-C88C8437333D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE065875-1B7B-4E20-9332-C88C8437333D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DE065875-1B7B-4E20-9332-C88C8437333D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -817,6 +817,7 @@ Global {49051DBC-6B80-4412-8505-BC2764A877BD} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {596EAFC1-0496-495C-B382-D57415FA456A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {B2608563-8EE4-49AA-A9A0-B1614486AEEF} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {7F91A202-4F07-470D-881C-9A46A700DCA5} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {8AB91636-9055-4900-A72A-7CFFACDFDBF0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {E6642255-667D-476B-B584-089AA5E6C0B1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {27D46863-65B9-4934-B3C8-2383B217A477} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -881,6 +882,7 @@ Global {F03FA970-BE2B-4AE2-96FE-7E1F805CEA20} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {3E55CBE8-AFE8-426D-9470-49D63CD1051C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {DE065875-1B7B-4E20-9332-C88C8437333D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {0F86BCE8-62E1-4BFC-AA84-63C7514C90AC} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {D05FCB31-793E-43E0-BD6C-077013AE9113} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} @@ -898,7 +900,5 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {E901DF14-6F05-4FC2-825A-3055FAD33561} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {B2E8B7FB-7D1E-4DD3-A25E-34DE4386B1EB} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} - {7F91A202-4F07-470D-881C-9A46A700DCA5} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} - {DE065875-1B7B-4E20-9332-C88C8437333D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.Playwright/PlaywrightBrowser.cs b/src/Testcontainers.Playwright/PlaywrightBrowser.cs index 96ab688b4..ca09bd8b8 100644 --- a/src/Testcontainers.Playwright/PlaywrightBrowser.cs +++ b/src/Testcontainers.Playwright/PlaywrightBrowser.cs @@ -3,50 +3,54 @@ /// /// Playwright browser configuration. /// +/// +/// The browsers are standalone, pre-built Playwright versions from the +/// https://github.com/JacobLinCool/playwright-docker repository. +/// [PublicAPI] public readonly struct PlaywrightBrowser { - /// - /// Gets the Playwright standalone Chrome configuration. - /// - public static readonly PlaywrightBrowser Chrome = new PlaywrightBrowser("jacoblincool/playwright:chrome-server"); + /// + /// Gets the Playwright standalone Chrome configuration. + /// + public static readonly PlaywrightBrowser Chrome = new PlaywrightBrowser("jacoblincool/playwright:chrome-server-1.55.1"); - /// - /// Gets the Playwright standalone Chromium configuration. - /// - public static readonly PlaywrightBrowser Chromium = new PlaywrightBrowser("jacoblincool/playwright:chromium-server"); + /// + /// Gets the Playwright standalone Chromium configuration. + /// + public static readonly PlaywrightBrowser Chromium = new PlaywrightBrowser("jacoblincool/playwright:chromium-server-1.55.1"); - /// - /// Gets the Playwright standalone Firefox configuration. - /// - public static readonly PlaywrightBrowser Firefox = new PlaywrightBrowser("jacoblincool/playwright:firefox-server"); + /// + /// Gets the Playwright standalone Firefox configuration. + /// + public static readonly PlaywrightBrowser Firefox = new PlaywrightBrowser("jacoblincool/playwright:firefox-server-1.55.1"); - /// - /// Gets the Playwright standalone Edge configuration. - /// - public static readonly PlaywrightBrowser Edge = new PlaywrightBrowser("jacoblincool/playwright:msedge-server"); + /// + /// Gets the Playwright standalone Edge configuration. + /// + public static readonly PlaywrightBrowser Edge = new PlaywrightBrowser("jacoblincool/playwright:msedge-server-1.55.1"); + /// + /// Initializes a new instance of the struct. + /// + /// The Playwright standalone Docker image. + public PlaywrightBrowser(string image) + : this(new DockerImage(image)) + { + } - /// - /// Initializes a new instance of the struct. - /// - /// The Playwright standalone Docker image. - public PlaywrightBrowser(string image) - : this(new DockerImage(image)) - { - } + /// + /// Initializes a new instance of the struct. + /// + /// The Playwright standalone Docker image. + public PlaywrightBrowser(IImage image) + { + Image = image; + } - /// - /// Initializes a new instance of the struct. - /// - /// The Playwright standalone Docker image. - public PlaywrightBrowser(IImage image) - { - Image = image; - } - /// - /// Gets the Playwright standalone Docker image. - /// - [NotNull] - public IImage Image { get; } -} + /// + /// Gets the Playwright standalone Docker image. + /// + [NotNull] + public IImage Image { get; } +} \ No newline at end of file diff --git a/src/Testcontainers.Playwright/PlaywrightBuilder.cs b/src/Testcontainers.Playwright/PlaywrightBuilder.cs index bb181cde0..48df5ad0e 100644 --- a/src/Testcontainers.Playwright/PlaywrightBuilder.cs +++ b/src/Testcontainers.Playwright/PlaywrightBuilder.cs @@ -1,98 +1,81 @@ namespace Testcontainers.Playwright; -/// -/// -/// Find further information about the Playwright image, here: https://playwright.dev/dotnet/docs/docker. -/// +/// [PublicAPI] -public class PlaywrightBuilder : ContainerBuilder +public sealed class PlaywrightBuilder : ContainerBuilder { - private const ushort PlaywrightPort = 53333; - private const string PlaywrightEndpointPath = "/playwright"; + public const string PlaywrightNetworkAlias = "standalone-container"; - /// - /// Initializes a new instance of the class. - /// - public PlaywrightBuilder() : this(new PlaywrightConfiguration()) - { - DockerResourceConfiguration = Init().DockerResourceConfiguration; - } + public const ushort PlaywrightPort = 53333; - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - private PlaywrightBuilder(PlaywrightConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - DockerResourceConfiguration = resourceConfiguration; - } + /// + /// Initializes a new instance of the class. + /// + public PlaywrightBuilder() + : this(new PlaywrightConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } - /// - protected override PlaywrightConfiguration DockerResourceConfiguration { get; } + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private PlaywrightBuilder(PlaywrightConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } - public override PlaywrightContainer Build() - { - Validate(); - return new PlaywrightContainer(DockerResourceConfiguration); - } + /// + protected override PlaywrightConfiguration DockerResourceConfiguration { get; } - /// - protected override PlaywrightBuilder Init() - { - return base.Init() - .WithBrowser(PlaywrightBrowser.Chrome) - .WithEndpointPath(PlaywrightEndpointPath) - .WithBrowserPort(PlaywrightPort) - .WithWaitStrategy(Wait.ForUnixContainer() - .UntilMessageIsLogged($"ws://.*:{PlaywrightPort}{PlaywrightEndpointPath}")); - } + /// + /// Sets the Playwright browser configuration. + /// + /// + /// https://github.com/JacobLinCool/playwright-docker. + /// + /// The Playwright browser configuration. + /// A configured instance of . + public PlaywrightBuilder WithBrowser(PlaywrightBrowser playwrightBrowser) + { + return WithImage(playwrightBrowser.Image); + } - public PlaywrightBuilder WithBrowser(PlaywrightBrowser playwrightBrowser) - { - return WithImage(playwrightBrowser.Image); - } + /// + public override PlaywrightContainer Build() + { + Validate(); + return new PlaywrightContainer(DockerResourceConfiguration); + } - /// - /// Sets the BROWSER WS ENDPOINT. - /// - /// The BROWSER WS ENDPOINT. - /// A configured instance of . - public PlaywrightBuilder WithEndpointPath(string endpointPath) - { - return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(endpoint: endpointPath)) - .WithEnvironment("BROWSER_WS_ENDPOINT", endpointPath); - } + /// + protected override PlaywrightBuilder Init() + { + return base.Init() + .WithBrowser(PlaywrightBrowser.Chrome) + .WithNetwork(new NetworkBuilder().Build()) + .WithNetworkAliases(PlaywrightNetworkAlias) + .WithPortBinding(PlaywrightPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("ws://localhost:" + PlaywrightPort + "/playwright")); + } - /// - /// Sets the BROWSER WS PORT. - /// - /// The BROWSER WS PORT. - /// A configured instance of . - public PlaywrightBuilder WithBrowserPort(int port, bool assignRandomHostPort=false) - { - return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(port: port)) - .WithEnvironment("BROWSER_PORT", port.ToString()) - .WithPortBinding(port, assignRandomHostPort); - } + /// + protected override PlaywrightBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(resourceConfiguration)); + } + /// + protected override PlaywrightBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(resourceConfiguration)); + } - - /// - protected override PlaywrightBuilder Clone(IResourceConfiguration resourceConfiguration) - { - return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(resourceConfiguration)); - } - - /// - protected override PlaywrightBuilder Clone(IContainerConfiguration resourceConfiguration) - { - return Merge(DockerResourceConfiguration, new PlaywrightConfiguration(resourceConfiguration)); - } - - /// - protected override PlaywrightBuilder Merge(PlaywrightConfiguration oldValue, PlaywrightConfiguration newValue) - { - return new PlaywrightBuilder(new PlaywrightConfiguration(oldValue, newValue)); - } -} + /// + protected override PlaywrightBuilder Merge(PlaywrightConfiguration oldValue, PlaywrightConfiguration newValue) + { + return new PlaywrightBuilder(new PlaywrightConfiguration(oldValue, newValue)); + } +} \ No newline at end of file diff --git a/src/Testcontainers.Playwright/PlaywrightConfiguration.cs b/src/Testcontainers.Playwright/PlaywrightConfiguration.cs index e7dc16a20..acf821c4d 100644 --- a/src/Testcontainers.Playwright/PlaywrightConfiguration.cs +++ b/src/Testcontainers.Playwright/PlaywrightConfiguration.cs @@ -1,70 +1,53 @@ namespace Testcontainers.Playwright; -/// +/// [PublicAPI] -public class PlaywrightConfiguration : ContainerConfiguration +public sealed class PlaywrightConfiguration : ContainerConfiguration { - /// - /// Initializes a new instance of the class. - /// - /// The Playwright endpoint. - public PlaywrightConfiguration(string endpoint = null, - int? port = null) - { - Endpoint = endpoint; - Port = port; - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public PlaywrightConfiguration(IResourceConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public PlaywrightConfiguration(IContainerConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public PlaywrightConfiguration(PlaywrightConfiguration resourceConfiguration) - : this(new PlaywrightConfiguration(), resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } - - /// - /// Initializes a new instance of the class. - /// - /// The old Docker resource configuration. - /// The new Docker resource configuration. - public PlaywrightConfiguration(PlaywrightConfiguration oldValue, PlaywrightConfiguration newValue) - : base(oldValue, newValue) - { - Endpoint = BuildConfiguration.Combine(oldValue.Endpoint, newValue.Endpoint); - } - - - /// - /// Gets the Playwright endpoint. - /// - public string Endpoint { get; } - - - /// - /// Gets the Playwright port. - /// - public int? Port { get; } -} + /// + /// Initializes a new instance of the class. + /// + public PlaywrightConfiguration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public PlaywrightConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public PlaywrightConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public PlaywrightConfiguration(PlaywrightConfiguration resourceConfiguration) + : this(new PlaywrightConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public PlaywrightConfiguration(PlaywrightConfiguration oldValue, PlaywrightConfiguration newValue) + : base(oldValue, newValue) + { + } +} \ No newline at end of file diff --git a/src/Testcontainers.Playwright/PlaywrightContainer.cs b/src/Testcontainers.Playwright/PlaywrightContainer.cs index c86007cae..043fbff9a 100644 --- a/src/Testcontainers.Playwright/PlaywrightContainer.cs +++ b/src/Testcontainers.Playwright/PlaywrightContainer.cs @@ -2,13 +2,35 @@ namespace Testcontainers.Playwright; /// [PublicAPI] -public class PlaywrightContainer : DockerContainer +public sealed class PlaywrightContainer : DockerContainer { - /// - /// Initializes a new instance of the class. - /// - /// The container configuration. - public PlaywrightContainer(PlaywrightConfiguration configuration) : base(configuration) - { - } -} + private readonly PlaywrightConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + public PlaywrightContainer(PlaywrightConfiguration configuration) + : base(configuration) + { + _configuration = configuration; + } + + /// + /// Gets the Playwright connection string. + /// + /// The Playwright connection string. + public string GetConnectionString() + { + return new UriBuilder("ws", Hostname, GetMappedPublicPort(PlaywrightBuilder.PlaywrightPort), "/playwright").ToString(); + } + + /// + /// Gets the Playwright network. + /// + /// The Playwright network. + public INetwork GetNetwork() + { + return _configuration.Networks.Single(); + } +} \ No newline at end of file diff --git a/src/Testcontainers.Playwright/Usings.cs b/src/Testcontainers.Playwright/Usings.cs index 00e88cf71..f1ac1e74c 100644 --- a/src/Testcontainers.Playwright/Usings.cs +++ b/src/Testcontainers.Playwright/Usings.cs @@ -1,16 +1,9 @@ global using System; -global using System.IO; global using System.Linq; -global using System.Net.Http; -global using System.Text.Json; -global using System.Threading; -global using System.Threading.Tasks; global using Docker.DotNet.Models; -global using DotNet.Testcontainers; global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Configurations; global using DotNet.Testcontainers.Containers; global using DotNet.Testcontainers.Images; global using DotNet.Testcontainers.Networks; -global using JetBrains.Annotations; -global using Microsoft.Extensions.Logging.Abstractions; \ No newline at end of file +global using JetBrains.Annotations; \ No newline at end of file diff --git a/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs b/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs deleted file mode 100644 index 40c3c0c7c..000000000 --- a/tests/Testcontainers.Playwright.Tests/GlobalUsings.cs +++ /dev/null @@ -1,3 +0,0 @@ -global using Xunit; -global using System; -global using System.Threading.Tasks; diff --git a/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs b/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs index 235d423c4..d3eaba474 100644 --- a/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs +++ b/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs @@ -1,28 +1,87 @@ -namespace Testcontainers.Playwright.Tests; +namespace Testcontainers.Playwright; -public class PlaywrightContainerTest : IClassFixture +public abstract class PlaywrightContainerTest : IAsyncLifetime { - private readonly Uri _helloWorldBaseAddress; - - public PlaywrightContainerTest(TestInitializer testInitializer) - { - _helloWorldBaseAddress = testInitializer._helloWorldBaseAddress; - } - - [Fact] - public async Task HeadingElementReturnsHelloWorld() - { - // Given - var playwright = await Microsoft.Playwright.Playwright.CreateAsync(); - var browser = await playwright.Chromium.ConnectAsync($"ws://localhost:63333/playwright"); - var page = await browser.NewPageAsync(); - - // When - await page.GotoAsync(_helloWorldBaseAddress.ToString()); - var headingElement = await page.QuerySelectorAsync("h1"); - var headingElementText = await headingElement.InnerTextAsync(); - - // Then - Assert.Equal("Hello world", headingElementText); - } -} + private readonly Uri _helloWorldBaseAddress = new UriBuilder(Uri.UriSchemeHttp, "hello-world-container", 8080).Uri; + + private readonly IContainer _helloWorldContainer; + + private readonly PlaywrightContainer _playwrightContainer; + + private PlaywrightContainerTest(PlaywrightContainer playwrightContainer) + { + _helloWorldContainer = new ContainerBuilder() + .WithImage(CommonImages.HelloWorld) + .WithNetwork(playwrightContainer.GetNetwork()) + .WithNetworkAliases(_helloWorldBaseAddress.Host) + .WithPortBinding(_helloWorldBaseAddress.Port, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => + request.ForPath("/").ForPort(Convert.ToUInt16(_helloWorldBaseAddress.Port)))) + .Build(); + + _playwrightContainer = playwrightContainer; + } + + public async ValueTask InitializeAsync() + { + await _playwrightContainer.StartAsync() + .ConfigureAwait(false); + + await _helloWorldContainer.StartAsync() + .ConfigureAwait(false); + } + + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore() + .ConfigureAwait(false); + + GC.SuppressFinalize(this); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task HeadingElementReturnsHelloWorld() + { + // Given + var playwright = await Microsoft.Playwright.Playwright.CreateAsync() + .ConfigureAwait(true); + + var browser = await playwright.Chromium.ConnectAsync(_playwrightContainer.GetConnectionString()) + .ConfigureAwait(true); + + var page = await browser.NewPageAsync() + .ConfigureAwait(true); + + // When + await page.GotoAsync(_helloWorldBaseAddress.ToString()) + .ConfigureAwait(true); + + var headingElement = await page.QuerySelectorAsync("h1") + .ConfigureAwait(true); + + var headingElementText = await headingElement!.InnerTextAsync() + .ConfigureAwait(true); + + // Then + Assert.Equal("Hello world", headingElementText); + } + + protected virtual async ValueTask DisposeAsyncCore() + { + await _helloWorldContainer.DisposeAsync() + .ConfigureAwait(false); + + await _playwrightContainer.DisposeAsync() + .ConfigureAwait(false); + } + + [UsedImplicitly] + public sealed class PlaywrightDefaultConfiguration : PlaywrightContainerTest + { + public PlaywrightDefaultConfiguration() + : base(new PlaywrightBuilder().Build()) + { + } + } +} \ No newline at end of file diff --git a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs b/tests/Testcontainers.Playwright.Tests/TestInitializer.cs deleted file mode 100644 index ce44587ec..000000000 --- a/tests/Testcontainers.Playwright.Tests/TestInitializer.cs +++ /dev/null @@ -1,75 +0,0 @@ -using DotNet.Testcontainers.Builders; -using DotNet.Testcontainers.Configurations; -using DotNet.Testcontainers.Containers; -using DotNet.Testcontainers.Networks; - -namespace Testcontainers.Playwright.Tests -{ - public class TestInitializer : IAsyncLifetime - { - internal readonly Uri _helloWorldBaseAddress = new UriBuilder(Uri.UriSchemeHttp, "hello-world-container", 8080).Uri; - private IContainer _helloWorldContainer; - private PlaywrightContainer _playwrightContainer; - - private const string PlaywrightContainerName = "testplaywright"; - - public async ValueTask InitializeAsync() - { - var network = CreateNetwork(); - - _helloWorldContainer = CreateHelloWorldContainer(network); - _playwrightContainer = CreatePlaywrightContainer(network); - - await StartContainersAsync(); - } - - public async ValueTask DisposeAsync() - { - await DisposeContainersAsync(); - } - - private INetwork CreateNetwork() - { - return new NetworkBuilder() - .WithName(Guid.NewGuid().ToString("D")) - .WithDriver(NetworkDriver.Bridge) - .WithCleanUp(true) - .Build(); - } - - private IContainer CreateHelloWorldContainer(INetwork network) - { - return new ContainerBuilder() - .WithImage("testcontainers/helloworld:1.1.0") - .WithNetwork(network) - .WithNetworkAliases(_helloWorldBaseAddress.Host) - .WithPortBinding(_helloWorldBaseAddress.Port, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => - request.ForPath("/").ForPort(Convert.ToUInt16(_helloWorldBaseAddress.Port)))) - .Build(); - } - - private PlaywrightContainer CreatePlaywrightContainer(INetwork network) - { - return new PlaywrightBuilder() - .WithNetwork(network) - .WithName(PlaywrightContainerName) - .WithNetworkAliases(PlaywrightContainerName) - .WithPortBinding(63333, 53333) - .WithBrowser(PlaywrightBrowser.Chromium) - .Build(); - } - - private async Task StartContainersAsync() - { - await _helloWorldContainer.StartAsync(); - await _playwrightContainer.StartAsync(); - } - - private async Task DisposeContainersAsync() - { - await _playwrightContainer.DisposeAsync(); - await _helloWorldContainer.DisposeAsync(); - } - } -} diff --git a/tests/Testcontainers.Playwright.Tests/Usings.cs b/tests/Testcontainers.Playwright.Tests/Usings.cs new file mode 100644 index 000000000..30832d99e --- /dev/null +++ b/tests/Testcontainers.Playwright.Tests/Usings.cs @@ -0,0 +1,7 @@ +global using System; +global using System.Threading.Tasks; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Commons; +global using DotNet.Testcontainers.Containers; +global using JetBrains.Annotations; +global using Xunit; \ No newline at end of file From 2534b901ecad307cba198f91e246e05f899042f6 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:56:46 +0100 Subject: [PATCH 12/15] chore: Remove BOM --- src/Testcontainers.Playwright/PlaywrightBrowser.cs | 2 +- src/Testcontainers.Playwright/Testcontainers.Playwright.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Testcontainers.Playwright/PlaywrightBrowser.cs b/src/Testcontainers.Playwright/PlaywrightBrowser.cs index ca09bd8b8..2e70fb6ff 100644 --- a/src/Testcontainers.Playwright/PlaywrightBrowser.cs +++ b/src/Testcontainers.Playwright/PlaywrightBrowser.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Playwright; +namespace Testcontainers.Playwright; /// /// Playwright browser configuration. diff --git a/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj b/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj index 906f34018..9a25b9c4d 100644 --- a/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj +++ b/src/Testcontainers.Playwright/Testcontainers.Playwright.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;netstandard2.0;netstandard2.1 latest From e2effe84d7f75eca8eed6c61908b2d60cb41a664 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:57:46 +0100 Subject: [PATCH 13/15] docs: Add Playwright module docs --- docs/modules/index.md | 1 + docs/modules/playwright.md | 72 +++++++++++++++++++ mkdocs.yml | 1 + .../PlaywrightContainerTest.cs | 2 + .../Testcontainers.Playwright.Tests.csproj | 2 + 5 files changed, 78 insertions(+) create mode 100644 docs/modules/playwright.md diff --git a/docs/modules/index.md b/docs/modules/index.md index bf029294b..e281b1de6 100644 --- a/docs/modules/index.md +++ b/docs/modules/index.md @@ -60,6 +60,7 @@ await moduleNameContainer.StartAsync(); | OpenSearch | `opensearchproject/opensearch:2.12.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.OpenSearch) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.OpenSearch) | | Oracle | `gvenzl/oracle-xe:21.3.0-slim-faststart` | [NuGet](https://www.nuget.org/packages/Testcontainers.Oracle) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Oracle) | | Papercut | `changemakerstudiosus/papercut-smtp:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.Papercut) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Papercut) | +| Playwright | `jacoblincool/playwright:chrome-server-1.55.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.Playwright) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Playwright) | | PostgreSQL | `postgres:15.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.PostgreSql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PostgreSql) | | PubSub | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.PubSub) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PubSub) | | Pulsar | `apachepulsar/pulsar:3.0.6` | [NuGet](https://www.nuget.org/packages/Testcontainers.Pulsar) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Pulsar) | diff --git a/docs/modules/playwright.md b/docs/modules/playwright.md new file mode 100644 index 000000000..fe43576ac --- /dev/null +++ b/docs/modules/playwright.md @@ -0,0 +1,72 @@ +# Playwright + +[Playwright](https://playwright.dev/) is a framework for web testing and automation. It allows testing across all modern rendering engines including Chromium, WebKit, and Firefox with a single API. This module provides pre-configured browser containers for automated testing. + +Add the following dependency to your project file: + +```shell title="NuGet" +dotnet add package Testcontainers.Playwright +``` + +You can start a Playwright container instance from any .NET application. To create and start a container instance with the default configuration (Chrome browser), use the module-specific builder as shown below: + +=== "Start a Playwright container" + ```csharp + var playwrightContainer = new PlaywrightBuilder().Build(); + await playwrightContainer.StartAsync(); + ``` + +This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. + +This example demonstrates the Playwright container accessing a web site running inside another container (using the [`testcontainers/helloworld`](https://github.com/testcontainers/helloworld) image). Both containers are assigned to a shared network (see the [Network configuration](#network-configuration) section) to enable communication between them. + +=== "Usage Example" + ```csharp + --8<-- "tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs:UsePlaywrightContainer" + ``` + +The test example uses the following NuGet dependencies: + +=== "Package References" + ```xml + --8<-- "tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj:PackageReferences" + ``` + +To execute the tests, use the command `dotnet test` from a terminal. + +--8<-- "docs/modules/_call_out_test_projects.txt" + +## Browser selection + +By default, the Playwright module uses Chrome as the browser. You can configure different browsers using the `WithBrowser` method: + +=== "Chromium" + ```csharp + var playwrightContainer = new PlaywrightBuilder() + .WithBrowser(PlaywrightBrowser.Chromium) + .Build(); + ``` + +=== "Firefox" + ```csharp + var playwrightContainer = new PlaywrightBuilder() + .WithBrowser(PlaywrightBrowser.Firefox) + .Build(); + ``` + +=== "Edge" + ```csharp + var playwrightContainer = new PlaywrightBuilder() + .WithBrowser(PlaywrightBrowser.Edge) + .Build(); + ``` + +## Network configuration + +The Playwright container is configured with a network that can be shared with other containers. This is useful when testing applications that need to communicate with other services. Use the `GetNetwork()` method to access the container's network: + +```csharp +var helloWorldContainer = new ContainerBuilder() + .WithNetwork(playwrightContainer.GetNetwork()) + .Build(); +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index af21de188..a13468237 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,6 +60,7 @@ nav: - modules/mssql.md - modules/neo4j.md - modules/opensearch.md + - modules/playwright.md - modules/postgres.md - modules/qdrant.md - modules/rabbitmq.md diff --git a/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs b/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs index d3eaba474..d9212eb5f 100644 --- a/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs +++ b/tests/Testcontainers.Playwright.Tests/PlaywrightContainerTest.cs @@ -39,6 +39,7 @@ await DisposeAsyncCore() GC.SuppressFinalize(this); } + // # --8<-- [start:UsePlaywrightContainer] [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public async Task HeadingElementReturnsHelloWorld() @@ -66,6 +67,7 @@ await page.GotoAsync(_helloWorldBaseAddress.ToString()) // Then Assert.Equal("Hello world", headingElementText); } + // # --8<-- [end:UsePlaywrightContainer] protected virtual async ValueTask DisposeAsyncCore() { diff --git a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj index 0a5f575ca..6a82af1cc 100644 --- a/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj +++ b/tests/Testcontainers.Playwright.Tests/Testcontainers.Playwright.Tests.csproj @@ -6,11 +6,13 @@ Exe + + From d7ce6acd509ea6d491b9d06e5516cd5ff09088f7 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:30:37 +0100 Subject: [PATCH 14/15] chore: Add some more info --- docs/modules/playwright.md | 2 +- src/Testcontainers.Playwright/PlaywrightBrowser.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/modules/playwright.md b/docs/modules/playwright.md index fe43576ac..27f77eb33 100644 --- a/docs/modules/playwright.md +++ b/docs/modules/playwright.md @@ -38,7 +38,7 @@ To execute the tests, use the command `dotnet test` from a terminal. ## Browser selection -By default, the Playwright module uses Chrome as the browser. You can configure different browsers using the `WithBrowser` method: +By default, the Playwright module uses Chrome as the browser. You can configure different browsers using the `WithBrowser` builder method: === "Chromium" ```csharp diff --git a/src/Testcontainers.Playwright/PlaywrightBrowser.cs b/src/Testcontainers.Playwright/PlaywrightBrowser.cs index 2e70fb6ff..847a69680 100644 --- a/src/Testcontainers.Playwright/PlaywrightBrowser.cs +++ b/src/Testcontainers.Playwright/PlaywrightBrowser.cs @@ -4,6 +4,8 @@ namespace Testcontainers.Playwright; /// Playwright browser configuration. /// /// +/// These configuration use Playwright version 1.55. +/// /// The browsers are standalone, pre-built Playwright versions from the /// https://github.com/JacobLinCool/playwright-docker repository. /// From 3d1d913da76737737a49ced095a20f7be0f5dcf5 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:36:48 +0100 Subject: [PATCH 15/15] chore: Use MS image --- docs/modules/index.md | 2 +- docs/modules/playwright.md | 27 +-------- .../PlaywrightBrowser.cs | 58 ------------------- .../PlaywrightBuilder.cs | 24 +++----- .../PlaywrightContainer.cs | 2 +- 5 files changed, 11 insertions(+), 102 deletions(-) delete mode 100644 src/Testcontainers.Playwright/PlaywrightBrowser.cs diff --git a/docs/modules/index.md b/docs/modules/index.md index e281b1de6..fcd99f51f 100644 --- a/docs/modules/index.md +++ b/docs/modules/index.md @@ -60,7 +60,7 @@ await moduleNameContainer.StartAsync(); | OpenSearch | `opensearchproject/opensearch:2.12.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.OpenSearch) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.OpenSearch) | | Oracle | `gvenzl/oracle-xe:21.3.0-slim-faststart` | [NuGet](https://www.nuget.org/packages/Testcontainers.Oracle) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Oracle) | | Papercut | `changemakerstudiosus/papercut-smtp:latest` | [NuGet](https://www.nuget.org/packages/Testcontainers.Papercut) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Papercut) | -| Playwright | `jacoblincool/playwright:chrome-server-1.55.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.Playwright) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Playwright) | +| Playwright | `mcr.microsoft.com/playwright:v1.55.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.Playwright) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Playwright) | | PostgreSQL | `postgres:15.1` | [NuGet](https://www.nuget.org/packages/Testcontainers.PostgreSql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PostgreSql) | | PubSub | `gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators` | [NuGet](https://www.nuget.org/packages/Testcontainers.PubSub) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.PubSub) | | Pulsar | `apachepulsar/pulsar:3.0.6` | [NuGet](https://www.nuget.org/packages/Testcontainers.Pulsar) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Pulsar) | diff --git a/docs/modules/playwright.md b/docs/modules/playwright.md index 27f77eb33..6c2bb3f74 100644 --- a/docs/modules/playwright.md +++ b/docs/modules/playwright.md @@ -8,7 +8,7 @@ Add the following dependency to your project file: dotnet add package Testcontainers.Playwright ``` -You can start a Playwright container instance from any .NET application. To create and start a container instance with the default configuration (Chrome browser), use the module-specific builder as shown below: +You can start a Playwright container instance from any .NET application. To create and start a container instance with the default configuration, use the module-specific builder as shown below: === "Start a Playwright container" ```csharp @@ -36,31 +36,6 @@ To execute the tests, use the command `dotnet test` from a terminal. --8<-- "docs/modules/_call_out_test_projects.txt" -## Browser selection - -By default, the Playwright module uses Chrome as the browser. You can configure different browsers using the `WithBrowser` builder method: - -=== "Chromium" - ```csharp - var playwrightContainer = new PlaywrightBuilder() - .WithBrowser(PlaywrightBrowser.Chromium) - .Build(); - ``` - -=== "Firefox" - ```csharp - var playwrightContainer = new PlaywrightBuilder() - .WithBrowser(PlaywrightBrowser.Firefox) - .Build(); - ``` - -=== "Edge" - ```csharp - var playwrightContainer = new PlaywrightBuilder() - .WithBrowser(PlaywrightBrowser.Edge) - .Build(); - ``` - ## Network configuration The Playwright container is configured with a network that can be shared with other containers. This is useful when testing applications that need to communicate with other services. Use the `GetNetwork()` method to access the container's network: diff --git a/src/Testcontainers.Playwright/PlaywrightBrowser.cs b/src/Testcontainers.Playwright/PlaywrightBrowser.cs deleted file mode 100644 index 847a69680..000000000 --- a/src/Testcontainers.Playwright/PlaywrightBrowser.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Testcontainers.Playwright; - -/// -/// Playwright browser configuration. -/// -/// -/// These configuration use Playwright version 1.55. -/// -/// The browsers are standalone, pre-built Playwright versions from the -/// https://github.com/JacobLinCool/playwright-docker repository. -/// -[PublicAPI] -public readonly struct PlaywrightBrowser -{ - /// - /// Gets the Playwright standalone Chrome configuration. - /// - public static readonly PlaywrightBrowser Chrome = new PlaywrightBrowser("jacoblincool/playwright:chrome-server-1.55.1"); - - /// - /// Gets the Playwright standalone Chromium configuration. - /// - public static readonly PlaywrightBrowser Chromium = new PlaywrightBrowser("jacoblincool/playwright:chromium-server-1.55.1"); - - /// - /// Gets the Playwright standalone Firefox configuration. - /// - public static readonly PlaywrightBrowser Firefox = new PlaywrightBrowser("jacoblincool/playwright:firefox-server-1.55.1"); - - /// - /// Gets the Playwright standalone Edge configuration. - /// - public static readonly PlaywrightBrowser Edge = new PlaywrightBrowser("jacoblincool/playwright:msedge-server-1.55.1"); - - /// - /// Initializes a new instance of the struct. - /// - /// The Playwright standalone Docker image. - public PlaywrightBrowser(string image) - : this(new DockerImage(image)) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// The Playwright standalone Docker image. - public PlaywrightBrowser(IImage image) - { - Image = image; - } - - /// - /// Gets the Playwright standalone Docker image. - /// - [NotNull] - public IImage Image { get; } -} \ No newline at end of file diff --git a/src/Testcontainers.Playwright/PlaywrightBuilder.cs b/src/Testcontainers.Playwright/PlaywrightBuilder.cs index 48df5ad0e..5711fc079 100644 --- a/src/Testcontainers.Playwright/PlaywrightBuilder.cs +++ b/src/Testcontainers.Playwright/PlaywrightBuilder.cs @@ -6,7 +6,9 @@ public sealed class PlaywrightBuilder : ContainerBuilder /// Initializes a new instance of the class. @@ -30,19 +32,6 @@ private PlaywrightBuilder(PlaywrightConfiguration resourceConfiguration) /// protected override PlaywrightConfiguration DockerResourceConfiguration { get; } - /// - /// Sets the Playwright browser configuration. - /// - /// - /// https://github.com/JacobLinCool/playwright-docker. - /// - /// The Playwright browser configuration. - /// A configured instance of . - public PlaywrightBuilder WithBrowser(PlaywrightBrowser playwrightBrowser) - { - return WithImage(playwrightBrowser.Image); - } - /// public override PlaywrightContainer Build() { @@ -54,11 +43,14 @@ public override PlaywrightContainer Build() protected override PlaywrightBuilder Init() { return base.Init() - .WithBrowser(PlaywrightBrowser.Chrome) + .WithImage(PlaywrightImage) .WithNetwork(new NetworkBuilder().Build()) .WithNetworkAliases(PlaywrightNetworkAlias) .WithPortBinding(PlaywrightPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("ws://localhost:" + PlaywrightPort + "/playwright")); + .WithEntrypoint("/bin/sh", "-c") + // Extract the Playwright version from the container at startup. + .WithCommand("npx -y playwright@$(sed --quiet 's/.*\\\"driverVersion\\\": *\"\\([^\"]*\\)\".*/\\1/p' ms-playwright/.docker-info) run-server --port " + PlaywrightPort) + .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("Listening on ws://localhost:8080/")); } /// diff --git a/src/Testcontainers.Playwright/PlaywrightContainer.cs b/src/Testcontainers.Playwright/PlaywrightContainer.cs index 043fbff9a..1c9df4eb8 100644 --- a/src/Testcontainers.Playwright/PlaywrightContainer.cs +++ b/src/Testcontainers.Playwright/PlaywrightContainer.cs @@ -22,7 +22,7 @@ public PlaywrightContainer(PlaywrightConfiguration configuration) /// The Playwright connection string. public string GetConnectionString() { - return new UriBuilder("ws", Hostname, GetMappedPublicPort(PlaywrightBuilder.PlaywrightPort), "/playwright").ToString(); + return new UriBuilder("ws", Hostname, GetMappedPublicPort(PlaywrightBuilder.PlaywrightPort)).ToString(); } ///