diff --git a/.github/actions/setup-dotnet/action.yml b/.github/actions/setup-dotnet/action.yml index a344a1538..055ef2910 100644 --- a/.github/actions/setup-dotnet/action.yml +++ b/.github/actions/setup-dotnet/action.yml @@ -5,6 +5,10 @@ inputs: description: 'Whether to run dotnet build after restore' required: false default: 'true' + configuration: + description: 'What configuration to run (e.g., Release or Debug)' + required: false + default: 'Release' runs: using: 'composite' steps: @@ -31,4 +35,4 @@ runs: - name: Build with dotnet if: inputs.build == 'true' shell: bash - run: dotnet build -p:ContinuousIntegrationBuild=True --configuration Release --no-restore + run: dotnet build -p:ContinuousIntegrationBuild=True --configuration ${{ inputs.configuration }} --no-restore diff --git a/.github/workflows/Build-Test-And-Deploy.yaml b/.github/workflows/Build-Test-And-Deploy.yaml index e80174049..15c49a55a 100644 --- a/.github/workflows/Build-Test-And-Deploy.yaml +++ b/.github/workflows/Build-Test-And-Deploy.yaml @@ -35,7 +35,7 @@ jobs: - name: Run .NET Tests id: run-dotnet-tests - run: dotnet test --no-build --configuration Release --blame-hang-timeout 15m --blame-hang-dump-type full -l trx --results-directory ./TestResults + run: dotnet test --no-build --configuration Release --blame-hang-timeout 15m --blame-hang-dump-type full -l trx --results-directory ./TestResults --filter TestType!=Integration env: POCKETLOGGER_LOG_PATH: ${{ github.workspace }}/artifacts/logs/pocketlogger.log @@ -43,9 +43,9 @@ jobs: if: ${{ steps.run-dotnet-tests.outcome == 'failure' || failure() }} uses: BenjaminMichaelis/trx-to-vsplaylist@v3 with: - trx-file-path: './TestResults/*.trx' - test-outcomes: 'Failed' - artifact-name: 'linux-test-playlists' + trx-file-path: "./TestResults/*.trx" + test-outcomes: "Failed" + artifact-name: "linux-test-playlists" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -93,7 +93,7 @@ jobs: - name: Run .NET Tests id: run-dotnet-tests - run: dotnet test --no-build --configuration Release --blame-hang-timeout 15m --blame-hang-dump-type full -l trx --results-directory ./TestResults + run: dotnet test --no-build --configuration Release --blame-hang-timeout 15m --blame-hang-dump-type full -l trx --results-directory ./TestResults --filter TestType!=Integration env: POCKETLOGGER_LOG_PATH: ${{ github.workspace }}/artifacts/logs/pocketlogger.log @@ -101,16 +101,25 @@ jobs: if: ${{ steps.run-dotnet-tests.outcome == 'failure' || failure() }} uses: BenjaminMichaelis/trx-to-vsplaylist@v3 with: - trx-file-path: './TestResults/*.trx' - test-outcomes: 'Failed' - artifact-name: 'windows-test-playlists' + trx-file-path: "./TestResults/*.trx" + test-outcomes: "Failed" + artifact-name: "windows-test-playlists" integration-tests: - # Integration tests use Playwright and are gated behind RunIntegrationTests=true. - # IntegrationTestFactAttribute explicitly skips these tests on Linux, so a - # Windows runner is required to actually execute them. - runs-on: windows-latest - + strategy: + fail-fast: false + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + browser: [chromium, firefox, webkit] + exclude: + - os: windows-latest + browser: webkit + - os: ubuntu-latest + browser: webkit + - os: macos-latest + browser: firefox + + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 @@ -119,22 +128,25 @@ jobs: - name: Set up .NET environment uses: ./.github/actions/setup-dotnet + with: + configuration: Debug - name: Run .NET Integration Tests id: run-dotnet-integration-tests - run: dotnet test --no-build --configuration Release --blame-hang-timeout 15m --blame-hang-dump-type full -l trx --results-directory ./TestResults + run: dotnet test --no-build --configuration Debug --blame-hang-timeout 15m --blame-hang-dump-type full -l trx --results-directory ./TestResults --filter TestType=Integration -- Playwright.BrowserName=${{ matrix.browser }} env: RunIntegrationTests: true POCKETLOGGER_LOG_PATH: ${{ github.workspace }}/artifacts/logs/pocketlogger.log TRYDOTNET_PREBUILDS_PATH: ${{ github.workspace }}/artifacts/trydotnet-prebuilds + Playwright_BrowserName: ${{ matrix.browser }} - name: Convert TRX to Playlist if: ${{ steps.run-dotnet-integration-tests.outcome == 'failure' || failure() }} uses: BenjaminMichaelis/trx-to-vsplaylist@v3 with: - trx-file-path: './TestResults/*.trx' - test-outcomes: 'Failed' - artifact-name: 'integration-test-playlists' + trx-file-path: "./TestResults/*.trx" + test-outcomes: "Failed" + artifact-name: "integration-test-playlists" deploy-development: if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' diff --git a/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs index 0b40edfaa..68ad4f676 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs +++ b/src/Microsoft.TryDotNet.IntegrationTests/EditorTests.cs @@ -15,6 +15,7 @@ namespace Microsoft.TryDotNet.IntegrationTests; [LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +[Trait("TestType", "Integration")] public class EditorTests : PlaywrightTestBase { public EditorTests(IntegratedServicesFixture services, ITestOutputHelper output) : base(services, output) diff --git a/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs b/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs index c18d3d969..359ce3222 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs +++ b/src/Microsoft.TryDotNet.IntegrationTests/IntegratedServicesFixture.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Interactive.CSharpProject.Build; diff --git a/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs b/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs index 995adfc48..04bc7efb7 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs +++ b/src/Microsoft.TryDotNet.IntegrationTests/IntegrationTestFactAttribute.cs @@ -1,33 +1,30 @@ -// Copyright (c) .NET Foundation and contributors. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Runtime.InteropServices; - -using Xunit; - -namespace Microsoft.TryDotNet.IntegrationTests -{ - internal class IntegrationTestFactAttribute : FactAttribute - { - private const string EnvironmentVariableName = "RunIntegrationTests"; - - public IntegrationTestFactAttribute(string? skipReason = null) - { - var variableValue = Environment.GetEnvironmentVariable(EnvironmentVariableName) ?? "false"; - switch (variableValue.ToLowerInvariant()) - { - case "1": - case "true": - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - Skip = string.IsNullOrWhiteSpace(skipReason) ? "Ignored on Linux" : skipReason; - } - break; - default: - Skip = $"Skipping integration tests because environment variable '{EnvironmentVariableName}' was not 'true' or '1'."; - break; - } - } - } -} +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Runtime.InteropServices; + +using Xunit; + +namespace Microsoft.TryDotNet.IntegrationTests +{ + internal class IntegrationTestFactAttribute : FactAttribute + { + private const string EnvironmentVariableName = "RunIntegrationTests"; + + public IntegrationTestFactAttribute(string? skipReason = null) + { + var variableValue = Environment.GetEnvironmentVariable(EnvironmentVariableName) ?? "false"; + switch (variableValue.ToLowerInvariant()) + { + case "1": + case "true": + // Run tests + break; + default: + Skip = $"Skipping integration tests because environment variable '{EnvironmentVariableName}' was not 'true' or '1'."; + break; + } + } + } +} diff --git a/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj b/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj index 2d64d056f..b32f2d379 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj +++ b/src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj @@ -10,6 +10,10 @@ embedded + + + + @@ -71,6 +75,5 @@ public static class BuildProperties - \ No newline at end of file diff --git a/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs b/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs index a525b54ce..1b26dc324 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs +++ b/src/Microsoft.TryDotNet.IntegrationTests/PlaywrightSession.cs @@ -20,9 +20,24 @@ public PlaywrightSession(IPlaywright playwright, IBrowser browser) public IBrowser Browser { get; } - public static async Task StartAsync() + public static async Task StartAsync(string? browserName = null) { - var exitCode = Playwright.Program.Main(["install", "chromium"]); + + string? selectedBrowser = browserName + ?? Environment.GetEnvironmentVariable("Playwright.BrowserName") + ?? Environment.GetEnvironmentVariable("Playwright_BrowserName"); + + int exitCode; + if (!string.IsNullOrWhiteSpace(selectedBrowser)) + { + selectedBrowser = selectedBrowser.ToLowerInvariant(); + exitCode = Playwright.Program.Main(["install", selectedBrowser]); + } + else + { + exitCode = Playwright.Program.Main(["install"]); + selectedBrowser = "chromium"; + } if (exitCode is not 0) { throw new Exception($"Playwright exited with code {exitCode}"); @@ -37,7 +52,13 @@ public static async Task StartAsync() browserTypeLaunchOptions.Headless = false; } - var browser = await session.Chromium.LaunchAsync(browserTypeLaunchOptions).Timeout(TimeSpan.FromMinutes(5), "Timeout launching browser"); + IBrowser browser = selectedBrowser switch + { + "chromium" => await session.Chromium.LaunchAsync(browserTypeLaunchOptions).Timeout(TimeSpan.FromMinutes(5), "Timeout launching browser"), + "firefox" => await session.Firefox.LaunchAsync(browserTypeLaunchOptions).Timeout(TimeSpan.FromMinutes(5), "Timeout launching browser"), + "webkit" => await session.Webkit.LaunchAsync(browserTypeLaunchOptions).Timeout(TimeSpan.FromMinutes(5), "Timeout launching browser"), + _ => throw new ArgumentException($"Unknown browser '{selectedBrowser}'. Valid values: chromium, firefox, webkit.") + }; return new PlaywrightSession(session, browser); } diff --git a/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs index a962e35ed..c85d64775 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs +++ b/src/Microsoft.TryDotNet.IntegrationTests/TryDotNetJsIntegrationTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.TryDotNet.IntegrationTests; [LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +[Trait("TestType", "Integration")] public class TryDotNetJsIntegrationTests : PlaywrightTestBase { public TryDotNetJsIntegrationTests(IntegratedServicesFixture services, ITestOutputHelper output) : base(services, output) diff --git a/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs b/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs index 1da87a7b5..81d4f4c6c 100644 --- a/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs +++ b/src/Microsoft.TryDotNet.IntegrationTests/WasmRunnerTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.TryDotNet.IntegrationTests; [LogToPocketLogger(FileNameEnvironmentVariable = "POCKETLOGGER_LOG_PATH")] +[Trait("TestType", "Integration")] public class WasmRunnerTests : PlaywrightTestBase { public WasmRunnerTests(IntegratedServicesFixture services, ITestOutputHelper output) : base(services, output)