Remove SPA project template tests (#20863)
This commit is contained in:
parent
5b5784d950
commit
820b0b20c0
|
|
@ -7,7 +7,6 @@ using System.Diagnostics;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
|
|
@ -24,9 +23,6 @@ namespace Templates.Test.Helpers
|
|||
{
|
||||
private const string _urls = "http://127.0.0.1:0;https://127.0.0.1:0";
|
||||
|
||||
public static bool IsCIEnvironment => typeof(Project).Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.Any(a => a.Key == "ContinuousIntegrationBuild");
|
||||
|
||||
public static string ArtifactsLogDir => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT")))
|
||||
? GetAssemblyMetadata("ArtifactsLogDir")
|
||||
: Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT");
|
||||
|
|
@ -48,12 +44,6 @@ namespace Templates.Test.Helpers
|
|||
public string TemplateBuildDir => Path.Combine(TemplateOutputDir, "bin", "Debug", TargetFramework);
|
||||
public string TemplatePublishDir => Path.Combine(TemplateOutputDir, "bin", "Release", TargetFramework, "publish");
|
||||
|
||||
private string TemplateServerDir => Path.Combine(TemplateOutputDir, $"{ProjectName}.Server");
|
||||
private string TemplateClientDir => Path.Combine(TemplateOutputDir, $"{ProjectName}.Client");
|
||||
public string TemplateClientDebugDir => Path.Combine(TemplateClientDir, "bin", "Debug", TargetFramework);
|
||||
public string TemplateClientReleaseDir => Path.Combine(TemplateClientDir, "bin", "Release", TargetFramework, "publish");
|
||||
public string TemplateServerReleaseDir => Path.Combine(TemplateServerDir, "bin", "Release", TargetFramework, "publish");
|
||||
|
||||
public ITestOutputHelper Output { get; set; }
|
||||
public IMessageSink DiagnosticsMessageSink { get; set; }
|
||||
|
||||
|
|
@ -164,50 +154,6 @@ namespace Templates.Test.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
internal AspNetProcess StartBuiltServerAsync()
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_ENVIRONMENT"] = "Development"
|
||||
};
|
||||
|
||||
var projectDll = Path.Combine(TemplateServerDir, $"{ProjectName}.Server.dll");
|
||||
return new AspNetProcess(Output, TemplateServerDir, projectDll, environment, published: false);
|
||||
}
|
||||
|
||||
internal AspNetProcess StartBuiltClientAsync(AspNetProcess serverProcess)
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_ENVIRONMENT"] = "Development"
|
||||
};
|
||||
|
||||
var projectDll = Path.Combine(TemplateClientDebugDir, $"{ProjectName}.Client.dll {serverProcess.ListeningUri.Port}");
|
||||
return new AspNetProcess(Output, TemplateOutputDir, projectDll, environment, hasListeningUri: false);
|
||||
}
|
||||
|
||||
internal AspNetProcess StartPublishedServerAsync()
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_URLS"] = _urls,
|
||||
};
|
||||
|
||||
var projectDll = $"{ProjectName}.Server.dll";
|
||||
return new AspNetProcess(Output, TemplateServerReleaseDir, projectDll, environment);
|
||||
}
|
||||
|
||||
internal AspNetProcess StartPublishedClientAsync()
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_URLS"] = _urls,
|
||||
};
|
||||
|
||||
var projectDll = $"{ProjectName}.Client.dll";
|
||||
return new AspNetProcess(Output, TemplateClientReleaseDir, projectDll, environment);
|
||||
}
|
||||
|
||||
internal AspNetProcess StartBuiltProjectAsync(bool hasListeningUri = true, ILogger logger = null)
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
|
|
@ -239,73 +185,6 @@ namespace Templates.Test.Helpers
|
|||
return new AspNetProcess(Output, TemplatePublishDir, projectDll, environment, hasListeningUri: hasListeningUri);
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RestoreWithRetryAsync(ITestOutputHelper output, string workingDirectory)
|
||||
{
|
||||
// "npm restore" sometimes fails randomly in AppVeyor with errors like:
|
||||
// EPERM: operation not permitted, scandir <path>...
|
||||
// This appears to be a general NPM reliability issue on Windows which has
|
||||
// been reported many times (e.g., https://github.com/npm/npm/issues/18380)
|
||||
// So, allow multiple attempts at the restore.
|
||||
const int maxAttempts = 3;
|
||||
var attemptNumber = 0;
|
||||
ProcessEx restoreResult;
|
||||
do
|
||||
{
|
||||
restoreResult = await RestoreAsync(output, workingDirectory);
|
||||
if (restoreResult.HasExited && restoreResult.ExitCode == 0)
|
||||
{
|
||||
return restoreResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: We should filter for EPEM here to avoid masking other errors silently.
|
||||
output.WriteLine(
|
||||
$"NPM restore in {workingDirectory} failed on attempt {attemptNumber} of {maxAttempts}. " +
|
||||
$"Error was: {restoreResult.GetFormattedOutput()}");
|
||||
|
||||
// Clean up the possibly-incomplete node_modules dir before retrying
|
||||
CleanNodeModulesFolder(workingDirectory, output);
|
||||
}
|
||||
attemptNumber++;
|
||||
} while (attemptNumber < maxAttempts);
|
||||
|
||||
output.WriteLine($"Giving up attempting NPM restore in {workingDirectory} after {attemptNumber} attempts.");
|
||||
return restoreResult;
|
||||
|
||||
void CleanNodeModulesFolder(string workingDirectory, ITestOutputHelper output)
|
||||
{
|
||||
var nodeModulesDir = Path.Combine(workingDirectory, "node_modules");
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(nodeModulesDir))
|
||||
{
|
||||
Directory.Delete(nodeModulesDir, recursive: true);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
output.WriteLine($"Failed to clean up node_modules folder at {nodeModulesDir}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<ProcessEx> RestoreAsync(ITestOutputHelper output, string workingDirectory)
|
||||
{
|
||||
// It's not safe to run multiple NPM installs in parallel
|
||||
// https://github.com/npm/npm/issues/2500
|
||||
await NodeLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
output.WriteLine($"Restoring NPM packages in '{workingDirectory}' using npm...");
|
||||
var result = ProcessEx.RunViaShell(output, workingDirectory, "npm install");
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
NodeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetEfCreateMigrationAsync(string migrationName)
|
||||
{
|
||||
var args = $"--verbose --no-build migrations add {migrationName}";
|
||||
|
|
@ -335,35 +214,6 @@ namespace Templates.Test.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetEfUpdateDatabaseAsync()
|
||||
{
|
||||
var args = "--verbose --no-build database update";
|
||||
|
||||
// Only run one instance of 'dotnet new' at once, as a workaround for
|
||||
// https://github.com/aspnet/templating/issues/63
|
||||
await DotNetNewLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var command = DotNetMuxer.MuxerPathOrDefault();
|
||||
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("DotNetEfFullPath")))
|
||||
{
|
||||
args = $"\"{DotNetEfFullPath}\" " + args;
|
||||
}
|
||||
else
|
||||
{
|
||||
command = "dotnet-ef";
|
||||
}
|
||||
|
||||
var result = ProcessEx.Run(Output, TemplateOutputDir, command, args);
|
||||
await result.Exited;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DotNetNewLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// If this fails, you should generate new migrations via migrations/updateMigrations.cmd
|
||||
public void AssertEmptyMigration(string migration)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Templates.Test.SpaTemplateTest
|
||||
{
|
||||
public class AngularTemplateTest : SpaTemplateTestBase
|
||||
{
|
||||
public AngularTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
|
||||
: base(projectFactory, browserFixture, output) { }
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task AngularTemplate_Works()
|
||||
=> SpaTemplateImplAsync("angularnoauth", "angular", useLocalDb: false, usesAuth: false);
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task AngularTemplate_IndividualAuth_Works()
|
||||
=> SpaTemplateImplAsync("angularindividual", "angular", useLocalDb: false, usesAuth: true);
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task AngularTemplate_IndividualAuth_Works_LocalDb()
|
||||
=> SpaTemplateImplAsync("angularindividualuld", "angular", useLocalDb: true, usesAuth: true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Templates.Test.SpaTemplateTest
|
||||
{
|
||||
public class ReactReduxTemplateTest : SpaTemplateTestBase
|
||||
{
|
||||
public ReactReduxTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
|
||||
: base(projectFactory, browserFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task ReactReduxTemplate_Works_NetCore()
|
||||
=> SpaTemplateImplAsync("reactredux", "reactredux", useLocalDb: false, usesAuth: false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// 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.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Templates.Test.SpaTemplateTest
|
||||
{
|
||||
public class ReactTemplateTest : SpaTemplateTestBase
|
||||
{
|
||||
public ReactTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
|
||||
: base(projectFactory, browserFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task ReactTemplate_Works_NetCore()
|
||||
=> SpaTemplateImplAsync("reactnoauth", "react", useLocalDb: false, usesAuth: false);
|
||||
|
||||
[QuarantinedTest]
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task ReactTemplate_IndividualAuth_NetCore()
|
||||
=> SpaTemplateImplAsync("reactindividual", "react", useLocalDb: false, usesAuth: true);
|
||||
|
||||
[ConditionalFact]
|
||||
[SkipOnHelix("selenium")]
|
||||
public Task ReactTemplate_IndividualAuth_NetCore_LocalDb()
|
||||
=> SpaTemplateImplAsync("reactindividualuld", "react", useLocalDb: true, usesAuth: true);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,349 +0,0 @@
|
|||
// 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.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Microsoft.AspNetCore.Internal;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Turn off parallel test run for Edge as the driver does not support multiple Selenium tests at the same time
|
||||
#if EDGE
|
||||
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
|
||||
#endif
|
||||
namespace Templates.Test.SpaTemplateTest
|
||||
{
|
||||
public class SpaTemplateTestBase : BrowserTestBase
|
||||
{
|
||||
public SpaTemplateTestBase(
|
||||
ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output) : base(browserFixture, output)
|
||||
{
|
||||
ProjectFactory = projectFactory;
|
||||
}
|
||||
|
||||
public ProjectFactoryFixture ProjectFactory { get; set; }
|
||||
|
||||
public Project Project { get; set; }
|
||||
|
||||
// Rather than using [Theory] to pass each of the different values for 'template',
|
||||
// it's important to distribute the SPA template tests over different test classes
|
||||
// so they can be run in parallel. Xunit doesn't parallelize within a test class.
|
||||
protected async Task SpaTemplateImplAsync(
|
||||
string key,
|
||||
string template,
|
||||
bool useLocalDb = false,
|
||||
bool usesAuth = false)
|
||||
{
|
||||
Project = await ProjectFactory.GetOrCreateProject(key, Output);
|
||||
|
||||
using var createResult = await Project.RunDotNetNewAsync(template, auth: usesAuth ? "Individual" : null, language: null, useLocalDb);
|
||||
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||
|
||||
// We shouldn't have to do the NPM restore in tests because it should happen
|
||||
// automatically at build time, but by doing it up front we can avoid having
|
||||
// multiple NPM installs run concurrently which otherwise causes errors when
|
||||
// tests run in parallel.
|
||||
var clientAppSubdirPath = Path.Combine(Project.TemplateOutputDir, "ClientApp");
|
||||
ValidatePackageJson(clientAppSubdirPath);
|
||||
|
||||
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj");
|
||||
if (usesAuth && !useLocalDb)
|
||||
{
|
||||
Assert.Contains(".db", projectFileContents);
|
||||
}
|
||||
|
||||
using var npmRestoreResult = await Project.RestoreWithRetryAsync(Output, clientAppSubdirPath);
|
||||
Assert.True(0 == npmRestoreResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm restore", Project, npmRestoreResult));
|
||||
|
||||
using var lintResult = ProcessEx.RunViaShell(Output, clientAppSubdirPath, "npm run lint");
|
||||
Assert.True(0 == lintResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run lint", Project, lintResult));
|
||||
|
||||
// The default behavior of angular tests is watch mode, which leaves the test process open after it finishes, which leads to delays/hangs.
|
||||
var testcommand = "npm run test" + template == "angular" ? "-- --watch=false" : "";
|
||||
|
||||
using var testResult = ProcessEx.RunViaShell(Output, clientAppSubdirPath, testcommand);
|
||||
Assert.True(0 == testResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run test", Project, testResult));
|
||||
|
||||
using 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.
|
||||
|
||||
using var buildResult = await Project.RunDotNetBuildAsync();
|
||||
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult));
|
||||
|
||||
// localdb is not installed on the CI machines, so skip it.
|
||||
var shouldVisitFetchData = !(useLocalDb && Project.IsCIEnvironment);
|
||||
|
||||
if (usesAuth)
|
||||
{
|
||||
using var migrationsResult = await Project.RunDotNetEfCreateMigrationAsync(template);
|
||||
Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", Project, migrationsResult));
|
||||
Project.AssertEmptyMigration(template);
|
||||
|
||||
if (shouldVisitFetchData)
|
||||
{
|
||||
using var dbUpdateResult = await Project.RunDotNetEfUpdateDatabaseAsync();
|
||||
Assert.True(0 == dbUpdateResult.ExitCode, ErrorMessages.GetFailedProcessMessage("update database", Project, dbUpdateResult));
|
||||
}
|
||||
}
|
||||
|
||||
if (template == "react" || template == "reactredux")
|
||||
{
|
||||
await CleanupReactClientAppBuildFolder(clientAppSubdirPath);
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||
|
||||
await WarmUpServer(aspNetProcess);
|
||||
await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
|
||||
|
||||
if (BrowserFixture.IsHostAutomationSupported())
|
||||
{
|
||||
var (browser, logs) = await BrowserFixture.GetOrCreateBrowserAsync(Output, $"{Project.ProjectName}.build");
|
||||
aspNetProcess.VisitInBrowser(browser);
|
||||
TestBasicNavigation(visitFetchData: shouldVisitFetchData, usesAuth, browser, logs);
|
||||
}
|
||||
else
|
||||
{
|
||||
BrowserFixture.EnforceSupportedConfigurations();
|
||||
}
|
||||
}
|
||||
|
||||
if (usesAuth)
|
||||
{
|
||||
UpdatePublishedSettings();
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process));
|
||||
|
||||
await WarmUpServer(aspNetProcess);
|
||||
await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
|
||||
|
||||
if (BrowserFixture.IsHostAutomationSupported())
|
||||
{
|
||||
var (browser, logs) = await BrowserFixture.GetOrCreateBrowserAsync(Output, $"{Project.ProjectName}.publish");
|
||||
aspNetProcess.VisitInBrowser(browser);
|
||||
TestBasicNavigation(visitFetchData: shouldVisitFetchData, usesAuth, browser, logs);
|
||||
}
|
||||
else
|
||||
{
|
||||
BrowserFixture.EnforceSupportedConfigurations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CleanupReactClientAppBuildFolder(string clientAppSubdirPath)
|
||||
{
|
||||
ProcessEx testResult = null;
|
||||
int? testResultExitCode = null;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
testResult = ProcessEx.RunViaShell(Output, clientAppSubdirPath, "npx rimraf ./build");
|
||||
testResultExitCode = testResult.ExitCode;
|
||||
if (testResultExitCode == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
testResult.Dispose();
|
||||
}
|
||||
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
|
||||
Assert.True(testResultExitCode == 0, ErrorMessages.GetFailedProcessMessage("npx rimraf ./build", Project, testResult));
|
||||
}
|
||||
|
||||
private void ValidatePackageJson(string clientAppSubdirPath)
|
||||
{
|
||||
Assert.True(File.Exists(Path.Combine(clientAppSubdirPath, "package.json")), "Missing a package.json");
|
||||
var packageJson = JObject.Parse(ReadFile(clientAppSubdirPath, "package.json"));
|
||||
|
||||
// NPM package names must match ^(?:@[a-z0-9-~][a-z0-9-._~]*/)?[a-z0-9-~][a-z0-9-._~]*$
|
||||
var packageName = (string)packageJson["name"];
|
||||
Regex regex = new Regex("^(?:@[a-z0-9-~][a-z0-9-._~]*/)?[a-z0-9-~][a-z0-9-._~]*$");
|
||||
Assert.True(regex.IsMatch(packageName), "package.json name is invalid format");
|
||||
}
|
||||
|
||||
private static async Task WarmUpServer(AspNetProcess aspNetProcess)
|
||||
{
|
||||
var intervalInSeconds = 5;
|
||||
var attempt = 0;
|
||||
var maxAttempts = 5;
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
attempt++;
|
||||
var response = await aspNetProcess.SendRequest("/");
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.Message.StartsWith("The SSL connection could not be established"))
|
||||
{
|
||||
}
|
||||
var currentDelay = intervalInSeconds * attempt;
|
||||
await Task.Delay(TimeSpan.FromSeconds(currentDelay));
|
||||
} while (attempt < maxAttempts);
|
||||
stopwatch.Stop();
|
||||
throw new TimeoutException($"Could not contact the server within {stopwatch.Elapsed.TotalSeconds} seconds");
|
||||
}
|
||||
|
||||
private void UpdatePublishedSettings()
|
||||
{
|
||||
// Hijack here the config file to use the development key during publish.
|
||||
var appSettings = JObject.Parse(File.ReadAllText(Path.Combine(Project.TemplateOutputDir, "appsettings.json")));
|
||||
var appSettingsDevelopment = JObject.Parse(File.ReadAllText(Path.Combine(Project.TemplateOutputDir, "appsettings.Development.json")));
|
||||
((JObject)appSettings["IdentityServer"]).Merge(appSettingsDevelopment["IdentityServer"]);
|
||||
((JObject)appSettings["IdentityServer"]).Merge(new
|
||||
{
|
||||
IdentityServer = new
|
||||
{
|
||||
Key = new
|
||||
{
|
||||
FilePath = "./tempkey.json"
|
||||
}
|
||||
}
|
||||
});
|
||||
var testAppSettings = appSettings.ToString();
|
||||
File.WriteAllText(Path.Combine(Project.TemplatePublishDir, "appsettings.json"), testAppSettings);
|
||||
}
|
||||
|
||||
private void TestBasicNavigation(bool visitFetchData, bool usesAuth, IWebDriver browser, ILogs logs)
|
||||
{
|
||||
browser.Exists(By.TagName("ul"));
|
||||
// <title> element gets project ID injected into it during template execution
|
||||
browser.Contains(Project.ProjectGuid.Replace(".", "._"), () => browser.Title);
|
||||
|
||||
// Initially displays the home page
|
||||
browser.Equal("Hello, world!", () => browser.FindElement(By.TagName("h1")).Text);
|
||||
|
||||
// Can navigate to the counter page
|
||||
browser.Click(By.PartialLinkText("Counter"));
|
||||
browser.Contains("counter", () => browser.Url);
|
||||
|
||||
browser.Equal("Counter", () => browser.FindElement(By.TagName("h1")).Text);
|
||||
|
||||
// Clicking the counter button works
|
||||
browser.Equal("0", () => browser.FindElement(By.CssSelector("p>strong")).Text);
|
||||
browser.Click(By.CssSelector("p+button")) ;
|
||||
browser.Equal("1", () => browser.FindElement(By.CssSelector("p>strong")).Text);
|
||||
|
||||
if (visitFetchData)
|
||||
{
|
||||
browser.Click(By.PartialLinkText("Fetch data"));
|
||||
|
||||
if (usesAuth)
|
||||
{
|
||||
// We will be redirected to the identity UI
|
||||
browser.Contains("/Identity/Account/Login", () => browser.Url);
|
||||
browser.Click(By.PartialLinkText("Register as a new user"));
|
||||
|
||||
var userName = $"{Guid.NewGuid()}@example.com";
|
||||
var password = $"!Test.Password1$";
|
||||
browser.Exists(By.Name("Input.Email"));
|
||||
browser.FindElement(By.Name("Input.Email")).SendKeys(userName);
|
||||
browser.FindElement(By.Name("Input.Password")).SendKeys(password);
|
||||
browser.FindElement(By.Name("Input.ConfirmPassword")).SendKeys(password);
|
||||
browser.Click(By.Id("registerSubmit"));
|
||||
|
||||
// We will be redirected to the RegisterConfirmation
|
||||
browser.Contains("/Identity/Account/RegisterConfirmation", () => browser.Url);
|
||||
browser.Click(By.PartialLinkText("Click here to confirm your account"));
|
||||
|
||||
// We will be redirected to the ConfirmEmail
|
||||
browser.Contains("/Identity/Account/ConfirmEmail", () => browser.Url);
|
||||
|
||||
// Now we can login
|
||||
browser.Click(By.PartialLinkText("Login"));
|
||||
browser.Exists(By.Name("Input.Email"));
|
||||
browser.FindElement(By.Name("Input.Email")).SendKeys(userName);
|
||||
browser.FindElement(By.Name("Input.Password")).SendKeys(password);
|
||||
browser.Click(By.Id("login-submit"));
|
||||
|
||||
// Need to navigate to fetch page
|
||||
browser.Click(By.PartialLinkText("Fetch data"));
|
||||
}
|
||||
|
||||
// Can navigate to the 'fetch data' page
|
||||
browser.Contains("fetch-data", () => browser.Url);
|
||||
browser.Equal("Weather forecast", () => browser.FindElement(By.TagName("h1")).Text);
|
||||
|
||||
// Asynchronously loads and displays the table of weather forecasts
|
||||
browser.Exists(By.CssSelector("table>tbody>tr"));
|
||||
browser.Equal(5, () => browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count);
|
||||
}
|
||||
|
||||
foreach (var logKind in logs.AvailableLogTypes)
|
||||
{
|
||||
var entries = logs.GetLog(logKind);
|
||||
var badEntries = entries.Where(e => new LogLevel[] { LogLevel.Warning, LogLevel.Severe }.Contains(e.Level));
|
||||
|
||||
// Based on https://github.com/webpack/webpack-dev-server/issues/2134
|
||||
badEntries = badEntries.Where(e =>
|
||||
!e.Message.Contains("failed: WebSocket is closed before the connection is established.")
|
||||
&& !e.Message.Contains("[WDS] Disconnected!")
|
||||
&& !e.Message.Contains("Timed out connecting to Chrome, retrying")
|
||||
&& !(e.Message.Contains("jsonp?c=") && e.Message.Contains("Uncaught TypeError:") && e.Message.Contains("is not a function")));
|
||||
|
||||
Assert.True(badEntries.Count() == 0, "There were Warnings or Errors from the browser." + Environment.NewLine + string.Join(Environment.NewLine, badEntries));
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertFileExists(string basePath, string path, bool shouldExist)
|
||||
{
|
||||
var fullPath = Path.Combine(basePath, path);
|
||||
var doesExist = File.Exists(fullPath);
|
||||
|
||||
if (shouldExist)
|
||||
{
|
||||
Assert.True(doesExist, "Expected file to exist, but it doesn't: " + path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(doesExist, "Expected file not to exist, but it does: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadFile(string basePath, string path)
|
||||
{
|
||||
AssertFileExists(basePath, path, shouldExist: true);
|
||||
return File.ReadAllText(Path.Combine(basePath, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue