diff --git a/AspNetCore.sln b/AspNetCore.sln index 591baf0d56bf..3a5ff2e462d5 100644 --- a/AspNetCore.sln +++ b/AspNetCore.sln @@ -1618,6 +1618,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWinFormsApp", "src\Co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks", "src\Http\Http.Abstractions\perf\Microbenchmarks\Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks.csproj", "{3F752B48-2936-4FCA-B0DC-4AB0F788F897}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.EToETests", "src\Components\test\EToETest\Microsoft.AspNetCore.Components.EToETests.csproj", "{60B82C36-E613-496D-AD1B-0015DDAEFD96}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{F0849E7E-61DB-4849-9368-9E7BC125DCB0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsTestApp", "src\Components\WebView\Platforms\WindowsForms\testassets\WinFormsTestApp\WinFormsTestApp.csproj", "{99EE7769-3C81-477B-B947-0A5CBCD5B27D}" @@ -7663,6 +7665,18 @@ Global {3F752B48-2936-4FCA-B0DC-4AB0F788F897}.Release|x64.Build.0 = Release|Any CPU {3F752B48-2936-4FCA-B0DC-4AB0F788F897}.Release|x86.ActiveCfg = Release|Any CPU {3F752B48-2936-4FCA-B0DC-4AB0F788F897}.Release|x86.Build.0 = Release|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Debug|x64.ActiveCfg = Debug|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Debug|x64.Build.0 = Debug|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Debug|x86.ActiveCfg = Debug|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Debug|x86.Build.0 = Debug|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Release|Any CPU.Build.0 = Release|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Release|x64.ActiveCfg = Release|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Release|x64.Build.0 = Release|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Release|x86.ActiveCfg = Release|Any CPU + {60B82C36-E613-496D-AD1B-0015DDAEFD96}.Release|x86.Build.0 = Release|Any CPU {99EE7769-3C81-477B-B947-0A5CBCD5B27D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {99EE7769-3C81-477B-B947-0A5CBCD5B27D}.Debug|Any CPU.Build.0 = Debug|Any CPU {99EE7769-3C81-477B-B947-0A5CBCD5B27D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -8498,6 +8512,7 @@ Global {3BA297F8-1CA1-492D-AE64-A60B825D8501} = {D4E9A2C5-0838-42DF-BC80-C829C4C9137E} {CC740832-D268-47A3-9058-B9054F8397E2} = {D3B76F4E-A980-45BF-AEA1-EA3175B0B5A1} {3F752B48-2936-4FCA-B0DC-4AB0F788F897} = {DCBBDB52-4A49-4141-8F4D-81C0FFFB7BD5} + {60B82C36-E613-496D-AD1B-0015DDAEFD96} = {0508E463-0269-40C9-B5C2-3B600FB2A28B} {F0849E7E-61DB-4849-9368-9E7BC125DCB0} = {D4E9A2C5-0838-42DF-BC80-C829C4C9137E} {99EE7769-3C81-477B-B947-0A5CBCD5B27D} = {F0849E7E-61DB-4849-9368-9E7BC125DCB0} {94D0D6F3-8632-41DE-908B-47A787D570FF} = {5241CF68-66A0-4724-9BAA-36DB959A5B11} diff --git a/src/Components/Components.slnf b/src/Components/Components.slnf index 0ed553f60d25..8d1521143844 100644 --- a/src/Components/Components.slnf +++ b/src/Components/Components.slnf @@ -40,6 +40,7 @@ "src\\Components\\benchmarkapps\\Wasm.Performance\\Driver\\Wasm.Performance.Driver.csproj", "src\\Components\\benchmarkapps\\Wasm.Performance\\TestApp\\Wasm.Performance.TestApp.csproj", "src\\Components\\test\\E2ETest\\Microsoft.AspNetCore.Components.E2ETests.csproj", + "src\\Components\\test\\EToETest\\Microsoft.AspNetCore.Components.EToETests.csproj", "src\\Components\\test\\testassets\\BasicTestApp\\BasicTestApp.csproj", "src\\Components\\test\\testassets\\ComponentsApp.Server\\ComponentsApp.Server.csproj", "src\\Components\\test\\testassets\\GlobalizationWasmApp\\GlobalizationWasmApp.csproj", @@ -100,6 +101,7 @@ "src\\Servers\\Kestrel\\Core\\src\\Microsoft.AspNetCore.Server.Kestrel.Core.csproj", "src\\Servers\\Kestrel\\Kestrel\\src\\Microsoft.AspNetCore.Server.Kestrel.csproj", "src\\Servers\\Kestrel\\Transport.Sockets\\src\\Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.csproj", + "src\\Shared\\BrowserTesting\\src\\Microsoft.AspNetCore.BrowserTesting.csproj", "src\\SignalR\\clients\\csharp\\Client.Core\\src\\Microsoft.AspNetCore.SignalR.Client.Core.csproj", "src\\SignalR\\clients\\csharp\\Client\\src\\Microsoft.AspNetCore.SignalR.Client.csproj", "src\\SignalR\\clients\\csharp\\Http.Connections.Client\\src\\Microsoft.AspNetCore.Http.Connections.Client.csproj", diff --git a/src/Components/test/EToETest/Infrastructure/PlaywrightTestBase.cs b/src/Components/test/EToETest/Infrastructure/PlaywrightTestBase.cs new file mode 100644 index 000000000000..040a3626a70f --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/PlaywrightTestBase.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.BrowserTesting; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure +{ + public class PlaywrightTestBase : LoggedTest, IAsyncLifetime + { + private static readonly bool _isCIEnvironment = + !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ContinuousIntegrationBuild")); + + public PlaywrightTestBase(ITestOutputHelper output) : base(output) { } + + public async Task InitializeAsync() + { + var testSink = new TestSink(); + testSink.MessageLogged += LogMessage; + var factory = new TestLoggerFactory(testSink, enabled: true); + BrowserManager = await BrowserManager.CreateAsync(CreateConfiguration(), factory); + BrowserContextInfo = new ContextInformation(factory); + + void LogMessage(WriteContext ctx) + { + TestOutputHelper.WriteLine($"{MapLogLevel(ctx)}: [Browser]{ctx.Message}"); + + static string MapLogLevel(WriteContext obj) => obj.LogLevel switch + { + LogLevel.Trace => "trace", + LogLevel.Debug => "dbug", + LogLevel.Information => "info", + LogLevel.Warning => "warn", + LogLevel.Error => "error", + LogLevel.Critical => "crit", + LogLevel.None => "info", + _ => "info" + }; + } + } + + private static IConfiguration CreateConfiguration() + { + var basePath = Path.GetDirectoryName(typeof(PlaywrightTestBase).Assembly.Location); + var os = Environment.OSVersion.Platform switch + { + PlatformID.Win32NT => "win", + PlatformID.Unix => "linux", + PlatformID.MacOSX => "osx", + _ => null + }; + + var builder = new ConfigurationBuilder() + .AddJsonFile(Path.Combine(basePath, "playwrightSettings.json")) + .AddJsonFile(Path.Combine(basePath, $"playwrightSettings.{os}.json"), optional: true); + + if (_isCIEnvironment) + { + builder.AddJsonFile(Path.Combine(basePath, "playwrightSettings.ci.json"), optional: true) + .AddJsonFile(Path.Combine(basePath, $"playwrightSettings.ci.{os}.json"), optional: true); + } + + if (Debugger.IsAttached) + { + builder.AddJsonFile(Path.Combine(basePath, "playwrightSettings.debug.json"), optional: true); + } + + return builder.Build(); + } + + public Task DisposeAsync() => BrowserManager.DisposeAsync(); + + public ITestOutputHelper Output => TestOutputHelper; + public ContextInformation BrowserContextInfo { get; protected set; } + public BrowserManager BrowserManager { get; private set; } + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/AspNetEnvironment.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/AspNetEnvironment.cs new file mode 100644 index 000000000000..54a76e303d2a --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/AspNetEnvironment.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public enum AspNetEnvironment + { + Development, + Production + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/AspNetSiteServerFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/AspNetSiteServerFixture.cs new file mode 100644 index 000000000000..a4e476aa6cf7 --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/AspNetSiteServerFixture.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public class AspNetSiteServerFixture : WebHostServerFixture + { + public delegate IHost BuildWebHost(string[] args); + + public Assembly ApplicationAssembly { get; set; } + + public BuildWebHost BuildWebHostMethod { get; set; } + + public AspNetEnvironment Environment { get; set; } = AspNetEnvironment.Production; + + public List AdditionalArguments { get; set; } = new List { "--test-execution-mode", "server" }; + + protected override IHost CreateWebHost() + { + if (BuildWebHostMethod == null) + { + throw new InvalidOperationException( + $"No value was provided for {nameof(BuildWebHostMethod)}"); + } + + var assembly = ApplicationAssembly ?? BuildWebHostMethod.Method.DeclaringType.Assembly; + var sampleSitePath = FindSampleOrTestSitePath(assembly.FullName); + + var host = "127.0.0.1"; + + return BuildWebHostMethod(new[] + { + "--urls", $"http://{host}:0", + "--contentroot", sampleSitePath, + "--environment", Environment.ToString(), + }.Concat(AdditionalArguments).ToArray()); + } + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs new file mode 100644 index 000000000000..c0b1c6d51a3e --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/BasicTestAppServerSiteFixture.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public class BasicTestAppServerSiteFixture : AspNetSiteServerFixture where TStartup : class + { + public BasicTestAppServerSiteFixture() + { + BuildWebHostMethod = TestServer.Program.BuildWebHost; + } + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs new file mode 100644 index 000000000000..a08fad650e2b --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/DevHostServerFixture.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Hosting; +using System.Collections.Generic; +using DevHostServerProgram = Microsoft.AspNetCore.Components.WebAssembly.DevServer.Server.Program; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public class DevHostServerFixture : WebHostServerFixture + { + public string Environment { get; set; } + public string PathBase { get; set; } + public string ContentRoot { get; private set; } + + protected override IHost CreateWebHost() + { + ContentRoot = FindSampleOrTestSitePath( + typeof(TProgram).Assembly.FullName); + + var host = "127.0.0.1"; + + var args = new List + { + "--urls", $"http://{host}:0", + "--contentroot", ContentRoot, + "--pathbase", PathBase, + "--applicationpath", typeof(TProgram).Assembly.Location, + }; + + if (!string.IsNullOrEmpty(Environment)) + { + args.Add("--environment"); + args.Add(Environment); + } + + return DevHostServerProgram.BuildWebHost(args.ToArray()); + } + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/ServerFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/ServerFixture.cs new file mode 100644 index 000000000000..2ee6a2d69306 --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/ServerFixture.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Threading; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public abstract class ServerFixture : IDisposable + { + private static readonly Lazy> _projects = new Lazy>(FindProjects); + + public Uri RootUri => _rootUriInitializer.Value; + + private readonly Lazy _rootUriInitializer; + + public ServerFixture() + { + _rootUriInitializer = new Lazy(() => + { + var uri = new Uri(StartAndGetRootUri()); + + return uri; + }); + } + + public abstract void Dispose(); + + protected abstract string StartAndGetRootUri(); + + private static Dictionary FindProjects() + { + return typeof(ServerFixture).Assembly.GetCustomAttributes() + .Where(m => m.Key.StartsWith("TestAssemblyApplication[", StringComparison.Ordinal)) + .ToDictionary(m => + m.Key.Replace("TestAssemblyApplication", "").TrimStart('[').TrimEnd(']'), + m => m.Value); + } + + public static string FindSampleOrTestSitePath(string projectName) + { + var projects = _projects.Value; + if (projects.TryGetValue(projectName, out var dir)) + { + return dir; + } + + throw new ArgumentException($"Cannot find a sample or test site with name '{projectName}'."); + } + + protected static void RunInBackgroundThread(Action action) + { + var isDone = new ManualResetEvent(false); + + ExceptionDispatchInfo edi = null; + new Thread(() => + { + try + { + action(); + } + catch (Exception ex) + { + edi = ExceptionDispatchInfo.Capture(ex); + } + + isDone.Set(); + }).Start(); + + if (!isDone.WaitOne(TimeSpan.FromSeconds(10))) + { + throw new TimeoutException("Timed out waiting for: " + action); + } + + if (edi != null) + { + throw edi.SourceException; + } + } + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/StaticSiteServerFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/StaticSiteServerFixture.cs new file mode 100644 index 000000000000..c01d5c826434 --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/StaticSiteServerFixture.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + // Although this is not used for anything meaningful related to Blazor yet, it + // will be used later when there's a mechanism for publishing standalone Blazor + // apps as a set of purely static files and we need E2E testing on the result. + + public class StaticSiteServerFixture : WebHostServerFixture + { + public string SampleSiteName { get; set; } + + protected override IHost CreateWebHost() + { + if (string.IsNullOrEmpty(SampleSiteName)) + { + throw new InvalidOperationException($"No value was provided for {nameof(SampleSiteName)}"); + } + + var sampleSitePath = FindSampleOrTestSitePath(SampleSiteName); + + var host = "127.0.0.1"; + + return new HostBuilder() + .ConfigureWebHost(webHostBuilder => webHostBuilder + .UseKestrel() + .UseContentRoot(sampleSitePath) + .UseWebRoot(string.Empty) + .UseStartup() + .UseUrls($"http://{host}:0")) + .Build(); + } + + private class StaticSiteStartup + { + public void Configure(IApplicationBuilder app) + { + app.UseFileServer(); + } + } + } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs new file mode 100644 index 000000000000..2088ddf53821 --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/ToggleExecutionModeServerFixture.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public class ToggleExecutionModeServerFixture + : ServerFixture + { + public string PathBase { get; set; } + + public IHost Host { get; set; } + + public ExecutionMode ExecutionMode { get; set; } = ExecutionMode.Client; + + private AspNetSiteServerFixture.BuildWebHost _buildWebHostMethod; + private IDisposable _serverToDispose; + + public List AspNetFixtureAdditionalArguments { get; set; } = new List(); + + public void UseAspNetHost(AspNetSiteServerFixture.BuildWebHost buildWebHostMethod) + { + _buildWebHostMethod = buildWebHostMethod + ?? throw new ArgumentNullException(nameof(buildWebHostMethod)); + } + + protected override string StartAndGetRootUri() + { + if (_buildWebHostMethod == null) + { + // Use Blazor's dev host server + var underlying = new DevHostServerFixture(); + underlying.PathBase = "/subdir"; + _serverToDispose = underlying; + var uri = underlying.RootUri.AbsoluteUri; // As a side-effect, this starts the server + + Host = underlying.Host; + + return uri; + } + else + { + // Use specified ASP.NET host server + var underlying = new AspNetSiteServerFixture(); + underlying.AdditionalArguments.AddRange(AspNetFixtureAdditionalArguments); + underlying.BuildWebHostMethod = _buildWebHostMethod; + _serverToDispose = underlying; + var uri = underlying.RootUri.AbsoluteUri; // As a side-effect, this starts the server + + Host = underlying.Host; + + return uri; + } + } + + public override void Dispose() + { + _serverToDispose?.Dispose(); + } + + internal ToggleExecutionModeServerFixture WithAdditionalArguments(string [] additionalArguments) + { + AspNetFixtureAdditionalArguments.AddRange(additionalArguments); + return this; + } + } + + public enum ExecutionMode { Client, Server } +} diff --git a/src/Components/test/EToETest/Infrastructure/ServerFixtures/WebHostServerFixture.cs b/src/Components/test/EToETest/Infrastructure/ServerFixtures/WebHostServerFixture.cs new file mode 100644 index 000000000000..a29f47315284 --- /dev/null +++ b/src/Components/test/EToETest/Infrastructure/ServerFixtures/WebHostServerFixture.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Microsoft.AspNetCore.Hosting.Server; + +namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures +{ + public abstract class WebHostServerFixture : ServerFixture + { + protected override string StartAndGetRootUri() + { + Host = CreateWebHost(); + RunInBackgroundThread(Host.Start); + return Host.Services.GetRequiredService().Features + .Get() + .Addresses.Single(); + } + + public IHost Host { get; set; } + + public override void Dispose() + { + // This can be null if creating the webhost throws, we don't want to throw here and hide + // the original exception. + Host?.Dispose(); + Host?.StopAsync(); + } + + protected abstract IHost CreateWebHost(); + } +} diff --git a/src/Components/test/EToETest/Microsoft.AspNetCore.Components.EToETests.csproj b/src/Components/test/EToETest/Microsoft.AspNetCore.Components.EToETests.csproj new file mode 100644 index 000000000000..006cd30ddedf --- /dev/null +++ b/src/Components/test/EToETest/Microsoft.AspNetCore.Components.EToETests.csproj @@ -0,0 +1,104 @@ + + + + $(DefaultNetCoreTargetFramework) + Components.EToETests + <_DefaultProjectFilter>$(MSBuildProjectDirectory)\..\..\.. + + + + + + + + false + + + + ;1701;1702;1705;;NU5105;RS0041;CA1416;CS0649 + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + playwrightSettings.json + + + PreserveNewest + + + + + + + + + + + + + + + + + + <_DefaultProjectRoot>$([System.IO.Path]::GetFullPath($(_DefaultProjectFilter))) + + + <_ContentRootProjectReferences + Include="@(ReferencePath)" + Condition="'%(ReferencePath.ReferenceSourceTarget)' == 'ProjectReference' AND $([System.String]::Copy(%(ReferencePath.MSBuildSourceProjectFile)).StartsWith('$(_DefaultProjectRoot)'))" /> + + + + + + <_ContentRootMetadata + Condition="'%(_ContentRootProjectReferences.Identity)' != ''" + Include="%(_ContentRootProjectReferences.Identity)" + AssemblyName="%(_ContentRootProjectReferences.FusionName)" + ContentRootPath="$([System.IO.Path]::GetDirectoryName(%(_ContentRootProjectReferences.MSBuildSourceProjectFile)))" + ContentRootTest="$([System.IO.Path]::GetFileName(%(_ContentRootProjectReferences.MSBuildSourceProjectFile)))" + Priority="0" /> + + + + + <_Parameter1>TestAssemblyApplication[%(_ContentRootMetadata.AssemblyName)] + <_Parameter2>%(_ContentRootMetadata.ContentRootPath) + + + + + + + PreserveNewest + + + + diff --git a/src/Components/test/EToETest/TestJsonSerializerOptionsProvider.cs b/src/Components/test/EToETest/TestJsonSerializerOptionsProvider.cs new file mode 100644 index 000000000000..2f893e85ea1a --- /dev/null +++ b/src/Components/test/EToETest/TestJsonSerializerOptionsProvider.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text.Json; + +namespace Microsoft.AspNetCore.Components.E2ETest +{ + internal static class TestJsonSerializerOptionsProvider + { + public static JsonSerializerOptions Options { get; } = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + }; + } +} diff --git a/src/Components/test/EToETest/Tests/BinaryHttpClientTest.cs b/src/Components/test/EToETest/Tests/BinaryHttpClientTest.cs new file mode 100644 index 000000000000..ecb1435c8bc6 --- /dev/null +++ b/src/Components/test/EToETest/Tests/BinaryHttpClientTest.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using BasicTestApp.HttpClientTest; +using Microsoft.AspNetCore.BrowserTesting; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures; +using Microsoft.AspNetCore.Components.E2ETest.Infrastructure; +using Microsoft.AspNetCore.Testing; +using PlaywrightSharp; +using TestServer; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Components.E2ETest.Tests +{ + public class BinaryHttpClientTest : PlaywrightTestBase, + IClassFixture>, + IClassFixture> + { + private readonly DevHostServerFixture _devHostServerFixture; + readonly ServerFixture _apiServerFixture; + //IWebElement _appElement; + //IWebElement _responseStatus; + //IWebElement _responseStatusText; + //IWebElement _testOutcome; + + public BinaryHttpClientTest( + DevHostServerFixture devHostServerFixture, + BasicTestAppServerSiteFixture apiServerFixture, + ITestOutputHelper output) + : base(output) + { + _devHostServerFixture = devHostServerFixture; + _devHostServerFixture.PathBase = "/subdir"; + _apiServerFixture = apiServerFixture; + } + + //protected override void InitializeAsyncCore() + //{ + // //Browser.Navigate(_devHostServerFixture.RootUri, "/subdir", noReload: true); + // //_appElement = Browser.MountTestComponent(); + //} + + [Fact] + [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/23366")] + public async Task CanSendAndReceiveBytes() + { + if (BrowserManager.IsAvailable(BrowserKind.Chromium)) + { + await using var browser = await BrowserManager.GetBrowserInstance(BrowserKind.Chromium, BrowserContextInfo); + var page = await browser.NewPageAsync(); + await page.GoToAsync(_devHostServerFixture.RootUri + "/subdir/api/data"); + +/* var socket = BrowserContextInfo.Pages[page].WebSockets.SingleOrDefault() ?? + (await page.WaitForEventAsync(PageEvent.WebSocket)).WebSocket; + + // Receive render batch + await socket.WaitForEventAsync(WebSocketEvent.FrameReceived); + await socket.WaitForEventAsync(WebSocketEvent.FrameSent); + + // JS interop call to intercept navigation + await socket.WaitForEventAsync(WebSocketEvent.FrameReceived); + await socket.WaitForEventAsync(WebSocketEvent.FrameSent); + + await page.WaitForSelectorAsync("ul");*/ + + await page.CloseAsync(); + } + + + //IssueRequest("/subdir/api/data"); + //Assert.Equal("OK", _responseStatus.Text); + //Assert.Equal("OK", _responseStatusText.Text); + //Assert.Equal("", _testOutcome.Text); + } + + private void IssueRequest() + { + //var targetUri = new Uri(_apiServerFixture.RootUri, relativeUri); + //SetValue("request-uri", targetUri.AbsoluteUri); + + //_appElement.FindElement(By.Id("send-request")).Click(); + + //_responseStatus = Browser.Exists(By.Id("response-status")); + //_responseStatusText = _appElement.FindElement(By.Id("response-status-text")); + //_testOutcome = _appElement.FindElement(By.Id("test-outcome")); + + } + } +} diff --git a/src/Components/test/EToETest/package.json b/src/Components/test/EToETest/package.json new file mode 100644 index 000000000000..607937be210d --- /dev/null +++ b/src/Components/test/EToETest/package.json @@ -0,0 +1,23 @@ +{ + "name": "microsoft.aspnetcore.components.e2etest", + "version": "0.0.1", + "description": "Not a real package. This file exists only to declare dependencies.", + "main": "index.js", + "private": true, + "scripts": { + "selenium-standalone": "selenium-standalone", + "prepare": "selenium-standalone install --config ../../../Shared/E2ETesting/selenium-config.json", + "sauce": "ts-node ./scripts/sauce.ts" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "sauce-connect-launcher": "^1.3.1", + "selenium-standalone": "^6.17.0" + }, + "devDependencies": { + "@types/node": "^13.1.7", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + } +} diff --git a/src/Components/test/EToETest/playwrightSettings.ci.json b/src/Components/test/EToETest/playwrightSettings.ci.json new file mode 100644 index 000000000000..146abcf62692 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.ci.json @@ -0,0 +1,15 @@ +{ + "TimeoutInMilliseconds": 120000, + "TimeoutAfterFirstFailureInMilliseconds": 20000, + "GlobalBrowserOptions": { + "Headless": true + }, + "BrowserOptions": { + "Firefox": { + "IsEnabled": false + }, + "Webkit": { + "IsEnabled": false + } + } +} diff --git a/src/Components/test/EToETest/playwrightSettings.ci.linux.json b/src/Components/test/EToETest/playwrightSettings.ci.linux.json new file mode 100644 index 000000000000..e416652ffe39 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.ci.linux.json @@ -0,0 +1,8 @@ +{ + "IsDisabled": true, + "BrowserOptions": { + "Chromium": { + "IsEnabled": false + } + } +} diff --git a/src/Components/test/EToETest/playwrightSettings.ci.osx.json b/src/Components/test/EToETest/playwrightSettings.ci.osx.json new file mode 100644 index 000000000000..137e7e958118 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.ci.osx.json @@ -0,0 +1,7 @@ +{ + "BrowserOptions": { + "Chromium": { + "IsEnabled": false + } + } +} diff --git a/src/Components/test/EToETest/playwrightSettings.ci.win.json b/src/Components/test/EToETest/playwrightSettings.ci.win.json new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.ci.win.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/Components/test/EToETest/playwrightSettings.debug.json b/src/Components/test/EToETest/playwrightSettings.debug.json new file mode 100644 index 000000000000..6899a11ae8f8 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.debug.json @@ -0,0 +1,7 @@ +{ + "TimeoutInMilliseconds": 120000000, + "TimeoutAfterFirstFailureInMilliseconds": 20000000, + "GlobalBrowserOptions": { + "Headless": false + } +} diff --git a/src/Components/test/EToETest/playwrightSettings.json b/src/Components/test/EToETest/playwrightSettings.json new file mode 100644 index 000000000000..d5e34b4795f4 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.json @@ -0,0 +1,38 @@ +{ + "TimeoutInMilliseconds": 30000, + "TimeoutAfterFirstFailureInMilliseconds": 10000, + "BaseArtifactsFolder": ".", + "GlobalBrowserOptions": { + "ChromiumSandbox": true, + "DumpIO": true, + "IgnoreHTTPSErrors": true, + "Headless": true, + "Timeout": 30000 + }, + "GlobalContextOptions": { + "RecordVideo": { + "Dir": "videos" + }, + "RecordHar": { + "Path": "har" + }, + "IgnoreHTTPSErrors": true + }, + "BrowserOptions": { + "Chromium": { + "BrowserKind": "Chromium", + "IsEnabled": true, + "Args": { + "--ignore-certificate-errors": true + } + }, + "Firefox": { + "BrowserKind": "Firefox", + "IsEnabled": true + }, + "Webkit": { + "BrowserKind": "Webkit", + "IsEnabled": true + } + } +} diff --git a/src/Components/test/EToETest/playwrightSettings.linux.json b/src/Components/test/EToETest/playwrightSettings.linux.json new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.linux.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/Components/test/EToETest/playwrightSettings.osx.json b/src/Components/test/EToETest/playwrightSettings.osx.json new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.osx.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/Components/test/EToETest/playwrightSettings.win.json b/src/Components/test/EToETest/playwrightSettings.win.json new file mode 100644 index 000000000000..2c63c0851048 --- /dev/null +++ b/src/Components/test/EToETest/playwrightSettings.win.json @@ -0,0 +1,2 @@ +{ +} diff --git a/src/Components/test/EToETest/xunit.runner.json b/src/Components/test/EToETest/xunit.runner.json new file mode 100644 index 000000000000..baa05fb93d2a --- /dev/null +++ b/src/Components/test/EToETest/xunit.runner.json @@ -0,0 +1,7 @@ +{ + // This is set to -1 to allow the usage of an + // unlimited ammount of threads. + "maxParallelThreads": -1, + "diagnosticMessages": true, + "longRunningTestSeconds": 30 +} diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs b/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs index 3eb30b095263..e3d87ba98ee3 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/AssemblyInfo.AssemblyFixtures.cs @@ -5,7 +5,8 @@ using ProjectTemplates.Tests.Infrastructure; using Templates.Test; using Templates.Test.Helpers; +using Xunit; [assembly: AssemblyFixture(typeof(ProjectFactoryFixture))] -[assembly: AssemblyFixture(typeof(PlaywrightFixture))] +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs index 24fe3fa75aa6..9671dbce573f 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.BrowserTesting; using Microsoft.AspNetCore.Testing; @@ -17,62 +16,47 @@ namespace Templates.Test { + [TestCaseOrderer("Templates.Test.PriorityOrderer", "BlazorTemplates.Tests")] public class BlazorServerTemplateTest : BlazorTemplateTest { - public BlazorServerTemplateTest(ProjectFactoryFixture projectFactory, PlaywrightFixture fixture, ITestOutputHelper output) - : base(fixture) + public BlazorServerTemplateTest(ProjectFactoryFixture projectFactory) + : base(projectFactory) { - ProjectFactory = projectFactory; ; - Output = output; - BrowserContextInfo = new ContextInformation(CreateFactory(output)); } - public ProjectFactoryFixture ProjectFactory { get; set; } - public ITestOutputHelper Output { get; } - public ContextInformation BrowserContextInfo { get; } - public Project Project { get; private set; } + public override string ProjectType { get; } = "blazorserver"; + // This test is required to run before BlazorServerTemplateWorks_NoAuth to create and build the project + // If this test is quarantined, BlazorServerTemplateWorks_NoAuth must be quarantined as well + [Fact, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + public Task BlazorServerTemplate_CreateBuildPublish_NoAuth() + => CreateBuildPublishAsync("blazorservernoauth" + BrowserKind.Chromium.ToString()); + // This tests depends on BlazorServerTemplate_CreateBuildPublish_NoAuth running first [Theory] [InlineData(BrowserKind.Chromium)] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30761")] + // [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30761")] public async Task BlazorServerTemplateWorks_NoAuth(BrowserKind browserKind) { - // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 - Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); + var project = await ProjectFactory.GetOrCreateProject("blazorservernoauth" + browserKind, Output); - Project = await ProjectFactory.GetOrCreateProject("blazorservernoauth" + browserKind.ToString(), Output); - - var createResult = await Project.RunDotNetNewAsync("blazorserver"); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); - - var publishResult = await Project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); - - // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build - // later, while the opposite is not true. - - var buildResult = await Project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); - - await using var browser = Fixture.BrowserManager.IsAvailable(browserKind) ? - await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo) : + await using var browser = BrowserManager.IsAvailable(browserKind) ? + await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo) : null; - using (var aspNetProcess = Project.StartBuiltProjectAsync()) + using (var aspNetProcess = project.StartBuiltProjectAsync()) { Assert.False( aspNetProcess.Process.HasExited, - ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); - await TestBasicNavigation(page); + await TestBasicNavigation(project, page); await page.CloseAsync(); } else @@ -81,18 +65,18 @@ await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo) } } - using (var aspNetProcess = Project.StartPublishedProjectAsync()) + using (var aspNetProcess = project.StartPublishedProjectAsync()) { Assert.False( aspNetProcess.Process.HasExited, - ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", project, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); - await TestBasicNavigation(page); + await TestBasicNavigation(project, page); await page.CloseAsync(); } else @@ -102,49 +86,41 @@ await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo) } } + // This test is required to run before BlazorServerTemplateWorks_IndividualAuth to create and build the project + // If this test is quarantined, BlazorServerTemplateWorks_IndividualAuth must be quarantined as well + [Theory, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + [MemberData(nameof(BlazorServerTemplateWorks_IndividualAuthData))] + public Task BlazorServerTemplate_CreateBuildPublish_IndividualAuth(BrowserKind browserKind, bool useLocalDB) + => CreateBuildPublishAsync("blazorserverindividual" + browserKind + (useLocalDB ? "uld" : "")); + public static IEnumerable BlazorServerTemplateWorks_IndividualAuthData => BrowserManager.WithBrowsers(new[] { BrowserKind.Chromium }, true, false); + // This tests depends on BlazorServerTemplate_CreateBuildPublish_IndividualAuth running first [Theory] [MemberData(nameof(BlazorServerTemplateWorks_IndividualAuthData))] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30807")] + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30807")] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] public async Task BlazorServerTemplateWorks_IndividualAuth(BrowserKind browserKind, bool useLocalDB) { - // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 - Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - - Project = await ProjectFactory.GetOrCreateProject("blazorserverindividual" + browserKind + (useLocalDB ? "uld" : ""), Output); - - var createResult = await Project.RunDotNetNewAsync("blazorserver", auth: "Individual", useLocalDB: useLocalDB); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); + var project = await ProjectFactory.GetOrCreateProject("blazorserverindividual" + browserKind + (useLocalDB ? "uld" : ""), Output); - var publishResult = await Project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); - - // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build - // later, while the opposite is not true. - - var buildResult = await Project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); - - var browser = !Fixture.BrowserManager.IsAvailable(browserKind) ? + var browser = !BrowserManager.IsAvailable(browserKind) ? null : - await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); - using (var aspNetProcess = Project.StartBuiltProjectAsync()) + using (var aspNetProcess = project.StartBuiltProjectAsync()) { Assert.False( aspNetProcess.Process.HasExited, - ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process)); + ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); - await TestBasicNavigation(page); + await TestBasicNavigation(project, page); await page.CloseAsync(); } else @@ -153,18 +129,18 @@ public async Task BlazorServerTemplateWorks_IndividualAuth(BrowserKind browserKi } } - using (var aspNetProcess = Project.StartPublishedProjectAsync()) + using (var aspNetProcess = project.StartPublishedProjectAsync()) { Assert.False( aspNetProcess.Process.HasExited, - ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process)); + ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", project, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); - await TestBasicNavigation(page); + await TestBasicNavigation(project, page); await page.CloseAsync(); } else @@ -174,7 +150,7 @@ public async Task BlazorServerTemplateWorks_IndividualAuth(BrowserKind browserKi } } - private async Task TestBasicNavigation(IPage page) + private async Task TestBasicNavigation(Project project, IPage page) { var socket = BrowserContextInfo.Pages[page].WebSockets.SingleOrDefault() ?? (await page.WaitForEventAsync(PageEvent.WebSocket)).WebSocket; @@ -189,7 +165,7 @@ private async Task TestBasicNavigation(IPage page) await page.WaitForSelectorAsync("ul"); // element gets project ID injected into it during template execution - Assert.Equal(Project.ProjectName.Trim(), (await page.GetTitleAsync()).Trim()); + Assert.Equal(project.ProjectName.Trim(), (await page.GetTitleAsync()).Trim()); // Initially displays the home page await page.WaitForSelectorAsync("h1 >> text=Hello, world!"); @@ -211,29 +187,15 @@ private async Task TestBasicNavigation(IPage page) Assert.Equal(5, (await page.QuerySelectorAllAsync("p+table>tbody>tr")).Count()); } - [Theory] + [Theory, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] [InlineData("IndividualB2C", null)] [InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", null)] [InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })] [InlineData("SingleOrg", new string[] { "--calls-graph" })] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] - public async Task BlazorServerTemplat_IdentityWeb_BuildAndPublish(string auth, string[] args) - { - Project = await ProjectFactory.GetOrCreateProject("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), Output); + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] + public Task BlazorServerTemplate_IdentityWeb_BuildAndPublish(string auth, string[] args) + => CreateBuildPublishAsync("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLowerInvariant(), auth, args); - var createResult = await Project.RunDotNetNewAsync("blazorserver", auth: auth, args: args); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult)); - - var publishResult = await Project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult)); - - // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build - // later, while the opposite is not true. - - var buildResult = await Project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult)); - } } } diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs index 4057e99fec6a..dee39da69f57 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorTemplateTest.cs @@ -1,49 +1,146 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Microsoft.AspNetCore.BrowserTesting; +using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Testing; -using ProjectTemplates.Tests.Infrastructure; +using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; namespace Templates.Test { - public class BlazorTemplateTest + public abstract class BlazorTemplateTest : LoggedTest, IAsyncLifetime { - public BlazorTemplateTest(PlaywrightFixture<BlazorServerTemplateTest> browserFixture) + public const int BUILDCREATEPUBLISH_PRIORITY = -1000; + + public BlazorTemplateTest(ProjectFactoryFixture projectFactory) + { + ProjectFactory = projectFactory; + } + + public ProjectFactoryFixture ProjectFactory { get; set; } + public ContextInformation BrowserContextInfo { get; protected set; } + public BrowserManager BrowserManager { get; private set; } + + private ITestOutputHelper _output; + public ITestOutputHelper Output { - Fixture = browserFixture; + get + { + if (_output == null) + { + _output = new TestOutputLogger(Logger); + } + return _output; + } } + + public abstract string ProjectType { get; } + private static readonly bool _isCIEnvironment = + !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ContinuousIntegrationBuild")); + + protected async override Task InitializeCoreAsync(TestContext context) + { + BrowserManager = await BrowserManager.CreateAsync(CreateConfiguration(), LoggerFactory); + BrowserContextInfo = new ContextInformation(LoggerFactory); + _output = new TestOutputLogger(Logger); + } + + public Task InitializeAsync() => Task.CompletedTask; + + public Task DisposeAsync() => BrowserManager.DisposeAsync(); + + private static IConfiguration CreateConfiguration() + { + var basePath = Path.GetDirectoryName(typeof(BlazorTemplateTest).Assembly.Location); + var os = Environment.OSVersion.Platform switch + { + PlatformID.Win32NT => "win", + PlatformID.Unix => "linux", + PlatformID.MacOSX => "osx", + _ => null + }; + + var builder = new ConfigurationBuilder() + .AddJsonFile(Path.Combine(basePath, "playwrightSettings.json")) + .AddJsonFile(Path.Combine(basePath, $"playwrightSettings.{os}.json"), optional: true); + + if (_isCIEnvironment) + { + builder.AddJsonFile(Path.Combine(basePath, "playwrightSettings.ci.json"), optional: true) + .AddJsonFile(Path.Combine(basePath, $"playwrightSettings.ci.{os}.json"), optional: true); + } - public PlaywrightFixture<BlazorServerTemplateTest> Fixture { get; } + if (Debugger.IsAttached) + { + builder.AddJsonFile(Path.Combine(basePath, "playwrightSettings.debug.json"), optional: true); + } + return builder.Build(); + } - public static ILoggerFactory CreateFactory(ITestOutputHelper output) + protected async Task<Project> CreateBuildPublishAsync(string projectName, string auth = null, string[] args = null, string targetFramework = null, bool serverProject = false, bool onlyCreate = false) { - var testSink = new TestSink(); - testSink.MessageLogged += LogMessage; - var loggerFactory = new TestLoggerFactory(testSink, enabled: true); - return loggerFactory; + // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 + Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - void LogMessage(WriteContext ctx) + var project = await ProjectFactory.GetOrCreateProject(projectName, Output); + if (targetFramework != null) { - output.WriteLine($"{MapLogLevel(ctx)}: [Browser]{ctx.Message}"); + project.TargetFramework = targetFramework; + } - static string MapLogLevel(WriteContext obj) => obj.LogLevel switch + var createResult = await project.RunDotNetNewAsync(ProjectType, auth: auth, args: args); + Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + + if (!onlyCreate) + { + var targetProject = project; + if (serverProject) { - LogLevel.Trace => "trace", - LogLevel.Debug => "dbug", - LogLevel.Information => "info", - LogLevel.Warning => "warn", - LogLevel.Error => "error", - LogLevel.Critical => "crit", - LogLevel.None => "info", - _ => "info" - }; + targetProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); + } + + var publishResult = await targetProject.RunDotNetPublishAsync(noRestore: !serverProject); + Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", targetProject, publishResult)); + + // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release + // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build + // later, while the opposite is not true. + + var buildResult = await targetProject.RunDotNetBuildAsync(); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", targetProject, buildResult)); } + + return project; + } + + protected static Project GetSubProject(Project project, string projectDirectory, string projectName) + { + var subProjectDirectory = Path.Combine(project.TemplateOutputDir, projectDirectory); + if (!Directory.Exists(subProjectDirectory)) + { + throw new DirectoryNotFoundException($"Directory {subProjectDirectory} was not found."); + } + + var subProject = new Project + { + Output = project.Output, + DiagnosticsMessageSink = project.DiagnosticsMessageSink, + ProjectName = projectName, + TemplateOutputDir = subProjectDirectory, + }; + + return subProject; } public static bool TryValidateBrowserRequired(BrowserKind browserKind, bool isRequired, out string error) @@ -57,7 +154,7 @@ protected void EnsureBrowserAvailable(BrowserKind browserKind) Assert.False( TryValidateBrowserRequired( browserKind, - isRequired: !Fixture.BrowserManager.IsExplicitlyDisabled(browserKind), + isRequired: !BrowserManager.IsExplicitlyDisabled(browserKind), out var errorMessage), errorMessage); } diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs index 9d2252ce079a..b4357a72bab3 100644 --- a/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs +++ b/src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs @@ -17,61 +17,46 @@ using Microsoft.Extensions.CommandLineUtils; using Newtonsoft.Json.Linq; using PlaywrightSharp; -using ProjectTemplates.Tests.Infrastructure; using Templates.Test.Helpers; using Xunit; using Xunit.Abstractions; namespace Templates.Test { + [TestCaseOrderer("Templates.Test.PriorityOrderer", "BlazorTemplates.Tests")] public class BlazorWasmTemplateTest : BlazorTemplateTest { - public BlazorWasmTemplateTest(ProjectFactoryFixture projectFactory, PlaywrightFixture<BlazorServerTemplateTest> browserFixture, ITestOutputHelper output) - : base(browserFixture) - { - ProjectFactory = projectFactory; - Output = output; - BrowserContextInfo = new ContextInformation(CreateFactory(output)); - } + public BlazorWasmTemplateTest(ProjectFactoryFixture projectFactory) + : base(projectFactory) { } - public ProjectFactoryFixture ProjectFactory { get; set; } + public override string ProjectType { get; } = "blazorwasm"; - public ITestOutputHelper Output { get; } + [Fact, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + public async Task BlazorWasmTemplate_CreateBuildPublish_Standalone() + { + var project = await CreateBuildPublishAsync("blazorstandalone" + BrowserKind.Chromium); - public ContextInformation BrowserContextInfo { get; } + // The service worker assets manifest isn't generated for non-PWA projects + var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); + Assert.False(File.Exists(Path.Combine(publishDir, "service-worker-assets.js")), "Non-PWA templates should not produce service-worker-assets.js"); + } [Theory] [InlineData(BrowserKind.Chromium)] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] public async Task BlazorWasmStandaloneTemplate_Works(BrowserKind browserKind) { - // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 - Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject("blazorstandalone" + browserKind, Output); - var createResult = await project.RunDotNetNewAsync("blazorwasm"); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); - - var publishResult = await project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); - - // The service worker assets manifest isn't generated for non-PWA projects - var publishDir = Path.Combine(project.TemplatePublishDir, "wwwroot"); - Assert.False(File.Exists(Path.Combine(publishDir, "service-worker-assets.js")), "Non-PWA templates should not produce service-worker-assets.js"); - - var buildResult = await project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); - await BuildAndRunTest(project.ProjectName, project, browserKind); var (serveProcess, listeningUri) = RunPublishedStandaloneBlazorProject(project); using (serveProcess) { Output.WriteLine($"Opening browser at {listeningUri}..."); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); var page = await NavigateToPage(browser, listeningUri); await TestBasicNavigation(project.ProjectName, page); } @@ -89,15 +74,13 @@ private async Task<IPage> NavigateToPage(IBrowserContext browser, string listeni return page; } - [Theory] - [InlineData(BrowserKind.Chromium)] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] - public async Task BlazorWasmHostedTemplate_Works(BrowserKind browserKind) + [Fact, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + public async Task BlazorWasmTemplate_CreateBuildPublish_Hosted() { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject("blazorhosted" + browserKind, Output); + var project = await ProjectFactory.GetOrCreateProject("blazorhosted" + BrowserKind.Chromium, Output); var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted" }); Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); @@ -107,7 +90,18 @@ public async Task BlazorWasmHostedTemplate_Works(BrowserKind browserKind) Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", serverProject, publishResult)); var buildResult = await serverProject.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult)); + Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult)); + } + // => CreateBuildPublishAsync("blazorhosted" + BrowserKind.Chromium, args: new[] { "--hosted" }, serverProject: true); + + [Theory] + [InlineData(BrowserKind.Chromium)] + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] + public async Task BlazorWasmHostedTemplate_Works(BrowserKind browserKind) + { + var project = await ProjectFactory.GetOrCreateProject("blazorhosted" + browserKind, Output); + + var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); await BuildAndRunTest(project.ProjectName, serverProject, browserKind); @@ -120,9 +114,9 @@ public async Task BlazorWasmHostedTemplate_Works(BrowserKind browserKind) await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); await AssertCompressionFormat(aspNetProcess, "br"); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); await TestBasicNavigation(project.ProjectName, page); @@ -149,33 +143,25 @@ private static async Task AssertCompressionFormat(AspNetProcess aspNetProcess, s Assert.Equal(expectedEncoding, response.Content.Headers.ContentEncoding.Single()); } - [Theory] + [Fact, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + public Task BlazorWasmTemplate_CreateBuildPublish_StandalonePwa() + => CreateBuildPublishAsync("blazorstandalonepwa", args: new[] { "--pwa" }); + + [Theory, TestPriority(100)] [InlineData(BrowserKind.Chromium)] [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] public async Task BlazorWasmStandalonePwaTemplate_Works(BrowserKind browserKind) { - // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 - Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject("blazorstandalonepwa", Output); - var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--pwa" }); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); - - var publishResult = await project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); - - var buildResult = await project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); - await BuildAndRunTest(project.ProjectName, project, browserKind); ValidatePublishedServiceWorker(project); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { var (serveProcess, listeningUri) = RunPublishedStandaloneBlazorProject(project); - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); Output.WriteLine($"Opening browser at {listeningUri}..."); var page = await NavigateToPage(browser, listeningUri); using (serveProcess) @@ -197,35 +183,27 @@ public async Task BlazorWasmStandalonePwaTemplate_Works(BrowserKind browserKind) } } - [Theory] + [Fact, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + public Task BlazorWasmTemplate_CreateBuildPublish_HostedPwa() + => CreateBuildPublishAsync("blazorhostedpwa", args: new[] { "--hosted", "--pwa" }, serverProject: true); + + [Theory, TestPriority(100)] [InlineData(BrowserKind.Chromium)] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30882")] public async Task BlazorWasmHostedPwaTemplate_Works(BrowserKind browserKind) { - // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 - Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject("blazorhostedpwa", Output); - var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "--pwa" }); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); - var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); - var publishResult = await serverProject.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", serverProject, publishResult)); - - var buildResult = await serverProject.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", serverProject, buildResult)); - await BuildAndRunTest(project.ProjectName, serverProject, browserKind); ValidatePublishedServiceWorker(serverProject); string listeningUri = null; - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); IPage page = null; using (var aspNetProcess = serverProject.StartPublishedProjectAsync()) { @@ -280,34 +258,40 @@ private void ValidatePublishedServiceWorker(Project project) Assert.True(serviceWorkerContents.Contains($"/* Manifest version: {serviceWorkerAssetsManifestVersion} */", StringComparison.Ordinal)); } + [ConditionalTheory, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + [InlineData(BrowserKind.Chromium)] + //// LocalDB doesn't work on non Windows platforms + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] + public Task BlazorWasmTemplate_CreateBuildPublish_IndividualAuthLocalDb(BrowserKind browserKind) + => CreateBuildPublishIndividualAuthProject(browserKind, useLocalDb: true); + [ConditionalTheory] [InlineData(BrowserKind.Chromium)] - // LocalDB doesn't work on non Windows platforms + //// LocalDB doesn't work on non Windows platforms [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30700")] + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30700")] public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithLocalDB(BrowserKind browserKind) - { - return BlazorWasmHostedTemplate_IndividualAuth_Works(browserKind, true); - } + => BlazorWasmHostedTemplate_IndividualAuth_Works(browserKind, true); + + [ConditionalTheory, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + [InlineData(BrowserKind.Chromium)] + public Task BlazorWasmTemplate_CreateBuildPublish_IndividualAuthNoLocalDb(BrowserKind browserKind) + => CreateBuildPublishIndividualAuthProject(browserKind, useLocalDb: false); [Theory] [InlineData(BrowserKind.Chromium)] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30820")] + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30820")] [SkipOnHelix("https://github.com/dotnet/aspnetcore/issues/30825", Queues = "All.OSX")] public Task BlazorWasmHostedTemplate_IndividualAuth_Works_WithOutLocalDB(BrowserKind browserKind) - { - return BlazorWasmHostedTemplate_IndividualAuth_Works(browserKind, false); - } + => BlazorWasmHostedTemplate_IndividualAuth_Works(browserKind, false); - private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(BrowserKind browserKind, bool useLocalDb) + private async Task CreateBuildPublishIndividualAuthProject(BrowserKind browserKind, bool useLocalDb) { // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - var project = await ProjectFactory.GetOrCreateProject("blazorhostedindividual" + browserKind + (useLocalDb ? "uld" : ""), Output); - - var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); + var project = await CreateBuildPublishAsync("blazorhostedindividual" + browserKind + (useLocalDb ? "uld" : ""), + args: new[] { "--hosted", "-au", "Individual", useLocalDb ? "-uld" : "" }); var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); @@ -343,12 +327,19 @@ private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(BrowserKind bro var dbUpdateResult = await serverProject.RunDotNetEfUpdateDatabaseAsync(); Assert.True(0 == dbUpdateResult.ExitCode, ErrorMessages.GetFailedProcessMessage("update database", serverProject, dbUpdateResult)); } + } + + private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(BrowserKind browserKind, bool useLocalDb) + { + var project = await ProjectFactory.GetOrCreateProject("blazorhostedindividual" + browserKind + (useLocalDb ? "uld" : ""), Output); + + var serverProject = GetSubProject(project, "Server", $"{project.ProjectName}.Server"); await BuildAndRunTest(project.ProjectName, serverProject, browserKind, usesAuth: true); UpdatePublishedSettings(serverProject); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { using var aspNetProcess = serverProject.StartPublishedProjectAsync(); @@ -358,7 +349,7 @@ private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(BrowserKind bro await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); await TestBasicNavigation(project.ProjectName, page, usesAuth: true); @@ -370,16 +361,10 @@ private async Task BlazorWasmHostedTemplate_IndividualAuth_Works(BrowserKind bro } } - [Theory] - [InlineData(BrowserKind.Chromium, Skip = "https://github.com/dotnet/aspnetcore/issues/28596")] - public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works(BrowserKind browserKind) - { - // Additional arguments are needed. See: https://github.com/dotnet/aspnetcore/issues/24278 - Environment.SetEnvironmentVariable("EnableDefaultScopedCssItems", "true"); - - var project = await ProjectFactory.GetOrCreateProject("blazorstandaloneindividual" + browserKind, Output); - - var createResult = await project.RunDotNetNewAsync("blazorwasm", args: new[] { + [Theory, TestPriority(BUILDCREATEPUBLISH_PRIORITY)] + [InlineData(BrowserKind.Chromium)] + public Task BlazorWasmStandaloneTemplate_CreateBuildPublish_IndividualAuth(BrowserKind browserKind) + => CreateBuildPublishAsync("blazorstandaloneindividual" + browserKind, args: new[] { "-au", "Individual", "--authority", @@ -388,17 +373,11 @@ public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works(BrowserKind "sample-client-id" }); - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); - - var publishResult = await project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); - - // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build - // later, while the opposite is not true. - - var buildResult = await project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); + [Theory] + [InlineData(BrowserKind.Chromium, Skip = "https://github.com/dotnet/aspnetcore/issues/28596")] + public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works(BrowserKind browserKind) + { + var project = await ProjectFactory.GetOrCreateProject("blazorstandaloneindividual" + browserKind, Output); // We don't want to test the auth flow as we don't have the required settings to talk to a third-party IdP // but we want to make sure that we are able to run the app without errors. @@ -410,7 +389,7 @@ public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works(BrowserKind using (serveProcess) { Output.WriteLine($"Opening browser at {listeningUri}..."); - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); var page = await NavigateToPage(browser, listeningUri); await TestBasicNavigation(project.ProjectName, page); await page.CloseAsync(); @@ -488,26 +467,9 @@ public TemplateInstance(string name, params string[] arguments) [Theory] [MemberData(nameof(TemplateData))] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30880")] - public async Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_Works(TemplateInstance instance) - { - var project = await ProjectFactory.GetOrCreateProject(instance.Name, Output); - project.TargetFramework = "netstandard2.1"; - - var createResult = await project.RunDotNetNewAsync("blazorwasm", args: instance.Arguments); - - Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", project, createResult)); - - var publishResult = await project.RunDotNetPublishAsync(); - Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", project, publishResult)); - - // Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release - // The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build - // later, while the opposite is not true. - - var buildResult = await project.RunDotNetBuildAsync(); - Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", project, buildResult)); - } + //[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/30880")] + public Task BlazorWasmHostedTemplate_AzureActiveDirectoryTemplate_Works(TemplateInstance instance) + => CreateBuildPublishAsync(instance.Name, args: instance.Arguments, targetFramework: "netstandard2.1"); protected async Task BuildAndRunTest(string appName, Project project, BrowserKind browserKind, bool usesAuth = false) { @@ -518,9 +480,9 @@ protected async Task BuildAndRunTest(string appName, Project project, BrowserKin ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", project, aspNetProcess.Process)); await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html"); - if (Fixture.BrowserManager.IsAvailable(browserKind)) + if (BrowserManager.IsAvailable(browserKind)) { - await using var browser = await Fixture.BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); + await using var browser = await BrowserManager.GetBrowserInstance(browserKind, BrowserContextInfo); var page = await browser.NewPageAsync(); await aspNetProcess.VisitInBrowserAsync(page); await TestBasicNavigation(appName, page, usesAuth); @@ -617,25 +579,6 @@ private string ReadFile(string basePath, string path) return File.ReadAllText(Path.Combine(basePath, path)); } - private Project GetSubProject(Project project, string projectDirectory, string projectName) - { - var subProjectDirectory = Path.Combine(project.TemplateOutputDir, projectDirectory); - if (!Directory.Exists(subProjectDirectory)) - { - throw new DirectoryNotFoundException($"Directory {subProjectDirectory} was not found."); - } - - var subProject = new Project - { - Output = project.Output, - DiagnosticsMessageSink = project.DiagnosticsMessageSink, - ProjectName = projectName, - TemplateOutputDir = subProjectDirectory, - }; - - return subProject; - } - private void UpdatePublishedSettings(Project serverProject) { // Hijack here the config file to use the development key during publish. @@ -674,7 +617,7 @@ private void UpdatePublishedSettings(Project serverProject) args = "--roll-forward LatestMajor " + args; // dotnet-serve targets net5.0 by default } - var serveProcess = ProcessEx.Run(Output, publishDir, command, args); + var serveProcess = ProcessEx.Run(TestOutputHelper, publishDir, command, args); var listeningUri = ResolveListeningUrl(serveProcess); return (serveProcess, listeningUri); } diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/PriorityOrderer.cs b/src/ProjectTemplates/BlazorTemplates.Tests/PriorityOrderer.cs new file mode 100644 index 000000000000..e1ebea357e3e --- /dev/null +++ b/src/ProjectTemplates/BlazorTemplates.Tests/PriorityOrderer.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Templates.Test +{ + public class PriorityOrderer : ITestCaseOrderer + { + public IEnumerable<TTestCase> OrderTestCases<TTestCase>( + IEnumerable<TTestCase> testCases) where TTestCase : ITestCase + { + string assemblyName = typeof(TestPriorityAttribute).AssemblyQualifiedName!; + var sortedMethods = new SortedDictionary<int, List<TTestCase>>(); + foreach (TTestCase testCase in testCases) + { + int priority = testCase.TestMethod.Method + .GetCustomAttributes(assemblyName) + .FirstOrDefault() + ?.GetNamedArgument<int>(nameof(TestPriorityAttribute.Priority)) ?? 0; + + GetOrCreate(sortedMethods, priority).Add(testCase); + } + + foreach (TTestCase testCase in + sortedMethods.Keys.SelectMany( + priority => sortedMethods[priority].OrderBy( + testCase => testCase.TestMethod.Method.Name))) + { + yield return testCase; + } + } + + private static TValue GetOrCreate<TKey, TValue>( + IDictionary<TKey, TValue> dictionary, TKey key) + where TKey : struct + where TValue : new() => + dictionary.TryGetValue(key, out TValue result) + ? result + : (dictionary[key] = new TValue()); + } +} diff --git a/src/ProjectTemplates/BlazorTemplates.Tests/TestPriorityAttribute.cs b/src/ProjectTemplates/BlazorTemplates.Tests/TestPriorityAttribute.cs new file mode 100644 index 000000000000..d4f8c6cd730d --- /dev/null +++ b/src/ProjectTemplates/BlazorTemplates.Tests/TestPriorityAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Templates.Test +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class TestPriorityAttribute : Attribute + { + public int Priority { get; private set; } + public TestPriorityAttribute(int priority) => Priority = priority; + } + +} diff --git a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs index 2f25714fd64b..52fbed63dd00 100644 --- a/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs +++ b/src/ProjectTemplates/Shared/ProjectFactoryFixture.cs @@ -26,6 +26,12 @@ public ProjectFactoryFixture(IMessageSink diagnosticsMessageSink) public async Task<Project> GetOrCreateProject(string projectKey, ITestOutputHelper output) { await TemplatePackageInstaller.EnsureTemplatingEngineInitializedAsync(output); + // Different tests may have different output helpers, so need to fix up the output to write to the correct log + if (_projects.TryGetValue(projectKey, out var project)) + { + project.Output = output; + return project; + } return _projects.GetOrAdd( projectKey, (key, outputHelper) => diff --git a/src/Testing/src/LoggedTest/LoggedTestBase.cs b/src/Testing/src/LoggedTest/LoggedTestBase.cs index 362904b3e38a..25f7f37dde72 100644 --- a/src/Testing/src/LoggedTest/LoggedTestBase.cs +++ b/src/Testing/src/LoggedTest/LoggedTestBase.cs @@ -95,7 +95,15 @@ public virtual void Initialize(TestContext context, MethodInfo methodInfo, objec _initializationException = ExceptionDispatchInfo.Capture(e); } } - + + public virtual Task InitializeAsync(TestContext context, MethodInfo methodInfo, object[] testMethodArguments, ITestOutputHelper testOutputHelper) + { + Initialize(context, methodInfo, testMethodArguments, testOutputHelper); + return InitializeCoreAsync(context); + } + + protected virtual Task InitializeCoreAsync(TestContext context) => Task.CompletedTask; + public virtual void Dispose() { if (_testLog == null) @@ -114,9 +122,7 @@ Task ITestMethodLifecycle.OnTestStartAsync(TestContext context, CancellationToke { Context = context; - - Initialize(context, context.TestMethod, context.MethodArguments, context.Output); - return Task.CompletedTask; + return InitializeAsync(context, context.TestMethod, context.MethodArguments, context.Output); } Task ITestMethodLifecycle.OnTestEndAsync(TestContext context, Exception exception, CancellationToken cancellationToken)