[Infrastructure improvements] (#8275)
* Improved selenium start and tear down
* Selenium is set up and torn down in an assembly fixture.
* Selenium is initialized lazily and in a non-blocking way.
* Selenium processes are tracked as part of the build and their pids
written to a file on disk for cleanup in the event of unexpected
termination of the test process.
* Browser fixture retries with linear backoff to create a remote
driver. Under heavy load (like when we are doing a simultaneous NPM
restore) the selenium server can become unresponsive so we retry
three times, with a longer comand timeout allowance each time up to
a max of 3 minutes.
* Moved test project setup to build time instead of runtime.
* Added target PrepareForTest to create the required files for testing
* The template creation folder.
* The template props file to use our built packages.
* The folder for the custom hive.
* Added assembly metadata attributes to find all the data we need to
run the tests.
* Path to the artifacts shipping packages folder.
* Path to the artifacts non-shipping packages folder.
* Path to the test templates creation folder.
* Path to use for the custom templating hive used in tests.
* Proper cleanup as part of the build
* Remove the test templates creation folder.
* Remove the test packages restore path.
* Recreate the test templates creation folder.
* Recreate the test packages restore path.
* Generated Directory.Build.Props and Directory.Build.Targets in the
test templates creation folder.
* Cleaned up potentially stale templatetestsprops.
* Improved test flows
* Initialization is done lazily and asynchronously.
* Selenium
* Browser fixture
* Template initialization.
* Flattened test flows to avoid assertions inside deep callstacks.
* All assertions happen at the test level with improved error messages.
* With the exception of the migrations assertions.
* Assertions contain information about which step failed, for what
project and what failure details.
* Broke down tests to perform individual steps instead of mixing build
and publish.
* Publish project.
* Build project. (Debug)
* Run built project.
* Run published project.
* Concentrated build logic into the Project class.
* Context between the different steps of a test is maintained in
this class.
* All operations that require coordination are performed within this
class.
* There is a lock for dotnet and a lock for nodejs. When building
SPAs we acquire the nodejs lock to correctly prevent multiple
runs of nodejs in parallel.
[ApiAuthorization template cleanups]
* Fix preview3 issues with breaking changes on Entity framework by
manually configuring the model in ApiAuthorizationDbContext.
* Add app.db to the project file when using local db.
* Fix linting errors on angular template.
* Fix react tests
* Add tests to cover new auth options in the SPA templates.
This commit is contained in:
parent
0456c9dcc9
commit
9f1a978230
|
|
@ -341,6 +341,8 @@ jobs:
|
|||
beforeBuild:
|
||||
- bash: "./eng/scripts/install-nginx-linux.sh"
|
||||
displayName: Installing Nginx
|
||||
- bash: "echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p"
|
||||
displayName: Increase inotify limit
|
||||
afterBuild:
|
||||
- bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true
|
||||
displayName: Run Flaky Tests
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ jobs:
|
|||
displayName: Install JDK 11
|
||||
- powershell: Write-Host "##vso[task.prependpath]$env:JAVA_HOME\bin"
|
||||
displayName: Prepend JAVA bin folder to the PATH.
|
||||
- powershell: Write-Host "##vso[task.setvariable variable=SeleniumProcessTrackingFolder]$(BuildDirectory)\obj\selenium\"
|
||||
displayName: Add Selenium process tracking folder environment variable
|
||||
- powershell: ./eng/scripts/InstallGoogleChrome.ps1
|
||||
displayName: Install chrome
|
||||
- ${{ if and(eq(variables['System.TeamProject'], 'internal'), eq(parameters.agentOs, 'Windows'), eq(parameters.codeSign, 'true')) }}:
|
||||
|
|
|
|||
|
|
@ -22,6 +22,22 @@ function _killJavaInstances() {
|
|||
}
|
||||
}
|
||||
|
||||
function _killSeleniumTrackedProcesses() {
|
||||
$files = Get-ChildItem $env:SeleniumProcessTrackingFolder -ErrorAction SilentlyContinue;
|
||||
# PID files have a format of <<pid>>.<<guid>>.pid
|
||||
$pids = $files |
|
||||
Where-Object { $_.Name -match "([0-9]+)\..*?.pid"; } |
|
||||
Foreach-Object { $Matches[1] };
|
||||
|
||||
foreach ($currentPid in $pids) {
|
||||
try {
|
||||
& cmd /c "taskkill /T /F /PID $currentPid 2>&1"
|
||||
} catch {
|
||||
Write-Host "Failed to kill process: $currentPid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_kill dotnet.exe
|
||||
_kill testhost.exe
|
||||
_kill iisexpress.exe
|
||||
|
|
@ -35,6 +51,7 @@ _kill chrome.exe
|
|||
_kill h2spec.exe
|
||||
_kill WerFault.exe
|
||||
_killJavaInstances
|
||||
_killSeleniumTrackedProcesses
|
||||
|
||||
if (Get-Command iisreset -ErrorAction ignore) {
|
||||
iisreset /restart
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
// 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.E2ETesting;
|
||||
using Xunit;
|
||||
|
||||
[assembly: TestFramework("Microsoft.AspNetCore.E2ETesting.XunitTestFrameworkWithAssemblyFixture", "Microsoft.AspNetCore.Components.E2ETests")]
|
||||
[assembly: AssemblyFixture(typeof(SeleniumStandaloneServer))]
|
||||
|
|
@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
|
||||
appElement.FindElement(By.Id("run-without-dispatch")).Click();
|
||||
|
||||
WaitAssert.Contains(
|
||||
Browser.Contains(
|
||||
$"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the renderer's synchronization context",
|
||||
() => result.Text);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using OpenQA.Selenium;
|
|||
using OpenQA.Selenium.Support.UI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -23,12 +24,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
{
|
||||
_serverFixture.Environment = AspNetEnvironment.Development;
|
||||
_serverFixture.BuildWebHostMethod = ComponentsApp.Server.Program.BuildWebHost;
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate("/", noReload: false);
|
||||
WaitUntilLoaded();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void HasTitle()
|
||||
{
|
||||
|
|
@ -56,13 +59,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
Browser.FindElement(By.LinkText("Counter")).Click();
|
||||
|
||||
// Verify we're now on the counter page, with that nav link (only) highlighted
|
||||
WaitAssert.Equal("Counter", () => Browser.FindElement(mainHeaderSelector).Text);
|
||||
Browser.Equal("Counter", () => Browser.FindElement(mainHeaderSelector).Text);
|
||||
Assert.Collection(Browser.FindElements(activeNavLinksSelector),
|
||||
item => Assert.Equal("Counter", item.Text));
|
||||
|
||||
// Verify we can navigate back to home too
|
||||
Browser.FindElement(By.LinkText("Home")).Click();
|
||||
WaitAssert.Equal("Hello, world!", () => Browser.FindElement(mainHeaderSelector).Text);
|
||||
Browser.Equal("Hello, world!", () => Browser.FindElement(mainHeaderSelector).Text);
|
||||
Assert.Collection(Browser.FindElements(activeNavLinksSelector),
|
||||
item => Assert.Equal("Home", item.Text));
|
||||
}
|
||||
|
|
@ -72,7 +75,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
{
|
||||
// Navigate to "Counter"
|
||||
Browser.FindElement(By.LinkText("Counter")).Click();
|
||||
WaitAssert.Equal("Counter", () => Browser.FindElement(By.TagName("h1")).Text);
|
||||
Browser.Equal("Counter", () => Browser.FindElement(By.TagName("h1")).Text);
|
||||
|
||||
// Observe the initial value is zero
|
||||
var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p"));
|
||||
|
|
@ -81,11 +84,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
// Click the button; see it counts
|
||||
var button = Browser.FindElement(By.CssSelector(".main button"));
|
||||
button.Click();
|
||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
button.Click();
|
||||
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 2", () => countDisplayElement.Text);
|
||||
button.Click();
|
||||
WaitAssert.Equal("Current count: 3", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 3", () => countDisplayElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -93,7 +96,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
|||
{
|
||||
// Navigate to "Fetch Data"
|
||||
Browser.FindElement(By.LinkText("Fetch data")).Click();
|
||||
WaitAssert.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text);
|
||||
Browser.Equal("Weather forecast", () => Browser.FindElement(By.TagName("h1")).Text);
|
||||
|
||||
// Wait until loaded
|
||||
var tableSelector = By.CssSelector("table.table");
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
|||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
public class BinaryHttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
|
||||
{
|
||||
readonly ServerFixture _apiServerFixture;
|
||||
readonly IWebElement _appElement;
|
||||
IWebElement _appElement;
|
||||
IWebElement _responseStatus;
|
||||
IWebElement _responseStatusText;
|
||||
IWebElement _testOutcome;
|
||||
|
|
@ -30,11 +31,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
{
|
||||
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||
_apiServerFixture = apiServerFixture;
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
_appElement = MountTestComponent<BinaryHttpRequestsComponent>();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CanSendAndReceiveBytes()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -19,9 +20,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
MountTestComponent<BindCasesComponent>();
|
||||
}
|
||||
|
||||
|
|
@ -41,12 +46,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Assert.Equal(string.Empty, boundValue.Text); // Doesn't update until change event
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
||||
Browser.Equal("Changed value", () => boundValue.Text);
|
||||
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Remove the value altogether
|
||||
setNullButton.Click();
|
||||
WaitAssert.Equal(string.Empty, () => target.GetAttribute("value"));
|
||||
Browser.Equal(string.Empty, () => target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
|
@ -65,12 +70,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("Changed value\t");
|
||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
||||
Browser.Equal("Changed value", () => boundValue.Text);
|
||||
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Remove the value altogether
|
||||
setNullButton.Click();
|
||||
WaitAssert.Equal(string.Empty, () => target.GetAttribute("value"));
|
||||
Browser.Equal(string.Empty, () => target.GetAttribute("value"));
|
||||
Assert.Equal(string.Empty, boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
|
@ -87,7 +92,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
target.SendKeys("Changed value");
|
||||
Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet.
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
||||
Browser.Equal("Changed value", () => boundValue.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -101,7 +106,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated
|
||||
target.Clear();
|
||||
target.SendKeys("Changed value\t");
|
||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
||||
Browser.Equal("Changed value", () => boundValue.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -115,13 +120,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Modify target; verify value is updated
|
||||
target.Click();
|
||||
WaitAssert.True(() => target.Selected);
|
||||
WaitAssert.Equal("True", () => boundValue.Text);
|
||||
Browser.True(() => target.Selected);
|
||||
Browser.Equal("True", () => boundValue.Text);
|
||||
|
||||
// Modify data; verify checkbox is updated
|
||||
invertButton.Click();
|
||||
WaitAssert.False(() => target.Selected);
|
||||
WaitAssert.Equal("False", () => boundValue.Text);
|
||||
Browser.False(() => target.Selected);
|
||||
Browser.Equal("False", () => boundValue.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -135,13 +140,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Modify target; verify value is updated
|
||||
target.Click();
|
||||
WaitAssert.True(() => target.Selected);
|
||||
WaitAssert.Equal("True", () => boundValue.Text);
|
||||
Browser.True(() => target.Selected);
|
||||
Browser.Equal("True", () => boundValue.Text);
|
||||
|
||||
// Modify data; verify checkbox is updated
|
||||
invertButton.Click();
|
||||
WaitAssert.False(() => target.Selected);
|
||||
WaitAssert.Equal("False", () => boundValue.Text);
|
||||
Browser.False(() => target.Selected);
|
||||
Browser.Equal("False", () => boundValue.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -155,13 +160,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Modify target; verify value is updated
|
||||
target.Click();
|
||||
WaitAssert.False(() => target.Selected);
|
||||
WaitAssert.Equal("False", () => boundValue.Text);
|
||||
Browser.False(() => target.Selected);
|
||||
Browser.Equal("False", () => boundValue.Text);
|
||||
|
||||
// Modify data; verify checkbox is updated
|
||||
invertButton.Click();
|
||||
WaitAssert.True(() => target.Selected);
|
||||
WaitAssert.Equal("True", () => boundValue.Text);
|
||||
Browser.True(() => target.Selected);
|
||||
Browser.Equal("True", () => boundValue.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -174,13 +179,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Modify target; verify value is updated
|
||||
target.SelectByText("Third choice");
|
||||
WaitAssert.Equal("Third", () => boundValue.Text);
|
||||
Browser.Equal("Third", () => boundValue.Text);
|
||||
|
||||
// Also verify we can add and select new options atomically
|
||||
// Don't move this into a separate test, because then the previous assertions
|
||||
// would be dependent on test execution order (or would require a full page reload)
|
||||
Browser.FindElement(By.Id("select-box-add-option")).Click();
|
||||
WaitAssert.Equal("Fourth", () => boundValue.Text);
|
||||
Browser.Equal("Fourth", () => boundValue.Text);
|
||||
Assert.Equal("Fourth choice", target.SelectedOption.Text);
|
||||
}
|
||||
|
||||
|
|
@ -197,7 +202,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("42\t");
|
||||
WaitAssert.Equal("42", () => boundValue.Text);
|
||||
Browser.Equal("42", () => boundValue.Text);
|
||||
Assert.Equal("42", mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -214,19 +219,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-42\t");
|
||||
WaitAssert.Equal("-42", () => boundValue.Text);
|
||||
Browser.Equal("-42", () => boundValue.Text);
|
||||
Assert.Equal("-42", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("42\t");
|
||||
WaitAssert.Equal("42", () => boundValue.Text);
|
||||
Browser.Equal("42", () => boundValue.Text);
|
||||
Assert.Equal("42", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +248,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-3000000000\t");
|
||||
WaitAssert.Equal("-3000000000", () => boundValue.Text);
|
||||
Browser.Equal("-3000000000", () => boundValue.Text);
|
||||
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -260,19 +265,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("3000000000\t");
|
||||
WaitAssert.Equal("3000000000", () => boundValue.Text);
|
||||
Browser.Equal("3000000000", () => boundValue.Text);
|
||||
Assert.Equal("3000000000", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-3000000000\t");
|
||||
WaitAssert.Equal("-3000000000", () => boundValue.Text);
|
||||
Browser.Equal("-3000000000", () => boundValue.Text);
|
||||
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +294,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-3.141\t");
|
||||
WaitAssert.Equal("-3.141", () => boundValue.Text);
|
||||
Browser.Equal("-3.141", () => boundValue.Text);
|
||||
Assert.Equal("-3.141", mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -306,19 +311,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("3.141\t");
|
||||
WaitAssert.Equal("3.141", () => boundValue.Text);
|
||||
Browser.Equal("3.141", () => boundValue.Text);
|
||||
Assert.Equal("3.141", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-3.141\t");
|
||||
WaitAssert.Equal("-3.141", () => boundValue.Text);
|
||||
Browser.Equal("-3.141", () => boundValue.Text);
|
||||
Assert.Equal("-3.141", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -335,14 +340,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-3.14159265359\t");
|
||||
WaitAssert.Equal("-3.14159265359", () => boundValue.Text);
|
||||
Browser.Equal("-3.14159265359", () => boundValue.Text);
|
||||
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// Double shouldn't preserve trailing zeros
|
||||
target.Clear();
|
||||
target.SendKeys("0.010\t");
|
||||
WaitAssert.Equal("0.01", () => boundValue.Text);
|
||||
Browser.Equal("0.01", () => boundValue.Text);
|
||||
Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -359,26 +364,26 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("3.14159265359\t");
|
||||
WaitAssert.Equal("3.14159265359", () => boundValue.Text);
|
||||
Browser.Equal("3.14159265359", () => boundValue.Text);
|
||||
Assert.Equal("3.14159265359", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("-3.14159265359\t");
|
||||
WaitAssert.Equal("-3.14159265359", () => boundValue.Text);
|
||||
Browser.Equal("-3.14159265359", () => boundValue.Text);
|
||||
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// Double shouldn't preserve trailing zeros
|
||||
target.Clear();
|
||||
target.SendKeys("0.010\t");
|
||||
WaitAssert.Equal("0.01", () => boundValue.Text);
|
||||
Browser.Equal("0.01", () => boundValue.Text);
|
||||
Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -396,7 +401,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Decimal should preserve trailing zeros
|
||||
target.Clear();
|
||||
target.SendKeys("0.010\t");
|
||||
WaitAssert.Equal("0.010", () => boundValue.Text);
|
||||
Browser.Equal("0.010", () => boundValue.Text);
|
||||
Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
|
||||
|
|
@ -413,20 +418,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("0.0000000000000000000000000001\t");
|
||||
WaitAssert.Equal("0.0000000000000000000000000001", () => boundValue.Text);
|
||||
Browser.Equal("0.0000000000000000000000000001", () => boundValue.Text);
|
||||
Assert.Equal("0.0000000000000000000000000001", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
// Decimal should preserve trailing zeros
|
||||
target.Clear();
|
||||
target.SendKeys("0.010\t");
|
||||
WaitAssert.Equal("0.010", () => boundValue.Text);
|
||||
Browser.Equal("0.010", () => boundValue.Text);
|
||||
Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
|
||||
|
||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||
target.Clear();
|
||||
target.SendKeys("\t");
|
||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
||||
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -19,10 +20,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
MountTestComponent<BasicTestApp.CascadingValueTest.CascadingValueSupplier>();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void CanUpdateValuesMatchedByType()
|
||||
{
|
||||
|
|
@ -30,13 +35,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var incrementButton = Browser.FindElement(By.Id("increment-count"));
|
||||
|
||||
// We have the correct initial value
|
||||
WaitAssert.Equal("100", () => currentCount.Text);
|
||||
Browser.Equal("100", () => currentCount.Text);
|
||||
|
||||
// Updates are propagated
|
||||
incrementButton.Click();
|
||||
WaitAssert.Equal("101", () => currentCount.Text);
|
||||
Browser.Equal("101", () => currentCount.Text);
|
||||
incrementButton.Click();
|
||||
WaitAssert.Equal("102", () => currentCount.Text);
|
||||
Browser.Equal("102", () => currentCount.Text);
|
||||
|
||||
// Didn't re-render unrelated descendants
|
||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
||||
|
|
@ -48,16 +53,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var currentFlag1Value = Browser.FindElement(By.Id("flag-1"));
|
||||
var currentFlag2Value = Browser.FindElement(By.Id("flag-2"));
|
||||
|
||||
WaitAssert.Equal("False", () => currentFlag1Value.Text);
|
||||
WaitAssert.Equal("False", () => currentFlag2Value.Text);
|
||||
Browser.Equal("False", () => currentFlag1Value.Text);
|
||||
Browser.Equal("False", () => currentFlag2Value.Text);
|
||||
|
||||
// Observe that the correct cascading parameter updates
|
||||
Browser.FindElement(By.Id("toggle-flag-1")).Click();
|
||||
WaitAssert.Equal("True", () => currentFlag1Value.Text);
|
||||
WaitAssert.Equal("False", () => currentFlag2Value.Text);
|
||||
Browser.Equal("True", () => currentFlag1Value.Text);
|
||||
Browser.Equal("False", () => currentFlag2Value.Text);
|
||||
Browser.FindElement(By.Id("toggle-flag-2")).Click();
|
||||
WaitAssert.Equal("True", () => currentFlag1Value.Text);
|
||||
WaitAssert.Equal("True", () => currentFlag2Value.Text);
|
||||
Browser.Equal("True", () => currentFlag1Value.Text);
|
||||
Browser.Equal("True", () => currentFlag2Value.Text);
|
||||
|
||||
// Didn't re-render unrelated descendants
|
||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
||||
|
|
@ -70,13 +75,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var decrementButton = Browser.FindElement(By.Id("decrement-count"));
|
||||
|
||||
// We have the correct initial value
|
||||
WaitAssert.Equal("100", () => currentCount.Text);
|
||||
Browser.Equal("100", () => currentCount.Text);
|
||||
|
||||
// Updates are propagated
|
||||
decrementButton.Click();
|
||||
WaitAssert.Equal("99", () => currentCount.Text);
|
||||
Browser.Equal("99", () => currentCount.Text);
|
||||
decrementButton.Click();
|
||||
WaitAssert.Equal("98", () => currentCount.Text);
|
||||
Browser.Equal("98", () => currentCount.Text);
|
||||
|
||||
// Didn't re-render descendants
|
||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
||||
|
|
|
|||
|
|
@ -27,7 +27,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -74,7 +78,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Clicking button increments count
|
||||
appElement.FindElement(By.TagName("button")).Click();
|
||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -87,11 +91,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Clicking 'tick' changes the state, and starts a task
|
||||
appElement.FindElement(By.Id("tick")).Click();
|
||||
WaitAssert.Equal("Started", () => stateElement.Text);
|
||||
Browser.Equal("Started", () => stateElement.Text);
|
||||
|
||||
// Clicking 'tock' completes the task, which updates the state
|
||||
appElement.FindElement(By.Id("tock")).Click();
|
||||
WaitAssert.Equal("Stopped", () => stateElement.Text);
|
||||
Browser.Equal("Stopped", () => stateElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -106,12 +110,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Typing adds element
|
||||
inputElement.SendKeys("a");
|
||||
WaitAssert.Collection(liElements,
|
||||
Browser.Collection(liElements,
|
||||
li => Assert.Equal("a", li.Text));
|
||||
|
||||
// Typing again adds another element
|
||||
inputElement.SendKeys("b");
|
||||
WaitAssert.Collection(liElements,
|
||||
Browser.Collection(liElements,
|
||||
li => Assert.Equal("a", li.Text),
|
||||
li => Assert.Equal("b", li.Text));
|
||||
|
||||
|
|
@ -130,19 +134,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Initial count is zero; clicking button increments count
|
||||
Assert.Equal("Current count: 0", countDisplayElement.Text);
|
||||
incrementButton.Click();
|
||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
|
||||
// We can remove an event handler
|
||||
toggleClickHandlerCheckbox.Click();
|
||||
WaitAssert.Empty(() => appElement.FindElements(By.Id("listening-message")));
|
||||
Browser.Empty(() => appElement.FindElements(By.Id("listening-message")));
|
||||
incrementButton.Click();
|
||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||
|
||||
// We can add an event handler
|
||||
toggleClickHandlerCheckbox.Click();
|
||||
appElement.FindElement(By.Id("listening-message"));
|
||||
incrementButton.Click();
|
||||
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
|
||||
Browser.Equal("Current count: 2", () => countDisplayElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -216,7 +220,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Clicking increments count in child component
|
||||
appElement.FindElement(By.TagName("button")).Click();
|
||||
WaitAssert.Equal("Current count: 1", () => counterDisplay.Text);
|
||||
Browser.Equal("Current count: 1", () => counterDisplay.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -231,7 +235,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Clicking increments count in child element
|
||||
appElement.FindElement(By.TagName("button")).Click();
|
||||
WaitAssert.Equal("1", () => messageElementInChild.Text);
|
||||
Browser.Equal("1", () => messageElementInChild.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -246,20 +250,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Click to add/remove some child components
|
||||
addButton.Click();
|
||||
WaitAssert.Collection(childComponentWrappers,
|
||||
Browser.Collection(childComponentWrappers,
|
||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
|
||||
|
||||
addButton.Click();
|
||||
WaitAssert.Collection(childComponentWrappers,
|
||||
Browser.Collection(childComponentWrappers,
|
||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
|
||||
elem => Assert.Equal("Child 2", elem.FindElement(By.ClassName("message")).Text));
|
||||
|
||||
removeButton.Click();
|
||||
WaitAssert.Collection(childComponentWrappers,
|
||||
Browser.Collection(childComponentWrappers,
|
||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
|
||||
|
||||
addButton.Click();
|
||||
WaitAssert.Collection(childComponentWrappers,
|
||||
Browser.Collection(childComponentWrappers,
|
||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
|
||||
elem => Assert.Equal("Child 3", elem.FindElement(By.ClassName("message")).Text));
|
||||
}
|
||||
|
|
@ -277,7 +281,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// When property changes, child is renotified before rerender
|
||||
incrementButton.Click();
|
||||
WaitAssert.Equal("You supplied: 101", () => suppliedValueElement.Text);
|
||||
Browser.Equal("You supplied: 101", () => suppliedValueElement.Text);
|
||||
Assert.Equal("I computed: 202", computedValueElement.Text);
|
||||
}
|
||||
|
||||
|
|
@ -296,11 +300,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// When we click the button, the region is shown
|
||||
originalButton.Click();
|
||||
WaitAssert.Single(fragmentElements);
|
||||
Browser.Single(fragmentElements);
|
||||
|
||||
// The button itself was preserved, so we can click it again and see the effect
|
||||
originalButton.Click();
|
||||
WaitAssert.Empty(fragmentElements);
|
||||
Browser.Empty(fragmentElements);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -329,7 +333,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
modal.SendKeys("Some value from test");
|
||||
modal.Accept();
|
||||
var promptResult = appElement.FindElement(By.TagName("strong"));
|
||||
WaitAssert.Equal("Some value from test", () => promptResult.Text);
|
||||
Browser.Equal("Some value from test", () => promptResult.Text);
|
||||
|
||||
// NuGet packages can also embed entire components (themselves
|
||||
// authored as Razor files), including static content. The CSS value
|
||||
|
|
@ -342,7 +346,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var externalComponentButton = specialStyleDiv.FindElement(By.TagName("button"));
|
||||
Assert.Equal("Click me", externalComponentButton.Text);
|
||||
externalComponentButton.Click();
|
||||
WaitAssert.Equal("It works", () => externalComponentButton.Text);
|
||||
Browser.Equal("It works", () => externalComponentButton.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -358,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Assert.Equal("10", svgCircleElement.GetAttribute("r"));
|
||||
|
||||
appElement.FindElement(By.TagName("button")).Click();
|
||||
WaitAssert.Equal("20", () => svgCircleElement.GetAttribute("r"));
|
||||
Browser.Equal("20", () => svgCircleElement.GetAttribute("r"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -377,7 +381,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
public void LogicalElementInsertionWorksHierarchically()
|
||||
{
|
||||
var appElement = MountTestComponent<LogicalElementInsertionCases>();
|
||||
WaitAssert.Equal("First Second Third", () => appElement.Text);
|
||||
Browser.Equal("First Second Third", () => appElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -390,9 +394,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
||||
|
||||
buttonElement.Click();
|
||||
WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
||||
Browser.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
||||
buttonElement.Click();
|
||||
WaitAssert.Equal("Clicks: 2", () => inputElement.GetAttribute("value"));
|
||||
Browser.Equal("Clicks: 2", () => inputElement.GetAttribute("value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -408,7 +412,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Remove the captured element
|
||||
checkbox.Click();
|
||||
WaitAssert.Empty(() => appElement.FindElements(By.Id("capturedElement")));
|
||||
Browser.Empty(() => appElement.FindElements(By.Id("capturedElement")));
|
||||
|
||||
// Re-add it; observe it starts empty again
|
||||
checkbox.Click();
|
||||
|
|
@ -417,7 +421,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// See that the capture variable was automatically updated to reference the new instance
|
||||
buttonElement.Click();
|
||||
WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
||||
Browser.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -432,23 +436,23 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Verify the reference was captured initially
|
||||
appElement.FindElement(incrementButtonSelector).Click();
|
||||
WaitAssert.Equal("Current count: 1", currentCountText);
|
||||
Browser.Equal("Current count: 1", currentCountText);
|
||||
resetButton.Click();
|
||||
WaitAssert.Equal("Current count: 0", currentCountText);
|
||||
Browser.Equal("Current count: 0", currentCountText);
|
||||
appElement.FindElement(incrementButtonSelector).Click();
|
||||
WaitAssert.Equal("Current count: 1", currentCountText);
|
||||
Browser.Equal("Current count: 1", currentCountText);
|
||||
|
||||
// Remove and re-add a new instance of the child, checking the text was reset
|
||||
toggleChildCheckbox.Click();
|
||||
WaitAssert.Empty(() => appElement.FindElements(incrementButtonSelector));
|
||||
Browser.Empty(() => appElement.FindElements(incrementButtonSelector));
|
||||
toggleChildCheckbox.Click();
|
||||
WaitAssert.Equal("Current count: 0", currentCountText);
|
||||
Browser.Equal("Current count: 0", currentCountText);
|
||||
|
||||
// Verify we have a new working reference
|
||||
appElement.FindElement(incrementButtonSelector).Click();
|
||||
WaitAssert.Equal("Current count: 1", currentCountText);
|
||||
Browser.Equal("Current count: 1", currentCountText);
|
||||
resetButton.Click();
|
||||
WaitAssert.Equal("Current count: 0", currentCountText);
|
||||
Browser.Equal("Current count: 0", currentCountText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -487,7 +491,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Updating markup blocks
|
||||
appElement.FindElement(By.TagName("button")).Click();
|
||||
WaitAssert.Equal(
|
||||
Browser.Equal(
|
||||
"[The output was changed completely.]",
|
||||
() => appElement.FindElement(By.Id("dynamic-markup-block")).Text);
|
||||
Assert.Equal(
|
||||
|
|
@ -529,7 +533,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var toggle = appElement.FindElement(By.Id("toggle"));
|
||||
toggle.Click();
|
||||
|
||||
WaitAssert.Collection(
|
||||
Browser.Collection(
|
||||
() => tfoot.FindElements(By.TagName("td")),
|
||||
e => Assert.Equal("The", e.Text),
|
||||
e => Assert.Equal("", e.Text),
|
||||
|
|
@ -550,7 +554,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
await Task.Delay(1000);
|
||||
|
||||
var outputElement = appElement.FindElement(By.Id("concurrent-render-output"));
|
||||
WaitAssert.Equal(expectedOutput, () => outputElement.Text);
|
||||
Browser.Equal(expectedOutput, () => outputElement.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -561,7 +565,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
appElement.FindElement(By.Id("run-with-dispatch")).Click();
|
||||
|
||||
WaitAssert.Equal("Success (completed synchronously)", () => result.Text);
|
||||
Browser.Equal("Success (completed synchronously)", () => result.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -572,7 +576,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
appElement.FindElement(By.Id("run-with-double-dispatch")).Click();
|
||||
|
||||
WaitAssert.Equal("Success (completed synchronously)", () => result.Text);
|
||||
Browser.Equal("Success (completed synchronously)", () => result.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -583,7 +587,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
appElement.FindElement(By.Id("run-async-with-dispatch")).Click();
|
||||
|
||||
WaitAssert.Equal("First Second Third Fourth Fifth", () => result.Text);
|
||||
Browser.Equal("First Second Third Fourth Fifth", () => result.Text);
|
||||
}
|
||||
|
||||
static IAlert SwitchToAlert(IWebDriver driver)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
|||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -26,17 +27,21 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
MountTestComponent<EventBubblingComponent>();
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void BubblingStandardEvent_FiredOnElementWithHandler()
|
||||
{
|
||||
Browser.FindElement(By.Id("button-with-onclick")).Click();
|
||||
|
||||
// Triggers event on target and ancestors with handler in upwards direction
|
||||
WaitAssert.Equal(
|
||||
Browser.Equal(
|
||||
new[] { "target onclick", "parent onclick" },
|
||||
GetLogLines);
|
||||
}
|
||||
|
|
@ -47,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.FindElement(By.Id("button-without-onclick")).Click();
|
||||
|
||||
// Triggers event on ancestors with handler in upwards direction
|
||||
WaitAssert.Equal(
|
||||
Browser.Equal(
|
||||
new[] { "parent onclick" },
|
||||
GetLogLines);
|
||||
}
|
||||
|
|
@ -58,7 +63,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze");
|
||||
|
||||
// Triggers event on target and ancestors with handler in upwards direction
|
||||
WaitAssert.Equal(
|
||||
Browser.Equal(
|
||||
new[] { "target onsneeze", "parent onsneeze" },
|
||||
GetLogLines);
|
||||
}
|
||||
|
|
@ -69,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze");
|
||||
|
||||
// Triggers event on ancestors with handler in upwards direction
|
||||
WaitAssert.Equal(
|
||||
Browser.Equal(
|
||||
new[] { "parent onsneeze" },
|
||||
GetLogLines);
|
||||
}
|
||||
|
|
@ -80,7 +85,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.FindElement(By.Id("input-with-onfocus")).Click();
|
||||
|
||||
// Triggers event only on target, not other ancestors with event handler
|
||||
WaitAssert.Equal(
|
||||
Browser.Equal(
|
||||
new[] { "target onfocus" },
|
||||
GetLogLines);
|
||||
}
|
||||
|
|
@ -91,7 +96,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
Browser.FindElement(By.Id("input-without-onfocus")).Click();
|
||||
|
||||
// Triggers no event
|
||||
WaitAssert.Empty(GetLogLines);
|
||||
Browser.Empty(GetLogLines);
|
||||
}
|
||||
|
||||
private string[] GetLogLines()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -18,9 +19,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
MountTestComponent<BasicTestApp.EventCallbackTest.EventCallbackCases>();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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 BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -19,6 +20,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
MountTestComponent<EventBubblingComponent>();
|
||||
|
|
@ -37,13 +42,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Focus the target, verify onfocusin is fired
|
||||
input.Click();
|
||||
|
||||
WaitAssert.Equal("onfocus,onfocusin,", () => output.Text);
|
||||
Browser.Equal("onfocus,onfocusin,", () => output.Text);
|
||||
|
||||
// Focus something else, verify onfocusout is also fired
|
||||
var other = Browser.FindElement(By.Id("other"));
|
||||
other.Click();
|
||||
|
||||
WaitAssert.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text);
|
||||
Browser.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -64,7 +69,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
.MoveToElement(other);
|
||||
|
||||
actions.Perform();
|
||||
WaitAssert.Equal("onmouseover,onmouseout,", () => output.Text);
|
||||
Browser.Equal("onmouseover,onmouseout,", () => output.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -83,7 +88,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
.MoveToElement(input, 10, 10);
|
||||
|
||||
actions.Perform();
|
||||
WaitAssert.Contains("onmousemove,", () => output.Text);
|
||||
Browser.Contains("onmousemove,", () => output.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -102,12 +107,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var actions = new Actions(Browser).ClickAndHold(input);
|
||||
|
||||
actions.Perform();
|
||||
WaitAssert.Equal("onmousedown,", () => output.Text);
|
||||
Browser.Equal("onmousedown,", () => output.Text);
|
||||
|
||||
actions = new Actions(Browser).Release(input);
|
||||
|
||||
actions.Perform();
|
||||
WaitAssert.Equal("onmousedown,onmouseup,", () => output.Text);
|
||||
Browser.Equal("onmousedown,onmouseup,", () => output.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -116,7 +121,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var appElement = MountTestComponent<EventPreventDefaultComponent>();
|
||||
|
||||
appElement.FindElement(By.Id("form-1-button")).Click();
|
||||
WaitAssert.Equal("Event was handled", () => appElement.FindElement(By.Id("event-handled")).Text);
|
||||
Browser.Equal("Event was handled", () => appElement.FindElement(By.Id("event-handled")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -135,13 +140,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var input = Browser.FindElement(By.TagName("input"));
|
||||
var output = Browser.FindElement(By.Id("test-result"));
|
||||
|
||||
WaitAssert.Equal(string.Empty, () => output.Text);
|
||||
Browser.Equal(string.Empty, () => output.Text);
|
||||
|
||||
SendKeysSequentially(input, "abcdefghijklmnopqrstuvwxyz");
|
||||
WaitAssert.Equal("abcdefghijklmnopqrstuvwxyz", () => output.Text);
|
||||
Browser.Equal("abcdefghijklmnopqrstuvwxyz", () => output.Text);
|
||||
|
||||
input.SendKeys(Keys.Backspace);
|
||||
WaitAssert.Equal("abcdefghijklmnopqrstuvwxy", () => output.Text);
|
||||
Browser.Equal("abcdefghijklmnopqrstuvwxy", () => output.Text);
|
||||
}
|
||||
|
||||
void SendKeysSequentially(IWebElement target, string text)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
// On WebAssembly, page reloads are expensive so skip if possible
|
||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
||||
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -42,26 +46,26 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
acceptsTermsInput.Click(); // Accept terms
|
||||
acceptsTermsInput.Click(); // Un-accept terms
|
||||
await Task.Delay(500); // There's no expected change to the UI, so just wait a moment before asserting
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Empty(messagesAccessor);
|
||||
Assert.Empty(appElement.FindElements(By.Id("last-callback")));
|
||||
|
||||
// Submitting the form does validate
|
||||
submitButton.Click();
|
||||
WaitAssert.Equal(new[] { "You must accept the terms" }, messagesAccessor);
|
||||
WaitAssert.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||
Browser.Equal(new[] { "You must accept the terms" }, messagesAccessor);
|
||||
Browser.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||
|
||||
// Can make another field invalid
|
||||
userNameInput.Clear();
|
||||
submitButton.Click();
|
||||
WaitAssert.Equal(new[] { "Please choose a username", "You must accept the terms" }, messagesAccessor);
|
||||
WaitAssert.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||
Browser.Equal(new[] { "Please choose a username", "You must accept the terms" }, messagesAccessor);
|
||||
Browser.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||
|
||||
// Can make valid
|
||||
userNameInput.SendKeys("Bert\t");
|
||||
acceptsTermsInput.Click();
|
||||
submitButton.Click();
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
WaitAssert.Equal("OnValidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||
Browser.Empty(messagesAccessor);
|
||||
Browser.Equal("OnValidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -70,22 +74,22 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var appElement = MountTestComponent<TypicalValidationComponent>();
|
||||
var nameInput = appElement.FindElement(By.ClassName("name")).FindElement(By.TagName("input"));
|
||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => nameInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => nameInput.GetAttribute("class"));
|
||||
nameInput.SendKeys("Bert\t");
|
||||
WaitAssert.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
nameInput.SendKeys("01234567890123456789\t");
|
||||
WaitAssert.Equal("modified invalid", () => nameInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "That name is too long" }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => nameInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "That name is too long" }, messagesAccessor);
|
||||
|
||||
// Can become valid
|
||||
nameInput.Clear();
|
||||
nameInput.SendKeys("Bert\t");
|
||||
WaitAssert.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
||||
Browser.Empty(messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -96,25 +100,25 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => ageInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => ageInput.GetAttribute("class"));
|
||||
ageInput.SendKeys("123\t");
|
||||
WaitAssert.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
ageInput.SendKeys("e100\t");
|
||||
WaitAssert.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
||||
|
||||
// Empty is invalid, because it's not a nullable int
|
||||
ageInput.Clear();
|
||||
ageInput.SendKeys("\t");
|
||||
WaitAssert.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
||||
|
||||
// Zero is within the allowed range
|
||||
ageInput.SendKeys("0\t");
|
||||
WaitAssert.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
||||
Browser.Empty(messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -125,20 +129,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => heightInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => heightInput.GetAttribute("class"));
|
||||
heightInput.SendKeys("123.456\t");
|
||||
WaitAssert.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
heightInput.SendKeys("e100\t");
|
||||
WaitAssert.Equal("modified invalid", () => heightInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The OptionalHeight field must be a number." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => heightInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The OptionalHeight field must be a number." }, messagesAccessor);
|
||||
|
||||
// Empty is valid, because it's a nullable float
|
||||
heightInput.Clear();
|
||||
heightInput.SendKeys("\t");
|
||||
WaitAssert.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
||||
Browser.Empty(messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -149,20 +153,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => descriptionInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => descriptionInput.GetAttribute("class"));
|
||||
descriptionInput.SendKeys("Hello\t");
|
||||
WaitAssert.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
descriptionInput.SendKeys("too long too long too long too long too long\t");
|
||||
WaitAssert.Equal("modified invalid", () => descriptionInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "Description is max 20 chars" }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => descriptionInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "Description is max 20 chars" }, messagesAccessor);
|
||||
|
||||
// Can become valid
|
||||
descriptionInput.Clear();
|
||||
descriptionInput.SendKeys("Hello\t");
|
||||
WaitAssert.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
||||
Browser.Empty(messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -173,24 +177,24 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => renewalDateInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => renewalDateInput.GetAttribute("class"));
|
||||
renewalDateInput.SendKeys("01/01/2000\t");
|
||||
WaitAssert.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
renewalDateInput.SendKeys("0/0/0");
|
||||
WaitAssert.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
||||
|
||||
// Empty is invalid, because it's not nullable
|
||||
renewalDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t");
|
||||
WaitAssert.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
||||
|
||||
// Can become valid
|
||||
renewalDateInput.SendKeys("01/01/01\t");
|
||||
WaitAssert.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
||||
Browser.Empty(messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -201,19 +205,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => expiryDateInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => expiryDateInput.GetAttribute("class"));
|
||||
expiryDateInput.SendKeys("01/01/2000\t");
|
||||
WaitAssert.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
expiryDateInput.SendKeys("111111111");
|
||||
WaitAssert.Equal("modified invalid", () => expiryDateInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The OptionalExpiryDate field must be a date." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => expiryDateInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The OptionalExpiryDate field must be a date." }, messagesAccessor);
|
||||
|
||||
// Empty is valid, because it's nullable
|
||||
expiryDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t");
|
||||
WaitAssert.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
||||
WaitAssert.Empty(messagesAccessor);
|
||||
Browser.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
||||
Browser.Empty(messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -225,14 +229,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => select.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => select.GetAttribute("class"));
|
||||
ticketClassInput.SelectByText("First class");
|
||||
WaitAssert.Equal("modified valid", () => select.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => select.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
ticketClassInput.SelectByText("(select)");
|
||||
WaitAssert.Equal("modified invalid", () => select.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => select.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -243,14 +247,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Validates on edit
|
||||
WaitAssert.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
acceptsTermsInput.Click();
|
||||
WaitAssert.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
|
||||
// Can become invalid
|
||||
acceptsTermsInput.Click();
|
||||
WaitAssert.Equal("modified invalid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "Must accept terms" }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "Must accept terms" }, messagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -264,30 +268,30 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var submissionStatus = appElement.FindElement(By.Id("submission-status"));
|
||||
|
||||
// Editing a field triggers validation immediately
|
||||
WaitAssert.Equal("valid", () => userNameInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => userNameInput.GetAttribute("class"));
|
||||
userNameInput.SendKeys("Too long too long\t");
|
||||
WaitAssert.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(new[] { "That name is too long" }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "That name is too long" }, messagesAccessor);
|
||||
|
||||
// Submitting the form validates remaining fields
|
||||
submitButton.Click();
|
||||
WaitAssert.Equal(new[] { "That name is too long", "You must accept the terms" }, messagesAccessor);
|
||||
WaitAssert.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
||||
WaitAssert.Equal("invalid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
Browser.Equal(new[] { "That name is too long", "You must accept the terms" }, messagesAccessor);
|
||||
Browser.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
||||
Browser.Equal("invalid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
|
||||
// Can make fields valid
|
||||
userNameInput.Clear();
|
||||
userNameInput.SendKeys("Bert\t");
|
||||
WaitAssert.Equal("modified valid", () => userNameInput.GetAttribute("class"));
|
||||
Browser.Equal("modified valid", () => userNameInput.GetAttribute("class"));
|
||||
acceptsTermsInput.Click();
|
||||
WaitAssert.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
WaitAssert.Equal(string.Empty, () => submissionStatus.Text);
|
||||
Browser.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
Browser.Equal(string.Empty, () => submissionStatus.Text);
|
||||
submitButton.Click();
|
||||
WaitAssert.True(() => submissionStatus.Text.StartsWith("Submitted"));
|
||||
Browser.True(() => submissionStatus.Text.StartsWith("Submitted"));
|
||||
|
||||
// Fields can revert to unmodified
|
||||
WaitAssert.Equal("valid", () => userNameInput.GetAttribute("class"));
|
||||
WaitAssert.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => userNameInput.GetAttribute("class"));
|
||||
Browser.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -301,20 +305,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
// Doesn't show messages for other fields
|
||||
submitButton.Click();
|
||||
WaitAssert.Empty(emailMessagesAccessor);
|
||||
Browser.Empty(emailMessagesAccessor);
|
||||
|
||||
// Updates on edit
|
||||
emailInput.SendKeys("abc\t");
|
||||
WaitAssert.Equal(new[] { "That doesn't look like a real email address" }, emailMessagesAccessor);
|
||||
Browser.Equal(new[] { "That doesn't look like a real email address" }, emailMessagesAccessor);
|
||||
|
||||
// Can show more than one message
|
||||
emailInput.SendKeys("too long too long too long\t");
|
||||
WaitAssert.Equal(new[] { "That doesn't look like a real email address", "We only accept very short email addresses (max 10 chars)" }, emailMessagesAccessor);
|
||||
Browser.Equal(new[] { "That doesn't look like a real email address", "We only accept very short email addresses (max 10 chars)" }, emailMessagesAccessor);
|
||||
|
||||
// Can become valid
|
||||
emailInput.Clear();
|
||||
emailInput.SendKeys("a@b.com\t");
|
||||
WaitAssert.Empty(emailMessagesAccessor);
|
||||
Browser.Empty(emailMessagesAccessor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -326,16 +330,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||
|
||||
// Shows initial state
|
||||
WaitAssert.Equal("Economy", () => selectedTicketClassDisplay.Text);
|
||||
Browser.Equal("Economy", () => selectedTicketClassDisplay.Text);
|
||||
|
||||
// Refreshes on edit
|
||||
ticketClassInput.SelectByValue("Premium");
|
||||
WaitAssert.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
||||
Browser.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
||||
|
||||
// Leaves previous value unchanged if new entry is unparseable
|
||||
ticketClassInput.SelectByText("(select)");
|
||||
WaitAssert.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
||||
WaitAssert.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
||||
Browser.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
||||
Browser.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
||||
}
|
||||
|
||||
private Func<string[]> CreateValidationMessagesAccessor(IWebElement appElement)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
|||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -15,13 +16,17 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
public class HostedInAspNetTest : ServerTestBase<AspNetSiteServerFixture>
|
||||
{
|
||||
public HostedInAspNetTest(
|
||||
BrowserFixture browserFixture,
|
||||
AspNetSiteServerFixture serverFixture,
|
||||
BrowserFixture browserFixture,
|
||||
AspNetSiteServerFixture serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
serverFixture.BuildWebHostMethod = HostedInAspNet.Server.Program.BuildWebHost;
|
||||
serverFixture.Environment = AspNetEnvironment.Development;
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate("/", noReload: true);
|
||||
WaitUntilLoaded();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
public class HttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
|
||||
{
|
||||
readonly ServerFixture _apiServerFixture;
|
||||
readonly IWebElement _appElement;
|
||||
IWebElement _appElement;
|
||||
IWebElement _responseStatus;
|
||||
IWebElement _responseBody;
|
||||
IWebElement _responseHeaders;
|
||||
|
|
@ -32,7 +32,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
{
|
||||
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||
_apiServerFixture = apiServerFixture;
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
_appElement = MountTestComponent<HttpRequestsComponent>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||
|
|
@ -18,6 +19,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
MountTestComponent<InteropComponent>();
|
||||
|
|
@ -106,7 +111,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
expectedValues.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var actualValues = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
|||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -21,6 +22,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
serverFixture.BuildWebHostMethod = MonoSanity.Program.BuildWebHost;
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate("/", noReload: true);
|
||||
WaitUntilMonoRunningInBrowser();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
|||
using OpenQA.Selenium;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -20,6 +21,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
DevHostServerFixture<Blazor.E2EPerformance.Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate("/", noReload: true);
|
||||
}
|
||||
|
|
@ -43,8 +48,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
runAllButton.Click();
|
||||
|
||||
// The "run" button goes away while the benchmarks execute, then it comes back
|
||||
WaitAssert.False(() => runAllButton.Displayed);
|
||||
WaitAssert.True(
|
||||
Browser.False(() => runAllButton.Displayed);
|
||||
Browser.True(
|
||||
() => runAllButton.Displayed || Browser.FindElements(By.CssSelector(".benchmark-error")).Any(),
|
||||
TimeSpan.FromSeconds(60));
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using BasicTestApp;
|
||||
using BasicTestApp.RouterTest;
|
||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||
|
|
@ -23,6 +24,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: false);
|
||||
WaitUntilTestSelectorReady();
|
||||
|
|
@ -92,7 +97,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
[Fact]
|
||||
public void CanArriveAtFallbackPageFromBadURI()
|
||||
{
|
||||
SetUrlViaPushState("/Oopsie_Daisies%20%This_Aint_A_Real_Page");
|
||||
SetUrlViaPushState("/Oopsie_Daisies%20%This_Aint_A_Real_Page");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
Assert.Equal("Oops, that component wasn't found!", app.FindElement(By.Id("test-info")).Text);
|
||||
|
|
@ -105,7 +110,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other")).Click();
|
||||
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
||||
}
|
||||
|
||||
|
|
@ -121,14 +126,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
var button = app.FindElement(By.LinkText("Other"));
|
||||
|
||||
|
||||
new Actions(Browser).KeyDown(key).Click(button).Build().Perform();
|
||||
|
||||
WaitAssert.Equal(2, () => Browser.WindowHandles.Count);
|
||||
Browser.Equal(2, () => Browser.WindowHandles.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Leaving the ctrl key up
|
||||
// Leaving the ctrl key up
|
||||
new Actions(Browser).KeyUp(key).Build().Perform();
|
||||
|
||||
// Closing newly opened windows if a new one was opened
|
||||
|
|
@ -151,11 +156,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
{
|
||||
SetUrlViaPushState("/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
|
||||
app.FindElement(By.LinkText("Target (_blank)")).Click();
|
||||
|
||||
WaitAssert.Equal(2, () => Browser.WindowHandles.Count);
|
||||
Browser.Equal(2, () => Browser.WindowHandles.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -178,20 +183,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
SetUrlViaPushState("/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
|
||||
|
||||
app.FindElement(By.LinkText("Other")).Click();
|
||||
|
||||
|
||||
Assert.Single(Browser.WindowHandles);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
|
||||
{
|
||||
SetUrlViaPushState("/");
|
||||
SetUrlViaPushState("/");
|
||||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click();
|
||||
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +207,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click();
|
||||
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||
}
|
||||
|
||||
|
|
@ -213,12 +218,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("With parameters")).Click();
|
||||
WaitAssert.Equal("Your full name is Abc .", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("Your full name is Abc .", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("With parameters");
|
||||
|
||||
// Can add more parameters while remaining on same page
|
||||
app.FindElement(By.LinkText("With more parameters")).Click();
|
||||
WaitAssert.Equal("Your full name is Abc McDef.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("Your full name is Abc McDef.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("With parameters", "With more parameters");
|
||||
|
||||
// Can remove parameters while remaining on same page
|
||||
|
|
@ -227,7 +232,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
// Without that, the page would retain the old value.
|
||||
// See https://github.com/aspnet/AspNetCore/issues/6864 where we reverted the logic to auto-reset.
|
||||
app.FindElement(By.LinkText("With parameters")).Click();
|
||||
WaitAssert.Equal("Your full name is Abc .", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("Your full name is Abc .", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("With parameters");
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +243,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default (matches all)")).Click();
|
||||
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Default (matches all)", "Default with base-relative URL (matches all)");
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +254,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other with query")).Click();
|
||||
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Other", "Other with query");
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +265,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default with query")).Click();
|
||||
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Default with query");
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +276,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Other with hash")).Click();
|
||||
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Other", "Other with hash");
|
||||
}
|
||||
|
||||
|
|
@ -282,7 +287,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
var app = MountTestComponent<TestRouter>();
|
||||
app.FindElement(By.LinkText("Default with hash")).Click();
|
||||
WaitAssert.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.Equal("This is the default page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Default with hash");
|
||||
}
|
||||
|
||||
|
|
@ -295,8 +300,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var testSelector = WaitUntilTestSelectorReady();
|
||||
|
||||
app.FindElement(By.Id("do-navigation")).Click();
|
||||
WaitAssert.True(() => Browser.Url.EndsWith("/Other"));
|
||||
WaitAssert.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
Browser.True(() => Browser.Url.EndsWith("/Other"));
|
||||
Browser.Equal("This is another page.", () => app.FindElement(By.Id("test-info")).Text);
|
||||
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
||||
|
||||
// Because this was client-side navigation, we didn't lose the state in the test selector
|
||||
|
|
@ -312,7 +317,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
var testSelector = WaitUntilTestSelectorReady();
|
||||
|
||||
app.FindElement(By.Id("do-navigation-forced")).Click();
|
||||
WaitAssert.True(() => Browser.Url.EndsWith("/Other"));
|
||||
Browser.True(() => Browser.Url.EndsWith("/Other"));
|
||||
|
||||
// Because this was a full-page load, our element references should no longer be valid
|
||||
Assert.Throws<StaleElementReferenceException>(() =>
|
||||
|
|
@ -344,7 +349,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
|
||||
private void AssertHighlightedLinks(params string[] linkTexts)
|
||||
{
|
||||
WaitAssert.Equal(linkTexts, () => Browser
|
||||
Browser.Equal(linkTexts, () => Browser
|
||||
.FindElements(By.CssSelector("a.active"))
|
||||
.Select(x => x.Text));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using OpenQA.Selenium;
|
|||
using OpenQA.Selenium.Support.UI;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -17,10 +18,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
|||
: ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>, IDisposable
|
||||
{
|
||||
public StandaloneAppTest(
|
||||
BrowserFixture browserFixture,
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<StandaloneApp.Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void InitializeAsyncCore()
|
||||
{
|
||||
Navigate("/", noReload: true);
|
||||
WaitUntilLoaded();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// 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.Threading.Tasks;
|
||||
using IdentityServer4.EntityFramework.Entities;
|
||||
using IdentityServer4.EntityFramework.Extensions;
|
||||
|
|
@ -49,9 +50,47 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
|||
/// <inheritdoc />
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
|
||||
base.OnModelCreating(builder);
|
||||
builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
|
||||
ConfigureGrantContext(builder, _operationalStoreOptions.Value);
|
||||
}
|
||||
|
||||
private void ConfigureGrantContext(ModelBuilder modelBuilder, OperationalStoreOptions storeOptions)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(storeOptions.DefaultSchema)) modelBuilder.HasDefaultSchema(storeOptions.DefaultSchema);
|
||||
|
||||
modelBuilder.Entity<PersistedGrant>(grant =>
|
||||
{
|
||||
grant.ToTable("PersistedGrants");
|
||||
|
||||
grant.Property(x => x.Key).HasMaxLength(200).ValueGeneratedNever();
|
||||
grant.Property(x => x.Type).HasMaxLength(50).IsRequired();
|
||||
grant.Property(x => x.SubjectId).HasMaxLength(200);
|
||||
grant.Property(x => x.ClientId).HasMaxLength(200).IsRequired();
|
||||
grant.Property(x => x.CreationTime).IsRequired();
|
||||
grant.Property(x => x.Data).HasMaxLength(50000).IsRequired();
|
||||
|
||||
grant.HasKey(x => x.Key);
|
||||
|
||||
grant.HasIndex(x => new { x.SubjectId, x.ClientId, x.Type });
|
||||
});
|
||||
|
||||
modelBuilder.Entity<DeviceFlowCodes>(codes =>
|
||||
{
|
||||
codes.ToTable("DeviceCodes");
|
||||
|
||||
codes.Property(x => x.DeviceCode).HasMaxLength(200).IsRequired();
|
||||
codes.Property(x => x.UserCode).HasMaxLength(200).IsRequired();
|
||||
codes.Property(x => x.SubjectId).HasMaxLength(200);
|
||||
codes.Property(x => x.ClientId).HasMaxLength(200).IsRequired();
|
||||
codes.Property(x => x.CreationTime).IsRequired();
|
||||
codes.Property(x => x.Expiration).IsRequired();
|
||||
codes.Property(x => x.Data).HasMaxLength(50000).IsRequired();
|
||||
|
||||
codes.HasKey(x => new { x.UserCode });
|
||||
|
||||
codes.HasIndex(x => x.DeviceCode).IsUnique();
|
||||
codes.HasIndex(x => x.UserCode).IsUnique();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.SpaServices
|
|||
/// <summary>
|
||||
/// Gets or sets the <see cref="StaticFileOptions"/> that supplies content
|
||||
/// for serving the SPA's default page.
|
||||
///
|
||||
///
|
||||
/// If not set, a default file provider will read files from the
|
||||
/// <see cref="IHostingEnvironment.WebRootPath"/>, which by default is
|
||||
/// the <c>wwwroot</c> directory.
|
||||
|
|
@ -73,6 +73,6 @@ namespace Microsoft.AspNetCore.SpaServices
|
|||
/// Gets or sets the maximum duration that a request will wait for the SPA
|
||||
/// to become ready to serve to the client.
|
||||
/// </summary>
|
||||
public TimeSpan StartupTimeout { get; set; } = TimeSpan.FromSeconds(50);
|
||||
public TimeSpan StartupTimeout { get; set; } = TimeSpan.FromSeconds(120);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,6 +26,9 @@
|
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||
<Content Remove="$(SpaRoot)**" />
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||
<Content Remove="$(SpaRoot)**" />
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
"requires": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.4.tgz",
|
||||
"integrity": "sha512-wJF8oz8MurtpFi0ik42bkI2F5gEnuOe79KHPO1i3SYfdhEp5NY8igVKZ6chB/eq4Ml50aHxas8Hh9ke12K+Pxw==",
|
||||
"version": "0.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.5.tgz",
|
||||
"integrity": "sha512-ouqDu5stZA2gsWnbKMThDfOG/D6lJQaLL+oGEoM5zfnKir3ctyV5rOm73m2pDYUblByTCb+rkj5KmooUWpnV1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"rxjs": "6.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -26,16 +26,16 @@
|
|||
}
|
||||
},
|
||||
"@angular-devkit/build-angular": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.4.tgz",
|
||||
"integrity": "sha512-7yJzgNk3ToiAHd8vnYonqiswvVNYzOUKg2xZfpx+SD5m7mVE+CSUp+P4YzUrI0Vm9WitZOYaCv1I6G1NguJHqA==",
|
||||
"version": "0.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.5.tgz",
|
||||
"integrity": "sha512-xJq46Jz7MMyprcJ4PflJjPtJ+2OVqbnz6HwtUyJLwYXmC0ldWnhGiNwn+0o7Em40Ol8gf2TYqcDGcSi5OyOZMg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "0.13.4",
|
||||
"@angular-devkit/build-optimizer": "0.13.4",
|
||||
"@angular-devkit/build-webpack": "0.13.4",
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@ngtools/webpack": "7.3.4",
|
||||
"@angular-devkit/architect": "0.13.5",
|
||||
"@angular-devkit/build-optimizer": "0.13.5",
|
||||
"@angular-devkit/build-webpack": "0.13.5",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"@ngtools/webpack": "7.3.5",
|
||||
"ajv": "6.9.1",
|
||||
"autoprefixer": "9.4.6",
|
||||
"circular-dependency-plugin": "5.0.2",
|
||||
|
|
@ -118,9 +118,9 @@
|
|||
}
|
||||
},
|
||||
"@angular-devkit/build-optimizer": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.4.tgz",
|
||||
"integrity": "sha512-YTpiE4F2GnFc4jbXZkmFUMHOvo3kWcMPAInVbjXNSIWMqW8Ibs7d6MAcualQX4NCvcn45+mVXLfY/8hWZ/b7lw==",
|
||||
"version": "0.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.5.tgz",
|
||||
"integrity": "sha512-bkyKYplkUnWCbXfDuS0gFuPDoi9OEUNRBtvYtY3rgE3XKSAJBjV+KLgoXSSpLL6ucLDx6gOyDXitUFLiRCDMqg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "1.2.3",
|
||||
|
|
@ -134,17 +134,23 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
||||
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz",
|
||||
"integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@angular-devkit/build-webpack": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.4.tgz",
|
||||
"integrity": "sha512-W5baPrsNUUyeD5K9ZjiTfiDsytBoqDvGDMKRUO9XWV8xF8LYF2ttsBQxlJK7SKkMyJXcjmiHhdkMq5wgRE7n0A==",
|
||||
"version": "0.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.5.tgz",
|
||||
"integrity": "sha512-/abR1cxCLiRJciaW0Dc0RYNbYQIhHFut1r1Dv8xx7He2/wYgCzGsYl9EeFm48Nrw62/9rIPJxhZoZtcf1Mrocg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "0.13.4",
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/architect": "0.13.5",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"rxjs": "6.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -160,9 +166,9 @@
|
|||
}
|
||||
},
|
||||
"@angular-devkit/core": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.4.tgz",
|
||||
"integrity": "sha512-MBfen51iOBKfK4tlg5KwmPxePsF1QoFNUMGLuvUUwPkteonrGcupX1Q7NWTpf+HA+i08mOnZGuepeuQkD12IQw==",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz",
|
||||
"integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "6.9.1",
|
||||
|
|
@ -202,12 +208,12 @@
|
|||
}
|
||||
},
|
||||
"@angular-devkit/schematics": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.4.tgz",
|
||||
"integrity": "sha512-BLI4MDHmpzw+snu/2Dw1nMmfJ0VAARTbU6DrmzXyl2Se45+iE/tdRy4yNx3IfHhyoCrVZ15R0y9CXeEsLftlIg==",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.5.tgz",
|
||||
"integrity": "sha512-BFCCkwRMBC4aFlngaloi1avCTgGrl1MFc/0Av2sCpBh/fdm1FqSVzmOiTfu93dehRVVL/bTrA2qj+xpNsXCxzA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"rxjs": "6.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
@ -223,24 +229,24 @@
|
|||
}
|
||||
},
|
||||
"@angular/animations": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.5.tgz",
|
||||
"integrity": "sha512-BJPm9pls6MuIhn6TF1f2ZwkGFTamuyJbhXz8n9u669tTI4deUAEEHCzYaEgVu4q007niVg2ZnO4MDcxXtc5nFQ==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.8.tgz",
|
||||
"integrity": "sha512-dJn9koYukyz15TouBc+z5z9fdThDk+bKgdlij25eYSu5Mpmtk04gB4eIMQA97K0UDh1d4YukgSJ5w3ZIk0m8DQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/cli": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.4.tgz",
|
||||
"integrity": "sha512-uGL8xiQf+GvuJvqvMUu/XHcijbq9ocbX487LO2PgJ29etHfI7dC0toJbQ8ob+HnF9e1qwMe+uu45OU4C2p+a1A==",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.5.tgz",
|
||||
"integrity": "sha512-WL339qoWMIsQympJAabtcvX6hMydGD/H0fm8K9ihD7xd6Af1QCSDN/aWTIvYyEzj9Hb8/sJ3mgRtvLlr1xTHzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "0.13.4",
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/schematics": "7.3.4",
|
||||
"@schematics/angular": "7.3.4",
|
||||
"@schematics/update": "0.13.4",
|
||||
"@angular-devkit/architect": "0.13.5",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"@angular-devkit/schematics": "7.3.5",
|
||||
"@schematics/angular": "7.3.5",
|
||||
"@schematics/update": "0.13.5",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"ini": "1.3.5",
|
||||
"inquirer": "6.2.1",
|
||||
|
|
@ -252,29 +258,29 @@
|
|||
}
|
||||
},
|
||||
"@angular/common": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.5.tgz",
|
||||
"integrity": "sha512-IW3vk0DDblbZMD8gkKVpPa/krXky4i5baFhKgqN2xYo48epXYvAezm5q71a982eadjUussbaYPlsXzYNAhdVKQ==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.8.tgz",
|
||||
"integrity": "sha512-LgOhf68+LPndGZhtnUlGFd2goReXYmHzaFZW8gCEi9aC+H+Io8bjYh0gkH3xDreevEOe3f0z6coXNFLIxSmTuA==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/compiler": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.5.tgz",
|
||||
"integrity": "sha512-/41ehOSupAA+uc32XHmN5jOvqmb4A4D+V+MXDmnlYVaYAYZrGf3AS+1RJuBy5cIUGQ1Nv+Nbj4Y7X/ydb6ncOQ==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.8.tgz",
|
||||
"integrity": "sha512-PrU97cTsOdofpaDkxK0rWUA/CGd0u6ESOI6XvFVm5xH9zJInsdY8ShSHklnr1JJnss70e1dGKZbZq32OChxWMw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/compiler-cli": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.5.tgz",
|
||||
"integrity": "sha512-3PzRaz3cKKnhhWKixKhXUvD2klKoAiFO/81ETMC+lp4GGWL35NAts0KnudSNxQIktYOlardQHEggtfgxq+spRg==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.8.tgz",
|
||||
"integrity": "sha512-DM35X5GHDCWGKfA+Q/nfBdw+hgahCT+zn7ywOvzyL4p+rkyOUHIHLnLfJekRpUXJYJrq5011MrUMw86HrR0vUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"canonical-path": "1.0.0",
|
||||
"chokidar": "^1.4.2",
|
||||
"chokidar": "^2.1.1",
|
||||
"convert-source-map": "^1.5.1",
|
||||
"dependency-graph": "^0.7.2",
|
||||
"magic-string": "^0.25.0",
|
||||
|
|
@ -292,42 +298,6 @@
|
|||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
|
||||
"integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"micromatch": "^2.1.5",
|
||||
"normalize-path": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
|
||||
"integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-flatten": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"array-unique": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
|
||||
"integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "1.8.5",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
|
||||
"integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"expand-range": "^1.8.1",
|
||||
"preserve": "^0.2.0",
|
||||
"repeat-element": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||
|
|
@ -335,20 +305,23 @@
|
|||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
|
||||
"integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz",
|
||||
"integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "^1.3.0",
|
||||
"async-each": "^1.0.0",
|
||||
"fsevents": "^1.0.0",
|
||||
"glob-parent": "^2.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"anymatch": "^2.0.0",
|
||||
"async-each": "^1.0.1",
|
||||
"braces": "^2.3.2",
|
||||
"fsevents": "^1.2.7",
|
||||
"glob-parent": "^3.1.0",
|
||||
"inherits": "^2.0.3",
|
||||
"is-binary-path": "^1.0.0",
|
||||
"is-glob": "^2.0.0",
|
||||
"is-glob": "^4.0.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"path-is-absolute": "^1.0.0",
|
||||
"readdirp": "^2.0.0"
|
||||
"readdirp": "^2.2.1",
|
||||
"upath": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
|
|
@ -377,24 +350,6 @@
|
|||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"expand-brackets": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
|
||||
"integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-posix-bracket": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"extglob": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
|
||||
"integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
|
|
@ -404,45 +359,12 @@
|
|||
"locate-path": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
|
||||
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"load-json-file": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
||||
|
|
@ -464,26 +386,11 @@
|
|||
"mimic-fn": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "2.3.11",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
|
||||
"integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arr-diff": "^2.0.0",
|
||||
"array-unique": "^0.2.1",
|
||||
"braces": "^1.8.2",
|
||||
"expand-brackets": "^0.1.4",
|
||||
"extglob": "^0.3.1",
|
||||
"filename-regex": "^2.0.0",
|
||||
"is-extglob": "^1.0.0",
|
||||
"is-glob": "^2.0.1",
|
||||
"kind-of": "^3.0.2",
|
||||
"normalize-path": "^2.0.1",
|
||||
"object.omit": "^2.0.0",
|
||||
"parse-glob": "^3.0.4",
|
||||
"regex-cache": "^0.4.2"
|
||||
}
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"os-locale": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -596,25 +503,25 @@
|
|||
}
|
||||
},
|
||||
"@angular/core": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.5.tgz",
|
||||
"integrity": "sha512-SKBDqoKNj9vjuLeNToFySafTWb+fyIhCj6C/yzlPcsRPLZj0Kzbvn1IKE+TWBLa/85dUiaE1xdBNQ66jTtpFSA==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.8.tgz",
|
||||
"integrity": "sha512-QKwug2kWJC00zm2rvmD9mCJzsOkMVhSu8vqPWf83poWTh8+F9aIVWcy29W0VoGpBkSchOnK8hf9DnKVv28j9nw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/forms": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.5.tgz",
|
||||
"integrity": "sha512-VBWbQ26ck1V014DSkFjlrlCksAZ3Q8rmHLZFy+o2k1CVyy49ojV/OxLDfJutp0QvflO+sWnzfDPaND/Ed9tS4w==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.8.tgz",
|
||||
"integrity": "sha512-lbSX4IHFHz/c4e2RHiPpL8MJlzDkCuQEHnqsujDaV2X9o9fApS6+C1X4x7Z2XDKqonmeX+aHQwv9+SLejX6OyQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/http": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.5.tgz",
|
||||
"integrity": "sha512-F5AE3QcNibShnhxokFaFhid2Abb+qtMbjfTZu3dSBOWbuz7+H0g7WbCFB4UZvWkTiOaQkTuk0J9IBrwrvt3fkQ==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.8.tgz",
|
||||
"integrity": "sha512-bCLgXKbYeSiZPXQ58YbxVKg50PwecDm9SqiXh1QOQOSJsAG7oXXWesN/IOnfP38XeRg9C2NBbJ6mKOyfD/4jdw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
|
|
@ -626,25 +533,25 @@
|
|||
"dev": true
|
||||
},
|
||||
"@angular/platform-browser": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.5.tgz",
|
||||
"integrity": "sha512-trSFOsRC+PrjqE709RQ7ezVCouehD7e82FhQNZQx9O1IZQyO0hxE2ncVB4Lvd7KpunAiFX7M1A2wfksHQl+0qw==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.8.tgz",
|
||||
"integrity": "sha512-SizCRMc7Or27g2CugcqWnaAikRPfgLgRvb9GFFGpcgoq8CRfOVwkyR5dFZuqN39H+uwtwuTMP5OUYhZcrFNKug==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/platform-browser-dynamic": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.5.tgz",
|
||||
"integrity": "sha512-GlipaKFqWlcaGWowccFxAgscpgMnWJucRnDrHRgvp3iUbqt2mC4sLko8BOi0S5FkE1D4+EqyEyp8DLM4o7VDvg==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.8.tgz",
|
||||
"integrity": "sha512-nOJt28A5pRn4mdL8y98V7bA6OOdMRjsQAcWCr/isGYF0l1yDC0ijUGWkHuRtj3z1/9tmERN0BLXx+xs1h4JhCQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@angular/platform-server": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-7.2.5.tgz",
|
||||
"integrity": "sha512-JgNSkFq9U+117UrIFM9f2lDJRdXqw4ZxST1kr5cLZ3BAnzlp1JQS9TT9RgIOLVD6rie8Aqgoli7fhaZQ7txvLQ==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-7.2.8.tgz",
|
||||
"integrity": "sha512-UAg+6rlEiFVw+Fvfyt5VnE5lS5xlLkPmxaKAGotj9y9sUjD7TTI4rJ7ciGAWOuVb5MprDWKlXxPYeBwBHiO0Mw==",
|
||||
"requires": {
|
||||
"domino": "^2.1.0",
|
||||
"tslib": "^1.9.0",
|
||||
|
|
@ -652,9 +559,9 @@
|
|||
}
|
||||
},
|
||||
"@angular/router": {
|
||||
"version": "7.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.5.tgz",
|
||||
"integrity": "sha512-WjEdnTyLQRntB8ixQ4qH8PFURFhgTtUjAsu3S3lf2wWbDDADIJO/xTMtXDhGubTmzRbBVROw6ZQzgDZtJyYKrw==",
|
||||
"version": "7.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.8.tgz",
|
||||
"integrity": "sha512-G8cA/JbaKFNeosCUGE/0Z7+5FBhZTVV/hacgUBRAEj8NNnECFqkAY9F16Twe+X8qx9WkpMw51WSYDNHPI1/dXQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
|
|
@ -843,12 +750,12 @@
|
|||
}
|
||||
},
|
||||
"@ngtools/webpack": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.4.tgz",
|
||||
"integrity": "sha512-qTfw/LGZ3kDZAgqb6gMVr36E0W3M+bnS/xAxNTxshxmJOCQr9AcKtX4sP65QweKS60KoBBR1a7nR6SOi1NJkxA==",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.5.tgz",
|
||||
"integrity": "sha512-KqJ4ZR8XicN+ElrSNiNPidTM134Z23F7ib0Rl8Ny3PDHkAYIBIxEnQDgZ2mazKRUVMlStUnjmzQIQj7/qZGLaw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"enhanced-resolve": "4.1.0",
|
||||
"rxjs": "6.3.3",
|
||||
"tree-kill": "1.2.1",
|
||||
|
|
@ -867,29 +774,37 @@
|
|||
}
|
||||
},
|
||||
"@nguniversal/module-map-ngfactory-loader": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-7.1.0.tgz",
|
||||
"integrity": "sha512-GYfb24OLJKBY58CgUsIsGgci5ceZAt4+GrVKh7RZRIHtZ/bjdGsvpIbfE9udqsnSowxIxHA5KzYHbC1x6AAB0A=="
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-7.1.1.tgz",
|
||||
"integrity": "sha512-cZaxdY64C5xPwbE3qqEQGmnKIEgIA57JTozAsT1gvlxNYwssxTlhCYT0HQcqNfNBBjf3xdqXTfRPC7lfpE4qWA=="
|
||||
},
|
||||
"@schematics/angular": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.4.tgz",
|
||||
"integrity": "sha512-Bb5DZQ8MeP8yhxPe6nVqyQ7sGVNwUx6nXPlrQV45ZycD3nJlqsuxr2DE75HFpn5oU+vlkq9J/Sys4WLJ4E/OMw==",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.5.tgz",
|
||||
"integrity": "sha512-fKNZccf1l2OcDwtDupYj54N/YuiMLCWeaXNxcJNUYvGnBtzxQJ4P2LtSCjB4HDvYCtseQM7iyc7D1Xrr5gI8nw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/schematics": "7.3.4",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"@angular-devkit/schematics": "7.3.5",
|
||||
"typescript": "3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.4.tgz",
|
||||
"integrity": "sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@schematics/update": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.4.tgz",
|
||||
"integrity": "sha512-YarSCCBSVPVG/MyN5H/FliRwaIDoeercy5Nip+NWZJsDyvtsAekO9s6QwizSvAr3541MmSQFeQICsjyM2dl3Bg==",
|
||||
"version": "0.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.5.tgz",
|
||||
"integrity": "sha512-bmwVKeyOmC948gJrIxPg0TY0999nusqSVqXJ8hqAgD0fyD6rnzF74nUhovQGvwFV0pZK8fCkRfdJIqgAPFcHcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@angular-devkit/core": "7.3.4",
|
||||
"@angular-devkit/schematics": "7.3.4",
|
||||
"@angular-devkit/core": "7.3.5",
|
||||
"@angular-devkit/schematics": "7.3.5",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"ini": "1.3.5",
|
||||
"pacote": "9.4.0",
|
||||
|
|
@ -925,9 +840,9 @@
|
|||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "11.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.6.tgz",
|
||||
"integrity": "sha512-4hS2K4gwo9/aXIcoYxCtHpdgd8XUeDmo1siRCAH3RziXB65JlPqUFMtfy9VPj+og7dp3w1TFjGwYga4e0m9GwA==",
|
||||
"version": "11.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz",
|
||||
"integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/q": {
|
||||
|
|
@ -2151,9 +2066,9 @@
|
|||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30000941",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000941.tgz",
|
||||
"integrity": "sha512-4vzGb2MfZcO20VMPj1j6nRAixhmtlhkypM4fL4zhgzEucQIYiRzSqPcWIu1OF8i0FETD93FMIPWfUJCAcFvrqA==",
|
||||
"version": "1.0.30000942",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000942.tgz",
|
||||
"integrity": "sha512-wLf+IhZUy2rfz48tc40OH7jHjXjnvDFEYqBHluINs/6MgzoNLPf25zhE4NOVzqxLKndf+hau81sAW0RcGHIaBQ==",
|
||||
"dev": true
|
||||
},
|
||||
"canonical-path": {
|
||||
|
|
@ -3482,57 +3397,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"expand-range": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
|
||||
"integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fill-range": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
|
||||
"integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^2.1.0",
|
||||
"isobject": "^2.0.0",
|
||||
"randomatic": "^3.0.0",
|
||||
"repeat-element": "^1.1.2",
|
||||
"repeat-string": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
|
||||
"integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"isobject": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
|
||||
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isarray": "1.0.0"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"express": {
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||
|
|
@ -3762,12 +3626,6 @@
|
|||
"schema-utils": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"filename-regex": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
|
||||
"integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
|
||||
"dev": true
|
||||
},
|
||||
"fileset": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
|
||||
|
|
@ -4598,42 +4456,6 @@
|
|||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"glob-base": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
|
||||
"integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob-parent": "^2.0.0",
|
||||
"is-glob": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob-parent": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
|
||||
"integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||
|
|
@ -5393,21 +5215,6 @@
|
|||
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-dotfile": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
|
||||
"integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-equal-shallow": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
|
||||
"integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-primitive": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-extendable": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||
|
|
@ -5495,18 +5302,6 @@
|
|||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"is-posix-bracket": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
|
||||
"integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
|
||||
"dev": true
|
||||
},
|
||||
"is-primitive": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
|
||||
"integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
|
||||
"dev": true
|
||||
},
|
||||
"is-promise": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||
|
|
@ -6422,12 +6217,6 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"math-random": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz",
|
||||
"integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==",
|
||||
"dev": true
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
@ -7082,27 +6871,6 @@
|
|||
"isobject": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"object.omit": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
|
||||
"integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"for-own": "^0.1.4",
|
||||
"is-extendable": "^0.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"for-own": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
|
||||
"integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"for-in": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"object.pick": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
|
||||
|
|
@ -7446,35 +7214,6 @@
|
|||
"safe-buffer": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"parse-glob": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
|
||||
"integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob-base": "^0.3.0",
|
||||
"is-dotfile": "^1.0.0",
|
||||
"is-extglob": "^1.0.0",
|
||||
"is-glob": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-extglob": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
|
||||
"integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
"integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
||||
|
|
@ -7776,12 +7515,6 @@
|
|||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
|
||||
"dev": true
|
||||
},
|
||||
"preserve": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
|
||||
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
|
||||
"dev": true
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
|
|
@ -7972,25 +7705,6 @@
|
|||
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
|
||||
"dev": true
|
||||
},
|
||||
"randomatic": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz",
|
||||
"integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^4.0.0",
|
||||
"kind-of": "^6.0.0",
|
||||
"math-random": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"is-number": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
|
||||
"integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
|
|
@ -8139,15 +7853,6 @@
|
|||
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
|
||||
"optional": true
|
||||
},
|
||||
"regex-cache": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
|
||||
"integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-equal-shallow": "^0.1.3"
|
||||
}
|
||||
},
|
||||
"regex-not": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@
|
|||
"popper.js": "^1.14.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.13.2",
|
||||
"@angular/cli": "~7.3.2",
|
||||
"@angular/compiler-cli": "7.2.5",
|
||||
"@angular-devkit/build-angular": "~0.13.5",
|
||||
"@angular/cli": "~7.3.5",
|
||||
"@angular/compiler-cli": "^7.2.8",
|
||||
"@angular/language-service": "^7.2.5",
|
||||
"@types/jasmine": "~3.3.9",
|
||||
"@types/jasminewd2": "~2.0.6",
|
||||
"@types/node": "~11.9.4",
|
||||
"@types/node": "~11.10.5",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~3.3.0",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } fr
|
|||
styleUrls: ['./login.component.css']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
private message = new BehaviorSubject<string>(null);
|
||||
public message = new BehaviorSubject<string>(null);
|
||||
|
||||
constructor(
|
||||
private authorizeService: AuthorizeService,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authoriza
|
|||
styleUrls: ['./logout.component.css']
|
||||
})
|
||||
export class LogoutComponent implements OnInit {
|
||||
private message = new BehaviorSubject<string>(null);
|
||||
public message = new BehaviorSubject<string>(null);
|
||||
|
||||
constructor(
|
||||
private authorizeService: AuthorizeService,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"Data/**",
|
||||
"Models/**",
|
||||
"ClientApp/src/components/api-authorization/**",
|
||||
"ClientApp/src/setupTests.js",
|
||||
"Controllers/OidcConfigurationController.cs"
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { FetchData } from './components/FetchData';
|
|||
import { Counter } from './components/Counter';
|
||||
////#if (IndividualLocalAuth)
|
||||
import AuthorizeRoute from './components/api-authorization/AuthorizeRoute';
|
||||
import ApiAuthorizationRoutes from './components/api-authorization/ApiAuthorizationRoutes'
|
||||
import ApiAuthorizationRoutes from './components/api-authorization/ApiAuthorizationRoutes';
|
||||
import { ApplicationPaths } from './components/api-authorization/ApiAuthorizationConstants';
|
||||
////#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ import ReactDOM from 'react-dom';
|
|||
import { MemoryRouter } from 'react-router-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
it('renders without crashing', async () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<MemoryRouter>
|
||||
<App />
|
||||
</MemoryRouter>, div);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React from 'react'
|
||||
import { Component } from 'react'
|
||||
import { Route, Redirect } from 'react-router-dom'
|
||||
import { ApplicationPaths, QueryParameterNames } from './ApiAuthorizationConstants'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { UserManager, WebStorageStateStore } from 'oidc-client';
|
||||
import { UserManager, WebStorageStateStore } from 'oidc-client';
|
||||
import { ApplicationPaths, ApplicationName } from './ApiAuthorizationConstants';
|
||||
|
||||
export class AuthorizeService {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React from 'react'
|
||||
import { Component } from 'react';
|
||||
import authService from './AuthorizeService';
|
||||
import { AuthenticationResultStatus } from './AuthorizeService';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Component, Fragment } from 'react';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { NavItem, NavLink } from 'reactstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import authService from './AuthorizeService';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React from 'react'
|
||||
import { Component } from 'react';
|
||||
import authService from './AuthorizeService';
|
||||
import { AuthenticationResultStatus } from './AuthorizeService';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
const localStorageMock = {
|
||||
getItem: jest.fn(),
|
||||
setItem: jest.fn(),
|
||||
removeItem: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
};
|
||||
global.localStorage = localStorageMock;
|
||||
|
||||
// Mock the request issued by the react app to get the client configuration parameters.
|
||||
window.fetch = () => {
|
||||
return Promise.resolve(
|
||||
{
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
"authority": "https://localhost:5001",
|
||||
"client_id": "Company.WebApplication1",
|
||||
"redirect_uri": "https://localhost:5001/authentication/login-callback",
|
||||
"post_logout_redirect_uri": "https://localhost:5001/authentication/logout-callback",
|
||||
"response_type": "id_token token",
|
||||
"scope": "Company.WebApplication1API openid profile"
|
||||
})
|
||||
});
|
||||
};
|
||||
|
|
@ -1,4 +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.
|
||||
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using ProjectTemplates.Tests.Helpers;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
|
||||
[assembly: TestFramework("Microsoft.AspNetCore.E2ETesting.XunitTestFrameworkWithAssemblyFixture", "ProjectTemplates.Tests")]
|
||||
|
||||
[assembly: AssemblyFixture(typeof(ProjectFactoryFixture))]
|
||||
[assembly: AssemblyFixture(typeof(SeleniumStandaloneServer))]
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ProjectTemplates.Tests.Helpers;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -14,9 +16,25 @@ namespace Templates.Test
|
|||
{
|
||||
public class BaselineTest
|
||||
{
|
||||
private static readonly Regex TemplateNameRegex = new Regex(
|
||||
"new (?<template>[a-zA-Z]+)",
|
||||
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline,
|
||||
TimeSpan.FromSeconds(1));
|
||||
|
||||
private static readonly Regex AuthenticationOptionRegex = new Regex(
|
||||
"-au (?<auth>[a-zA-Z]+)",
|
||||
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline,
|
||||
TimeSpan.FromSeconds(1));
|
||||
|
||||
private static readonly Regex LanguageRegex = new Regex(
|
||||
"--language (?<language>\\w+)",
|
||||
RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.Singleline,
|
||||
TimeSpan.FromSeconds(1));
|
||||
|
||||
public BaselineTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
||||
{
|
||||
Project = projectFactory.CreateProject(output);
|
||||
ProjectFactory = projectFactory;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
public Project Project { get; set; }
|
||||
|
|
@ -47,14 +65,20 @@ namespace Templates.Test
|
|||
}
|
||||
}
|
||||
|
||||
public ProjectFactoryFixture ProjectFactory { get; }
|
||||
public ITestOutputHelper Output { get; }
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(TemplateBaselines))]
|
||||
public void Template_Produces_The_Right_Set_Of_Files(string arguments, string[] expectedFiles)
|
||||
public async Task Template_Produces_The_Right_Set_Of_FilesAsync(string arguments, string[] expectedFiles)
|
||||
{
|
||||
Project.RunDotNet(arguments);
|
||||
Project = await ProjectFactory.GetOrCreateProject("baseline" + SanitizeArgs(arguments), Output);
|
||||
var createResult = await Project.RunDotNetNewRawAsync(arguments);
|
||||
Assert.True(createResult.ExitCode == 0, createResult.GetFormattedOutput());
|
||||
|
||||
foreach (var file in expectedFiles)
|
||||
{
|
||||
Project.AssertFileExists(file, shouldExist: true);
|
||||
AssertFileExists(Project.TemplateOutputDir, file, shouldExist: true);
|
||||
}
|
||||
|
||||
var filesInFolder = Directory.EnumerateFiles(Project.TemplateOutputDir, "*", SearchOption.AllDirectories);
|
||||
|
|
@ -73,5 +97,36 @@ namespace Templates.Test
|
|||
Assert.Contains(relativePath, expectedFiles);
|
||||
}
|
||||
}
|
||||
|
||||
private string SanitizeArgs(string arguments)
|
||||
{
|
||||
var text = TemplateNameRegex.Match(arguments)
|
||||
.Groups.TryGetValue("template", out var template) ? template.Value : "";
|
||||
|
||||
text += AuthenticationOptionRegex.Match(arguments)
|
||||
.Groups.TryGetValue("auth", out var auth) ? auth.Value : "";
|
||||
|
||||
text += arguments.Contains("--uld") ? "uld" : "";
|
||||
|
||||
text += LanguageRegex.Match(arguments)
|
||||
.Groups.TryGetValue("language", out var language) ? language.Value.Replace("#", "Sharp") : "";
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Templates.Test.Helpers;
|
||||
|
|
@ -27,14 +28,22 @@ namespace Templates.Test
|
|||
private readonly HttpClient _httpClient;
|
||||
private static List<ScriptTag> _scriptTags;
|
||||
private static List<LinkTag> _linkTags;
|
||||
private static readonly string[] _packages;
|
||||
|
||||
static CdnScriptTagTests()
|
||||
{
|
||||
var packages = MondoHelpers.GetNupkgFiles();
|
||||
var searchPattern = "*.nupkg";
|
||||
_packages = Directory.EnumerateFiles(
|
||||
ResolveFolder("ArtifactsShippingPackagesDir"),
|
||||
searchPattern)
|
||||
.Concat(Directory.EnumerateFiles(
|
||||
ResolveFolder("ArtifactsNonShippingPackagesDir"),
|
||||
searchPattern))
|
||||
.ToArray();
|
||||
|
||||
_scriptTags = new List<ScriptTag>();
|
||||
_linkTags = new List<LinkTag>();
|
||||
foreach (var packagePath in packages)
|
||||
foreach (var packagePath in _packages)
|
||||
{
|
||||
var tags = GetTags(packagePath);
|
||||
_scriptTags.AddRange(tags.scripts);
|
||||
|
|
@ -42,6 +51,11 @@ namespace Templates.Test
|
|||
}
|
||||
}
|
||||
|
||||
private static string ResolveFolder(string folder) =>
|
||||
typeof(CdnScriptTagTests).Assembly
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.Single(a => a.Key == folder).Value;
|
||||
|
||||
public CdnScriptTagTests(ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
|
|
@ -162,7 +176,8 @@ namespace Templates.Test
|
|||
private async Task<HttpResponseMessage> GetFromCDN(string src)
|
||||
{
|
||||
var logger = NullLogger.Instance;
|
||||
return await RetryHelper.RetryRequest(async () => {
|
||||
return await RetryHelper.RetryRequest(async () =>
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(src));
|
||||
return await _httpClient.SendAsync(request);
|
||||
}, logger);
|
||||
|
|
@ -191,7 +206,7 @@ namespace Templates.Test
|
|||
|
||||
private static string GetFileContentFromArchive(ScriptTag scriptTag, string relativeFilePath)
|
||||
{
|
||||
var file = MondoHelpers.GetNupkgFiles().Single(f => f.EndsWith(scriptTag.FileName));
|
||||
var file = _packages.Single(f => f.EndsWith(scriptTag.FileName));
|
||||
using (var zip = new ZipArchive(File.OpenRead(file), ZipArchiveMode.Read, leaveOpen: false))
|
||||
{
|
||||
var entry = zip.Entries
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
||||
<Import Project="GenerateTestProps.targets" />
|
||||
<Import Project="$(MSBuildThisFileDirectory)Infrastructure\GenerateTestProps.targets" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// 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.E2ETesting;
|
||||
using ProjectTemplates.Tests.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -12,22 +12,50 @@ namespace Templates.Test
|
|||
{
|
||||
public EmptyWebTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
||||
{
|
||||
Project = projectFactory.CreateProject(output);
|
||||
ProjectFactory = projectFactory;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
public Project Project { get; }
|
||||
public Project Project { get; set; }
|
||||
|
||||
public ProjectFactoryFixture ProjectFactory { get; }
|
||||
|
||||
public ITestOutputHelper Output { get; }
|
||||
|
||||
[Fact]
|
||||
public void EmptyWebTemplate()
|
||||
public async Task EmptyWebTemplateAsync()
|
||||
{
|
||||
Project.RunDotNetNew("web");
|
||||
Project = await ProjectFactory.GetOrCreateProject("empty", Output);
|
||||
|
||||
foreach (var publish in new[] { false, true })
|
||||
var createResult = await Project.RunDotNetNewAsync("web");
|
||||
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/netcoreapp3.0/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));
|
||||
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
{
|
||||
aspNetProcess.AssertOk("/");
|
||||
}
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/");
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
internal class AddFirewallExclusion : IDisposable
|
||||
{
|
||||
private bool _disposedValue = false;
|
||||
private readonly string _exclusionPath;
|
||||
|
||||
public AddFirewallExclusion(string exclusionPath)
|
||||
{
|
||||
if (!File.Exists(exclusionPath))
|
||||
{
|
||||
throw new FileNotFoundException($"File {exclusionPath} was not found.");
|
||||
}
|
||||
|
||||
_exclusionPath = exclusionPath;
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c netsh advfirewall firewall add rule name=\"Allow {exclusionPath}\" dir=in action=allow program=\"{exclusionPath}\"",
|
||||
UseShellExecute = false,
|
||||
Verb = "runas",
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c netsh advfirewall firewall delete rule name=\"Allow {_exclusionPath}\"",
|
||||
UseShellExecute = false,
|
||||
Verb = "runas",
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Certificates.Generation;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using OpenQA.Selenium;
|
||||
|
|
@ -18,16 +18,18 @@ namespace Templates.Test.Helpers
|
|||
{
|
||||
public class AspNetProcess : IDisposable
|
||||
{
|
||||
private const string DefaultFramework = "netcoreapp3.0";
|
||||
private const string ListeningMessagePrefix = "Now listening on: ";
|
||||
private static int Port = 5000 + new Random().Next(3000);
|
||||
|
||||
private readonly ProcessEx _process;
|
||||
private readonly Uri _listeningUri;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public AspNetProcess(ITestOutputHelper output, string workingDirectory, string projectName, bool publish)
|
||||
internal ProcessEx Process { get; }
|
||||
|
||||
public AspNetProcess(
|
||||
ITestOutputHelper output,
|
||||
string workingDirectory,
|
||||
string dllPath,
|
||||
IDictionary<string,string> environmentVariables)
|
||||
{
|
||||
_output = output;
|
||||
_httpClient = new HttpClient(new HttpClientHandler()
|
||||
|
|
@ -35,54 +37,23 @@ namespace Templates.Test.Helpers
|
|||
AllowAutoRedirect = true,
|
||||
UseCookies = true,
|
||||
CookieContainer = new CookieContainer(),
|
||||
ServerCertificateCustomValidationCallback = (m, c, ch, p) => true
|
||||
});
|
||||
ServerCertificateCustomValidationCallback = (m, c, ch, p) => true,
|
||||
})
|
||||
{
|
||||
Timeout = TimeSpan.FromMinutes(2)
|
||||
};
|
||||
|
||||
var now = DateTimeOffset.Now;
|
||||
new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1));
|
||||
|
||||
if (publish)
|
||||
{
|
||||
output.WriteLine("Publishing ASP.NET application...");
|
||||
|
||||
// Workaround for issue with runtime store not yet being published
|
||||
// https://github.com/aspnet/Home/issues/2254#issuecomment-339709628
|
||||
var extraArgs = "-p:PublishWithAspNetCoreTargetManifest=false";
|
||||
|
||||
ProcessEx
|
||||
.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), $"publish -c Release {extraArgs}")
|
||||
.WaitForExit(assertSuccess: true);
|
||||
workingDirectory = Path.Combine(workingDirectory, "bin", "Release", DefaultFramework, "publish");
|
||||
if (File.Exists(Path.Combine(workingDirectory, "ClientApp", "package.json")))
|
||||
{
|
||||
Npm.RestoreWithRetry(output, Path.Combine(workingDirectory, "ClientApp"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine("Building ASP.NET application...");
|
||||
ProcessEx
|
||||
.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), "build -c Debug")
|
||||
.WaitForExit(assertSuccess: true);
|
||||
}
|
||||
|
||||
var envVars = new Dictionary<string, string>
|
||||
{
|
||||
{ "ASPNETCORE_URLS", $"http://127.0.0.1:0;https://127.0.0.1:0" }
|
||||
};
|
||||
|
||||
if (!publish)
|
||||
{
|
||||
envVars["ASPNETCORE_ENVIRONMENT"] = "Development";
|
||||
}
|
||||
|
||||
output.WriteLine("Running ASP.NET application...");
|
||||
|
||||
var dllPath = publish ? $"{projectName}.dll" : $"bin/Debug/{DefaultFramework}/{projectName}.dll";
|
||||
_process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), $"exec {dllPath}", envVars: envVars);
|
||||
Process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), $"exec {dllPath}", envVars: environmentVariables);
|
||||
_listeningUri = GetListeningUri(output);
|
||||
}
|
||||
|
||||
|
||||
public void VisitInBrowser(IWebDriver driver)
|
||||
{
|
||||
_output.WriteLine($"Opening browser at {_listeningUri}...");
|
||||
|
|
@ -116,31 +87,42 @@ namespace Templates.Test.Helpers
|
|||
{
|
||||
// Wait until the app is accepting HTTP requests
|
||||
output.WriteLine("Waiting until ASP.NET application is accepting connections...");
|
||||
var listeningMessage = _process
|
||||
var listeningMessage = Process
|
||||
.OutputLinesAsEnumerable
|
||||
.Where(line => line != null)
|
||||
.FirstOrDefault(line => line.Trim().StartsWith(ListeningMessagePrefix, StringComparison.Ordinal));
|
||||
Assert.True(!string.IsNullOrEmpty(listeningMessage), $"ASP.NET process exited without listening for requests.\nOutput: { _process.Output }\nError: { _process.Error }");
|
||||
listeningMessage = listeningMessage.Trim();
|
||||
|
||||
// Verify we have a valid URL to make requests to
|
||||
var listeningUrlString = listeningMessage.Substring(ListeningMessagePrefix.Length);
|
||||
output.WriteLine($"Detected that ASP.NET application is accepting connections on: {listeningUrlString}");
|
||||
listeningUrlString = listeningUrlString.Substring(0, listeningUrlString.IndexOf(':')) +
|
||||
"://localhost" +
|
||||
listeningUrlString.Substring(listeningUrlString.LastIndexOf(':'));
|
||||
if (!string.IsNullOrEmpty(listeningMessage))
|
||||
{
|
||||
listeningMessage = listeningMessage.Trim();
|
||||
// Verify we have a valid URL to make requests to
|
||||
var listeningUrlString = listeningMessage.Substring(ListeningMessagePrefix.Length);
|
||||
output.WriteLine($"Detected that ASP.NET application is accepting connections on: {listeningUrlString}");
|
||||
listeningUrlString = listeningUrlString.Substring(0, listeningUrlString.IndexOf(':')) +
|
||||
"://localhost" +
|
||||
listeningUrlString.Substring(listeningUrlString.LastIndexOf(':'));
|
||||
|
||||
output.WriteLine("Sending requests to " + listeningUrlString);
|
||||
return new Uri(listeningUrlString, UriKind.Absolute);
|
||||
output.WriteLine("Sending requests to " + listeningUrlString);
|
||||
return new Uri(listeningUrlString, UriKind.Absolute);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void AssertOk(string requestUrl)
|
||||
public Task AssertOk(string requestUrl)
|
||||
=> AssertStatusCode(requestUrl, HttpStatusCode.OK);
|
||||
|
||||
public void AssertNotFound(string requestUrl)
|
||||
public Task AssertNotFound(string requestUrl)
|
||||
=> AssertStatusCode(requestUrl, HttpStatusCode.NotFound);
|
||||
|
||||
public void AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null)
|
||||
internal Task<HttpResponseMessage> SendRequest(string path)
|
||||
{
|
||||
return _httpClient.GetAsync(new Uri(_listeningUri, path));
|
||||
}
|
||||
|
||||
public async Task AssertStatusCode(string requestUrl, HttpStatusCode statusCode, string acceptContentType = null)
|
||||
{
|
||||
var request = new HttpRequestMessage(
|
||||
HttpMethod.Get,
|
||||
|
|
@ -151,14 +133,14 @@ namespace Templates.Test.Helpers
|
|||
request.Headers.Add("Accept", acceptContentType);
|
||||
}
|
||||
|
||||
var response = _httpClient.SendAsync(request).Result;
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
Assert.Equal(statusCode, response.StatusCode);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpClient.Dispose();
|
||||
_process.Dispose();
|
||||
Process.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
// 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 Templates.Test.Helpers
|
||||
{
|
||||
internal static class ErrorMessages
|
||||
{
|
||||
public static string GetFailedProcessMessage(string step, Project project, ProcessEx processResult)
|
||||
{
|
||||
return $@"Project {project.ProjectArguments} failed to {step}.
|
||||
{processResult.GetFormattedOutput()}";
|
||||
}
|
||||
|
||||
public static string GetFailedProcessMessageOrEmpty(string step, Project project, ProcessEx processResult)
|
||||
{
|
||||
return processResult.HasExited ? $@"Project {project.ProjectArguments} failed to {step}.
|
||||
{processResult.GetFormattedOutput()}" : "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +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.IO;
|
||||
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
public static class MondoHelpers
|
||||
{
|
||||
public static string[] GetNupkgFiles()
|
||||
{
|
||||
var mondoRoot = GetMondoRepoRoot();
|
||||
#if DEBUG
|
||||
var configuration = "Debug";
|
||||
#else
|
||||
var configuration = "Release";
|
||||
#endif
|
||||
|
||||
return Directory.GetFiles(Path.Combine(mondoRoot, "artifacts", "packages", configuration), "*.nupkg", SearchOption.AllDirectories);
|
||||
}
|
||||
|
||||
private static string GetMondoRepoRoot()
|
||||
{
|
||||
return FindAncestorDirectoryContaining(".gitmodules");
|
||||
}
|
||||
|
||||
private static string FindAncestorDirectoryContaining(string filename)
|
||||
{
|
||||
var dir = AppContext.BaseDirectory;
|
||||
while (dir != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(dir, filename)))
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
|
||||
dir = Directory.GetParent(dir)?.FullName;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not find any ancestor directory containing {filename} at or above {AppContext.BaseDirectory}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
public static class Npm
|
||||
{
|
||||
private static object NpmInstallLock = new object();
|
||||
|
||||
public static void RestoreWithRetry(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;
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
attemptNumber++;
|
||||
Restore(output, workingDirectory);
|
||||
break; // Success
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (attemptNumber < maxAttempts)
|
||||
{
|
||||
output.WriteLine(
|
||||
$"NPM restore in {workingDirectory} failed on attempt {attemptNumber} of {maxAttempts}. " +
|
||||
$"Error was: {ex}");
|
||||
|
||||
// Clean up the possibly-incomplete node_modules dir before retrying
|
||||
var nodeModulesDir = Path.Combine(workingDirectory, "node_modules");
|
||||
if (Directory.Exists(nodeModulesDir))
|
||||
{
|
||||
Directory.Delete(nodeModulesDir, recursive: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteLine(
|
||||
$"Giving up attempting NPM restore in {workingDirectory} after {attemptNumber} attempts.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void Restore(ITestOutputHelper output, string workingDirectory)
|
||||
{
|
||||
// It's not safe to run multiple NPM installs in parallel
|
||||
// https://github.com/npm/npm/issues/2500
|
||||
lock (NpmInstallLock)
|
||||
{
|
||||
output.WriteLine($"Restoring NPM packages in '{workingDirectory}' using npm...");
|
||||
ProcessEx.RunViaShell(output, workingDirectory, "npm install");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Test(ITestOutputHelper outputHelper, string workingDirectory)
|
||||
{
|
||||
ProcessEx.RunViaShell(outputHelper, workingDirectory, "npm run lint");
|
||||
if (!File.Exists(Path.Join(workingDirectory, "angular.json")))
|
||||
{
|
||||
ProcessEx.RunViaShell(outputHelper, workingDirectory, "npm run test");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
// 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -15,10 +17,7 @@ namespace Templates.Test.Helpers
|
|||
{
|
||||
internal class ProcessEx : IDisposable
|
||||
{
|
||||
private static readonly string NUGET_PACKAGES = typeof(ProcessEx).Assembly
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.First(attribute => attribute.Key == "TestPackageRestorePath")
|
||||
.Value;
|
||||
private static readonly string NUGET_PACKAGES = GetNugetPackagesRestorePath();
|
||||
|
||||
private readonly ITestOutputHelper _output;
|
||||
private readonly Process _process;
|
||||
|
|
@ -28,6 +27,54 @@ namespace Templates.Test.Helpers
|
|||
private BlockingCollection<string> _stdoutLines;
|
||||
private TaskCompletionSource<int> _exited;
|
||||
|
||||
public ProcessEx(ITestOutputHelper output, Process proc)
|
||||
{
|
||||
_output = output;
|
||||
_stdoutCapture = new StringBuilder();
|
||||
_stderrCapture = new StringBuilder();
|
||||
_stdoutLines = new BlockingCollection<string>();
|
||||
|
||||
_process = proc;
|
||||
proc.EnableRaisingEvents = true;
|
||||
proc.OutputDataReceived += OnOutputData;
|
||||
proc.ErrorDataReceived += OnErrorData;
|
||||
proc.Exited += OnProcessExited;
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
|
||||
_exited = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
|
||||
public Task Exited => _exited.Task;
|
||||
|
||||
public bool HasExited => _process.HasExited;
|
||||
|
||||
public string Error
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_pipeCaptureLock)
|
||||
{
|
||||
return _stderrCapture.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Output
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_pipeCaptureLock)
|
||||
{
|
||||
return _stdoutCapture.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> OutputLinesAsEnumerable => _stdoutLines.GetConsumingEnumerable();
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary<string, string> envVars = null)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo(command, args)
|
||||
|
|
@ -55,59 +102,17 @@ namespace Templates.Test.Helpers
|
|||
return new ProcessEx(output, proc);
|
||||
}
|
||||
|
||||
public static void RunViaShell(ITestOutputHelper output, string workingDirectory, string commandAndArgs)
|
||||
public static async Task<ProcessEx> RunViaShellAsync(ITestOutputHelper output, string workingDirectory, string commandAndArgs)
|
||||
{
|
||||
var (shellExe, argsPrefix) = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? ("cmd", "/c")
|
||||
: ("bash", "-c");
|
||||
Run(output, workingDirectory, shellExe, $"{argsPrefix} \"{commandAndArgs}\"")
|
||||
.WaitForExit(assertSuccess: true);
|
||||
|
||||
var result = Run(output, workingDirectory, shellExe, $"{argsPrefix} \"{commandAndArgs}\"");
|
||||
await result.Exited;
|
||||
return result;
|
||||
}
|
||||
|
||||
public ProcessEx(ITestOutputHelper output, Process proc)
|
||||
{
|
||||
_output = output;
|
||||
_stdoutCapture = new StringBuilder();
|
||||
_stderrCapture = new StringBuilder();
|
||||
_stdoutLines = new BlockingCollection<string>();
|
||||
|
||||
_process = proc;
|
||||
proc.EnableRaisingEvents = true;
|
||||
proc.OutputDataReceived += OnOutputData;
|
||||
proc.ErrorDataReceived += OnErrorData;
|
||||
proc.Exited += OnProcessExited;
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
|
||||
_exited = new TaskCompletionSource<int>();
|
||||
}
|
||||
|
||||
public Task Exited => _exited.Task;
|
||||
|
||||
public string Error
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_pipeCaptureLock)
|
||||
{
|
||||
return _stderrCapture.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Output
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_pipeCaptureLock)
|
||||
{
|
||||
return _stdoutCapture.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
private void OnErrorData(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data == null)
|
||||
|
|
@ -151,6 +156,16 @@ namespace Templates.Test.Helpers
|
|||
_exited.TrySetResult(_process.ExitCode);
|
||||
}
|
||||
|
||||
internal string GetFormattedOutput()
|
||||
{
|
||||
if (!_process.HasExited)
|
||||
{
|
||||
throw new InvalidOperationException("Process has not finished running.");
|
||||
}
|
||||
|
||||
return $"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}";
|
||||
}
|
||||
|
||||
public void WaitForExit(bool assertSuccess)
|
||||
{
|
||||
Exited.Wait();
|
||||
|
|
@ -161,6 +176,12 @@ namespace Templates.Test.Helpers
|
|||
}
|
||||
}
|
||||
|
||||
private static string GetNugetPackagesRestorePath() =>
|
||||
typeof(ProcessEx).Assembly
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.First(attribute => attribute.Key == "TestPackageRestorePath")
|
||||
.Value;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_process != null && !_process.HasExited)
|
||||
|
|
@ -176,7 +197,5 @@ namespace Templates.Test.Helpers
|
|||
_process.Exited -= OnProcessExited;
|
||||
_process.Dispose();
|
||||
}
|
||||
|
||||
public IEnumerable<string> OutputLinesAsEnumerable => _stdoutLines.GetConsumingEnumerable();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,388 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
public class Project
|
||||
{
|
||||
public const string DefaultFramework = "netcoreapp3.0";
|
||||
|
||||
public SemaphoreSlim DotNetNewLock { get; set; }
|
||||
public SemaphoreSlim NodeLock { get; set; }
|
||||
public string ProjectName { get; set; }
|
||||
public string ProjectArguments { get; set; }
|
||||
public string ProjectGuid { get; set; }
|
||||
public string TemplateOutputDir { get; set; }
|
||||
public string TemplateBuildDir => Path.Combine(TemplateOutputDir, "bin", "Debug", DefaultFramework);
|
||||
public string TemplatePublishDir => Path.Combine(TemplateOutputDir, "bin", "Release", DefaultFramework, "publish");
|
||||
|
||||
public ITestOutputHelper Output { get; set; }
|
||||
public IMessageSink DiagnosticsMessageSink { get; set; }
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetNewAsync(string templateName, string auth = null, string language = null, bool useLocalDB = false, bool noHttps = false)
|
||||
{
|
||||
var hiveArg = $"--debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"";
|
||||
var args = $"new {templateName} {hiveArg}";
|
||||
|
||||
if (!string.IsNullOrEmpty(auth))
|
||||
{
|
||||
args += $" --auth {auth}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(language))
|
||||
{
|
||||
args += $" -lang {language}";
|
||||
}
|
||||
|
||||
if (useLocalDB)
|
||||
{
|
||||
args += $" --use-local-db";
|
||||
}
|
||||
|
||||
if (noHttps)
|
||||
{
|
||||
args += $" --no-https";
|
||||
}
|
||||
|
||||
// Save a copy of the arguments used for better diagnostic error messages later.
|
||||
// We omit the hive argument and the template output dir as they are not relevant and add noise.
|
||||
ProjectArguments = args.Replace(hiveArg, "");
|
||||
|
||||
args += $" -o {TemplateOutputDir}";
|
||||
|
||||
// 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 execution = ProcessEx.Run(Output, AppContext.BaseDirectory, DotNetMuxer.MuxerPathOrDefault(), args);
|
||||
await execution.Exited;
|
||||
return execution;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DotNetNewLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetPublishAsync(bool takeNodeLock = false)
|
||||
{
|
||||
Output.WriteLine("Publishing ASP.NET application...");
|
||||
|
||||
// Workaround for issue with runtime store not yet being published
|
||||
// https://github.com/aspnet/Home/issues/2254#issuecomment-339709628
|
||||
var extraArgs = "-p:PublishWithAspNetCoreTargetManifest=false";
|
||||
|
||||
|
||||
// This is going to trigger a build, so we need to acquire the lock like in the other cases.
|
||||
// We want to take the node lock as some builds run NPM as part of the build and we want to make sure
|
||||
// it's run without interruptions.
|
||||
var effectiveLock = takeNodeLock ? new OrderedLock(NodeLock, DotNetNewLock) : new OrderedLock(nodeLock: null, DotNetNewLock);
|
||||
await effectiveLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), $"publish -c Release {extraArgs}");
|
||||
await result.Exited;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
effectiveLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetBuildAsync(bool takeNodeLock = false)
|
||||
{
|
||||
Output.WriteLine("Building ASP.NET application...");
|
||||
|
||||
// This is going to trigger a build, so we need to acquire the lock like in the other cases.
|
||||
// We want to take the node lock as some builds run NPM as part of the build and we want to make sure
|
||||
// it's run without interruptions.
|
||||
var effectiveLock = takeNodeLock ? new OrderedLock(NodeLock, DotNetNewLock) : new OrderedLock(nodeLock: null, DotNetNewLock);
|
||||
await effectiveLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), "build -c Debug");
|
||||
await result.Exited;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
effectiveLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
internal AspNetProcess StartBuiltProjectAsync()
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_URLS"] = $"http://127.0.0.1:0;https://127.0.0.1:0",
|
||||
["ASPNETCORE_ENVIRONMENT"] = "Development"
|
||||
};
|
||||
|
||||
var projectDll = Path.Combine(TemplateBuildDir, $"{ProjectName}.dll");
|
||||
return new AspNetProcess(Output, TemplateOutputDir, projectDll, environment);
|
||||
}
|
||||
|
||||
internal AspNetProcess StartPublishedProjectAsync()
|
||||
{
|
||||
var environment = new Dictionary<string, string>
|
||||
{
|
||||
["ASPNETCORE_URLS"] = $"http://127.0.0.1:0;https://127.0.0.1:0",
|
||||
};
|
||||
|
||||
var projectDll = $"{ProjectName}.dll";
|
||||
return new AspNetProcess(Output, TemplatePublishDir, projectDll, environment);
|
||||
}
|
||||
|
||||
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.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 = await ProcessEx.RunViaShellAsync(output, workingDirectory, "npm install");
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
NodeLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetEfCreateMigrationAsync(string migrationName)
|
||||
{
|
||||
var assembly = typeof(ProjectFactoryFixture).Assembly;
|
||||
|
||||
var dotNetEfFullPath = assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.First(attribute => attribute.Key == "DotNetEfFullPath")
|
||||
.Value;
|
||||
|
||||
var args = $"\"{dotNetEfFullPath}\" --verbose --no-build migrations add {migrationName}";
|
||||
|
||||
// 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 result = ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), 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)
|
||||
{
|
||||
var fullPath = Path.Combine(TemplateOutputDir, "Data/Migrations");
|
||||
var file = Directory.EnumerateFiles(fullPath).Where(f => f.EndsWith($"{migration}.cs")).FirstOrDefault();
|
||||
|
||||
Assert.NotNull(file);
|
||||
var contents = File.ReadAllText(file);
|
||||
|
||||
var emptyMigration = @"protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}";
|
||||
|
||||
// This comparison can break depending on how GIT checked out newlines on different files.
|
||||
Assert.Contains(RemoveNewLines(emptyMigration), RemoveNewLines(contents));
|
||||
|
||||
static string RemoveNewLines(string str)
|
||||
{
|
||||
return str.Replace("\n", string.Empty).Replace("\r", string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
internal async Task<ProcessEx> RunDotNetNewRawAsync(string arguments)
|
||||
{
|
||||
await DotNetNewLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
var result = ProcessEx.Run(
|
||||
Output,
|
||||
AppContext.BaseDirectory,
|
||||
DotNetMuxer.MuxerPathOrDefault(),
|
||||
arguments +
|
||||
$" --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"" +
|
||||
$" -o {TemplateOutputDir}");
|
||||
await result.Exited;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DotNetNewLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeleteOutputDirectory();
|
||||
}
|
||||
|
||||
public void DeleteOutputDirectory()
|
||||
{
|
||||
const int NumAttempts = 10;
|
||||
|
||||
for (var numAttemptsRemaining = NumAttempts; numAttemptsRemaining > 0; numAttemptsRemaining--)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(TemplateOutputDir, true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (numAttemptsRemaining > 1)
|
||||
{
|
||||
DiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"Failed to delete directory {TemplateOutputDir} because of error {ex.Message}. Will try again {numAttemptsRemaining - 1} more time(s)."));
|
||||
Thread.Sleep(3000);
|
||||
}
|
||||
else
|
||||
{
|
||||
DiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"Giving up trying to delete directory {TemplateOutputDir} after {NumAttempts} attempts. Most recent error was: {ex.StackTrace}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OrderedLock
|
||||
{
|
||||
private bool _nodeLockTaken;
|
||||
private bool _dotNetLockTaken;
|
||||
|
||||
public OrderedLock(SemaphoreSlim nodeLock, SemaphoreSlim dotnetLock)
|
||||
{
|
||||
NodeLock = nodeLock;
|
||||
DotnetLock = dotnetLock;
|
||||
}
|
||||
|
||||
public SemaphoreSlim NodeLock { get; }
|
||||
public SemaphoreSlim DotnetLock { get; }
|
||||
|
||||
public async Task WaitAsync()
|
||||
{
|
||||
if (NodeLock == null)
|
||||
{
|
||||
await DotnetLock.WaitAsync();
|
||||
_dotNetLockTaken = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// We want to take the NPM lock first as is going to be the busiest one, and we want other threads to be
|
||||
// able to run dotnet new while we are waiting for another thread to finish running NPM.
|
||||
await NodeLock.WaitAsync();
|
||||
_nodeLockTaken = true;
|
||||
await DotnetLock.WaitAsync();
|
||||
_dotNetLockTaken = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (_nodeLockTaken)
|
||||
{
|
||||
NodeLock.Release();
|
||||
_nodeLockTaken = false;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_dotNetLockTaken)
|
||||
{
|
||||
|
||||
DotnetLock.Release();
|
||||
_dotNetLockTaken = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_nodeLockTaken)
|
||||
{
|
||||
NodeLock.Release();
|
||||
_nodeLockTaken = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,61 +8,55 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ProjectTemplates.Tests.Helpers
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
public class ProjectFactoryFixture : IDisposable
|
||||
{
|
||||
private static object DotNetNewLock = new object();
|
||||
private static SemaphoreSlim DotNetNewLock = new SemaphoreSlim(1);
|
||||
private static SemaphoreSlim NodeLock = new SemaphoreSlim(1);
|
||||
|
||||
private ConcurrentBag<Project> _projects = new ConcurrentBag<Project>();
|
||||
private ConcurrentDictionary<string, Project> _projects = new ConcurrentDictionary<string, Project>();
|
||||
|
||||
public Project CreateProject(ITestOutputHelper output)
|
||||
public IMessageSink DiagnosticsMessageSink { get; }
|
||||
|
||||
public ProjectFactoryFixture(IMessageSink diagnosticsMessageSink)
|
||||
{
|
||||
TemplatePackageInstaller.EnsureTemplatingEngineInitialized(output);
|
||||
var project = new Project
|
||||
{
|
||||
DotNetNewLock = DotNetNewLock,
|
||||
Output = output,
|
||||
ProjectGuid = Guid.NewGuid().ToString("N").Substring(0, 6)
|
||||
};
|
||||
project.ProjectName = $"AspNet.Template.{project.ProjectGuid}";
|
||||
|
||||
_projects.Add(project);
|
||||
|
||||
var assemblyPath = GetType().GetTypeInfo().Assembly.CodeBase;
|
||||
var assemblyUri = new Uri(assemblyPath, UriKind.Absolute);
|
||||
var basePath = Path.GetDirectoryName(assemblyUri.LocalPath);
|
||||
project.TemplateOutputDir = Path.Combine(basePath, "TestTemplates", project.ProjectName);
|
||||
Directory.CreateDirectory(project.TemplateOutputDir);
|
||||
|
||||
// We don't want any of the host repo's build config interfering with
|
||||
// how the test project is built, so disconnect it from the
|
||||
// Directory.Build.props/targets context
|
||||
|
||||
var templatesTestsPropsFilePath = Path.Combine(basePath, "TemplateTests.props");
|
||||
var directoryBuildPropsContent =
|
||||
$@"<Project>
|
||||
<Import Project=""Directory.Build.After.props"" Condition=""Exists('Directory.Build.After.props')"" />
|
||||
</Project>";
|
||||
File.WriteAllText(Path.Combine(project.TemplateOutputDir, "Directory.Build.props"), directoryBuildPropsContent);
|
||||
|
||||
// TODO: remove this once we get a newer version of the SDK which supports an implicit FrameworkReference
|
||||
// cref https://github.com/aspnet/websdk/issues/424
|
||||
var directoryBuildTargetsContent =
|
||||
$@"<Project>
|
||||
<Import Project=""{templatesTestsPropsFilePath}"" />
|
||||
</Project>";
|
||||
|
||||
File.WriteAllText(Path.Combine(project.TemplateOutputDir, "Directory.Build.targets"), directoryBuildTargetsContent);
|
||||
|
||||
return project;
|
||||
DiagnosticsMessageSink = diagnosticsMessageSink;
|
||||
}
|
||||
|
||||
public async Task<Project> GetOrCreateProject(string projectKey, ITestOutputHelper output)
|
||||
{
|
||||
await TemplatePackageInstaller.EnsureTemplatingEngineInitializedAsync(output);
|
||||
return _projects.GetOrAdd(
|
||||
projectKey,
|
||||
(key, outputHelper) =>
|
||||
{
|
||||
var project = new Project
|
||||
{
|
||||
DotNetNewLock = DotNetNewLock,
|
||||
NodeLock = NodeLock,
|
||||
Output = outputHelper,
|
||||
DiagnosticsMessageSink = DiagnosticsMessageSink,
|
||||
ProjectGuid = Guid.NewGuid().ToString("N").Substring(0, 6)
|
||||
};
|
||||
project.ProjectName = $"AspNet.{key}.{project.ProjectGuid}";
|
||||
|
||||
var assemblyPath = GetType().Assembly;
|
||||
string basePath = GetTemplateFolderBasePath(assemblyPath);
|
||||
project.TemplateOutputDir = Path.Combine(basePath, project.ProjectName);
|
||||
return project;
|
||||
},
|
||||
output);
|
||||
}
|
||||
|
||||
private static string GetTemplateFolderBasePath(Assembly assembly) =>
|
||||
assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.Single(a => a.Key == "TestTemplateCreationFolder")
|
||||
.Value;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var list = new List<Exception>();
|
||||
|
|
@ -70,9 +64,9 @@ $@"<Project>
|
|||
{
|
||||
try
|
||||
{
|
||||
project.Dispose();
|
||||
project.Value.Dispose();
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
list.Add(e);
|
||||
}
|
||||
|
|
@ -84,171 +78,4 @@ $@"<Project>
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Project
|
||||
{
|
||||
public string ProjectName { get; set; }
|
||||
public string ProjectGuid { get; set; }
|
||||
public string TemplateOutputDir { get; set; }
|
||||
public ITestOutputHelper Output { get; set; }
|
||||
public object DotNetNewLock { get; set; }
|
||||
|
||||
public void RunDotNetNew(string templateName, string auth = null, string language = null, bool useLocalDB = false, bool noHttps = false)
|
||||
{
|
||||
var args = $"new {templateName} --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"";
|
||||
|
||||
if (!string.IsNullOrEmpty(auth))
|
||||
{
|
||||
args += $" --auth {auth}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(language))
|
||||
{
|
||||
args += $" -lang {language}";
|
||||
}
|
||||
|
||||
if (useLocalDB)
|
||||
{
|
||||
args += $" --use-local-db";
|
||||
}
|
||||
|
||||
if (noHttps)
|
||||
{
|
||||
args += $" --no-https";
|
||||
}
|
||||
|
||||
// Only run one instance of 'dotnet new' at once, as a workaround for
|
||||
// https://github.com/aspnet/templating/issues/63
|
||||
lock (DotNetNewLock)
|
||||
{
|
||||
ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), args).WaitForExit(assertSuccess: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void RunDotNet(string arguments)
|
||||
{
|
||||
lock (DotNetNewLock)
|
||||
{
|
||||
ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), arguments + $" --debug:custom-hive \"{TemplatePackageInstaller.CustomHivePath}\"").WaitForExit(assertSuccess: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void RunDotNetEfCreateMigration(string migrationName)
|
||||
{
|
||||
var assembly = typeof(ProjectFactoryFixture).Assembly;
|
||||
|
||||
var dotNetEfFullPath = assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.First(attribute => attribute.Key == "DotNetEfFullPath")
|
||||
.Value;
|
||||
|
||||
var args = $"\"{dotNetEfFullPath}\" --verbose migrations add {migrationName}";
|
||||
|
||||
// Only run one instance of 'dotnet new' at once, as a workaround for
|
||||
// https://github.com/aspnet/templating/issues/63
|
||||
lock (DotNetNewLock)
|
||||
{
|
||||
ProcessEx.Run(Output, TemplateOutputDir, DotNetMuxer.MuxerPathOrDefault(), args).WaitForExit(assertSuccess: true);
|
||||
}
|
||||
}
|
||||
|
||||
public void AssertDirectoryExists(string path, bool shouldExist)
|
||||
{
|
||||
var fullPath = Path.Combine(TemplateOutputDir, path);
|
||||
var doesExist = Directory.Exists(fullPath);
|
||||
|
||||
if (shouldExist)
|
||||
{
|
||||
Assert.True(doesExist, "Expected directory to exist, but it doesn't: " + path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(doesExist, "Expected directory not to exist, but it does: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
// If this fails, you should generate new migrations via migrations/updateMigrations.cmd
|
||||
public void AssertEmptyMigration(string migration)
|
||||
{
|
||||
var fullPath = Path.Combine(TemplateOutputDir, "Data/Migrations");
|
||||
var file = Directory.EnumerateFiles(fullPath).Where(f => f.EndsWith($"{migration}.cs")).FirstOrDefault();
|
||||
|
||||
Assert.NotNull(file);
|
||||
var contents = File.ReadAllText(file);
|
||||
|
||||
var emptyMigration = @"protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}";
|
||||
|
||||
// This comparison can break depending on how GIT checked out newlines on different files.
|
||||
Assert.Contains(RemoveNewLines(emptyMigration), RemoveNewLines(contents));
|
||||
}
|
||||
|
||||
private static string RemoveNewLines(string str)
|
||||
{
|
||||
return str.Replace("\n", string.Empty).Replace("\r", string.Empty);
|
||||
}
|
||||
|
||||
public void AssertFileExists(string path, bool shouldExist)
|
||||
{
|
||||
var fullPath = Path.Combine(TemplateOutputDir, 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);
|
||||
}
|
||||
}
|
||||
|
||||
public string ReadFile(string path)
|
||||
{
|
||||
AssertFileExists(path, shouldExist: true);
|
||||
return File.ReadAllText(Path.Combine(TemplateOutputDir, path));
|
||||
}
|
||||
|
||||
public AspNetProcess StartAspNetProcess(bool publish = false)
|
||||
{
|
||||
return new AspNetProcess(Output, TemplateOutputDir, ProjectName, publish);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DeleteOutputDirectory();
|
||||
}
|
||||
|
||||
public void DeleteOutputDirectory()
|
||||
{
|
||||
const int NumAttempts = 10;
|
||||
|
||||
for (var numAttemptsRemaining = NumAttempts; numAttemptsRemaining > 0; numAttemptsRemaining--)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(TemplateOutputDir, true);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (numAttemptsRemaining > 1)
|
||||
{
|
||||
Output.WriteLine($"Failed to delete directory {TemplateOutputDir} because of error {ex.Message}. Will try again {numAttemptsRemaining - 1} more time(s).");
|
||||
Thread.Sleep(3000);
|
||||
}
|
||||
else
|
||||
{
|
||||
Output.WriteLine($"Giving up trying to delete directory {TemplateOutputDir} after {NumAttempts} attempts. Most recent error was: {ex.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,36 +4,20 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
internal class NullTestOutputHelper : ITestOutputHelper
|
||||
{
|
||||
public bool Throw { get; set; }
|
||||
|
||||
public string Output => null;
|
||||
|
||||
public void WriteLine(string message)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public void WriteLine(string format, params object[] args)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TemplatePackageInstaller
|
||||
{
|
||||
private static object _templatePackagesReinstallationLock = new object();
|
||||
private static SemaphoreSlim InstallerLock = new SemaphoreSlim(1);
|
||||
private static bool _haveReinstalledTemplatePackages;
|
||||
|
||||
private static object DotNetNewLock = new object();
|
||||
|
||||
private static readonly string[] _templatePackages = new[]
|
||||
{
|
||||
"Microsoft.DotNet.Common.ItemTemplates",
|
||||
|
|
@ -51,11 +35,14 @@ namespace Templates.Test.Helpers
|
|||
"Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0"
|
||||
};
|
||||
|
||||
public static string CustomHivePath { get; } = Path.Combine(AppContext.BaseDirectory, ".templateengine");
|
||||
public static string CustomHivePath { get; } = typeof(TemplatePackageInstaller)
|
||||
.Assembly.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.Single(s => s.Key == "CustomTemplateHivePath").Value;
|
||||
|
||||
public static void EnsureTemplatingEngineInitialized(ITestOutputHelper output)
|
||||
public static async Task EnsureTemplatingEngineInitializedAsync(ITestOutputHelper output)
|
||||
{
|
||||
lock (_templatePackagesReinstallationLock)
|
||||
await InstallerLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!_haveReinstalledTemplatePackages)
|
||||
{
|
||||
|
|
@ -63,68 +50,78 @@ namespace Templates.Test.Helpers
|
|||
{
|
||||
Directory.Delete(CustomHivePath, recursive: true);
|
||||
}
|
||||
InstallTemplatePackages(output);
|
||||
await InstallTemplatePackages(output);
|
||||
_haveReinstalledTemplatePackages = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ProcessEx RunDotNetNew(ITestOutputHelper output, string arguments, bool assertSuccess)
|
||||
{
|
||||
lock (DotNetNewLock)
|
||||
finally
|
||||
{
|
||||
var proc = ProcessEx.Run(
|
||||
output,
|
||||
AppContext.BaseDirectory,
|
||||
DotNetMuxer.MuxerPathOrDefault(),
|
||||
$"new {arguments} --debug:custom-hive \"{CustomHivePath}\"");
|
||||
proc.WaitForExit(assertSuccess);
|
||||
|
||||
return proc;
|
||||
InstallerLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallTemplatePackages(ITestOutputHelper output)
|
||||
public static async Task<ProcessEx> RunDotNetNew(ITestOutputHelper output, string arguments)
|
||||
{
|
||||
var proc = ProcessEx.Run(
|
||||
output,
|
||||
AppContext.BaseDirectory,
|
||||
DotNetMuxer.MuxerPathOrDefault(),
|
||||
$"new {arguments} --debug:custom-hive \"{CustomHivePath}\"");
|
||||
await proc.Exited;
|
||||
|
||||
return proc;
|
||||
}
|
||||
|
||||
private static async Task InstallTemplatePackages(ITestOutputHelper output)
|
||||
{
|
||||
var builtPackages = Directory.EnumerateFiles(
|
||||
typeof(TemplatePackageInstaller).Assembly
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.Single(a => a.Key == "ArtifactsShippingPackagesDir").Value,
|
||||
"*.nupkg")
|
||||
.Where(p => _templatePackages.Any(t => Path.GetFileName(p).StartsWith(t, StringComparison.OrdinalIgnoreCase)))
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(4, builtPackages.Length);
|
||||
|
||||
// Remove any previous or prebundled version of the template packages
|
||||
foreach (var packageName in _templatePackages)
|
||||
{
|
||||
// We don't need this command to succeed, because we'll verify next that
|
||||
// uninstallation had the desired effect. This command is expected to fail
|
||||
// in the case where the package wasn't previously installed.
|
||||
RunDotNetNew(new NullTestOutputHelper(), $"--uninstall {packageName}", assertSuccess: false);
|
||||
await RunDotNetNew(output, $"--uninstall {packageName}");
|
||||
}
|
||||
|
||||
VerifyCannotFindTemplate(output, "web");
|
||||
VerifyCannotFindTemplate(output, "webapp");
|
||||
VerifyCannotFindTemplate(output, "mvc");
|
||||
VerifyCannotFindTemplate(output, "react");
|
||||
VerifyCannotFindTemplate(output, "reactredux");
|
||||
VerifyCannotFindTemplate(output, "angular");
|
||||
await VerifyCannotFindTemplateAsync(output, "web");
|
||||
await VerifyCannotFindTemplateAsync(output, "webapp");
|
||||
await VerifyCannotFindTemplateAsync(output, "mvc");
|
||||
await VerifyCannotFindTemplateAsync(output, "react");
|
||||
await VerifyCannotFindTemplateAsync(output, "reactredux");
|
||||
await VerifyCannotFindTemplateAsync(output, "angular");
|
||||
|
||||
var builtPackages = MondoHelpers.GetNupkgFiles();
|
||||
var templatePackages = builtPackages.Where(b => _templatePackages.Any(t => Path.GetFileName(b).StartsWith(t, StringComparison.OrdinalIgnoreCase)));
|
||||
Assert.Equal(4, templatePackages.Count());
|
||||
foreach (var packagePath in templatePackages)
|
||||
foreach (var packagePath in builtPackages)
|
||||
{
|
||||
output.WriteLine($"Installing templates package {packagePath}...");
|
||||
RunDotNetNew(output, $"--install \"{packagePath}\"", assertSuccess: true);
|
||||
var result = await RunDotNetNew(output, $"--install \"{packagePath}\"");
|
||||
Assert.True(result.ExitCode == 0, result.GetFormattedOutput());
|
||||
}
|
||||
VerifyCanFindTemplate(output, "webapp");
|
||||
VerifyCanFindTemplate(output, "web");
|
||||
VerifyCanFindTemplate(output, "react");
|
||||
|
||||
await VerifyCanFindTemplate(output, "webapp");
|
||||
await VerifyCanFindTemplate(output, "web");
|
||||
await VerifyCanFindTemplate(output, "react");
|
||||
}
|
||||
|
||||
private static void VerifyCanFindTemplate(ITestOutputHelper output, string templateName)
|
||||
private static async Task VerifyCanFindTemplate(ITestOutputHelper output, string templateName)
|
||||
{
|
||||
var proc = RunDotNetNew(output, $"", assertSuccess: false);
|
||||
var proc = await RunDotNetNew(output, $"");
|
||||
if (!proc.Output.Contains($" {templateName} "))
|
||||
{
|
||||
throw new InvalidOperationException($"Couldn't find {templateName} as an option in {proc.Output}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void VerifyCannotFindTemplate(ITestOutputHelper output, string templateName)
|
||||
private static async Task VerifyCannotFindTemplateAsync(ITestOutputHelper output, string templateName)
|
||||
{
|
||||
// Verify we really did remove the previous templates
|
||||
var tempDir = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName(), Guid.NewGuid().ToString("D"));
|
||||
|
|
@ -132,7 +129,7 @@ namespace Templates.Test.Helpers
|
|||
|
||||
try
|
||||
{
|
||||
var proc = RunDotNetNew(output, $"\"{templateName}\"", assertSuccess: false);
|
||||
var proc = await RunDotNetNew(output, $"\"{templateName}\"");
|
||||
|
||||
if (!proc.Error.Contains($"No templates matched the input template name: {templateName}."))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
// 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 OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
|
|
@ -8,6 +11,12 @@ namespace Templates.Test.Helpers
|
|||
{
|
||||
public static class WebDriverExtensions
|
||||
{
|
||||
// Maximum time any action performed by WebDriver will wait before failing.
|
||||
// Any action will have to be completed in at most 10 seconds.
|
||||
// Providing a smaller value won't improve the speed of the tests in any
|
||||
// significant way and will make them more prone to fail on slower drivers.
|
||||
internal const int DefaultMaxWaitTimeInSeconds = 10;
|
||||
|
||||
public static string GetText(this ISearchContext driver, string cssSelector)
|
||||
{
|
||||
return driver.FindElement(By.CssSelector(cssSelector)).Text;
|
||||
|
|
@ -27,10 +36,6 @@ namespace Templates.Test.Helpers
|
|||
.Perform();
|
||||
}
|
||||
|
||||
public static void Click(this IWebDriver driver, string cssSelector)
|
||||
{
|
||||
Click(driver, null, cssSelector);
|
||||
}
|
||||
|
||||
public static void Click(this IWebDriver driver, ISearchContext searchContext, string cssSelector)
|
||||
{
|
||||
|
|
@ -47,21 +52,11 @@ namespace Templates.Test.Helpers
|
|||
return webElement.FindElement(By.XPath(".."));
|
||||
}
|
||||
|
||||
public static IWebElement FindElement(this IWebDriver driver, string cssSelector, int timeoutSeconds)
|
||||
{
|
||||
return FindElement(driver, null, cssSelector, timeoutSeconds);
|
||||
}
|
||||
|
||||
public static IWebElement FindElement(this IWebDriver driver, ISearchContext searchContext, string cssSelector, int timeoutSeconds)
|
||||
{
|
||||
return FindElement(driver, searchContext, By.CssSelector(cssSelector), timeoutSeconds);
|
||||
}
|
||||
|
||||
public static IWebElement FindElement(this IWebDriver driver, By by, int timeoutSeconds)
|
||||
{
|
||||
return FindElement(driver, null, by, timeoutSeconds);
|
||||
}
|
||||
|
||||
public static IWebElement FindElement(this IWebDriver driver, ISearchContext searchContext, By by, int timeoutSeconds)
|
||||
{
|
||||
return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
|
||||
|
|
@ -70,19 +65,19 @@ namespace Templates.Test.Helpers
|
|||
|
||||
public static void WaitForUrl(this IWebDriver browser, string expectedUrl)
|
||||
{
|
||||
new WebDriverWait(browser, TimeSpan.FromSeconds(WebDriverFactory.DefaultMaxWaitTimeInSeconds))
|
||||
new WebDriverWait(browser, TimeSpan.FromSeconds(DefaultMaxWaitTimeInSeconds))
|
||||
.Until(driver => driver.Url.Contains(expectedUrl, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public static void WaitForElement(this IWebDriver browser, string expectedElementCss)
|
||||
{
|
||||
new WebDriverWait(browser, TimeSpan.FromSeconds(WebDriverFactory.DefaultMaxWaitTimeInSeconds))
|
||||
new WebDriverWait(browser, TimeSpan.FromSeconds(DefaultMaxWaitTimeInSeconds))
|
||||
.Until(driver => driver.FindElements(By.CssSelector(expectedElementCss)).Count > 0);
|
||||
}
|
||||
|
||||
public static void WaitForText(this IWebDriver browser, string cssSelector, string expectedText)
|
||||
{
|
||||
new WebDriverWait(browser, TimeSpan.FromSeconds(WebDriverFactory.DefaultMaxWaitTimeInSeconds))
|
||||
new WebDriverWait(browser, TimeSpan.FromSeconds(DefaultMaxWaitTimeInSeconds))
|
||||
.Until(driver => {
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Templates.Test.Helpers
|
||||
{
|
||||
public static class WebDriverFactory
|
||||
{
|
||||
// Maximum time any action performed by WebDriver will wait before failing.
|
||||
// Any action will have to be completed in at most 10 seconds.
|
||||
// Providing a smaller value won't improve the speed of the tests in any
|
||||
// significant way and will make them more prone to fail on slower drivers.
|
||||
internal const int DefaultMaxWaitTimeInSeconds = 10;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Project>
|
||||
<!-- This file gets copied above the template test projects so that we disconnect the templates from the rest of the repository -->
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Project>
|
||||
<Import Project="${TemplateTestsPropsPath}" />
|
||||
</Project>
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
<Project>
|
||||
<Target Name="GenerateTestProps" BeforeTargets="CoreCompile">
|
||||
<Target
|
||||
Name="GenerateTestProps"
|
||||
BeforeTargets="CoreCompile"
|
||||
DependsOnTargets="PrepareForTest"
|
||||
Condition="$(DesignTimeBuild) != true">
|
||||
<PropertyGroup>
|
||||
<PropsProperties>
|
||||
RestoreSources=$([MSBuild]::Escape("$(RestoreSources);$(ArtifactsShippingPackagesDir);$(ArtifactsNonShippingPackagesDir)"));
|
||||
|
|
@ -12,8 +16,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<Sdk_GenerateFileFromTemplate
|
||||
TemplateFile="$(MSBuildThisFileDirectory)TemplateTests.props.in"
|
||||
TemplateFile="$(MSBuildThisFileDirectory)\TemplateTests.props.in"
|
||||
Properties="$(PropsProperties)"
|
||||
OutputPath="$(OutputPath)TemplateTests.props" />
|
||||
OutputPath="$(TestTemplateTestsProps)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
// 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 ProjectTemplates.Tests.Helpers;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -11,71 +13,152 @@ namespace Templates.Test
|
|||
{
|
||||
public MvcTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
||||
{
|
||||
Project = projectFactory.CreateProject(output);
|
||||
ProjectFactory = projectFactory;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
public Project Project { get; }
|
||||
public Project Project { get; set; }
|
||||
|
||||
public ProjectFactoryFixture ProjectFactory { get; }
|
||||
public ITestOutputHelper Output { get; }
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("F#")]
|
||||
private void MvcTemplate_NoAuthImpl(string languageOverride)
|
||||
public async Task MvcTemplate_NoAuthImplAsync(string languageOverride)
|
||||
{
|
||||
Project.RunDotNetNew("mvc", language: languageOverride);
|
||||
Project = await ProjectFactory.GetOrCreateProject("mvcnoauth" + (languageOverride == "F#" ? "fsharp" : "csharp"), Output);
|
||||
|
||||
Project.AssertDirectoryExists("Areas", false);
|
||||
Project.AssertDirectoryExists("Extensions", false);
|
||||
Project.AssertFileExists("urlRewrite.config", false);
|
||||
Project.AssertFileExists("Controllers/AccountController.cs", false);
|
||||
var createResult = await Project.RunDotNetNewAsync("mvc", language: languageOverride);
|
||||
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||
|
||||
AssertDirectoryExists(Project.TemplateOutputDir, "Areas", false);
|
||||
AssertDirectoryExists(Project.TemplateOutputDir, "Extensions", false);
|
||||
AssertFileExists(Project.TemplateOutputDir, "urlRewrite.config", false);
|
||||
AssertFileExists(Project.TemplateOutputDir, "Controllers/AccountController.cs", false);
|
||||
|
||||
var projectExtension = languageOverride == "F#" ? "fsproj" : "csproj";
|
||||
var projectFileContents = Project.ReadFile($"{Project.ProjectName}.{projectExtension}");
|
||||
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.{projectExtension}");
|
||||
Assert.DoesNotContain(".db", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", projectFileContents);
|
||||
|
||||
foreach (var publish in new[] { false, true })
|
||||
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/netcoreapp3.0/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));
|
||||
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
{
|
||||
aspNetProcess.AssertOk("/");
|
||||
aspNetProcess.AssertOk("/Home/Privacy");
|
||||
}
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Home/Privacy");
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Home/Privacy");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void MvcTemplate_IndividualAuthImpl(bool useLocalDB)
|
||||
public async Task MvcTemplate_IndividualAuthImplAsync(bool useLocalDB)
|
||||
{
|
||||
Project.RunDotNetNew("mvc", auth: "Individual", useLocalDB: useLocalDB);
|
||||
Project = await ProjectFactory.GetOrCreateProject("mvcindividual" + (useLocalDB ? "uld" : ""), Output);
|
||||
|
||||
Project.AssertDirectoryExists("Extensions", false);
|
||||
Project.AssertFileExists("urlRewrite.config", false);
|
||||
Project.AssertFileExists("Controllers/AccountController.cs", false);
|
||||
var createResult = await Project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB);
|
||||
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||
|
||||
var projectFileContents = Project.ReadFile($"{Project.ProjectName}.csproj");
|
||||
AssertDirectoryExists(Project.TemplateOutputDir, "Extensions", false);
|
||||
AssertFileExists(Project.TemplateOutputDir, "urlRewrite.config", false);
|
||||
AssertFileExists(Project.TemplateOutputDir, "Controllers/AccountController.cs", false);
|
||||
|
||||
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj");
|
||||
if (!useLocalDB)
|
||||
{
|
||||
Assert.Contains(".db", projectFileContents);
|
||||
}
|
||||
|
||||
Project.RunDotNetEfCreateMigration("mvc");
|
||||
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/netcoreapp3.0/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 migrationsResult = await Project.RunDotNetEfCreateMigrationAsync("mvc");
|
||||
Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", Project, migrationsResult));
|
||||
Project.AssertEmptyMigration("mvc");
|
||||
|
||||
foreach (var publish in new[] { false, true })
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
{
|
||||
aspNetProcess.AssertOk("/");
|
||||
aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||
aspNetProcess.AssertOk("/Home/Privacy");
|
||||
}
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||
await aspNetProcess.AssertOk("/Home/Privacy");
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||
await aspNetProcess.AssertOk("/Home/Privacy");
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertDirectoryExists(string basePath, string path, bool shouldExist)
|
||||
{
|
||||
var fullPath = Path.Combine(basePath, path);
|
||||
var doesExist = Directory.Exists(fullPath);
|
||||
|
||||
if (shouldExist)
|
||||
{
|
||||
Assert.True(doesExist, "Expected directory to exist, but it doesn't: " + path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(doesExist, "Expected directory not to exist, but it does: " + path);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@
|
|||
<SkipTests Condition="'$(RunTemplateTests)' != 'true'">true</SkipTests>
|
||||
<!-- https://github.com/aspnet/AspNetCore/issues/6857 -->
|
||||
<BuildHelixPayload>false</BuildHelixPayload>
|
||||
|
||||
<!-- Properties that affect test runs -->
|
||||
<!-- TestTemplateCreationFolder is the folder where the templates will be created. Will point out to $(OutputDir)$(TestTemplateCreationFolder) -->
|
||||
<TestTemplateCreationFolder>TestTemplates</TestTemplateCreationFolder>
|
||||
<TestPackageRestorePath>$([MSBuild]::EnsureTrailingSlash('$(RepositoryRoot)'))obj\template-restore\</TestPackageRestorePath>
|
||||
<TestTemplateTestsProps>TemplateTests.props</TestTemplateTestsProps>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -45,10 +51,81 @@
|
|||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>TestPackageRestorePath</_Parameter1>
|
||||
<_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(RepositoryRoot)'))obj\template-restore\</_Parameter2>
|
||||
<_Parameter2>$(TestPackageRestorePath)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PrepareForTest" BeforeTargets="CoreCompile" Condition="$(DesignTimeBuild) != true">
|
||||
<PropertyGroup>
|
||||
<TestTemplateCreationFolder>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)$([MSBuild]::EnsureTrailingSlash('$(OutputPath)$(TestTemplateCreationFolder)'))'))</TestTemplateCreationFolder>
|
||||
<TestTemplateTestsProps>$(TestTemplateCreationFolder)$(TestTemplateTestsProps)</TestTemplateTestsProps>
|
||||
<CustomTemplateHivePath>$(TestTemplateCreationFolder)\Hives\$([System.Guid]::NewGuid())\.templateengine</CustomTemplateHivePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>ArtifactsShippingPackagesDir</_Parameter1>
|
||||
<_Parameter2>$(ArtifactsShippingPackagesDir)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>ArtifactsNonShippingPackagesDir</_Parameter1>
|
||||
<_Parameter2>$(ArtifactsNonShippingPackagesDir)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>TestTemplateCreationFolder</_Parameter1>
|
||||
<_Parameter2>$(TestTemplateCreationFolder)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>CustomTemplateHivePath</_Parameter1>
|
||||
<_Parameter2>$(CustomTemplateHivePath)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
<Message Importance="high" Text="Preparing environment for tests" />
|
||||
<!-- Remove the template creation folders and the package-restore folders to ensure that when we run the tests we don't
|
||||
get cached results and changes show up.
|
||||
-->
|
||||
|
||||
<ItemGroup>
|
||||
<_ExistingFilesFromLastRun Include="$(TestTemplateCreationFolder)**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Delete Files="@(_ExistingFilesFromLastRun)" ContinueOnError="true" />
|
||||
|
||||
<Removedir Directories="$(TestTemplateCreationFolder)" Condition="Exists('$(TestTemplateCreationFolder)')" ContinueOnError="true">
|
||||
<Output TaskParameter="RemovedDirectories" ItemName="_CleanedUpDirectories" />
|
||||
</Removedir>
|
||||
<Removedir Directories="$(TestPackageRestorePath)" Condition="Exists('$(TestPackageRestorePath)')" ContinueOnError="true">
|
||||
<Output TaskParameter="RemovedDirectories" ItemName="_CleanedUpDirectories" />
|
||||
</Removedir>
|
||||
|
||||
<Message Importance="high" Text="Removed directory %(_CleanedUpDirectories.Identity)" />
|
||||
|
||||
<MakeDir Directories="$(TestTemplateCreationFolder)">
|
||||
<Output TaskParameter="DirectoriesCreated" ItemName="_CreatedDirectories" />
|
||||
</MakeDir>
|
||||
<MakeDir Directories="$(TestPackageRestorePath)">
|
||||
<Output TaskParameter="DirectoriesCreated" ItemName="_CreatedDirectories" />
|
||||
</MakeDir>
|
||||
|
||||
<Message Importance="high" Text="Created directory %(_CreatedDirectories.Identity)" />
|
||||
|
||||
<Sdk_GenerateFileFromTemplate
|
||||
TemplateFile="$(MSBuildThisFileDirectory)Infrastructure\Directory.Build.targets.in"
|
||||
Properties="TemplateTestsPropsPath=$(TestTemplateTestsProps)"
|
||||
OutputPath="$(TestTemplateCreationFolder)Directory.Build.targets" />
|
||||
|
||||
<Sdk_GenerateFileFromTemplate
|
||||
TemplateFile="$(MSBuildThisFileDirectory)Infrastructure\Directory.Build.props.in"
|
||||
Properties=""
|
||||
OutputPath="$(TestTemplateCreationFolder)Directory.Build.props" />
|
||||
|
||||
<Delete Files="$(TestTemplateTestsProps)" />
|
||||
|
||||
</Target>
|
||||
|
||||
<!-- Shared testing infrastructure for running E2E tests using selenium -->
|
||||
<Import Project="$(SharedSourceRoot)E2ETesting\E2ETesting.targets" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +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.
|
||||
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using OpenQA.Selenium;
|
||||
using ProjectTemplates.Tests.Helpers;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
|
@ -18,25 +16,52 @@ namespace Templates.Test
|
|||
{
|
||||
public RazorComponentsTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output) : base(browserFixture, output)
|
||||
{
|
||||
Project = projectFactory.CreateProject(output);
|
||||
ProjectFactory = projectFactory;
|
||||
}
|
||||
|
||||
public Project Project { get; }
|
||||
public ProjectFactoryFixture ProjectFactory { get; set; }
|
||||
|
||||
public Project Project { get; private set; }
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8244")]
|
||||
public void RazorComponentsTemplateWorks()
|
||||
public async Task RazorComponentsTemplateWorksAsync()
|
||||
{
|
||||
Project.RunDotNetNew("razorcomponents");
|
||||
TestApplication(publish: false);
|
||||
TestApplication(publish: true);
|
||||
}
|
||||
Project = await ProjectFactory.GetOrCreateProject("razorcomponents", Output);
|
||||
|
||||
private void TestApplication(bool publish)
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
var createResult = await Project.RunDotNetNewAsync("razorcomponents");
|
||||
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/netcoreapp3.0/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));
|
||||
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
|
||||
if (BrowserFixture.IsHostAutomationSupported())
|
||||
{
|
||||
aspNetProcess.VisitInBrowser(Browser);
|
||||
TestBasicNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
|
||||
if (BrowserFixture.IsHostAutomationSupported())
|
||||
{
|
||||
aspNetProcess.VisitInBrowser(Browser);
|
||||
|
|
@ -67,7 +92,7 @@ namespace Templates.Test
|
|||
var counterDisplay = Browser.FindElement("h1 + p");
|
||||
Assert.Equal("Current count: 0", counterDisplay.Text);
|
||||
Browser.Click(counterComponent, "button");
|
||||
WaitAssert.Equal("Current count: 1", () => Browser.FindElement("h1+p").Text);
|
||||
Browser.Equal("Current count: 1", () => Browser.FindElement("h1+p").Text);
|
||||
|
||||
// Can navigate to the 'fetch data' page
|
||||
Browser.Click(By.PartialLinkText("Fetch data"));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// 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.Testing.xunit;
|
||||
using ProjectTemplates.Tests.Helpers;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -12,63 +13,130 @@ namespace Templates.Test
|
|||
{
|
||||
public RazorPagesTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
||||
{
|
||||
Project = projectFactory.CreateProject(output);
|
||||
ProjectFactory = projectFactory;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
public Project Project { get; }
|
||||
public Project Project { get; set; }
|
||||
|
||||
public ProjectFactoryFixture ProjectFactory { get; set; }
|
||||
|
||||
public ITestOutputHelper Output { get; }
|
||||
|
||||
[Fact]
|
||||
private void RazorPagesTemplate_NoAuthImpl()
|
||||
public async Task RazorPagesTemplate_NoAuthImplAsync()
|
||||
{
|
||||
Project.RunDotNetNew("razor");
|
||||
Project = await ProjectFactory.GetOrCreateProject("razorpagesnoauth", Output);
|
||||
|
||||
Project.AssertFileExists("Pages/Shared/_LoginPartial.cshtml", false);
|
||||
var createResult = await Project.RunDotNetNewAsync("razor");
|
||||
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("razor", Project, createResult));
|
||||
|
||||
var projectFileContents = Project.ReadFile($"{Project.ProjectName}.csproj");
|
||||
AssertFileExists(Project.TemplateOutputDir, "Pages/Shared/_LoginPartial.cshtml", false);
|
||||
|
||||
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj");
|
||||
Assert.DoesNotContain(".db", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents);
|
||||
Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", projectFileContents);
|
||||
|
||||
foreach (var publish in new[] { false, true })
|
||||
var publishResult = await Project.RunDotNetPublishAsync();
|
||||
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, createResult));
|
||||
|
||||
// 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/netcoreapp3.0/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, createResult));
|
||||
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
{
|
||||
aspNetProcess.AssertOk("/");
|
||||
aspNetProcess.AssertOk("/Privacy");
|
||||
}
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Privacy");
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Privacy");
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void RazorPagesTemplate_IndividualAuthImpl( bool useLocalDB)
|
||||
public async Task RazorPagesTemplate_IndividualAuthImplAsync(bool useLocalDB)
|
||||
{
|
||||
Project.RunDotNetNew("razor", auth: "Individual", useLocalDB: useLocalDB);
|
||||
Project = await ProjectFactory.GetOrCreateProject("razorpagesindividual" + (useLocalDB ? "uld" : ""), Output);
|
||||
|
||||
Project.AssertFileExists("Pages/Shared/_LoginPartial.cshtml", true);
|
||||
var createResult = await Project.RunDotNetNewAsync("razor", auth: "Individual", useLocalDB: useLocalDB);
|
||||
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||
|
||||
var projectFileContents = Project.ReadFile($"{Project.ProjectName}.csproj");
|
||||
AssertFileExists(Project.TemplateOutputDir, "Pages/Shared/_LoginPartial.cshtml", true);
|
||||
|
||||
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj");
|
||||
if (!useLocalDB)
|
||||
{
|
||||
Assert.Contains(".db", projectFileContents);
|
||||
}
|
||||
|
||||
Project.RunDotNetEfCreateMigration("razorpages");
|
||||
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/netcoreapp3.0/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 migrationsResult = await Project.RunDotNetEfCreateMigrationAsync("razorpages");
|
||||
Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", Project, migrationsResult));
|
||||
Project.AssertEmptyMigration("razorpages");
|
||||
|
||||
foreach (var publish in new[] { false, true })
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
{
|
||||
aspNetProcess.AssertOk("/");
|
||||
aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||
aspNetProcess.AssertOk("/Privacy");
|
||||
}
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||
await aspNetProcess.AssertOk("/Privacy");
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
await aspNetProcess.AssertOk("/");
|
||||
await aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||
await aspNetProcess.AssertOk("/Privacy");
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// 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 ProjectTemplates.Tests.Helpers;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -13,8 +14,16 @@ namespace Templates.Test.SpaTemplateTest
|
|||
public AngularTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
|
||||
: base(projectFactory, browserFixture, output) { }
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1854")]
|
||||
public void AngularTemplate_Works()
|
||||
=> SpaTemplateImpl("angular");
|
||||
[Fact]
|
||||
public Task AngularTemplate_Works()
|
||||
=> SpaTemplateImplAsync("angularnoauth", "angular", useLocalDb: false, usesAuth: false);
|
||||
|
||||
[Fact]
|
||||
public Task AngularTemplate_IndividualAuth_Works()
|
||||
=> SpaTemplateImplAsync("angularindividual", "angular", useLocalDb: false, usesAuth: true);
|
||||
|
||||
[Fact]
|
||||
public Task AngularTemplate_IndividualAuth_Works_LocalDb()
|
||||
=> SpaTemplateImplAsync("angularindividualuld", "angular", useLocalDb: true, usesAuth: true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// 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 ProjectTemplates.Tests.Helpers;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -15,8 +16,8 @@ namespace Templates.Test.SpaTemplateTest
|
|||
{
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/7377")]
|
||||
public void ReactReduxTemplate_Works_NetCore()
|
||||
=> SpaTemplateImpl("reactredux");
|
||||
[Fact]
|
||||
public Task ReactReduxTemplate_Works_NetCore()
|
||||
=> SpaTemplateImplAsync("reactredux", "reactredux",useLocalDb: false, usesAuth: false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// 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 ProjectTemplates.Tests.Helpers;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -15,8 +16,16 @@ namespace Templates.Test.SpaTemplateTest
|
|||
{
|
||||
}
|
||||
|
||||
[Fact(Skip="This test is flaky. Using https://github.com/aspnet/AspNetCore-Internal/issues/1745 to track re-enabling this.")]
|
||||
public void ReactTemplate_Works_NetCore()
|
||||
=> SpaTemplateImpl("react");
|
||||
[Fact]
|
||||
public Task ReactTemplate_Works_NetCore()
|
||||
=> SpaTemplateImplAsync("reactnoauth", "react", useLocalDb: false, usesAuth: false);
|
||||
|
||||
[Fact]
|
||||
public Task ReactTemplate_IndividualAuth_NetCore()
|
||||
=> SpaTemplateImplAsync("reactindividual", "react", useLocalDb: false, usesAuth: true);
|
||||
|
||||
[Fact]
|
||||
public Task ReactTemplate_IndividualAuth_NetCore_LocalDb()
|
||||
=> SpaTemplateImplAsync("reactindividualuld", "react", useLocalDb: true, usesAuth: true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +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.
|
||||
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using OpenQA.Selenium;
|
||||
using ProjectTemplates.Tests.Helpers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
|
@ -14,7 +16,6 @@ using Xunit.Abstractions;
|
|||
#if EDGE
|
||||
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
|
||||
#endif
|
||||
[assembly: TestFramework("Microsoft.AspNetCore.E2ETesting.XunitTestFrameworkWithAssemblyFixture", "ProjectTemplates.Tests")]
|
||||
namespace Templates.Test.SpaTemplateTest
|
||||
{
|
||||
public class SpaTemplateTestBase : BrowserTestBase
|
||||
|
|
@ -22,49 +23,150 @@ namespace Templates.Test.SpaTemplateTest
|
|||
public SpaTemplateTestBase(
|
||||
ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output) : base(browserFixture, output)
|
||||
{
|
||||
Project = projectFactory.CreateProject(output);
|
||||
ProjectFactory = projectFactory;
|
||||
}
|
||||
|
||||
public Project Project { get; }
|
||||
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 void SpaTemplateImpl(string template, bool noHttps = false)
|
||||
protected async Task SpaTemplateImplAsync(
|
||||
string key,
|
||||
string template,
|
||||
bool useLocalDb = false,
|
||||
bool usesAuth = false)
|
||||
{
|
||||
Project.RunDotNetNew(template, noHttps: noHttps);
|
||||
Project = await ProjectFactory.GetOrCreateProject(key, Output);
|
||||
|
||||
// For some SPA templates, the NPM root directory is './ClientApp'. In other
|
||||
// templates it's at the project root. Strictly speaking 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 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");
|
||||
Assert.True(File.Exists(Path.Combine(clientAppSubdirPath, "package.json")), "Missing a package.json");
|
||||
|
||||
Npm.RestoreWithRetry(Output, clientAppSubdirPath);
|
||||
Npm.Test(Output, clientAppSubdirPath);
|
||||
|
||||
TestApplication(publish: false);
|
||||
TestApplication(publish: true);
|
||||
}
|
||||
|
||||
private void TestApplication(bool publish)
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj");
|
||||
if (usesAuth && !useLocalDb)
|
||||
{
|
||||
aspNetProcess.AssertStatusCode("/", HttpStatusCode.OK, "text/html");
|
||||
Assert.Contains(".db", projectFileContents);
|
||||
}
|
||||
|
||||
var npmRestoreResult = await Project.RestoreWithRetryAsync(Output, clientAppSubdirPath);
|
||||
Assert.True(0 == npmRestoreResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm restore", Project, npmRestoreResult));
|
||||
|
||||
var lintResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run lint");
|
||||
Assert.True(0 == lintResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run lint", Project, lintResult));
|
||||
|
||||
if (template == "react" || template == "reactredux")
|
||||
{
|
||||
var testResult = await ProcessEx.RunViaShellAsync(Output, clientAppSubdirPath, "npm run test");
|
||||
Assert.True(0 == testResult.ExitCode, ErrorMessages.GetFailedProcessMessage("npm run test", Project, testResult));
|
||||
}
|
||||
|
||||
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/netcoreapp3.0/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));
|
||||
|
||||
if (usesAuth)
|
||||
{
|
||||
var migrationsResult = await Project.RunDotNetEfCreateMigrationAsync(template);
|
||||
Assert.True(0 == migrationsResult.ExitCode, ErrorMessages.GetFailedProcessMessage("run EF migrations", Project, migrationsResult));
|
||||
Project.AssertEmptyMigration(template);
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
aspNetProcess.VisitInBrowser(Browser);
|
||||
TestBasicNavigation();
|
||||
TestBasicNavigation(visitFetchData: !usesAuth);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
aspNetProcess.VisitInBrowser(Browser);
|
||||
TestBasicNavigation(visitFetchData: !usesAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TestBasicNavigation()
|
||||
private static async Task WarmUpServer(AspNetProcess aspNetProcess)
|
||||
{
|
||||
var attempt = 0;
|
||||
var maxAttempts = 3;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
attempt++;
|
||||
var response = await aspNetProcess.SendRequest("/");
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromSeconds(5 * attempt));
|
||||
} while (attempt < maxAttempts);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Browser.WaitForElement("ul");
|
||||
// <title> element gets project ID injected into it during template execution
|
||||
|
|
@ -85,16 +187,40 @@ namespace Templates.Test.SpaTemplateTest
|
|||
Browser.Click(counterComponent, "button");
|
||||
Assert.Equal("1", counterComponent.GetText("strong"));
|
||||
|
||||
// Can navigate to the 'fetch data' page
|
||||
Browser.Click(By.PartialLinkText("Fetch data"));
|
||||
Browser.WaitForUrl("fetch-data");
|
||||
Assert.Equal("Weather forecast", Browser.GetText("h1"));
|
||||
if (visitFetchData)
|
||||
{
|
||||
// Can navigate to the 'fetch data' page
|
||||
Browser.Click(By.PartialLinkText("Fetch data"));
|
||||
Browser.WaitForUrl("fetch-data");
|
||||
Assert.Equal("Weather forecast", Browser.GetText("h1"));
|
||||
|
||||
// Asynchronously loads and displays the table of weather forecasts
|
||||
var fetchDataComponent = Browser.FindElement("h1").Parent();
|
||||
Browser.WaitForElement("table>tbody>tr");
|
||||
var table = Browser.FindElement(fetchDataComponent, "table", timeoutSeconds: 5);
|
||||
Assert.Equal(5, table.FindElements(By.CssSelector("tbody tr")).Count);
|
||||
// Asynchronously loads and displays the table of weather forecasts
|
||||
var fetchDataComponent = Browser.FindElement("h1").Parent();
|
||||
Browser.WaitForElement("table>tbody>tr");
|
||||
var table = Browser.FindElement(fetchDataComponent, "table", timeoutSeconds: 5);
|
||||
Assert.Equal(5, table.FindElements(By.CssSelector("tbody tr")).Count);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// 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 ProjectTemplates.Tests.Helpers;
|
||||
using System.Threading.Tasks;
|
||||
using Templates.Test.Helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
|
@ -11,23 +12,53 @@ namespace Templates.Test
|
|||
{
|
||||
public WebApiTemplateTest(ProjectFactoryFixture factoryFixture, ITestOutputHelper output)
|
||||
{
|
||||
Project = factoryFixture.CreateProject(output);
|
||||
FactoryFixture = factoryFixture;
|
||||
Output = output;
|
||||
}
|
||||
|
||||
public Project Project { get; }
|
||||
public ProjectFactoryFixture FactoryFixture { get; }
|
||||
|
||||
public ITestOutputHelper Output { get; }
|
||||
|
||||
public Project Project { get; set; }
|
||||
|
||||
[Fact]
|
||||
public void WebApiTemplate()
|
||||
public async Task WebApiTemplateAsync()
|
||||
{
|
||||
Project.RunDotNetNew("webapi");
|
||||
Project = await FactoryFixture.GetOrCreateProject("webapi", Output);
|
||||
|
||||
foreach (var publish in new[] { false, true })
|
||||
var createResult = await Project.RunDotNetNewAsync("webapi");
|
||||
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/netcoreapp3.0/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));
|
||||
|
||||
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||
{
|
||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
||||
{
|
||||
aspNetProcess.AssertOk("/api/values");
|
||||
aspNetProcess.AssertNotFound("/");
|
||||
}
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||
|
||||
await aspNetProcess.AssertOk("/api/values");
|
||||
await aspNetProcess.AssertNotFound("/");
|
||||
}
|
||||
|
||||
using (var aspNetProcess = Project.StartPublishedProjectAsync())
|
||||
{
|
||||
Assert.False(
|
||||
aspNetProcess.Process.HasExited,
|
||||
ErrorMessages.GetFailedProcessMessageOrEmpty("Run published project", Project, aspNetProcess.Process));
|
||||
|
||||
|
||||
await aspNetProcess.AssertOk("/api/values");
|
||||
await aspNetProcess.AssertNotFound("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,52 +4,25 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using OpenQA.Selenium.Remote;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.E2ETesting
|
||||
{
|
||||
public class BrowserFixture : IDisposable
|
||||
{
|
||||
private RemoteWebDriver _browser;
|
||||
private RemoteLogs _logs;
|
||||
|
||||
public BrowserFixture(IMessageSink diagnosticsMessageSink)
|
||||
{
|
||||
DiagnosticsMessageSink = diagnosticsMessageSink;
|
||||
|
||||
if (!IsHostAutomationSupported())
|
||||
{
|
||||
DiagnosticsMessageSink.OnMessage(new DiagnosticMessage("Host does not support browser automation."));
|
||||
return;
|
||||
}
|
||||
|
||||
var opts = new ChromeOptions();
|
||||
|
||||
// Comment this out if you want to watch or interact with the browser (e.g., for debugging)
|
||||
opts.AddArgument("--headless");
|
||||
|
||||
// Log errors
|
||||
opts.SetLoggingPreference(LogType.Browser, LogLevel.All);
|
||||
|
||||
// On Windows/Linux, we don't need to set opts.BinaryLocation
|
||||
// But for Travis Mac builds we do
|
||||
var binaryLocation = Environment.GetEnvironmentVariable("TEST_CHROME_BINARY");
|
||||
if (!string.IsNullOrEmpty(binaryLocation))
|
||||
{
|
||||
opts.BinaryLocation = binaryLocation;
|
||||
DiagnosticsMessageSink.OnMessage(new DiagnosticMessage($"Set {nameof(ChromeOptions)}.{nameof(opts.BinaryLocation)} to {binaryLocation}"));
|
||||
}
|
||||
|
||||
var driver = new RemoteWebDriver(SeleniumStandaloneServer.Instance.Uri, opts);
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
|
||||
Browser = driver;
|
||||
Logs = new RemoteLogs(driver);
|
||||
}
|
||||
|
||||
public IWebDriver Browser { get; }
|
||||
|
||||
public ILogs Logs { get; }
|
||||
public ILogs Logs { get; private set; }
|
||||
|
||||
public IMessageSink DiagnosticsMessageSink { get; }
|
||||
|
||||
|
|
@ -79,10 +52,81 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Browser != null)
|
||||
_browser?.Dispose();
|
||||
}
|
||||
|
||||
public async Task<(IWebDriver, ILogs)> GetOrCreateBrowserAsync(ITestOutputHelper output)
|
||||
{
|
||||
if (!IsHostAutomationSupported())
|
||||
{
|
||||
Browser.Dispose();
|
||||
output.WriteLine($"{nameof(BrowserFixture)}: Host does not support browser automation.");
|
||||
return default;
|
||||
}
|
||||
|
||||
if ((_browser, _logs) != (null, null))
|
||||
{
|
||||
return (_browser, _logs);
|
||||
}
|
||||
|
||||
var opts = new ChromeOptions();
|
||||
|
||||
// Comment this out if you want to watch or interact with the browser (e.g., for debugging)
|
||||
opts.AddArgument("--headless");
|
||||
|
||||
// Log errors
|
||||
opts.SetLoggingPreference(LogType.Browser, LogLevel.All);
|
||||
|
||||
// On Windows/Linux, we don't need to set opts.BinaryLocation
|
||||
// But for Travis Mac builds we do
|
||||
var binaryLocation = Environment.GetEnvironmentVariable("TEST_CHROME_BINARY");
|
||||
if (!string.IsNullOrEmpty(binaryLocation))
|
||||
{
|
||||
opts.BinaryLocation = binaryLocation;
|
||||
output.WriteLine($"Set {nameof(ChromeOptions)}.{nameof(opts.BinaryLocation)} to {binaryLocation}");
|
||||
}
|
||||
|
||||
var instance = await SeleniumStandaloneServer.GetInstanceAsync(output);
|
||||
|
||||
var attempt = 0;
|
||||
var maxAttempts = 3;
|
||||
do
|
||||
{
|
||||
try
|
||||
{
|
||||
// The driver opens the browser window and tries to connect to it on the constructor.
|
||||
// Under heavy load, this can cause issues
|
||||
// To prevent this we let the client attempt several times to connect to the server, increasing
|
||||
// the max allowed timeout for a command on each attempt linearly.
|
||||
// This can also be caused if many tests are running concurrently, we might want to manage
|
||||
// chrome and chromedriver instances more aggresively if we have to.
|
||||
// Additionally, if we think the selenium server has become irresponsive, we could spin up
|
||||
// replace the current selenium server instance and let a new instance take over for the
|
||||
// remaining tests.
|
||||
var driver = new RemoteWebDriver(
|
||||
instance.Uri,
|
||||
opts.ToCapabilities(),
|
||||
TimeSpan.FromSeconds(60).Add(TimeSpan.FromSeconds(attempt * 60)));
|
||||
|
||||
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(1);
|
||||
var logs = new RemoteLogs(driver);
|
||||
|
||||
_browser = driver;
|
||||
_logs = logs;
|
||||
|
||||
return (_browser, _logs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (attempt >= maxAttempts)
|
||||
{
|
||||
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is irresponsive");
|
||||
}
|
||||
}
|
||||
attempt++;
|
||||
} while (attempt < maxAttempts);
|
||||
|
||||
// We will never get here. Keeping the compiler happy.
|
||||
throw new InvalidOperationException("Couldn't create a Selenium remote driver client. The server is unresponsive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
|
@ -9,23 +10,46 @@ using Xunit.Abstractions;
|
|||
namespace Microsoft.AspNetCore.E2ETesting
|
||||
{
|
||||
[CaptureSeleniumLogs]
|
||||
public class BrowserTestBase : IClassFixture<BrowserFixture>
|
||||
public class BrowserTestBase : IClassFixture<BrowserFixture>, IAsyncLifetime
|
||||
{
|
||||
private static readonly AsyncLocal<IWebDriver> _browser = new AsyncLocal<IWebDriver>();
|
||||
private static readonly AsyncLocal<IWebDriver> _asyncBrowser = new AsyncLocal<IWebDriver>();
|
||||
private static readonly AsyncLocal<ILogs> _logs = new AsyncLocal<ILogs>();
|
||||
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
|
||||
|
||||
public static IWebDriver Browser => _browser.Value;
|
||||
public BrowserTestBase(BrowserFixture browserFixture, ITestOutputHelper output)
|
||||
{
|
||||
BrowserFixture = browserFixture;
|
||||
_output.Value = output;
|
||||
}
|
||||
|
||||
public IWebDriver Browser { get; set; }
|
||||
|
||||
public static IWebDriver BrowserAccessor => _asyncBrowser.Value;
|
||||
|
||||
public static ILogs Logs => _logs.Value;
|
||||
|
||||
public static ITestOutputHelper Output => _output.Value;
|
||||
|
||||
public BrowserTestBase(BrowserFixture browserFixture, ITestOutputHelper output)
|
||||
public BrowserFixture BrowserFixture { get; }
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public virtual async Task InitializeAsync()
|
||||
{
|
||||
var (browser, logs) = await BrowserFixture.GetOrCreateBrowserAsync(Output);
|
||||
_asyncBrowser.Value = browser;
|
||||
_logs.Value = logs;
|
||||
|
||||
Browser = browser;
|
||||
|
||||
InitializeAsyncCore();
|
||||
}
|
||||
|
||||
protected virtual void InitializeAsyncCore()
|
||||
{
|
||||
_browser.Value = browserFixture.Browser;
|
||||
_logs.Value = browserFixture.Logs;
|
||||
_output.Value = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
|
||||
public override void After(MethodInfo methodUnderTest)
|
||||
{
|
||||
var browser = BrowserTestBase.Browser;
|
||||
var browser = BrowserTestBase.BrowserAccessor;
|
||||
var logs = BrowserTestBase.Logs;
|
||||
var output = BrowserTestBase.Output;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<_DefaultProjectFilter>$(MSBuildProjectDirectory)\..\..</_DefaultProjectFilter>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);node_modules\**</DefaultItemExcludes>
|
||||
<SeleniumProcessTrackingFolder Condition="'$(SeleniumProcessTrackingFolder)' == ''">$([MSBuild]::EnsureTrailingSlash('$(RepositoryRoot)'))obj\selenium\</SeleniumProcessTrackingFolder>
|
||||
<SeleniumE2ETestsSupported Condition="'$(SeleniumE2ETestsSupported)' == '' and '$(TargetArchitecture)' != 'arm' and '$(OS)' == 'Windows_NT'">true</SeleniumE2ETestsSupported>
|
||||
<EnforcePrerequisites Condition="'$(SeleniumE2ETestsSupported)' == 'true' and '$(EnforcePrerequisites)' == ''">true</EnforcePrerequisites>
|
||||
|
||||
|
|
|
|||
|
|
@ -102,4 +102,15 @@
|
|||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_AddProcessTrackingMetadataAttribute" BeforeTargets="BeforeCompile">
|
||||
<MakeDir Directories="$(SeleniumProcessTrackingFolder)" />
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute
|
||||
Include="System.Reflection.AssemblyMetadataAttribute">
|
||||
<_Parameter1>Microsoft.AspNetCore.Testing.Selenium.ProcessTracking</_Parameter1>
|
||||
<_Parameter2>$(SeleniumProcessTrackingFolder)</_Parameter2>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,31 +2,93 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Microsoft.AspNetCore.E2ETesting;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.E2ETesting
|
||||
{
|
||||
class SeleniumStandaloneServer
|
||||
public class SeleniumStandaloneServer : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
|
||||
private static Lazy<SeleniumStandaloneServer> _instance = new Lazy<SeleniumStandaloneServer>(() => new SeleniumStandaloneServer());
|
||||
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1);
|
||||
|
||||
public Uri Uri { get; }
|
||||
private Process _process;
|
||||
private string _sentinelPath;
|
||||
private Process _sentinelProcess;
|
||||
private static IMessageSink _diagnosticsMessageSink;
|
||||
|
||||
public static SeleniumStandaloneServer Instance => _instance.Value;
|
||||
// 1h 30 min
|
||||
private static int SeleniumProcessTimeout = 5400;
|
||||
|
||||
private SeleniumStandaloneServer()
|
||||
public SeleniumStandaloneServer(IMessageSink diagnosticsMessageSink)
|
||||
{
|
||||
if (Instance != null || _diagnosticsMessageSink != null)
|
||||
{
|
||||
throw new InvalidOperationException("Selenium standalone singleton already created.");
|
||||
}
|
||||
|
||||
// The assembly level attribute AssemblyFixture takes care of this being being instantiated before tests run
|
||||
// and disposed after tests are run, gracefully shutting down the server when possible by calling Dispose on
|
||||
// the singleton.
|
||||
Instance = this;
|
||||
_diagnosticsMessageSink = diagnosticsMessageSink;
|
||||
}
|
||||
|
||||
private void Initialize(
|
||||
Uri uri,
|
||||
Process process,
|
||||
string sentinelPath,
|
||||
Process sentinelProcess)
|
||||
{
|
||||
Uri = uri;
|
||||
_process = process;
|
||||
_sentinelPath = sentinelPath;
|
||||
_sentinelProcess = sentinelProcess;
|
||||
}
|
||||
|
||||
public Uri Uri { get; private set; }
|
||||
|
||||
internal static SeleniumStandaloneServer Instance { get; private set; }
|
||||
|
||||
public static async Task<SeleniumStandaloneServer> GetInstanceAsync(ITestOutputHelper output)
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
|
||||
}
|
||||
if (Instance._process == null)
|
||||
{
|
||||
// No process was started, meaning the instance wasn't initialized.
|
||||
await InitializeInstance(output);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_semaphore.Release();
|
||||
}
|
||||
|
||||
return Instance;
|
||||
}
|
||||
|
||||
private static async Task InitializeInstance(ITestOutputHelper output)
|
||||
{
|
||||
var port = FindAvailablePort();
|
||||
Uri = new UriBuilder("http", "localhost", port, "/wd/hub").Uri;
|
||||
var uri = new UriBuilder("http", "localhost", port, "/wd/hub").Uri;
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
|
|
@ -42,9 +104,34 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
psi.Arguments = $"/c npm {psi.Arguments}";
|
||||
}
|
||||
|
||||
var process = Process.Start(psi);
|
||||
// It's important that we get the folder value before we start the process to prevent
|
||||
// untracked processes when the tracking folder is not correctly configure.
|
||||
var trackingFolder = GetProcessTrackingFolder();
|
||||
if (!Directory.Exists(trackingFolder))
|
||||
{
|
||||
throw new InvalidOperationException($"Invalid tracking folder. Set the 'SeleniumProcessTrackingFolder' MSBuild property to a valid folder.");
|
||||
}
|
||||
|
||||
Process process = null;
|
||||
Process sentinel = null;
|
||||
string pidFilePath = null;
|
||||
try
|
||||
{
|
||||
process = Process.Start(psi);
|
||||
pidFilePath = await WriteTrackingFileAsync(output, trackingFolder, process);
|
||||
sentinel = StartSentinelProcess(process, pidFilePath, SeleniumProcessTimeout);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ProcessCleanup(process, pidFilePath);
|
||||
ProcessCleanup(sentinel, pidFilePath: null);
|
||||
throw;
|
||||
}
|
||||
|
||||
// Log output for selenium standalone process.
|
||||
// This is for the case where the server fails to launch.
|
||||
var logOutput = new BlockingCollection<string>();
|
||||
|
||||
var builder = new StringBuilder();
|
||||
process.OutputDataReceived += LogOutput;
|
||||
process.ErrorDataReceived += LogOutput;
|
||||
|
||||
|
|
@ -52,72 +139,120 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
process.BeginErrorReadLine();
|
||||
|
||||
// The Selenium sever has to be up for the entirety of the tests and is only shutdown when the application (i.e. the test) exits.
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
|
||||
{
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.KillTree(TimeSpan.FromSeconds(10));
|
||||
process.Dispose();
|
||||
}
|
||||
};
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, args) => ProcessCleanup(process, pidFilePath);
|
||||
|
||||
// Log
|
||||
void LogOutput(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
lock (builder)
|
||||
logOutput.TryAdd(e.Data);
|
||||
|
||||
// We avoid logging on the output here because it is unreliable. We can only log in the diagnostics sink.
|
||||
lock (_diagnosticsMessageSink)
|
||||
{
|
||||
builder.AppendLine(e.Data);
|
||||
_diagnosticsMessageSink.OnMessage(new DiagnosticMessage(e.Data));
|
||||
}
|
||||
}
|
||||
|
||||
var waitForStart = Task.Run(async () =>
|
||||
var httpClient = new HttpClient
|
||||
{
|
||||
var httpClient = new HttpClient
|
||||
Timeout = TimeSpan.FromSeconds(1),
|
||||
};
|
||||
|
||||
var retries = 0;
|
||||
do
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
try
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(1),
|
||||
};
|
||||
|
||||
var retries = 0;
|
||||
while (retries++ < 30)
|
||||
var response = await httpClient.GetAsync(uri);
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
output = null;
|
||||
Instance.Initialize(uri, process, pidFilePath, sentinel);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
try
|
||||
{
|
||||
var responseTask = httpClient.GetAsync(Uri);
|
||||
|
||||
var response = await responseTask;
|
||||
if (response.StatusCode == HttpStatusCode.OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
}
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
throw new Exception("Failed to launch the server");
|
||||
});
|
||||
retries++;
|
||||
} while (retries < 30);
|
||||
|
||||
// Make output null so that we stop logging to it.
|
||||
output = null;
|
||||
logOutput.CompleteAdding();
|
||||
var exitCodeString = process.HasExited ? process.ExitCode.ToString() : "Process has not yet exited.";
|
||||
var message = @$"Failed to launch the server.
|
||||
ExitCode: {exitCodeString}
|
||||
Captured output lines:
|
||||
{string.Join(Environment.NewLine, logOutput.GetConsumingEnumerable())}.";
|
||||
|
||||
// If we got here, we couldn't launch Selenium or get it to respond. So shut it down.
|
||||
ProcessCleanup(process, pidFilePath);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
private static Process StartSentinelProcess(Process process, string sentinelFile, int timeout)
|
||||
{
|
||||
// This sentinel process will start and will kill any roge selenium server that want' torn down
|
||||
// via normal means.
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell",
|
||||
Arguments = $"-NoProfile -NonInteractive -Command \"Start-Sleep {timeout}; " +
|
||||
$"if(Test-Path {sentinelFile}){{ " +
|
||||
$"Write-Output 'Stopping process {process.Id}'; Stop-Process {process.Id}; }}" +
|
||||
$"else{{ Write-Output 'Sentinel file {sentinelFile} not found.'}}",
|
||||
};
|
||||
|
||||
return Process.Start(psi);
|
||||
}
|
||||
|
||||
private static void ProcessCleanup(Process process, string pidFilePath)
|
||||
{
|
||||
if (process?.HasExited == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
process?.KillTree(TimeSpan.FromSeconds(10));
|
||||
process?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
// Wait in intervals instead of indefinitely to prevent thread starvation.
|
||||
while (!waitForStart.TimeoutAfter(Timeout).Wait(1000))
|
||||
if (pidFilePath != null && File.Exists(pidFilePath))
|
||||
{
|
||||
File.Delete(pidFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
string output;
|
||||
lock (builder)
|
||||
{
|
||||
output = builder.ToString();
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to start selenium sever. {System.Environment.NewLine}{output}", ex.GetBaseException());
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string> WriteTrackingFileAsync(ITestOutputHelper output, string trackingFolder, Process process)
|
||||
{
|
||||
var pidFile = Path.Combine(trackingFolder, $"{process.Id}.{Guid.NewGuid()}.pid");
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(pidFile, process.Id.ToString());
|
||||
return pidFile;
|
||||
}
|
||||
catch
|
||||
{
|
||||
output.WriteLine($"Can't write file to process tracking folder: {trackingFolder}");
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to write file for process {process.Id}");
|
||||
}
|
||||
|
||||
static int FindAvailablePort()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
|
|
@ -132,5 +267,16 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
listener.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetProcessTrackingFolder() =>
|
||||
typeof(SeleniumStandaloneServer).Assembly
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.Single(a => a.Key == "Microsoft.AspNetCore.Testing.Selenium.ProcessTracking").Value;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ProcessCleanup(_process, _sentinelPath);
|
||||
ProcessCleanup(_sentinelProcess, pidFilePath: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,35 +13,35 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
{
|
||||
// XUnit assertions, but hooked into Selenium's polling mechanism
|
||||
|
||||
public class WaitAssert
|
||||
public static class WaitAssert
|
||||
{
|
||||
private readonly static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(3);
|
||||
|
||||
public static void Equal<T>(T expected, Func<T> actual)
|
||||
=> WaitAssertCore(() => Assert.Equal(expected, actual()));
|
||||
public static void Equal<T>(this IWebDriver driver, T expected, Func<T> actual)
|
||||
=> WaitAssertCore(driver, () => Assert.Equal(expected, actual()));
|
||||
|
||||
public static void True(Func<bool> actual)
|
||||
=> WaitAssertCore(() => Assert.True(actual()));
|
||||
public static void True(this IWebDriver driver, Func<bool> actual)
|
||||
=> WaitAssertCore(driver, () => Assert.True(actual()));
|
||||
|
||||
public static void True(Func<bool> actual, TimeSpan timeout)
|
||||
=> WaitAssertCore(() => Assert.True(actual()), timeout);
|
||||
public static void True(this IWebDriver driver, Func<bool> actual, TimeSpan timeout)
|
||||
=> WaitAssertCore(driver, () => Assert.True(actual()), timeout);
|
||||
|
||||
public static void False(Func<bool> actual)
|
||||
=> WaitAssertCore(() => Assert.False(actual()));
|
||||
public static void False(this IWebDriver driver, Func<bool> actual)
|
||||
=> WaitAssertCore(driver, () => Assert.False(actual()));
|
||||
|
||||
public static void Contains(string expectedSubstring, Func<string> actualString)
|
||||
=> WaitAssertCore(() => Assert.Contains(expectedSubstring, actualString()));
|
||||
public static void Contains(this IWebDriver driver, string expectedSubstring, Func<string> actualString)
|
||||
=> WaitAssertCore(driver, () => Assert.Contains(expectedSubstring, actualString()));
|
||||
|
||||
public static void Collection<T>(Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors)
|
||||
=> WaitAssertCore(() => Assert.Collection(actualValues(), elementInspectors));
|
||||
public static void Collection<T>(this IWebDriver driver, Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors)
|
||||
=> WaitAssertCore(driver, () => Assert.Collection(actualValues(), elementInspectors));
|
||||
|
||||
public static void Empty(Func<IEnumerable> actualValues)
|
||||
=> WaitAssertCore(() => Assert.Empty(actualValues()));
|
||||
public static void Empty(this IWebDriver driver, Func<IEnumerable> actualValues)
|
||||
=> WaitAssertCore(driver, () => Assert.Empty(actualValues()));
|
||||
|
||||
public static void Single(Func<IEnumerable> actualValues)
|
||||
=> WaitAssertCore(() => Assert.Single(actualValues()));
|
||||
public static void Single(this IWebDriver driver, Func<IEnumerable> actualValues)
|
||||
=> WaitAssertCore(driver, () => Assert.Single(actualValues()));
|
||||
|
||||
private static void WaitAssertCore(Action assertion, TimeSpan timeout = default)
|
||||
private static void WaitAssertCore(IWebDriver driver, Action assertion, TimeSpan timeout = default)
|
||||
{
|
||||
if (timeout == default)
|
||||
{
|
||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
|
||||
try
|
||||
{
|
||||
new WebDriverWait(BrowserTestBase.Browser, timeout).Until(_ =>
|
||||
new WebDriverWait(driver, timeout).Until(_ =>
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,15 +31,24 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
// Find all the AssemblyFixtureAttributes on the test assembly
|
||||
Aggregator.Run(() =>
|
||||
{
|
||||
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly).Assembly
|
||||
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
|
||||
.Cast<AssemblyFixtureAttribute>()
|
||||
.ToList();
|
||||
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly)
|
||||
.Assembly
|
||||
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
|
||||
.Cast<AssemblyFixtureAttribute>()
|
||||
.ToList();
|
||||
|
||||
// Instantiate all the fixtures
|
||||
foreach (var fixtureAttribute in fixturesAttributes)
|
||||
{
|
||||
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType);
|
||||
var ctorWithDiagnostics = fixtureAttribute.FixtureType.GetConstructor(new[] { typeof(IMessageSink) });
|
||||
if (ctorWithDiagnostics != null)
|
||||
{
|
||||
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType, DiagnosticMessageSink);
|
||||
}
|
||||
else
|
||||
{
|
||||
_assemblyFixtureMappings[fixtureAttribute.FixtureType] = Activator.CreateInstance(fixtureAttribute.FixtureType);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -55,10 +64,11 @@ namespace Microsoft.AspNetCore.E2ETesting
|
|||
return base.BeforeTestAssemblyFinishedAsync();
|
||||
}
|
||||
|
||||
protected override Task<RunSummary> RunTestCollectionAsync(IMessageBus messageBus,
|
||||
ITestCollection testCollection,
|
||||
IEnumerable<IXunitTestCase> testCases,
|
||||
CancellationTokenSource cancellationTokenSource)
|
||||
protected override Task<RunSummary> RunTestCollectionAsync(
|
||||
IMessageBus messageBus,
|
||||
ITestCollection testCollection,
|
||||
IEnumerable<IXunitTestCase> testCases,
|
||||
CancellationTokenSource cancellationTokenSource)
|
||||
=> new XunitTestCollectionRunnerWithAssemblyFixture(
|
||||
_assemblyFixtureMappings,
|
||||
testCollection,
|
||||
|
|
|
|||
Loading…
Reference in New Issue