[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:
|
beforeBuild:
|
||||||
- bash: "./eng/scripts/install-nginx-linux.sh"
|
- bash: "./eng/scripts/install-nginx-linux.sh"
|
||||||
displayName: Installing Nginx
|
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:
|
afterBuild:
|
||||||
- bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true
|
- bash: ./build.sh --no-build --ci --test -p:RunFlakyTests=true
|
||||||
displayName: Run Flaky Tests
|
displayName: Run Flaky Tests
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,8 @@ jobs:
|
||||||
displayName: Install JDK 11
|
displayName: Install JDK 11
|
||||||
- powershell: Write-Host "##vso[task.prependpath]$env:JAVA_HOME\bin"
|
- powershell: Write-Host "##vso[task.prependpath]$env:JAVA_HOME\bin"
|
||||||
displayName: Prepend JAVA bin folder to the PATH.
|
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
|
- powershell: ./eng/scripts/InstallGoogleChrome.ps1
|
||||||
displayName: Install chrome
|
displayName: Install chrome
|
||||||
- ${{ if and(eq(variables['System.TeamProject'], 'internal'), eq(parameters.agentOs, 'Windows'), eq(parameters.codeSign, 'true')) }}:
|
- ${{ 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 dotnet.exe
|
||||||
_kill testhost.exe
|
_kill testhost.exe
|
||||||
_kill iisexpress.exe
|
_kill iisexpress.exe
|
||||||
|
|
@ -35,6 +51,7 @@ _kill chrome.exe
|
||||||
_kill h2spec.exe
|
_kill h2spec.exe
|
||||||
_kill WerFault.exe
|
_kill WerFault.exe
|
||||||
_killJavaInstances
|
_killJavaInstances
|
||||||
|
_killSeleniumTrackedProcesses
|
||||||
|
|
||||||
if (Get-Command iisreset -ErrorAction ignore) {
|
if (Get-Command iisreset -ErrorAction ignore) {
|
||||||
iisreset /restart
|
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();
|
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",
|
$"{typeof(InvalidOperationException).FullName}: The current thread is not associated with the renderer's synchronization context",
|
||||||
() => result.Text);
|
() => result.Text);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -23,12 +24,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
_serverFixture.Environment = AspNetEnvironment.Development;
|
_serverFixture.Environment = AspNetEnvironment.Development;
|
||||||
_serverFixture.BuildWebHostMethod = ComponentsApp.Server.Program.BuildWebHost;
|
_serverFixture.BuildWebHostMethod = ComponentsApp.Server.Program.BuildWebHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
Navigate("/", noReload: false);
|
Navigate("/", noReload: false);
|
||||||
WaitUntilLoaded();
|
WaitUntilLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void HasTitle()
|
public void HasTitle()
|
||||||
{
|
{
|
||||||
|
|
@ -56,13 +59,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
Browser.FindElement(By.LinkText("Counter")).Click();
|
Browser.FindElement(By.LinkText("Counter")).Click();
|
||||||
|
|
||||||
// Verify we're now on the counter page, with that nav link (only) highlighted
|
// 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),
|
Assert.Collection(Browser.FindElements(activeNavLinksSelector),
|
||||||
item => Assert.Equal("Counter", item.Text));
|
item => Assert.Equal("Counter", item.Text));
|
||||||
|
|
||||||
// Verify we can navigate back to home too
|
// Verify we can navigate back to home too
|
||||||
Browser.FindElement(By.LinkText("Home")).Click();
|
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),
|
Assert.Collection(Browser.FindElements(activeNavLinksSelector),
|
||||||
item => Assert.Equal("Home", item.Text));
|
item => Assert.Equal("Home", item.Text));
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +75,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Navigate to "Counter"
|
// Navigate to "Counter"
|
||||||
Browser.FindElement(By.LinkText("Counter")).Click();
|
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
|
// Observe the initial value is zero
|
||||||
var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p"));
|
var countDisplayElement = Browser.FindElement(By.CssSelector("h1 + p"));
|
||||||
|
|
@ -81,11 +84,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
// Click the button; see it counts
|
// Click the button; see it counts
|
||||||
var button = Browser.FindElement(By.CssSelector(".main button"));
|
var button = Browser.FindElement(By.CssSelector(".main button"));
|
||||||
button.Click();
|
button.Click();
|
||||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||||
button.Click();
|
button.Click();
|
||||||
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 2", () => countDisplayElement.Text);
|
||||||
button.Click();
|
button.Click();
|
||||||
WaitAssert.Equal("Current count: 3", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 3", () => countDisplayElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -93,7 +96,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests
|
||||||
{
|
{
|
||||||
// Navigate to "Fetch Data"
|
// Navigate to "Fetch Data"
|
||||||
Browser.FindElement(By.LinkText("Fetch data")).Click();
|
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
|
// Wait until loaded
|
||||||
var tableSelector = By.CssSelector("table.table");
|
var tableSelector = By.CssSelector("table.table");
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
public class BinaryHttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
|
public class BinaryHttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
|
||||||
{
|
{
|
||||||
readonly ServerFixture _apiServerFixture;
|
readonly ServerFixture _apiServerFixture;
|
||||||
readonly IWebElement _appElement;
|
IWebElement _appElement;
|
||||||
IWebElement _responseStatus;
|
IWebElement _responseStatus;
|
||||||
IWebElement _responseStatusText;
|
IWebElement _responseStatusText;
|
||||||
IWebElement _testOutcome;
|
IWebElement _testOutcome;
|
||||||
|
|
@ -30,11 +31,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
{
|
{
|
||||||
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||||
_apiServerFixture = apiServerFixture;
|
_apiServerFixture = apiServerFixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
Navigate(ServerPathBase, noReload: true);
|
Navigate(ServerPathBase, noReload: true);
|
||||||
_appElement = MountTestComponent<BinaryHttpRequestsComponent>();
|
_appElement = MountTestComponent<BinaryHttpRequestsComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanSendAndReceiveBytes()
|
public void CanSendAndReceiveBytes()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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 BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -19,9 +20,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
// On WebAssembly, page reloads are expensive so skip if possible
|
// On WebAssembly, page reloads are expensive so skip if possible
|
||||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||||
MountTestComponent<BindCasesComponent>();
|
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, boundValue.Text); // Doesn't update until change event
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
Browser.Equal("Changed value", () => boundValue.Text);
|
||||||
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
|
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Remove the value altogether
|
// Remove the value altogether
|
||||||
setNullButton.Click();
|
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, boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("Changed value\t");
|
target.SendKeys("Changed value\t");
|
||||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
Browser.Equal("Changed value", () => boundValue.Text);
|
||||||
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
|
Assert.Equal("Changed value", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Remove the value altogether
|
// Remove the value altogether
|
||||||
setNullButton.Click();
|
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, boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +92,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
target.SendKeys("Changed value");
|
target.SendKeys("Changed value");
|
||||||
Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet.
|
Assert.Equal(string.Empty, boundValue.Text); // Don't update as there's no change event fired yet.
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
Browser.Equal("Changed value", () => boundValue.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -101,7 +106,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
// Modify target; verify value is updated
|
// Modify target; verify value is updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("Changed value\t");
|
target.SendKeys("Changed value\t");
|
||||||
WaitAssert.Equal("Changed value", () => boundValue.Text);
|
Browser.Equal("Changed value", () => boundValue.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -115,13 +120,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Modify target; verify value is updated
|
// Modify target; verify value is updated
|
||||||
target.Click();
|
target.Click();
|
||||||
WaitAssert.True(() => target.Selected);
|
Browser.True(() => target.Selected);
|
||||||
WaitAssert.Equal("True", () => boundValue.Text);
|
Browser.Equal("True", () => boundValue.Text);
|
||||||
|
|
||||||
// Modify data; verify checkbox is updated
|
// Modify data; verify checkbox is updated
|
||||||
invertButton.Click();
|
invertButton.Click();
|
||||||
WaitAssert.False(() => target.Selected);
|
Browser.False(() => target.Selected);
|
||||||
WaitAssert.Equal("False", () => boundValue.Text);
|
Browser.Equal("False", () => boundValue.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -135,13 +140,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Modify target; verify value is updated
|
// Modify target; verify value is updated
|
||||||
target.Click();
|
target.Click();
|
||||||
WaitAssert.True(() => target.Selected);
|
Browser.True(() => target.Selected);
|
||||||
WaitAssert.Equal("True", () => boundValue.Text);
|
Browser.Equal("True", () => boundValue.Text);
|
||||||
|
|
||||||
// Modify data; verify checkbox is updated
|
// Modify data; verify checkbox is updated
|
||||||
invertButton.Click();
|
invertButton.Click();
|
||||||
WaitAssert.False(() => target.Selected);
|
Browser.False(() => target.Selected);
|
||||||
WaitAssert.Equal("False", () => boundValue.Text);
|
Browser.Equal("False", () => boundValue.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -155,13 +160,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Modify target; verify value is updated
|
// Modify target; verify value is updated
|
||||||
target.Click();
|
target.Click();
|
||||||
WaitAssert.False(() => target.Selected);
|
Browser.False(() => target.Selected);
|
||||||
WaitAssert.Equal("False", () => boundValue.Text);
|
Browser.Equal("False", () => boundValue.Text);
|
||||||
|
|
||||||
// Modify data; verify checkbox is updated
|
// Modify data; verify checkbox is updated
|
||||||
invertButton.Click();
|
invertButton.Click();
|
||||||
WaitAssert.True(() => target.Selected);
|
Browser.True(() => target.Selected);
|
||||||
WaitAssert.Equal("True", () => boundValue.Text);
|
Browser.Equal("True", () => boundValue.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -174,13 +179,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Modify target; verify value is updated
|
// Modify target; verify value is updated
|
||||||
target.SelectByText("Third choice");
|
target.SelectByText("Third choice");
|
||||||
WaitAssert.Equal("Third", () => boundValue.Text);
|
Browser.Equal("Third", () => boundValue.Text);
|
||||||
|
|
||||||
// Also verify we can add and select new options atomically
|
// Also verify we can add and select new options atomically
|
||||||
// Don't move this into a separate test, because then the previous assertions
|
// 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)
|
// would be dependent on test execution order (or would require a full page reload)
|
||||||
Browser.FindElement(By.Id("select-box-add-option")).Click();
|
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);
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("42\t");
|
target.SendKeys("42\t");
|
||||||
WaitAssert.Equal("42", () => boundValue.Text);
|
Browser.Equal("42", () => boundValue.Text);
|
||||||
Assert.Equal("42", mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-42\t");
|
target.SendKeys("-42\t");
|
||||||
WaitAssert.Equal("-42", () => boundValue.Text);
|
Browser.Equal("-42", () => boundValue.Text);
|
||||||
Assert.Equal("-42", mirrorValue.GetAttribute("value"));
|
Assert.Equal("-42", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("42\t");
|
target.SendKeys("42\t");
|
||||||
WaitAssert.Equal("42", () => boundValue.Text);
|
Browser.Equal("42", () => boundValue.Text);
|
||||||
Assert.Equal("42", mirrorValue.GetAttribute("value"));
|
Assert.Equal("42", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-3000000000\t");
|
target.SendKeys("-3000000000\t");
|
||||||
WaitAssert.Equal("-3000000000", () => boundValue.Text);
|
Browser.Equal("-3000000000", () => boundValue.Text);
|
||||||
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("3000000000\t");
|
target.SendKeys("3000000000\t");
|
||||||
WaitAssert.Equal("3000000000", () => boundValue.Text);
|
Browser.Equal("3000000000", () => boundValue.Text);
|
||||||
Assert.Equal("3000000000", mirrorValue.GetAttribute("value"));
|
Assert.Equal("3000000000", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-3000000000\t");
|
target.SendKeys("-3000000000\t");
|
||||||
WaitAssert.Equal("-3000000000", () => boundValue.Text);
|
Browser.Equal("-3000000000", () => boundValue.Text);
|
||||||
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value"));
|
Assert.Equal("-3000000000", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-3.141\t");
|
target.SendKeys("-3.141\t");
|
||||||
WaitAssert.Equal("-3.141", () => boundValue.Text);
|
Browser.Equal("-3.141", () => boundValue.Text);
|
||||||
Assert.Equal("-3.141", mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("3.141\t");
|
target.SendKeys("3.141\t");
|
||||||
WaitAssert.Equal("3.141", () => boundValue.Text);
|
Browser.Equal("3.141", () => boundValue.Text);
|
||||||
Assert.Equal("3.141", mirrorValue.GetAttribute("value"));
|
Assert.Equal("3.141", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-3.141\t");
|
target.SendKeys("-3.141\t");
|
||||||
WaitAssert.Equal("-3.141", () => boundValue.Text);
|
Browser.Equal("-3.141", () => boundValue.Text);
|
||||||
Assert.Equal("-3.141", mirrorValue.GetAttribute("value"));
|
Assert.Equal("-3.141", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-3.14159265359\t");
|
target.SendKeys("-3.14159265359\t");
|
||||||
WaitAssert.Equal("-3.14159265359", () => boundValue.Text);
|
Browser.Equal("-3.14159265359", () => boundValue.Text);
|
||||||
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
|
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
// Double shouldn't preserve trailing zeros
|
// Double shouldn't preserve trailing zeros
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("0.010\t");
|
target.SendKeys("0.010\t");
|
||||||
WaitAssert.Equal("0.01", () => boundValue.Text);
|
Browser.Equal("0.01", () => boundValue.Text);
|
||||||
Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("3.14159265359\t");
|
target.SendKeys("3.14159265359\t");
|
||||||
WaitAssert.Equal("3.14159265359", () => boundValue.Text);
|
Browser.Equal("3.14159265359", () => boundValue.Text);
|
||||||
Assert.Equal("3.14159265359", mirrorValue.GetAttribute("value"));
|
Assert.Equal("3.14159265359", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("-3.14159265359\t");
|
target.SendKeys("-3.14159265359\t");
|
||||||
WaitAssert.Equal("-3.14159265359", () => boundValue.Text);
|
Browser.Equal("-3.14159265359", () => boundValue.Text);
|
||||||
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
|
Assert.Equal("-3.14159265359", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
// Double shouldn't preserve trailing zeros
|
// Double shouldn't preserve trailing zeros
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("0.010\t");
|
target.SendKeys("0.010\t");
|
||||||
WaitAssert.Equal("0.01", () => boundValue.Text);
|
Browser.Equal("0.01", () => boundValue.Text);
|
||||||
Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
|
Assert.Equal("0.01", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,7 +401,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
// Decimal should preserve trailing zeros
|
// Decimal should preserve trailing zeros
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("0.010\t");
|
target.SendKeys("0.010\t");
|
||||||
WaitAssert.Equal("0.010", () => boundValue.Text);
|
Browser.Equal("0.010", () => boundValue.Text);
|
||||||
Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
|
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
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("0.0000000000000000000000000001\t");
|
target.SendKeys("0.0000000000000000000000000001\t");
|
||||||
WaitAssert.Equal("0.0000000000000000000000000001", () => boundValue.Text);
|
Browser.Equal("0.0000000000000000000000000001", () => boundValue.Text);
|
||||||
Assert.Equal("0.0000000000000000000000000001", mirrorValue.GetAttribute("value"));
|
Assert.Equal("0.0000000000000000000000000001", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
// Decimal should preserve trailing zeros
|
// Decimal should preserve trailing zeros
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("0.010\t");
|
target.SendKeys("0.010\t");
|
||||||
WaitAssert.Equal("0.010", () => boundValue.Text);
|
Browser.Equal("0.010", () => boundValue.Text);
|
||||||
Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
|
Assert.Equal("0.010", mirrorValue.GetAttribute("value"));
|
||||||
|
|
||||||
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
// Modify target; verify value is updated and that textboxes linked to the same data are updated
|
||||||
target.Clear();
|
target.Clear();
|
||||||
target.SendKeys("\t");
|
target.SendKeys("\t");
|
||||||
WaitAssert.Equal(string.Empty, () => boundValue.Text);
|
Browser.Equal(string.Empty, () => boundValue.Text);
|
||||||
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
Assert.Equal(string.Empty, mirrorValue.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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 BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -19,10 +20,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
|
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||||
MountTestComponent<BasicTestApp.CascadingValueTest.CascadingValueSupplier>();
|
MountTestComponent<BasicTestApp.CascadingValueTest.CascadingValueSupplier>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanUpdateValuesMatchedByType()
|
public void CanUpdateValuesMatchedByType()
|
||||||
{
|
{
|
||||||
|
|
@ -30,13 +35,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var incrementButton = Browser.FindElement(By.Id("increment-count"));
|
var incrementButton = Browser.FindElement(By.Id("increment-count"));
|
||||||
|
|
||||||
// We have the correct initial value
|
// We have the correct initial value
|
||||||
WaitAssert.Equal("100", () => currentCount.Text);
|
Browser.Equal("100", () => currentCount.Text);
|
||||||
|
|
||||||
// Updates are propagated
|
// Updates are propagated
|
||||||
incrementButton.Click();
|
incrementButton.Click();
|
||||||
WaitAssert.Equal("101", () => currentCount.Text);
|
Browser.Equal("101", () => currentCount.Text);
|
||||||
incrementButton.Click();
|
incrementButton.Click();
|
||||||
WaitAssert.Equal("102", () => currentCount.Text);
|
Browser.Equal("102", () => currentCount.Text);
|
||||||
|
|
||||||
// Didn't re-render unrelated descendants
|
// Didn't re-render unrelated descendants
|
||||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
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 currentFlag1Value = Browser.FindElement(By.Id("flag-1"));
|
||||||
var currentFlag2Value = Browser.FindElement(By.Id("flag-2"));
|
var currentFlag2Value = Browser.FindElement(By.Id("flag-2"));
|
||||||
|
|
||||||
WaitAssert.Equal("False", () => currentFlag1Value.Text);
|
Browser.Equal("False", () => currentFlag1Value.Text);
|
||||||
WaitAssert.Equal("False", () => currentFlag2Value.Text);
|
Browser.Equal("False", () => currentFlag2Value.Text);
|
||||||
|
|
||||||
// Observe that the correct cascading parameter updates
|
// Observe that the correct cascading parameter updates
|
||||||
Browser.FindElement(By.Id("toggle-flag-1")).Click();
|
Browser.FindElement(By.Id("toggle-flag-1")).Click();
|
||||||
WaitAssert.Equal("True", () => currentFlag1Value.Text);
|
Browser.Equal("True", () => currentFlag1Value.Text);
|
||||||
WaitAssert.Equal("False", () => currentFlag2Value.Text);
|
Browser.Equal("False", () => currentFlag2Value.Text);
|
||||||
Browser.FindElement(By.Id("toggle-flag-2")).Click();
|
Browser.FindElement(By.Id("toggle-flag-2")).Click();
|
||||||
WaitAssert.Equal("True", () => currentFlag1Value.Text);
|
Browser.Equal("True", () => currentFlag1Value.Text);
|
||||||
WaitAssert.Equal("True", () => currentFlag2Value.Text);
|
Browser.Equal("True", () => currentFlag2Value.Text);
|
||||||
|
|
||||||
// Didn't re-render unrelated descendants
|
// Didn't re-render unrelated descendants
|
||||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
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"));
|
var decrementButton = Browser.FindElement(By.Id("decrement-count"));
|
||||||
|
|
||||||
// We have the correct initial value
|
// We have the correct initial value
|
||||||
WaitAssert.Equal("100", () => currentCount.Text);
|
Browser.Equal("100", () => currentCount.Text);
|
||||||
|
|
||||||
// Updates are propagated
|
// Updates are propagated
|
||||||
decrementButton.Click();
|
decrementButton.Click();
|
||||||
WaitAssert.Equal("99", () => currentCount.Text);
|
Browser.Equal("99", () => currentCount.Text);
|
||||||
decrementButton.Click();
|
decrementButton.Click();
|
||||||
WaitAssert.Equal("98", () => currentCount.Text);
|
Browser.Equal("98", () => currentCount.Text);
|
||||||
|
|
||||||
// Didn't re-render descendants
|
// Didn't re-render descendants
|
||||||
Assert.Equal("1", Browser.FindElement(By.Id("receive-by-interface-num-renders")).Text);
|
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)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
|
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -74,7 +78,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Clicking button increments count
|
// Clicking button increments count
|
||||||
appElement.FindElement(By.TagName("button")).Click();
|
appElement.FindElement(By.TagName("button")).Click();
|
||||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -87,11 +91,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Clicking 'tick' changes the state, and starts a task
|
// Clicking 'tick' changes the state, and starts a task
|
||||||
appElement.FindElement(By.Id("tick")).Click();
|
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
|
// Clicking 'tock' completes the task, which updates the state
|
||||||
appElement.FindElement(By.Id("tock")).Click();
|
appElement.FindElement(By.Id("tock")).Click();
|
||||||
WaitAssert.Equal("Stopped", () => stateElement.Text);
|
Browser.Equal("Stopped", () => stateElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -106,12 +110,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Typing adds element
|
// Typing adds element
|
||||||
inputElement.SendKeys("a");
|
inputElement.SendKeys("a");
|
||||||
WaitAssert.Collection(liElements,
|
Browser.Collection(liElements,
|
||||||
li => Assert.Equal("a", li.Text));
|
li => Assert.Equal("a", li.Text));
|
||||||
|
|
||||||
// Typing again adds another element
|
// Typing again adds another element
|
||||||
inputElement.SendKeys("b");
|
inputElement.SendKeys("b");
|
||||||
WaitAssert.Collection(liElements,
|
Browser.Collection(liElements,
|
||||||
li => Assert.Equal("a", li.Text),
|
li => Assert.Equal("a", li.Text),
|
||||||
li => Assert.Equal("b", 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
|
// Initial count is zero; clicking button increments count
|
||||||
Assert.Equal("Current count: 0", countDisplayElement.Text);
|
Assert.Equal("Current count: 0", countDisplayElement.Text);
|
||||||
incrementButton.Click();
|
incrementButton.Click();
|
||||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||||
|
|
||||||
// We can remove an event handler
|
// We can remove an event handler
|
||||||
toggleClickHandlerCheckbox.Click();
|
toggleClickHandlerCheckbox.Click();
|
||||||
WaitAssert.Empty(() => appElement.FindElements(By.Id("listening-message")));
|
Browser.Empty(() => appElement.FindElements(By.Id("listening-message")));
|
||||||
incrementButton.Click();
|
incrementButton.Click();
|
||||||
WaitAssert.Equal("Current count: 1", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 1", () => countDisplayElement.Text);
|
||||||
|
|
||||||
// We can add an event handler
|
// We can add an event handler
|
||||||
toggleClickHandlerCheckbox.Click();
|
toggleClickHandlerCheckbox.Click();
|
||||||
appElement.FindElement(By.Id("listening-message"));
|
appElement.FindElement(By.Id("listening-message"));
|
||||||
incrementButton.Click();
|
incrementButton.Click();
|
||||||
WaitAssert.Equal("Current count: 2", () => countDisplayElement.Text);
|
Browser.Equal("Current count: 2", () => countDisplayElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -216,7 +220,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Clicking increments count in child component
|
// Clicking increments count in child component
|
||||||
appElement.FindElement(By.TagName("button")).Click();
|
appElement.FindElement(By.TagName("button")).Click();
|
||||||
WaitAssert.Equal("Current count: 1", () => counterDisplay.Text);
|
Browser.Equal("Current count: 1", () => counterDisplay.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -231,7 +235,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Clicking increments count in child element
|
// Clicking increments count in child element
|
||||||
appElement.FindElement(By.TagName("button")).Click();
|
appElement.FindElement(By.TagName("button")).Click();
|
||||||
WaitAssert.Equal("1", () => messageElementInChild.Text);
|
Browser.Equal("1", () => messageElementInChild.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -246,20 +250,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Click to add/remove some child components
|
// Click to add/remove some child components
|
||||||
addButton.Click();
|
addButton.Click();
|
||||||
WaitAssert.Collection(childComponentWrappers,
|
Browser.Collection(childComponentWrappers,
|
||||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
|
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
|
||||||
|
|
||||||
addButton.Click();
|
addButton.Click();
|
||||||
WaitAssert.Collection(childComponentWrappers,
|
Browser.Collection(childComponentWrappers,
|
||||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
|
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
|
||||||
elem => Assert.Equal("Child 2", elem.FindElement(By.ClassName("message")).Text));
|
elem => Assert.Equal("Child 2", elem.FindElement(By.ClassName("message")).Text));
|
||||||
|
|
||||||
removeButton.Click();
|
removeButton.Click();
|
||||||
WaitAssert.Collection(childComponentWrappers,
|
Browser.Collection(childComponentWrappers,
|
||||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
|
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text));
|
||||||
|
|
||||||
addButton.Click();
|
addButton.Click();
|
||||||
WaitAssert.Collection(childComponentWrappers,
|
Browser.Collection(childComponentWrappers,
|
||||||
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
|
elem => Assert.Equal("Child 1", elem.FindElement(By.ClassName("message")).Text),
|
||||||
elem => Assert.Equal("Child 3", 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
|
// When property changes, child is renotified before rerender
|
||||||
incrementButton.Click();
|
incrementButton.Click();
|
||||||
WaitAssert.Equal("You supplied: 101", () => suppliedValueElement.Text);
|
Browser.Equal("You supplied: 101", () => suppliedValueElement.Text);
|
||||||
Assert.Equal("I computed: 202", computedValueElement.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
|
// When we click the button, the region is shown
|
||||||
originalButton.Click();
|
originalButton.Click();
|
||||||
WaitAssert.Single(fragmentElements);
|
Browser.Single(fragmentElements);
|
||||||
|
|
||||||
// The button itself was preserved, so we can click it again and see the effect
|
// The button itself was preserved, so we can click it again and see the effect
|
||||||
originalButton.Click();
|
originalButton.Click();
|
||||||
WaitAssert.Empty(fragmentElements);
|
Browser.Empty(fragmentElements);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -329,7 +333,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
modal.SendKeys("Some value from test");
|
modal.SendKeys("Some value from test");
|
||||||
modal.Accept();
|
modal.Accept();
|
||||||
var promptResult = appElement.FindElement(By.TagName("strong"));
|
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
|
// NuGet packages can also embed entire components (themselves
|
||||||
// authored as Razor files), including static content. The CSS value
|
// 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"));
|
var externalComponentButton = specialStyleDiv.FindElement(By.TagName("button"));
|
||||||
Assert.Equal("Click me", externalComponentButton.Text);
|
Assert.Equal("Click me", externalComponentButton.Text);
|
||||||
externalComponentButton.Click();
|
externalComponentButton.Click();
|
||||||
WaitAssert.Equal("It works", () => externalComponentButton.Text);
|
Browser.Equal("It works", () => externalComponentButton.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -358,7 +362,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Assert.Equal("10", svgCircleElement.GetAttribute("r"));
|
Assert.Equal("10", svgCircleElement.GetAttribute("r"));
|
||||||
|
|
||||||
appElement.FindElement(By.TagName("button")).Click();
|
appElement.FindElement(By.TagName("button")).Click();
|
||||||
WaitAssert.Equal("20", () => svgCircleElement.GetAttribute("r"));
|
Browser.Equal("20", () => svgCircleElement.GetAttribute("r"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -377,7 +381,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
public void LogicalElementInsertionWorksHierarchically()
|
public void LogicalElementInsertionWorksHierarchically()
|
||||||
{
|
{
|
||||||
var appElement = MountTestComponent<LogicalElementInsertionCases>();
|
var appElement = MountTestComponent<LogicalElementInsertionCases>();
|
||||||
WaitAssert.Equal("First Second Third", () => appElement.Text);
|
Browser.Equal("First Second Third", () => appElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -390,9 +394,9 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
Assert.Equal(string.Empty, inputElement.GetAttribute("value"));
|
||||||
|
|
||||||
buttonElement.Click();
|
buttonElement.Click();
|
||||||
WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
Browser.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
||||||
buttonElement.Click();
|
buttonElement.Click();
|
||||||
WaitAssert.Equal("Clicks: 2", () => inputElement.GetAttribute("value"));
|
Browser.Equal("Clicks: 2", () => inputElement.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -408,7 +412,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Remove the captured element
|
// Remove the captured element
|
||||||
checkbox.Click();
|
checkbox.Click();
|
||||||
WaitAssert.Empty(() => appElement.FindElements(By.Id("capturedElement")));
|
Browser.Empty(() => appElement.FindElements(By.Id("capturedElement")));
|
||||||
|
|
||||||
// Re-add it; observe it starts empty again
|
// Re-add it; observe it starts empty again
|
||||||
checkbox.Click();
|
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
|
// See that the capture variable was automatically updated to reference the new instance
|
||||||
buttonElement.Click();
|
buttonElement.Click();
|
||||||
WaitAssert.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
Browser.Equal("Clicks: 1", () => inputElement.GetAttribute("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -432,23 +436,23 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Verify the reference was captured initially
|
// Verify the reference was captured initially
|
||||||
appElement.FindElement(incrementButtonSelector).Click();
|
appElement.FindElement(incrementButtonSelector).Click();
|
||||||
WaitAssert.Equal("Current count: 1", currentCountText);
|
Browser.Equal("Current count: 1", currentCountText);
|
||||||
resetButton.Click();
|
resetButton.Click();
|
||||||
WaitAssert.Equal("Current count: 0", currentCountText);
|
Browser.Equal("Current count: 0", currentCountText);
|
||||||
appElement.FindElement(incrementButtonSelector).Click();
|
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
|
// Remove and re-add a new instance of the child, checking the text was reset
|
||||||
toggleChildCheckbox.Click();
|
toggleChildCheckbox.Click();
|
||||||
WaitAssert.Empty(() => appElement.FindElements(incrementButtonSelector));
|
Browser.Empty(() => appElement.FindElements(incrementButtonSelector));
|
||||||
toggleChildCheckbox.Click();
|
toggleChildCheckbox.Click();
|
||||||
WaitAssert.Equal("Current count: 0", currentCountText);
|
Browser.Equal("Current count: 0", currentCountText);
|
||||||
|
|
||||||
// Verify we have a new working reference
|
// Verify we have a new working reference
|
||||||
appElement.FindElement(incrementButtonSelector).Click();
|
appElement.FindElement(incrementButtonSelector).Click();
|
||||||
WaitAssert.Equal("Current count: 1", currentCountText);
|
Browser.Equal("Current count: 1", currentCountText);
|
||||||
resetButton.Click();
|
resetButton.Click();
|
||||||
WaitAssert.Equal("Current count: 0", currentCountText);
|
Browser.Equal("Current count: 0", currentCountText);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -487,7 +491,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Updating markup blocks
|
// Updating markup blocks
|
||||||
appElement.FindElement(By.TagName("button")).Click();
|
appElement.FindElement(By.TagName("button")).Click();
|
||||||
WaitAssert.Equal(
|
Browser.Equal(
|
||||||
"[The output was changed completely.]",
|
"[The output was changed completely.]",
|
||||||
() => appElement.FindElement(By.Id("dynamic-markup-block")).Text);
|
() => appElement.FindElement(By.Id("dynamic-markup-block")).Text);
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
|
|
@ -529,7 +533,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var toggle = appElement.FindElement(By.Id("toggle"));
|
var toggle = appElement.FindElement(By.Id("toggle"));
|
||||||
toggle.Click();
|
toggle.Click();
|
||||||
|
|
||||||
WaitAssert.Collection(
|
Browser.Collection(
|
||||||
() => tfoot.FindElements(By.TagName("td")),
|
() => tfoot.FindElements(By.TagName("td")),
|
||||||
e => Assert.Equal("The", e.Text),
|
e => Assert.Equal("The", e.Text),
|
||||||
e => Assert.Equal("", e.Text),
|
e => Assert.Equal("", e.Text),
|
||||||
|
|
@ -550,7 +554,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
var outputElement = appElement.FindElement(By.Id("concurrent-render-output"));
|
var outputElement = appElement.FindElement(By.Id("concurrent-render-output"));
|
||||||
WaitAssert.Equal(expectedOutput, () => outputElement.Text);
|
Browser.Equal(expectedOutput, () => outputElement.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -561,7 +565,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
appElement.FindElement(By.Id("run-with-dispatch")).Click();
|
appElement.FindElement(By.Id("run-with-dispatch")).Click();
|
||||||
|
|
||||||
WaitAssert.Equal("Success (completed synchronously)", () => result.Text);
|
Browser.Equal("Success (completed synchronously)", () => result.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -572,7 +576,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
appElement.FindElement(By.Id("run-with-double-dispatch")).Click();
|
appElement.FindElement(By.Id("run-with-double-dispatch")).Click();
|
||||||
|
|
||||||
WaitAssert.Equal("Success (completed synchronously)", () => result.Text);
|
Browser.Equal("Success (completed synchronously)", () => result.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -583,7 +587,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
appElement.FindElement(By.Id("run-async-with-dispatch")).Click();
|
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)
|
static IAlert SwitchToAlert(IWebDriver driver)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
using Microsoft.AspNetCore.E2ETesting;
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -26,17 +27,21 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
|
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||||
MountTestComponent<EventBubblingComponent>();
|
MountTestComponent<EventBubblingComponent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void BubblingStandardEvent_FiredOnElementWithHandler()
|
public void BubblingStandardEvent_FiredOnElementWithHandler()
|
||||||
{
|
{
|
||||||
Browser.FindElement(By.Id("button-with-onclick")).Click();
|
Browser.FindElement(By.Id("button-with-onclick")).Click();
|
||||||
|
|
||||||
// Triggers event on target and ancestors with handler in upwards direction
|
// Triggers event on target and ancestors with handler in upwards direction
|
||||||
WaitAssert.Equal(
|
Browser.Equal(
|
||||||
new[] { "target onclick", "parent onclick" },
|
new[] { "target onclick", "parent onclick" },
|
||||||
GetLogLines);
|
GetLogLines);
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +52,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Browser.FindElement(By.Id("button-without-onclick")).Click();
|
Browser.FindElement(By.Id("button-without-onclick")).Click();
|
||||||
|
|
||||||
// Triggers event on ancestors with handler in upwards direction
|
// Triggers event on ancestors with handler in upwards direction
|
||||||
WaitAssert.Equal(
|
Browser.Equal(
|
||||||
new[] { "parent onclick" },
|
new[] { "parent onclick" },
|
||||||
GetLogLines);
|
GetLogLines);
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +63,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze");
|
TriggerCustomBubblingEvent("element-with-onsneeze", "sneeze");
|
||||||
|
|
||||||
// Triggers event on target and ancestors with handler in upwards direction
|
// Triggers event on target and ancestors with handler in upwards direction
|
||||||
WaitAssert.Equal(
|
Browser.Equal(
|
||||||
new[] { "target onsneeze", "parent onsneeze" },
|
new[] { "target onsneeze", "parent onsneeze" },
|
||||||
GetLogLines);
|
GetLogLines);
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +74,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze");
|
TriggerCustomBubblingEvent("element-without-onsneeze", "sneeze");
|
||||||
|
|
||||||
// Triggers event on ancestors with handler in upwards direction
|
// Triggers event on ancestors with handler in upwards direction
|
||||||
WaitAssert.Equal(
|
Browser.Equal(
|
||||||
new[] { "parent onsneeze" },
|
new[] { "parent onsneeze" },
|
||||||
GetLogLines);
|
GetLogLines);
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +85,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Browser.FindElement(By.Id("input-with-onfocus")).Click();
|
Browser.FindElement(By.Id("input-with-onfocus")).Click();
|
||||||
|
|
||||||
// Triggers event only on target, not other ancestors with event handler
|
// Triggers event only on target, not other ancestors with event handler
|
||||||
WaitAssert.Equal(
|
Browser.Equal(
|
||||||
new[] { "target onfocus" },
|
new[] { "target onfocus" },
|
||||||
GetLogLines);
|
GetLogLines);
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +96,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
Browser.FindElement(By.Id("input-without-onfocus")).Click();
|
Browser.FindElement(By.Id("input-without-onfocus")).Click();
|
||||||
|
|
||||||
// Triggers no event
|
// Triggers no event
|
||||||
WaitAssert.Empty(GetLogLines);
|
Browser.Empty(GetLogLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] GetLogLines()
|
private string[] GetLogLines()
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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 BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -18,9 +19,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
// On WebAssembly, page reloads are expensive so skip if possible
|
// On WebAssembly, page reloads are expensive so skip if possible
|
||||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||||
MountTestComponent<BasicTestApp.EventCallbackTest.EventCallbackCases>();
|
MountTestComponent<BasicTestApp.EventCallbackTest.EventCallbackCases>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
// 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 BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -19,6 +20,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: true);
|
Navigate(ServerPathBase, noReload: true);
|
||||||
MountTestComponent<EventBubblingComponent>();
|
MountTestComponent<EventBubblingComponent>();
|
||||||
|
|
@ -37,13 +42,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
// Focus the target, verify onfocusin is fired
|
// Focus the target, verify onfocusin is fired
|
||||||
input.Click();
|
input.Click();
|
||||||
|
|
||||||
WaitAssert.Equal("onfocus,onfocusin,", () => output.Text);
|
Browser.Equal("onfocus,onfocusin,", () => output.Text);
|
||||||
|
|
||||||
// Focus something else, verify onfocusout is also fired
|
// Focus something else, verify onfocusout is also fired
|
||||||
var other = Browser.FindElement(By.Id("other"));
|
var other = Browser.FindElement(By.Id("other"));
|
||||||
other.Click();
|
other.Click();
|
||||||
|
|
||||||
WaitAssert.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text);
|
Browser.Equal("onfocus,onfocusin,onblur,onfocusout,", () => output.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -64,7 +69,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
.MoveToElement(other);
|
.MoveToElement(other);
|
||||||
|
|
||||||
actions.Perform();
|
actions.Perform();
|
||||||
WaitAssert.Equal("onmouseover,onmouseout,", () => output.Text);
|
Browser.Equal("onmouseover,onmouseout,", () => output.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -83,7 +88,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
.MoveToElement(input, 10, 10);
|
.MoveToElement(input, 10, 10);
|
||||||
|
|
||||||
actions.Perform();
|
actions.Perform();
|
||||||
WaitAssert.Contains("onmousemove,", () => output.Text);
|
Browser.Contains("onmousemove,", () => output.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -102,12 +107,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var actions = new Actions(Browser).ClickAndHold(input);
|
var actions = new Actions(Browser).ClickAndHold(input);
|
||||||
|
|
||||||
actions.Perform();
|
actions.Perform();
|
||||||
WaitAssert.Equal("onmousedown,", () => output.Text);
|
Browser.Equal("onmousedown,", () => output.Text);
|
||||||
|
|
||||||
actions = new Actions(Browser).Release(input);
|
actions = new Actions(Browser).Release(input);
|
||||||
|
|
||||||
actions.Perform();
|
actions.Perform();
|
||||||
WaitAssert.Equal("onmousedown,onmouseup,", () => output.Text);
|
Browser.Equal("onmousedown,onmouseup,", () => output.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -116,7 +121,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var appElement = MountTestComponent<EventPreventDefaultComponent>();
|
var appElement = MountTestComponent<EventPreventDefaultComponent>();
|
||||||
|
|
||||||
appElement.FindElement(By.Id("form-1-button")).Click();
|
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]
|
[Fact]
|
||||||
|
|
@ -135,13 +140,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var input = Browser.FindElement(By.TagName("input"));
|
var input = Browser.FindElement(By.TagName("input"));
|
||||||
var output = Browser.FindElement(By.Id("test-result"));
|
var output = Browser.FindElement(By.Id("test-result"));
|
||||||
|
|
||||||
WaitAssert.Equal(string.Empty, () => output.Text);
|
Browser.Equal(string.Empty, () => output.Text);
|
||||||
|
|
||||||
SendKeysSequentially(input, "abcdefghijklmnopqrstuvwxyz");
|
SendKeysSequentially(input, "abcdefghijklmnopqrstuvwxyz");
|
||||||
WaitAssert.Equal("abcdefghijklmnopqrstuvwxyz", () => output.Text);
|
Browser.Equal("abcdefghijklmnopqrstuvwxyz", () => output.Text);
|
||||||
|
|
||||||
input.SendKeys(Keys.Backspace);
|
input.SendKeys(Keys.Backspace);
|
||||||
WaitAssert.Equal("abcdefghijklmnopqrstuvwxy", () => output.Text);
|
Browser.Equal("abcdefghijklmnopqrstuvwxy", () => output.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendKeysSequentially(IWebElement target, string text)
|
void SendKeysSequentially(IWebElement target, string text)
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,13 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
// On WebAssembly, page reloads are expensive so skip if possible
|
// On WebAssembly, page reloads are expensive so skip if possible
|
||||||
Navigate(ServerPathBase, noReload: !serverFixture.UsingAspNetHost);
|
Navigate(ServerPathBase, noReload: !_serverFixture.UsingAspNetHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -42,26 +46,26 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
acceptsTermsInput.Click(); // Accept terms
|
acceptsTermsInput.Click(); // Accept terms
|
||||||
acceptsTermsInput.Click(); // Un-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
|
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")));
|
Assert.Empty(appElement.FindElements(By.Id("last-callback")));
|
||||||
|
|
||||||
// Submitting the form does validate
|
// Submitting the form does validate
|
||||||
submitButton.Click();
|
submitButton.Click();
|
||||||
WaitAssert.Equal(new[] { "You must accept the terms" }, messagesAccessor);
|
Browser.Equal(new[] { "You must accept the terms" }, messagesAccessor);
|
||||||
WaitAssert.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
Browser.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||||
|
|
||||||
// Can make another field invalid
|
// Can make another field invalid
|
||||||
userNameInput.Clear();
|
userNameInput.Clear();
|
||||||
submitButton.Click();
|
submitButton.Click();
|
||||||
WaitAssert.Equal(new[] { "Please choose a username", "You must accept the terms" }, messagesAccessor);
|
Browser.Equal(new[] { "Please choose a username", "You must accept the terms" }, messagesAccessor);
|
||||||
WaitAssert.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
Browser.Equal("OnInvalidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||||
|
|
||||||
// Can make valid
|
// Can make valid
|
||||||
userNameInput.SendKeys("Bert\t");
|
userNameInput.SendKeys("Bert\t");
|
||||||
acceptsTermsInput.Click();
|
acceptsTermsInput.Click();
|
||||||
submitButton.Click();
|
submitButton.Click();
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
WaitAssert.Equal("OnValidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
Browser.Equal("OnValidSubmit", () => appElement.FindElement(By.Id("last-callback")).Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -70,22 +74,22 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var appElement = MountTestComponent<TypicalValidationComponent>();
|
var appElement = MountTestComponent<TypicalValidationComponent>();
|
||||||
var nameInput = appElement.FindElement(By.ClassName("name")).FindElement(By.TagName("input"));
|
var nameInput = appElement.FindElement(By.ClassName("name")).FindElement(By.TagName("input"));
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => nameInput.GetAttribute("class"));
|
Browser.Equal("valid", () => nameInput.GetAttribute("class"));
|
||||||
nameInput.SendKeys("Bert\t");
|
nameInput.SendKeys("Bert\t");
|
||||||
WaitAssert.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
nameInput.SendKeys("01234567890123456789\t");
|
nameInput.SendKeys("01234567890123456789\t");
|
||||||
WaitAssert.Equal("modified invalid", () => nameInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => nameInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "That name is too long" }, messagesAccessor);
|
Browser.Equal(new[] { "That name is too long" }, messagesAccessor);
|
||||||
|
|
||||||
// Can become valid
|
// Can become valid
|
||||||
nameInput.Clear();
|
nameInput.Clear();
|
||||||
nameInput.SendKeys("Bert\t");
|
nameInput.SendKeys("Bert\t");
|
||||||
WaitAssert.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => nameInput.GetAttribute("class"));
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -96,25 +100,25 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => ageInput.GetAttribute("class"));
|
Browser.Equal("valid", () => ageInput.GetAttribute("class"));
|
||||||
ageInput.SendKeys("123\t");
|
ageInput.SendKeys("123\t");
|
||||||
WaitAssert.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
ageInput.SendKeys("e100\t");
|
ageInput.SendKeys("e100\t");
|
||||||
WaitAssert.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
Browser.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
||||||
|
|
||||||
// Empty is invalid, because it's not a nullable int
|
// Empty is invalid, because it's not a nullable int
|
||||||
ageInput.Clear();
|
ageInput.Clear();
|
||||||
ageInput.SendKeys("\t");
|
ageInput.SendKeys("\t");
|
||||||
WaitAssert.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => ageInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
Browser.Equal(new[] { "The AgeInYears field must be a number." }, messagesAccessor);
|
||||||
|
|
||||||
// Zero is within the allowed range
|
// Zero is within the allowed range
|
||||||
ageInput.SendKeys("0\t");
|
ageInput.SendKeys("0\t");
|
||||||
WaitAssert.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => ageInput.GetAttribute("class"));
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -125,20 +129,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => heightInput.GetAttribute("class"));
|
Browser.Equal("valid", () => heightInput.GetAttribute("class"));
|
||||||
heightInput.SendKeys("123.456\t");
|
heightInput.SendKeys("123.456\t");
|
||||||
WaitAssert.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
heightInput.SendKeys("e100\t");
|
heightInput.SendKeys("e100\t");
|
||||||
WaitAssert.Equal("modified invalid", () => heightInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => heightInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The OptionalHeight field must be a number." }, messagesAccessor);
|
Browser.Equal(new[] { "The OptionalHeight field must be a number." }, messagesAccessor);
|
||||||
|
|
||||||
// Empty is valid, because it's a nullable float
|
// Empty is valid, because it's a nullable float
|
||||||
heightInput.Clear();
|
heightInput.Clear();
|
||||||
heightInput.SendKeys("\t");
|
heightInput.SendKeys("\t");
|
||||||
WaitAssert.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => heightInput.GetAttribute("class"));
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -149,20 +153,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => descriptionInput.GetAttribute("class"));
|
Browser.Equal("valid", () => descriptionInput.GetAttribute("class"));
|
||||||
descriptionInput.SendKeys("Hello\t");
|
descriptionInput.SendKeys("Hello\t");
|
||||||
WaitAssert.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
descriptionInput.SendKeys("too long too long too long too long too long\t");
|
descriptionInput.SendKeys("too long too long too long too long too long\t");
|
||||||
WaitAssert.Equal("modified invalid", () => descriptionInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => descriptionInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "Description is max 20 chars" }, messagesAccessor);
|
Browser.Equal(new[] { "Description is max 20 chars" }, messagesAccessor);
|
||||||
|
|
||||||
// Can become valid
|
// Can become valid
|
||||||
descriptionInput.Clear();
|
descriptionInput.Clear();
|
||||||
descriptionInput.SendKeys("Hello\t");
|
descriptionInput.SendKeys("Hello\t");
|
||||||
WaitAssert.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => descriptionInput.GetAttribute("class"));
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -173,24 +177,24 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => renewalDateInput.GetAttribute("class"));
|
Browser.Equal("valid", () => renewalDateInput.GetAttribute("class"));
|
||||||
renewalDateInput.SendKeys("01/01/2000\t");
|
renewalDateInput.SendKeys("01/01/2000\t");
|
||||||
WaitAssert.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
renewalDateInput.SendKeys("0/0/0");
|
renewalDateInput.SendKeys("0/0/0");
|
||||||
WaitAssert.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
Browser.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
||||||
|
|
||||||
// Empty is invalid, because it's not nullable
|
// Empty is invalid, because it's not nullable
|
||||||
renewalDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t");
|
renewalDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t");
|
||||||
WaitAssert.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => renewalDateInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
Browser.Equal(new[] { "The RenewalDate field must be a date." }, messagesAccessor);
|
||||||
|
|
||||||
// Can become valid
|
// Can become valid
|
||||||
renewalDateInput.SendKeys("01/01/01\t");
|
renewalDateInput.SendKeys("01/01/01\t");
|
||||||
WaitAssert.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => renewalDateInput.GetAttribute("class"));
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -201,19 +205,19 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => expiryDateInput.GetAttribute("class"));
|
Browser.Equal("valid", () => expiryDateInput.GetAttribute("class"));
|
||||||
expiryDateInput.SendKeys("01/01/2000\t");
|
expiryDateInput.SendKeys("01/01/2000\t");
|
||||||
WaitAssert.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
expiryDateInput.SendKeys("111111111");
|
expiryDateInput.SendKeys("111111111");
|
||||||
WaitAssert.Equal("modified invalid", () => expiryDateInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => expiryDateInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The OptionalExpiryDate field must be a date." }, messagesAccessor);
|
Browser.Equal(new[] { "The OptionalExpiryDate field must be a date." }, messagesAccessor);
|
||||||
|
|
||||||
// Empty is valid, because it's nullable
|
// Empty is valid, because it's nullable
|
||||||
expiryDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t");
|
expiryDateInput.SendKeys($"{Keys.Backspace}\t{Keys.Backspace}\t{Keys.Backspace}\t");
|
||||||
WaitAssert.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => expiryDateInput.GetAttribute("class"));
|
||||||
WaitAssert.Empty(messagesAccessor);
|
Browser.Empty(messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -225,14 +229,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => select.GetAttribute("class"));
|
Browser.Equal("valid", () => select.GetAttribute("class"));
|
||||||
ticketClassInput.SelectByText("First class");
|
ticketClassInput.SelectByText("First class");
|
||||||
WaitAssert.Equal("modified valid", () => select.GetAttribute("class"));
|
Browser.Equal("modified valid", () => select.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
ticketClassInput.SelectByText("(select)");
|
ticketClassInput.SelectByText("(select)");
|
||||||
WaitAssert.Equal("modified invalid", () => select.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => select.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
Browser.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -243,14 +247,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Validates on edit
|
// Validates on edit
|
||||||
WaitAssert.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
Browser.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||||
acceptsTermsInput.Click();
|
acceptsTermsInput.Click();
|
||||||
WaitAssert.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can become invalid
|
// Can become invalid
|
||||||
acceptsTermsInput.Click();
|
acceptsTermsInput.Click();
|
||||||
WaitAssert.Equal("modified invalid", () => acceptsTermsInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => acceptsTermsInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "Must accept terms" }, messagesAccessor);
|
Browser.Equal(new[] { "Must accept terms" }, messagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -264,30 +268,30 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var submissionStatus = appElement.FindElement(By.Id("submission-status"));
|
var submissionStatus = appElement.FindElement(By.Id("submission-status"));
|
||||||
|
|
||||||
// Editing a field triggers validation immediately
|
// 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");
|
userNameInput.SendKeys("Too long too long\t");
|
||||||
WaitAssert.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(new[] { "That name is too long" }, messagesAccessor);
|
Browser.Equal(new[] { "That name is too long" }, messagesAccessor);
|
||||||
|
|
||||||
// Submitting the form validates remaining fields
|
// Submitting the form validates remaining fields
|
||||||
submitButton.Click();
|
submitButton.Click();
|
||||||
WaitAssert.Equal(new[] { "That name is too long", "You must accept the terms" }, messagesAccessor);
|
Browser.Equal(new[] { "That name is too long", "You must accept the terms" }, messagesAccessor);
|
||||||
WaitAssert.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
Browser.Equal("modified invalid", () => userNameInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal("invalid", () => acceptsTermsInput.GetAttribute("class"));
|
Browser.Equal("invalid", () => acceptsTermsInput.GetAttribute("class"));
|
||||||
|
|
||||||
// Can make fields valid
|
// Can make fields valid
|
||||||
userNameInput.Clear();
|
userNameInput.Clear();
|
||||||
userNameInput.SendKeys("Bert\t");
|
userNameInput.SendKeys("Bert\t");
|
||||||
WaitAssert.Equal("modified valid", () => userNameInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => userNameInput.GetAttribute("class"));
|
||||||
acceptsTermsInput.Click();
|
acceptsTermsInput.Click();
|
||||||
WaitAssert.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
Browser.Equal("modified valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal(string.Empty, () => submissionStatus.Text);
|
Browser.Equal(string.Empty, () => submissionStatus.Text);
|
||||||
submitButton.Click();
|
submitButton.Click();
|
||||||
WaitAssert.True(() => submissionStatus.Text.StartsWith("Submitted"));
|
Browser.True(() => submissionStatus.Text.StartsWith("Submitted"));
|
||||||
|
|
||||||
// Fields can revert to unmodified
|
// Fields can revert to unmodified
|
||||||
WaitAssert.Equal("valid", () => userNameInput.GetAttribute("class"));
|
Browser.Equal("valid", () => userNameInput.GetAttribute("class"));
|
||||||
WaitAssert.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
Browser.Equal("valid", () => acceptsTermsInput.GetAttribute("class"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -301,20 +305,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
// Doesn't show messages for other fields
|
// Doesn't show messages for other fields
|
||||||
submitButton.Click();
|
submitButton.Click();
|
||||||
WaitAssert.Empty(emailMessagesAccessor);
|
Browser.Empty(emailMessagesAccessor);
|
||||||
|
|
||||||
// Updates on edit
|
// Updates on edit
|
||||||
emailInput.SendKeys("abc\t");
|
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
|
// Can show more than one message
|
||||||
emailInput.SendKeys("too long too long too long\t");
|
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
|
// Can become valid
|
||||||
emailInput.Clear();
|
emailInput.Clear();
|
||||||
emailInput.SendKeys("a@b.com\t");
|
emailInput.SendKeys("a@b.com\t");
|
||||||
WaitAssert.Empty(emailMessagesAccessor);
|
Browser.Empty(emailMessagesAccessor);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -326,16 +330,16 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
var messagesAccessor = CreateValidationMessagesAccessor(appElement);
|
||||||
|
|
||||||
// Shows initial state
|
// Shows initial state
|
||||||
WaitAssert.Equal("Economy", () => selectedTicketClassDisplay.Text);
|
Browser.Equal("Economy", () => selectedTicketClassDisplay.Text);
|
||||||
|
|
||||||
// Refreshes on edit
|
// Refreshes on edit
|
||||||
ticketClassInput.SelectByValue("Premium");
|
ticketClassInput.SelectByValue("Premium");
|
||||||
WaitAssert.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
Browser.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
||||||
|
|
||||||
// Leaves previous value unchanged if new entry is unparseable
|
// Leaves previous value unchanged if new entry is unparseable
|
||||||
ticketClassInput.SelectByText("(select)");
|
ticketClassInput.SelectByText("(select)");
|
||||||
WaitAssert.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
Browser.Equal(new[] { "The TicketClass field is not valid." }, messagesAccessor);
|
||||||
WaitAssert.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
Browser.Equal("Premium", () => selectedTicketClassDisplay.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Func<string[]> CreateValidationMessagesAccessor(IWebElement appElement)
|
private Func<string[]> CreateValidationMessagesAccessor(IWebElement appElement)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -15,13 +16,17 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
public class HostedInAspNetTest : ServerTestBase<AspNetSiteServerFixture>
|
public class HostedInAspNetTest : ServerTestBase<AspNetSiteServerFixture>
|
||||||
{
|
{
|
||||||
public HostedInAspNetTest(
|
public HostedInAspNetTest(
|
||||||
BrowserFixture browserFixture,
|
BrowserFixture browserFixture,
|
||||||
AspNetSiteServerFixture serverFixture,
|
AspNetSiteServerFixture serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
{
|
{
|
||||||
serverFixture.BuildWebHostMethod = HostedInAspNet.Server.Program.BuildWebHost;
|
serverFixture.BuildWebHostMethod = HostedInAspNet.Server.Program.BuildWebHost;
|
||||||
serverFixture.Environment = AspNetEnvironment.Development;
|
serverFixture.Environment = AspNetEnvironment.Development;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
Navigate("/", noReload: true);
|
Navigate("/", noReload: true);
|
||||||
WaitUntilLoaded();
|
WaitUntilLoaded();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
public class HttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
|
public class HttpClientTest : BasicTestAppTestBase, IClassFixture<AspNetSiteServerFixture>
|
||||||
{
|
{
|
||||||
readonly ServerFixture _apiServerFixture;
|
readonly ServerFixture _apiServerFixture;
|
||||||
readonly IWebElement _appElement;
|
IWebElement _appElement;
|
||||||
IWebElement _responseStatus;
|
IWebElement _responseStatus;
|
||||||
IWebElement _responseBody;
|
IWebElement _responseBody;
|
||||||
IWebElement _responseHeaders;
|
IWebElement _responseHeaders;
|
||||||
|
|
@ -32,7 +32,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
{
|
{
|
||||||
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
apiServerFixture.BuildWebHostMethod = TestServer.Program.BuildWebHost;
|
||||||
_apiServerFixture = apiServerFixture;
|
_apiServerFixture = apiServerFixture;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
Navigate(ServerPathBase, noReload: true);
|
Navigate(ServerPathBase, noReload: true);
|
||||||
_appElement = MountTestComponent<HttpRequestsComponent>();
|
_appElement = MountTestComponent<HttpRequestsComponent>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -18,6 +19,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: true);
|
Navigate(ServerPathBase, noReload: true);
|
||||||
MountTestComponent<InteropComponent>();
|
MountTestComponent<InteropComponent>();
|
||||||
|
|
@ -106,7 +111,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
expectedValues.Add(kvp.Key, kvp.Value);
|
expectedValues.Add(kvp.Key, kvp.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var actualValues = new Dictionary<string, string>();
|
var actualValues = new Dictionary<string, string>();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -21,6 +22,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
{
|
{
|
||||||
serverFixture.BuildWebHostMethod = MonoSanity.Program.BuildWebHost;
|
serverFixture.BuildWebHostMethod = MonoSanity.Program.BuildWebHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
|
{
|
||||||
Navigate("/", noReload: true);
|
Navigate("/", noReload: true);
|
||||||
WaitUntilMonoRunningInBrowser();
|
WaitUntilMonoRunningInBrowser();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.E2ETesting;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -20,6 +21,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
DevHostServerFixture<Blazor.E2EPerformance.Program> serverFixture,
|
DevHostServerFixture<Blazor.E2EPerformance.Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate("/", noReload: true);
|
Navigate("/", noReload: true);
|
||||||
}
|
}
|
||||||
|
|
@ -43,8 +48,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
runAllButton.Click();
|
runAllButton.Click();
|
||||||
|
|
||||||
// The "run" button goes away while the benchmarks execute, then it comes back
|
// The "run" button goes away while the benchmarks execute, then it comes back
|
||||||
WaitAssert.False(() => runAllButton.Displayed);
|
Browser.False(() => runAllButton.Displayed);
|
||||||
WaitAssert.True(
|
Browser.True(
|
||||||
() => runAllButton.Displayed || Browser.FindElements(By.CssSelector(".benchmark-error")).Any(),
|
() => runAllButton.Displayed || Browser.FindElements(By.CssSelector(".benchmark-error")).Any(),
|
||||||
TimeSpan.FromSeconds(60));
|
TimeSpan.FromSeconds(60));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
using BasicTestApp.RouterTest;
|
using BasicTestApp.RouterTest;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
|
|
@ -23,6 +24,10 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
ToggleExecutionModeServerFixture<Program> serverFixture,
|
ToggleExecutionModeServerFixture<Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: false);
|
Navigate(ServerPathBase, noReload: false);
|
||||||
WaitUntilTestSelectorReady();
|
WaitUntilTestSelectorReady();
|
||||||
|
|
@ -92,7 +97,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanArriveAtFallbackPageFromBadURI()
|
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>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
Assert.Equal("Oops, that component wasn't found!", app.FindElement(By.Id("test-info")).Text);
|
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>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other")).Click();
|
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)");
|
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,14 +126,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
var button = app.FindElement(By.LinkText("Other"));
|
var button = app.FindElement(By.LinkText("Other"));
|
||||||
|
|
||||||
new Actions(Browser).KeyDown(key).Click(button).Build().Perform();
|
new Actions(Browser).KeyDown(key).Click(button).Build().Perform();
|
||||||
|
|
||||||
WaitAssert.Equal(2, () => Browser.WindowHandles.Count);
|
Browser.Equal(2, () => Browser.WindowHandles.Count);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Leaving the ctrl key up
|
// Leaving the ctrl key up
|
||||||
new Actions(Browser).KeyUp(key).Build().Perform();
|
new Actions(Browser).KeyUp(key).Build().Perform();
|
||||||
|
|
||||||
// Closing newly opened windows if a new one was opened
|
// Closing newly opened windows if a new one was opened
|
||||||
|
|
@ -151,11 +156,11 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
{
|
{
|
||||||
SetUrlViaPushState("/");
|
SetUrlViaPushState("/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
|
|
||||||
app.FindElement(By.LinkText("Target (_blank)")).Click();
|
app.FindElement(By.LinkText("Target (_blank)")).Click();
|
||||||
|
|
||||||
WaitAssert.Equal(2, () => Browser.WindowHandles.Count);
|
Browser.Equal(2, () => Browser.WindowHandles.Count);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -178,20 +183,20 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
SetUrlViaPushState("/");
|
SetUrlViaPushState("/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
|
|
||||||
app.FindElement(By.LinkText("Other")).Click();
|
app.FindElement(By.LinkText("Other")).Click();
|
||||||
|
|
||||||
Assert.Single(Browser.WindowHandles);
|
Assert.Single(Browser.WindowHandles);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
|
public void CanFollowLinkToOtherPageWithBaseRelativeUrl()
|
||||||
{
|
{
|
||||||
SetUrlViaPushState("/");
|
SetUrlViaPushState("/");
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other with base-relative URL (matches all)")).Click();
|
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)");
|
AssertHighlightedLinks("Other", "Other with base-relative URL (matches all)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +207,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default with base-relative URL (matches all)")).Click();
|
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)");
|
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>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("With parameters")).Click();
|
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");
|
AssertHighlightedLinks("With parameters");
|
||||||
|
|
||||||
// Can add more parameters while remaining on same page
|
// Can add more parameters while remaining on same page
|
||||||
app.FindElement(By.LinkText("With more parameters")).Click();
|
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");
|
AssertHighlightedLinks("With parameters", "With more parameters");
|
||||||
|
|
||||||
// Can remove parameters while remaining on same page
|
// 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.
|
// 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.
|
// See https://github.com/aspnet/AspNetCore/issues/6864 where we reverted the logic to auto-reset.
|
||||||
app.FindElement(By.LinkText("With parameters")).Click();
|
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");
|
AssertHighlightedLinks("With parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +243,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default (matches all)")).Click();
|
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)");
|
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>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other with query")).Click();
|
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");
|
AssertHighlightedLinks("Other", "Other with query");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,7 +265,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default with query")).Click();
|
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");
|
AssertHighlightedLinks("Default with query");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -271,7 +276,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Other with hash")).Click();
|
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");
|
AssertHighlightedLinks("Other", "Other with hash");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,7 +287,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
var app = MountTestComponent<TestRouter>();
|
var app = MountTestComponent<TestRouter>();
|
||||||
app.FindElement(By.LinkText("Default with hash")).Click();
|
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");
|
AssertHighlightedLinks("Default with hash");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,8 +300,8 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
var testSelector = WaitUntilTestSelectorReady();
|
var testSelector = WaitUntilTestSelectorReady();
|
||||||
|
|
||||||
app.FindElement(By.Id("do-navigation")).Click();
|
app.FindElement(By.Id("do-navigation")).Click();
|
||||||
WaitAssert.True(() => Browser.Url.EndsWith("/Other"));
|
Browser.True(() => Browser.Url.EndsWith("/Other"));
|
||||||
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)");
|
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
|
// 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();
|
var testSelector = WaitUntilTestSelectorReady();
|
||||||
|
|
||||||
app.FindElement(By.Id("do-navigation-forced")).Click();
|
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
|
// Because this was a full-page load, our element references should no longer be valid
|
||||||
Assert.Throws<StaleElementReferenceException>(() =>
|
Assert.Throws<StaleElementReferenceException>(() =>
|
||||||
|
|
@ -344,7 +349,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
|
|
||||||
private void AssertHighlightedLinks(params string[] linkTexts)
|
private void AssertHighlightedLinks(params string[] linkTexts)
|
||||||
{
|
{
|
||||||
WaitAssert.Equal(linkTexts, () => Browser
|
Browser.Equal(linkTexts, () => Browser
|
||||||
.FindElements(By.CssSelector("a.active"))
|
.FindElements(By.CssSelector("a.active"))
|
||||||
.Select(x => x.Text));
|
.Select(x => x.Text));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -17,10 +18,14 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
: ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>, IDisposable
|
: ServerTestBase<DevHostServerFixture<StandaloneApp.Program>>, IDisposable
|
||||||
{
|
{
|
||||||
public StandaloneAppTest(
|
public StandaloneAppTest(
|
||||||
BrowserFixture browserFixture,
|
BrowserFixture browserFixture,
|
||||||
DevHostServerFixture<StandaloneApp.Program> serverFixture,
|
DevHostServerFixture<StandaloneApp.Program> serverFixture,
|
||||||
ITestOutputHelper output)
|
ITestOutputHelper output)
|
||||||
: base(browserFixture, serverFixture, output)
|
: base(browserFixture, serverFixture, output)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate("/", noReload: true);
|
Navigate("/", noReload: true);
|
||||||
WaitUntilLoaded();
|
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.
|
// 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 System.Threading.Tasks;
|
||||||
using IdentityServer4.EntityFramework.Entities;
|
using IdentityServer4.EntityFramework.Entities;
|
||||||
using IdentityServer4.EntityFramework.Extensions;
|
using IdentityServer4.EntityFramework.Extensions;
|
||||||
|
|
@ -49,9 +50,47 @@ namespace Microsoft.AspNetCore.ApiAuthorization.IdentityServer
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
|
|
||||||
base.OnModelCreating(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>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="StaticFileOptions"/> that supplies content
|
/// Gets or sets the <see cref="StaticFileOptions"/> that supplies content
|
||||||
/// for serving the SPA's default page.
|
/// for serving the SPA's default page.
|
||||||
///
|
///
|
||||||
/// If not set, a default file provider will read files from the
|
/// If not set, a default file provider will read files from the
|
||||||
/// <see cref="IHostingEnvironment.WebRootPath"/>, which by default is
|
/// <see cref="IHostingEnvironment.WebRootPath"/>, which by default is
|
||||||
/// the <c>wwwroot</c> directory.
|
/// 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
|
/// Gets or sets the maximum duration that a request will wait for the SPA
|
||||||
/// to become ready to serve to the client.
|
/// to become ready to serve to the client.
|
||||||
/// </summary>
|
/// </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' " />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||||
|
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||||
<Content Remove="$(SpaRoot)**" />
|
<Content Remove="$(SpaRoot)**" />
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,9 @@
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True' ">
|
||||||
|
<None Update="app.db" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
<!-- Don't publish the SPA source files, but do show them in the project files list -->
|
||||||
<Content Remove="$(SpaRoot)**" />
|
<Content Remove="$(SpaRoot)**" />
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/architect": {
|
"@angular-devkit/architect": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.13.5.tgz",
|
||||||
"integrity": "sha512-wJF8oz8MurtpFi0ik42bkI2F5gEnuOe79KHPO1i3SYfdhEp5NY8igVKZ6chB/eq4Ml50aHxas8Hh9ke12K+Pxw==",
|
"integrity": "sha512-ouqDu5stZA2gsWnbKMThDfOG/D6lJQaLL+oGEoM5zfnKir3ctyV5rOm73m2pDYUblByTCb+rkj5KmooUWpnV1g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"rxjs": "6.3.3"
|
"rxjs": "6.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -26,16 +26,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular-devkit/build-angular": {
|
"@angular-devkit/build-angular": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.13.5.tgz",
|
||||||
"integrity": "sha512-7yJzgNk3ToiAHd8vnYonqiswvVNYzOUKg2xZfpx+SD5m7mVE+CSUp+P4YzUrI0Vm9WitZOYaCv1I6G1NguJHqA==",
|
"integrity": "sha512-xJq46Jz7MMyprcJ4PflJjPtJ+2OVqbnz6HwtUyJLwYXmC0ldWnhGiNwn+0o7Em40Ol8gf2TYqcDGcSi5OyOZMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/architect": "0.13.4",
|
"@angular-devkit/architect": "0.13.5",
|
||||||
"@angular-devkit/build-optimizer": "0.13.4",
|
"@angular-devkit/build-optimizer": "0.13.5",
|
||||||
"@angular-devkit/build-webpack": "0.13.4",
|
"@angular-devkit/build-webpack": "0.13.5",
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"@ngtools/webpack": "7.3.4",
|
"@ngtools/webpack": "7.3.5",
|
||||||
"ajv": "6.9.1",
|
"ajv": "6.9.1",
|
||||||
"autoprefixer": "9.4.6",
|
"autoprefixer": "9.4.6",
|
||||||
"circular-dependency-plugin": "5.0.2",
|
"circular-dependency-plugin": "5.0.2",
|
||||||
|
|
@ -118,9 +118,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular-devkit/build-optimizer": {
|
"@angular-devkit/build-optimizer": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.13.5.tgz",
|
||||||
"integrity": "sha512-YTpiE4F2GnFc4jbXZkmFUMHOvo3kWcMPAInVbjXNSIWMqW8Ibs7d6MAcualQX4NCvcn45+mVXLfY/8hWZ/b7lw==",
|
"integrity": "sha512-bkyKYplkUnWCbXfDuS0gFuPDoi9OEUNRBtvYtY3rgE3XKSAJBjV+KLgoXSSpLL6ucLDx6gOyDXitUFLiRCDMqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"loader-utils": "1.2.3",
|
"loader-utils": "1.2.3",
|
||||||
|
|
@ -134,17 +134,23 @@
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
|
||||||
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
|
"integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=",
|
||||||
"dev": true
|
"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": {
|
"@angular-devkit/build-webpack": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.13.5.tgz",
|
||||||
"integrity": "sha512-W5baPrsNUUyeD5K9ZjiTfiDsytBoqDvGDMKRUO9XWV8xF8LYF2ttsBQxlJK7SKkMyJXcjmiHhdkMq5wgRE7n0A==",
|
"integrity": "sha512-/abR1cxCLiRJciaW0Dc0RYNbYQIhHFut1r1Dv8xx7He2/wYgCzGsYl9EeFm48Nrw62/9rIPJxhZoZtcf1Mrocg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/architect": "0.13.4",
|
"@angular-devkit/architect": "0.13.5",
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"rxjs": "6.3.3"
|
"rxjs": "6.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -160,9 +166,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular-devkit/core": {
|
"@angular-devkit/core": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-7.3.5.tgz",
|
||||||
"integrity": "sha512-MBfen51iOBKfK4tlg5KwmPxePsF1QoFNUMGLuvUUwPkteonrGcupX1Q7NWTpf+HA+i08mOnZGuepeuQkD12IQw==",
|
"integrity": "sha512-J/Tztq2BZ3tpwUsbiz8N61rf9lwqn85UvJsDui2SPIdzDR9KmPr5ESI2Irc/PEb9i+CBXtVuhr8AIqo7rR6ZTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "6.9.1",
|
"ajv": "6.9.1",
|
||||||
|
|
@ -202,12 +208,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular-devkit/schematics": {
|
"@angular-devkit/schematics": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-7.3.5.tgz",
|
||||||
"integrity": "sha512-BLI4MDHmpzw+snu/2Dw1nMmfJ0VAARTbU6DrmzXyl2Se45+iE/tdRy4yNx3IfHhyoCrVZ15R0y9CXeEsLftlIg==",
|
"integrity": "sha512-BFCCkwRMBC4aFlngaloi1avCTgGrl1MFc/0Av2sCpBh/fdm1FqSVzmOiTfu93dehRVVL/bTrA2qj+xpNsXCxzA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"rxjs": "6.3.3"
|
"rxjs": "6.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -223,24 +229,24 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/animations": {
|
"@angular/animations": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-7.2.8.tgz",
|
||||||
"integrity": "sha512-BJPm9pls6MuIhn6TF1f2ZwkGFTamuyJbhXz8n9u669tTI4deUAEEHCzYaEgVu4q007niVg2ZnO4MDcxXtc5nFQ==",
|
"integrity": "sha512-dJn9koYukyz15TouBc+z5z9fdThDk+bKgdlij25eYSu5Mpmtk04gB4eIMQA97K0UDh1d4YukgSJ5w3ZIk0m8DQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/cli": {
|
"@angular/cli": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.3.5.tgz",
|
||||||
"integrity": "sha512-uGL8xiQf+GvuJvqvMUu/XHcijbq9ocbX487LO2PgJ29etHfI7dC0toJbQ8ob+HnF9e1qwMe+uu45OU4C2p+a1A==",
|
"integrity": "sha512-WL339qoWMIsQympJAabtcvX6hMydGD/H0fm8K9ihD7xd6Af1QCSDN/aWTIvYyEzj9Hb8/sJ3mgRtvLlr1xTHzg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/architect": "0.13.4",
|
"@angular-devkit/architect": "0.13.5",
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"@angular-devkit/schematics": "7.3.4",
|
"@angular-devkit/schematics": "7.3.5",
|
||||||
"@schematics/angular": "7.3.4",
|
"@schematics/angular": "7.3.5",
|
||||||
"@schematics/update": "0.13.4",
|
"@schematics/update": "0.13.5",
|
||||||
"@yarnpkg/lockfile": "1.1.0",
|
"@yarnpkg/lockfile": "1.1.0",
|
||||||
"ini": "1.3.5",
|
"ini": "1.3.5",
|
||||||
"inquirer": "6.2.1",
|
"inquirer": "6.2.1",
|
||||||
|
|
@ -252,29 +258,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/common": {
|
"@angular/common": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/common/-/common-7.2.8.tgz",
|
||||||
"integrity": "sha512-IW3vk0DDblbZMD8gkKVpPa/krXky4i5baFhKgqN2xYo48epXYvAezm5q71a982eadjUussbaYPlsXzYNAhdVKQ==",
|
"integrity": "sha512-LgOhf68+LPndGZhtnUlGFd2goReXYmHzaFZW8gCEi9aC+H+Io8bjYh0gkH3xDreevEOe3f0z6coXNFLIxSmTuA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/compiler": {
|
"@angular/compiler": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-7.2.8.tgz",
|
||||||
"integrity": "sha512-/41ehOSupAA+uc32XHmN5jOvqmb4A4D+V+MXDmnlYVaYAYZrGf3AS+1RJuBy5cIUGQ1Nv+Nbj4Y7X/ydb6ncOQ==",
|
"integrity": "sha512-PrU97cTsOdofpaDkxK0rWUA/CGd0u6ESOI6XvFVm5xH9zJInsdY8ShSHklnr1JJnss70e1dGKZbZq32OChxWMw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/compiler-cli": {
|
"@angular/compiler-cli": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.8.tgz",
|
||||||
"integrity": "sha512-3PzRaz3cKKnhhWKixKhXUvD2klKoAiFO/81ETMC+lp4GGWL35NAts0KnudSNxQIktYOlardQHEggtfgxq+spRg==",
|
"integrity": "sha512-DM35X5GHDCWGKfA+Q/nfBdw+hgahCT+zn7ywOvzyL4p+rkyOUHIHLnLfJekRpUXJYJrq5011MrUMw86HrR0vUg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"canonical-path": "1.0.0",
|
"canonical-path": "1.0.0",
|
||||||
"chokidar": "^1.4.2",
|
"chokidar": "^2.1.1",
|
||||||
"convert-source-map": "^1.5.1",
|
"convert-source-map": "^1.5.1",
|
||||||
"dependency-graph": "^0.7.2",
|
"dependency-graph": "^0.7.2",
|
||||||
"magic-string": "^0.25.0",
|
"magic-string": "^0.25.0",
|
||||||
|
|
@ -292,42 +298,6 @@
|
||||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||||
"dev": true
|
"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": {
|
"camelcase": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||||
|
|
@ -335,20 +305,23 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "1.7.0",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz",
|
||||||
"integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
|
"integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"anymatch": "^1.3.0",
|
"anymatch": "^2.0.0",
|
||||||
"async-each": "^1.0.0",
|
"async-each": "^1.0.1",
|
||||||
"fsevents": "^1.0.0",
|
"braces": "^2.3.2",
|
||||||
"glob-parent": "^2.0.0",
|
"fsevents": "^1.2.7",
|
||||||
"inherits": "^2.0.1",
|
"glob-parent": "^3.1.0",
|
||||||
|
"inherits": "^2.0.3",
|
||||||
"is-binary-path": "^1.0.0",
|
"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",
|
"path-is-absolute": "^1.0.0",
|
||||||
"readdirp": "^2.0.0"
|
"readdirp": "^2.2.1",
|
||||||
|
"upath": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
|
|
@ -377,24 +350,6 @@
|
||||||
"strip-eof": "^1.0.0"
|
"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": {
|
"find-up": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||||
|
|
@ -404,45 +359,12 @@
|
||||||
"locate-path": "^2.0.0"
|
"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": {
|
"is-fullwidth-code-point": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
|
||||||
"dev": true
|
"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": {
|
"load-json-file": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
||||||
|
|
@ -464,26 +386,11 @@
|
||||||
"mimic-fn": "^1.0.0"
|
"mimic-fn": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"micromatch": {
|
"normalize-path": {
|
||||||
"version": "2.3.11",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
"dev": true,
|
"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"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"os-locale": {
|
"os-locale": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
|
@ -596,25 +503,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/core": {
|
"@angular/core": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/core/-/core-7.2.8.tgz",
|
||||||
"integrity": "sha512-SKBDqoKNj9vjuLeNToFySafTWb+fyIhCj6C/yzlPcsRPLZj0Kzbvn1IKE+TWBLa/85dUiaE1xdBNQ66jTtpFSA==",
|
"integrity": "sha512-QKwug2kWJC00zm2rvmD9mCJzsOkMVhSu8vqPWf83poWTh8+F9aIVWcy29W0VoGpBkSchOnK8hf9DnKVv28j9nw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/forms": {
|
"@angular/forms": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.8.tgz",
|
||||||
"integrity": "sha512-VBWbQ26ck1V014DSkFjlrlCksAZ3Q8rmHLZFy+o2k1CVyy49ojV/OxLDfJutp0QvflO+sWnzfDPaND/Ed9tS4w==",
|
"integrity": "sha512-lbSX4IHFHz/c4e2RHiPpL8MJlzDkCuQEHnqsujDaV2X9o9fApS6+C1X4x7Z2XDKqonmeX+aHQwv9+SLejX6OyQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/http": {
|
"@angular/http": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.8.tgz",
|
||||||
"integrity": "sha512-F5AE3QcNibShnhxokFaFhid2Abb+qtMbjfTZu3dSBOWbuz7+H0g7WbCFB4UZvWkTiOaQkTuk0J9IBrwrvt3fkQ==",
|
"integrity": "sha512-bCLgXKbYeSiZPXQ58YbxVKg50PwecDm9SqiXh1QOQOSJsAG7oXXWesN/IOnfP38XeRg9C2NBbJ6mKOyfD/4jdw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
|
|
@ -626,25 +533,25 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@angular/platform-browser": {
|
"@angular/platform-browser": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.8.tgz",
|
||||||
"integrity": "sha512-trSFOsRC+PrjqE709RQ7ezVCouehD7e82FhQNZQx9O1IZQyO0hxE2ncVB4Lvd7KpunAiFX7M1A2wfksHQl+0qw==",
|
"integrity": "sha512-SizCRMc7Or27g2CugcqWnaAikRPfgLgRvb9GFFGpcgoq8CRfOVwkyR5dFZuqN39H+uwtwuTMP5OUYhZcrFNKug==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/platform-browser-dynamic": {
|
"@angular/platform-browser-dynamic": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.2.8.tgz",
|
||||||
"integrity": "sha512-GlipaKFqWlcaGWowccFxAgscpgMnWJucRnDrHRgvp3iUbqt2mC4sLko8BOi0S5FkE1D4+EqyEyp8DLM4o7VDvg==",
|
"integrity": "sha512-nOJt28A5pRn4mdL8y98V7bA6OOdMRjsQAcWCr/isGYF0l1yDC0ijUGWkHuRtj3z1/9tmERN0BLXx+xs1h4JhCQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/platform-server": {
|
"@angular/platform-server": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-7.2.8.tgz",
|
||||||
"integrity": "sha512-JgNSkFq9U+117UrIFM9f2lDJRdXqw4ZxST1kr5cLZ3BAnzlp1JQS9TT9RgIOLVD6rie8Aqgoli7fhaZQ7txvLQ==",
|
"integrity": "sha512-UAg+6rlEiFVw+Fvfyt5VnE5lS5xlLkPmxaKAGotj9y9sUjD7TTI4rJ7ciGAWOuVb5MprDWKlXxPYeBwBHiO0Mw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"domino": "^2.1.0",
|
"domino": "^2.1.0",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.9.0",
|
||||||
|
|
@ -652,9 +559,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@angular/router": {
|
"@angular/router": {
|
||||||
"version": "7.2.5",
|
"version": "7.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/router/-/router-7.2.8.tgz",
|
||||||
"integrity": "sha512-WjEdnTyLQRntB8ixQ4qH8PFURFhgTtUjAsu3S3lf2wWbDDADIJO/xTMtXDhGubTmzRbBVROw6ZQzgDZtJyYKrw==",
|
"integrity": "sha512-G8cA/JbaKFNeosCUGE/0Z7+5FBhZTVV/hacgUBRAEj8NNnECFqkAY9F16Twe+X8qx9WkpMw51WSYDNHPI1/dXQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
}
|
}
|
||||||
|
|
@ -843,12 +750,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@ngtools/webpack": {
|
"@ngtools/webpack": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-7.3.5.tgz",
|
||||||
"integrity": "sha512-qTfw/LGZ3kDZAgqb6gMVr36E0W3M+bnS/xAxNTxshxmJOCQr9AcKtX4sP65QweKS60KoBBR1a7nR6SOi1NJkxA==",
|
"integrity": "sha512-KqJ4ZR8XicN+ElrSNiNPidTM134Z23F7ib0Rl8Ny3PDHkAYIBIxEnQDgZ2mazKRUVMlStUnjmzQIQj7/qZGLaw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"enhanced-resolve": "4.1.0",
|
"enhanced-resolve": "4.1.0",
|
||||||
"rxjs": "6.3.3",
|
"rxjs": "6.3.3",
|
||||||
"tree-kill": "1.2.1",
|
"tree-kill": "1.2.1",
|
||||||
|
|
@ -867,29 +774,37 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nguniversal/module-map-ngfactory-loader": {
|
"@nguniversal/module-map-ngfactory-loader": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nguniversal/module-map-ngfactory-loader/-/module-map-ngfactory-loader-7.1.1.tgz",
|
||||||
"integrity": "sha512-GYfb24OLJKBY58CgUsIsGgci5ceZAt4+GrVKh7RZRIHtZ/bjdGsvpIbfE9udqsnSowxIxHA5KzYHbC1x6AAB0A=="
|
"integrity": "sha512-cZaxdY64C5xPwbE3qqEQGmnKIEgIA57JTozAsT1gvlxNYwssxTlhCYT0HQcqNfNBBjf3xdqXTfRPC7lfpE4qWA=="
|
||||||
},
|
},
|
||||||
"@schematics/angular": {
|
"@schematics/angular": {
|
||||||
"version": "7.3.4",
|
"version": "7.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-7.3.5.tgz",
|
||||||
"integrity": "sha512-Bb5DZQ8MeP8yhxPe6nVqyQ7sGVNwUx6nXPlrQV45ZycD3nJlqsuxr2DE75HFpn5oU+vlkq9J/Sys4WLJ4E/OMw==",
|
"integrity": "sha512-fKNZccf1l2OcDwtDupYj54N/YuiMLCWeaXNxcJNUYvGnBtzxQJ4P2LtSCjB4HDvYCtseQM7iyc7D1Xrr5gI8nw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"@angular-devkit/schematics": "7.3.4",
|
"@angular-devkit/schematics": "7.3.5",
|
||||||
"typescript": "3.2.4"
|
"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": {
|
"@schematics/update": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.13.5.tgz",
|
||||||
"integrity": "sha512-YarSCCBSVPVG/MyN5H/FliRwaIDoeercy5Nip+NWZJsDyvtsAekO9s6QwizSvAr3541MmSQFeQICsjyM2dl3Bg==",
|
"integrity": "sha512-bmwVKeyOmC948gJrIxPg0TY0999nusqSVqXJ8hqAgD0fyD6rnzF74nUhovQGvwFV0pZK8fCkRfdJIqgAPFcHcw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@angular-devkit/core": "7.3.4",
|
"@angular-devkit/core": "7.3.5",
|
||||||
"@angular-devkit/schematics": "7.3.4",
|
"@angular-devkit/schematics": "7.3.5",
|
||||||
"@yarnpkg/lockfile": "1.1.0",
|
"@yarnpkg/lockfile": "1.1.0",
|
||||||
"ini": "1.3.5",
|
"ini": "1.3.5",
|
||||||
"pacote": "9.4.0",
|
"pacote": "9.4.0",
|
||||||
|
|
@ -925,9 +840,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "11.9.6",
|
"version": "11.10.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.10.5.tgz",
|
||||||
"integrity": "sha512-4hS2K4gwo9/aXIcoYxCtHpdgd8XUeDmo1siRCAH3RziXB65JlPqUFMtfy9VPj+og7dp3w1TFjGwYga4e0m9GwA==",
|
"integrity": "sha512-DuIRlQbX4K+d5I+GMnv+UfnGh+ist0RdlvOp+JZ7ePJ6KQONCFQv/gKYSU1ZzbVdFSUCKZOltjmpFAGGv5MdYA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/q": {
|
"@types/q": {
|
||||||
|
|
@ -2151,9 +2066,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30000941",
|
"version": "1.0.30000942",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000941.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000942.tgz",
|
||||||
"integrity": "sha512-4vzGb2MfZcO20VMPj1j6nRAixhmtlhkypM4fL4zhgzEucQIYiRzSqPcWIu1OF8i0FETD93FMIPWfUJCAcFvrqA==",
|
"integrity": "sha512-wLf+IhZUy2rfz48tc40OH7jHjXjnvDFEYqBHluINs/6MgzoNLPf25zhE4NOVzqxLKndf+hau81sAW0RcGHIaBQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"canonical-path": {
|
"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": {
|
"express": {
|
||||||
"version": "4.16.4",
|
"version": "4.16.4",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||||
|
|
@ -3762,12 +3626,6 @@
|
||||||
"schema-utils": "^1.0.0"
|
"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": {
|
"fileset": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
|
||||||
|
|
@ -4598,42 +4456,6 @@
|
||||||
"path-is-absolute": "^1.0.0"
|
"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": {
|
"glob-parent": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
|
||||||
|
|
@ -5393,21 +5215,6 @@
|
||||||
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
|
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
|
||||||
"dev": true
|
"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": {
|
"is-extendable": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
|
||||||
|
|
@ -5495,18 +5302,6 @@
|
||||||
"isobject": "^3.0.1"
|
"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": {
|
"is-promise": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
|
||||||
|
|
@ -6422,12 +6217,6 @@
|
||||||
"object-visit": "^1.0.0"
|
"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": {
|
"md5.js": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||||
|
|
@ -7082,27 +6871,6 @@
|
||||||
"isobject": "^3.0.0"
|
"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": {
|
"object.pick": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
|
||||||
|
|
@ -7446,35 +7214,6 @@
|
||||||
"safe-buffer": "^5.1.1"
|
"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": {
|
"parse-json": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
||||||
|
|
@ -7776,12 +7515,6 @@
|
||||||
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
|
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"preserve": {
|
|
||||||
"version": "0.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
|
|
||||||
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"process": {
|
"process": {
|
||||||
"version": "0.11.10",
|
"version": "0.11.10",
|
||||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||||
|
|
@ -7972,25 +7705,6 @@
|
||||||
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
|
"integrity": "sha512-sluvZZ1YiTLD5jsqZcDmFyV2EwToyXZBfpoVOmktMmW+VEnhgakFHnasVph65fOjGPTWN0Nw3+XQaSeMayr0kg==",
|
||||||
"dev": true
|
"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": {
|
"randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
|
|
@ -8139,15 +7853,6 @@
|
||||||
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
|
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
|
||||||
"optional": true
|
"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": {
|
"regex-not": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,13 @@
|
||||||
"popper.js": "^1.14.3"
|
"popper.js": "^1.14.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.13.2",
|
"@angular-devkit/build-angular": "~0.13.5",
|
||||||
"@angular/cli": "~7.3.2",
|
"@angular/cli": "~7.3.5",
|
||||||
"@angular/compiler-cli": "7.2.5",
|
"@angular/compiler-cli": "^7.2.8",
|
||||||
"@angular/language-service": "^7.2.5",
|
"@angular/language-service": "^7.2.5",
|
||||||
"@types/jasmine": "~3.3.9",
|
"@types/jasmine": "~3.3.9",
|
||||||
"@types/jasminewd2": "~2.0.6",
|
"@types/jasminewd2": "~2.0.6",
|
||||||
"@types/node": "~11.9.4",
|
"@types/node": "~11.10.5",
|
||||||
"codelyzer": "~4.5.0",
|
"codelyzer": "~4.5.0",
|
||||||
"jasmine-core": "~3.3.0",
|
"jasmine-core": "~3.3.0",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { LoginActions, QueryParameterNames, ApplicationPaths, ReturnUrlType } fr
|
||||||
styleUrls: ['./login.component.css']
|
styleUrls: ['./login.component.css']
|
||||||
})
|
})
|
||||||
export class LoginComponent implements OnInit {
|
export class LoginComponent implements OnInit {
|
||||||
private message = new BehaviorSubject<string>(null);
|
public message = new BehaviorSubject<string>(null);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authorizeService: AuthorizeService,
|
private authorizeService: AuthorizeService,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { LogoutActions, ApplicationPaths, ReturnUrlType } from '../api-authoriza
|
||||||
styleUrls: ['./logout.component.css']
|
styleUrls: ['./logout.component.css']
|
||||||
})
|
})
|
||||||
export class LogoutComponent implements OnInit {
|
export class LogoutComponent implements OnInit {
|
||||||
private message = new BehaviorSubject<string>(null);
|
public message = new BehaviorSubject<string>(null);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authorizeService: AuthorizeService,
|
private authorizeService: AuthorizeService,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
"Data/**",
|
"Data/**",
|
||||||
"Models/**",
|
"Models/**",
|
||||||
"ClientApp/src/components/api-authorization/**",
|
"ClientApp/src/components/api-authorization/**",
|
||||||
|
"ClientApp/src/setupTests.js",
|
||||||
"Controllers/OidcConfigurationController.cs"
|
"Controllers/OidcConfigurationController.cs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { FetchData } from './components/FetchData';
|
||||||
import { Counter } from './components/Counter';
|
import { Counter } from './components/Counter';
|
||||||
////#if (IndividualLocalAuth)
|
////#if (IndividualLocalAuth)
|
||||||
import AuthorizeRoute from './components/api-authorization/AuthorizeRoute';
|
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';
|
import { ApplicationPaths } from './components/api-authorization/ApiAuthorizationConstants';
|
||||||
////#endif
|
////#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ import ReactDOM from 'react-dom';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
it('renders without crashing', () => {
|
it('renders without crashing', async () => {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<App />
|
<App />
|
||||||
</MemoryRouter>, div);
|
</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 { Component } from 'react'
|
||||||
import { Route, Redirect } from 'react-router-dom'
|
import { Route, Redirect } from 'react-router-dom'
|
||||||
import { ApplicationPaths, QueryParameterNames } from './ApiAuthorizationConstants'
|
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';
|
import { ApplicationPaths, ApplicationName } from './ApiAuthorizationConstants';
|
||||||
|
|
||||||
export class AuthorizeService {
|
export class AuthorizeService {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import authService from './AuthorizeService';
|
import authService from './AuthorizeService';
|
||||||
import { AuthenticationResultStatus } 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 { NavItem, NavLink } from 'reactstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import authService from './AuthorizeService';
|
import authService from './AuthorizeService';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import authService from './AuthorizeService';
|
import authService from './AuthorizeService';
|
||||||
import { AuthenticationResultStatus } 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 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(ProjectFactoryFixture))]
|
||||||
|
[assembly: AssemblyFixture(typeof(SeleniumStandaloneServer))]
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using ProjectTemplates.Tests.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -14,9 +16,25 @@ namespace Templates.Test
|
||||||
{
|
{
|
||||||
public class BaselineTest
|
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)
|
public BaselineTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
Project = projectFactory.CreateProject(output);
|
ProjectFactory = projectFactory;
|
||||||
|
Output = output;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Project Project { get; set; }
|
public Project Project { get; set; }
|
||||||
|
|
@ -47,14 +65,20 @@ namespace Templates.Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProjectFactoryFixture ProjectFactory { get; }
|
||||||
|
public ITestOutputHelper Output { get; }
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(TemplateBaselines))]
|
[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)
|
foreach (var file in expectedFiles)
|
||||||
{
|
{
|
||||||
Project.AssertFileExists(file, shouldExist: true);
|
AssertFileExists(Project.TemplateOutputDir, file, shouldExist: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var filesInFolder = Directory.EnumerateFiles(Project.TemplateOutputDir, "*", SearchOption.AllDirectories);
|
var filesInFolder = Directory.EnumerateFiles(Project.TemplateOutputDir, "*", SearchOption.AllDirectories);
|
||||||
|
|
@ -73,5 +97,36 @@ namespace Templates.Test
|
||||||
Assert.Contains(relativePath, expectedFiles);
|
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.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Templates.Test.Helpers;
|
using Templates.Test.Helpers;
|
||||||
|
|
@ -27,14 +28,22 @@ namespace Templates.Test
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private static List<ScriptTag> _scriptTags;
|
private static List<ScriptTag> _scriptTags;
|
||||||
private static List<LinkTag> _linkTags;
|
private static List<LinkTag> _linkTags;
|
||||||
|
private static readonly string[] _packages;
|
||||||
|
|
||||||
static CdnScriptTagTests()
|
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>();
|
_scriptTags = new List<ScriptTag>();
|
||||||
_linkTags = new List<LinkTag>();
|
_linkTags = new List<LinkTag>();
|
||||||
foreach (var packagePath in packages)
|
foreach (var packagePath in _packages)
|
||||||
{
|
{
|
||||||
var tags = GetTags(packagePath);
|
var tags = GetTags(packagePath);
|
||||||
_scriptTags.AddRange(tags.scripts);
|
_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)
|
public CdnScriptTagTests(ITestOutputHelper output)
|
||||||
{
|
{
|
||||||
_output = output;
|
_output = output;
|
||||||
|
|
@ -162,7 +176,8 @@ namespace Templates.Test
|
||||||
private async Task<HttpResponseMessage> GetFromCDN(string src)
|
private async Task<HttpResponseMessage> GetFromCDN(string src)
|
||||||
{
|
{
|
||||||
var logger = NullLogger.Instance;
|
var logger = NullLogger.Instance;
|
||||||
return await RetryHelper.RetryRequest(async () => {
|
return await RetryHelper.RetryRequest(async () =>
|
||||||
|
{
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(src));
|
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(src));
|
||||||
return await _httpClient.SendAsync(request);
|
return await _httpClient.SendAsync(request);
|
||||||
}, logger);
|
}, logger);
|
||||||
|
|
@ -191,7 +206,7 @@ namespace Templates.Test
|
||||||
|
|
||||||
private static string GetFileContentFromArchive(ScriptTag scriptTag, string relativeFilePath)
|
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))
|
using (var zip = new ZipArchive(File.OpenRead(file), ZipArchiveMode.Read, leaveOpen: false))
|
||||||
{
|
{
|
||||||
var entry = zip.Entries
|
var entry = zip.Entries
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<Project>
|
<Project>
|
||||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.targets))\Directory.Build.targets" />
|
||||||
<Import Project="GenerateTestProps.targets" />
|
<Import Project="$(MSBuildThisFileDirectory)Infrastructure\GenerateTestProps.targets" />
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.E2ETesting;
|
using System.Threading.Tasks;
|
||||||
using ProjectTemplates.Tests.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -12,22 +12,50 @@ namespace Templates.Test
|
||||||
{
|
{
|
||||||
public EmptyWebTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
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]
|
[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))
|
Assert.False(
|
||||||
{
|
aspNetProcess.Process.HasExited,
|
||||||
aspNetProcess.AssertOk("/");
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Certificates.Generation;
|
using Microsoft.AspNetCore.Certificates.Generation;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
|
|
@ -18,16 +18,18 @@ namespace Templates.Test.Helpers
|
||||||
{
|
{
|
||||||
public class AspNetProcess : IDisposable
|
public class AspNetProcess : IDisposable
|
||||||
{
|
{
|
||||||
private const string DefaultFramework = "netcoreapp3.0";
|
|
||||||
private const string ListeningMessagePrefix = "Now listening on: ";
|
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 Uri _listeningUri;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly ITestOutputHelper _output;
|
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;
|
_output = output;
|
||||||
_httpClient = new HttpClient(new HttpClientHandler()
|
_httpClient = new HttpClient(new HttpClientHandler()
|
||||||
|
|
@ -35,54 +37,23 @@ namespace Templates.Test.Helpers
|
||||||
AllowAutoRedirect = true,
|
AllowAutoRedirect = true,
|
||||||
UseCookies = true,
|
UseCookies = true,
|
||||||
CookieContainer = new CookieContainer(),
|
CookieContainer = new CookieContainer(),
|
||||||
ServerCertificateCustomValidationCallback = (m, c, ch, p) => true
|
ServerCertificateCustomValidationCallback = (m, c, ch, p) => true,
|
||||||
});
|
})
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromMinutes(2)
|
||||||
|
};
|
||||||
|
|
||||||
var now = DateTimeOffset.Now;
|
var now = DateTimeOffset.Now;
|
||||||
new CertificateManager().EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1));
|
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...");
|
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: environmentVariables);
|
||||||
_process = ProcessEx.Run(output, workingDirectory, DotNetMuxer.MuxerPathOrDefault(), $"exec {dllPath}", envVars: envVars);
|
|
||||||
_listeningUri = GetListeningUri(output);
|
_listeningUri = GetListeningUri(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void VisitInBrowser(IWebDriver driver)
|
public void VisitInBrowser(IWebDriver driver)
|
||||||
{
|
{
|
||||||
_output.WriteLine($"Opening browser at {_listeningUri}...");
|
_output.WriteLine($"Opening browser at {_listeningUri}...");
|
||||||
|
|
@ -116,31 +87,42 @@ namespace Templates.Test.Helpers
|
||||||
{
|
{
|
||||||
// Wait until the app is accepting HTTP requests
|
// Wait until the app is accepting HTTP requests
|
||||||
output.WriteLine("Waiting until ASP.NET application is accepting connections...");
|
output.WriteLine("Waiting until ASP.NET application is accepting connections...");
|
||||||
var listeningMessage = _process
|
var listeningMessage = Process
|
||||||
.OutputLinesAsEnumerable
|
.OutputLinesAsEnumerable
|
||||||
.Where(line => line != null)
|
.Where(line => line != null)
|
||||||
.FirstOrDefault(line => line.Trim().StartsWith(ListeningMessagePrefix, StringComparison.Ordinal));
|
.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
|
if (!string.IsNullOrEmpty(listeningMessage))
|
||||||
var listeningUrlString = listeningMessage.Substring(ListeningMessagePrefix.Length);
|
{
|
||||||
output.WriteLine($"Detected that ASP.NET application is accepting connections on: {listeningUrlString}");
|
listeningMessage = listeningMessage.Trim();
|
||||||
listeningUrlString = listeningUrlString.Substring(0, listeningUrlString.IndexOf(':')) +
|
// Verify we have a valid URL to make requests to
|
||||||
"://localhost" +
|
var listeningUrlString = listeningMessage.Substring(ListeningMessagePrefix.Length);
|
||||||
listeningUrlString.Substring(listeningUrlString.LastIndexOf(':'));
|
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);
|
output.WriteLine("Sending requests to " + listeningUrlString);
|
||||||
return new Uri(listeningUrlString, UriKind.Absolute);
|
return new Uri(listeningUrlString, UriKind.Absolute);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AssertOk(string requestUrl)
|
public Task AssertOk(string requestUrl)
|
||||||
=> AssertStatusCode(requestUrl, HttpStatusCode.OK);
|
=> AssertStatusCode(requestUrl, HttpStatusCode.OK);
|
||||||
|
|
||||||
public void AssertNotFound(string requestUrl)
|
public Task AssertNotFound(string requestUrl)
|
||||||
=> AssertStatusCode(requestUrl, HttpStatusCode.NotFound);
|
=> 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(
|
var request = new HttpRequestMessage(
|
||||||
HttpMethod.Get,
|
HttpMethod.Get,
|
||||||
|
|
@ -151,14 +133,14 @@ namespace Templates.Test.Helpers
|
||||||
request.Headers.Add("Accept", acceptContentType);
|
request.Headers.Add("Accept", acceptContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = _httpClient.SendAsync(request).Result;
|
var response = await _httpClient.SendAsync(request);
|
||||||
Assert.Equal(statusCode, response.StatusCode);
|
Assert.Equal(statusCode, response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_httpClient.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.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
@ -15,10 +17,7 @@ namespace Templates.Test.Helpers
|
||||||
{
|
{
|
||||||
internal class ProcessEx : IDisposable
|
internal class ProcessEx : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly string NUGET_PACKAGES = typeof(ProcessEx).Assembly
|
private static readonly string NUGET_PACKAGES = GetNugetPackagesRestorePath();
|
||||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
|
||||||
.First(attribute => attribute.Key == "TestPackageRestorePath")
|
|
||||||
.Value;
|
|
||||||
|
|
||||||
private readonly ITestOutputHelper _output;
|
private readonly ITestOutputHelper _output;
|
||||||
private readonly Process _process;
|
private readonly Process _process;
|
||||||
|
|
@ -28,6 +27,54 @@ namespace Templates.Test.Helpers
|
||||||
private BlockingCollection<string> _stdoutLines;
|
private BlockingCollection<string> _stdoutLines;
|
||||||
private TaskCompletionSource<int> _exited;
|
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)
|
public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary<string, string> envVars = null)
|
||||||
{
|
{
|
||||||
var startInfo = new ProcessStartInfo(command, args)
|
var startInfo = new ProcessStartInfo(command, args)
|
||||||
|
|
@ -55,59 +102,17 @@ namespace Templates.Test.Helpers
|
||||||
return new ProcessEx(output, proc);
|
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)
|
var (shellExe, argsPrefix) = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||||
? ("cmd", "/c")
|
? ("cmd", "/c")
|
||||||
: ("bash", "-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)
|
private void OnErrorData(object sender, DataReceivedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.Data == null)
|
if (e.Data == null)
|
||||||
|
|
@ -151,6 +156,16 @@ namespace Templates.Test.Helpers
|
||||||
_exited.TrySetResult(_process.ExitCode);
|
_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)
|
public void WaitForExit(bool assertSuccess)
|
||||||
{
|
{
|
||||||
Exited.Wait();
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_process != null && !_process.HasExited)
|
if (_process != null && !_process.HasExited)
|
||||||
|
|
@ -176,7 +197,5 @@ namespace Templates.Test.Helpers
|
||||||
_process.Exited -= OnProcessExited;
|
_process.Exited -= OnProcessExited;
|
||||||
_process.Dispose();
|
_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.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using System.Threading.Tasks;
|
||||||
using Templates.Test.Helpers;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace ProjectTemplates.Tests.Helpers
|
namespace Templates.Test.Helpers
|
||||||
{
|
{
|
||||||
public class ProjectFactoryFixture : IDisposable
|
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);
|
DiagnosticsMessageSink = diagnosticsMessageSink;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
var list = new List<Exception>();
|
var list = new List<Exception>();
|
||||||
|
|
@ -70,9 +64,9 @@ $@"<Project>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
project.Dispose();
|
project.Value.Dispose();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
list.Add(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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Templates.Test.Helpers
|
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
|
internal static class TemplatePackageInstaller
|
||||||
{
|
{
|
||||||
private static object _templatePackagesReinstallationLock = new object();
|
private static SemaphoreSlim InstallerLock = new SemaphoreSlim(1);
|
||||||
private static bool _haveReinstalledTemplatePackages;
|
private static bool _haveReinstalledTemplatePackages;
|
||||||
|
|
||||||
private static object DotNetNewLock = new object();
|
|
||||||
|
|
||||||
private static readonly string[] _templatePackages = new[]
|
private static readonly string[] _templatePackages = new[]
|
||||||
{
|
{
|
||||||
"Microsoft.DotNet.Common.ItemTemplates",
|
"Microsoft.DotNet.Common.ItemTemplates",
|
||||||
|
|
@ -51,11 +35,14 @@ namespace Templates.Test.Helpers
|
||||||
"Microsoft.DotNet.Web.Spa.ProjectTemplates.3.0"
|
"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)
|
if (!_haveReinstalledTemplatePackages)
|
||||||
{
|
{
|
||||||
|
|
@ -63,68 +50,78 @@ namespace Templates.Test.Helpers
|
||||||
{
|
{
|
||||||
Directory.Delete(CustomHivePath, recursive: true);
|
Directory.Delete(CustomHivePath, recursive: true);
|
||||||
}
|
}
|
||||||
InstallTemplatePackages(output);
|
await InstallTemplatePackages(output);
|
||||||
_haveReinstalledTemplatePackages = true;
|
_haveReinstalledTemplatePackages = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
finally
|
||||||
|
|
||||||
public static ProcessEx RunDotNetNew(ITestOutputHelper output, string arguments, bool assertSuccess)
|
|
||||||
{
|
|
||||||
lock (DotNetNewLock)
|
|
||||||
{
|
{
|
||||||
var proc = ProcessEx.Run(
|
InstallerLock.Release();
|
||||||
output,
|
|
||||||
AppContext.BaseDirectory,
|
|
||||||
DotNetMuxer.MuxerPathOrDefault(),
|
|
||||||
$"new {arguments} --debug:custom-hive \"{CustomHivePath}\"");
|
|
||||||
proc.WaitForExit(assertSuccess);
|
|
||||||
|
|
||||||
return proc;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// Remove any previous or prebundled version of the template packages
|
||||||
foreach (var packageName in _templatePackages)
|
foreach (var packageName in _templatePackages)
|
||||||
{
|
{
|
||||||
// We don't need this command to succeed, because we'll verify next that
|
// 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
|
// uninstallation had the desired effect. This command is expected to fail
|
||||||
// in the case where the package wasn't previously installed.
|
// 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");
|
await VerifyCannotFindTemplateAsync(output, "web");
|
||||||
VerifyCannotFindTemplate(output, "webapp");
|
await VerifyCannotFindTemplateAsync(output, "webapp");
|
||||||
VerifyCannotFindTemplate(output, "mvc");
|
await VerifyCannotFindTemplateAsync(output, "mvc");
|
||||||
VerifyCannotFindTemplate(output, "react");
|
await VerifyCannotFindTemplateAsync(output, "react");
|
||||||
VerifyCannotFindTemplate(output, "reactredux");
|
await VerifyCannotFindTemplateAsync(output, "reactredux");
|
||||||
VerifyCannotFindTemplate(output, "angular");
|
await VerifyCannotFindTemplateAsync(output, "angular");
|
||||||
|
|
||||||
var builtPackages = MondoHelpers.GetNupkgFiles();
|
foreach (var packagePath in builtPackages)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
output.WriteLine($"Installing templates package {packagePath}...");
|
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");
|
await VerifyCanFindTemplate(output, "webapp");
|
||||||
VerifyCanFindTemplate(output, "react");
|
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} "))
|
if (!proc.Output.Contains($" {templateName} "))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Couldn't find {templateName} as an option in {proc.Output}.");
|
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
|
// Verify we really did remove the previous templates
|
||||||
var tempDir = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName(), Guid.NewGuid().ToString("D"));
|
var tempDir = Path.Combine(AppContext.BaseDirectory, Path.GetRandomFileName(), Guid.NewGuid().ToString("D"));
|
||||||
|
|
@ -132,7 +129,7 @@ namespace Templates.Test.Helpers
|
||||||
|
|
||||||
try
|
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}."))
|
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;
|
||||||
using OpenQA.Selenium.Interactions;
|
using OpenQA.Selenium.Interactions;
|
||||||
using OpenQA.Selenium.Support.UI;
|
using OpenQA.Selenium.Support.UI;
|
||||||
|
|
@ -8,6 +11,12 @@ namespace Templates.Test.Helpers
|
||||||
{
|
{
|
||||||
public static class WebDriverExtensions
|
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)
|
public static string GetText(this ISearchContext driver, string cssSelector)
|
||||||
{
|
{
|
||||||
return driver.FindElement(By.CssSelector(cssSelector)).Text;
|
return driver.FindElement(By.CssSelector(cssSelector)).Text;
|
||||||
|
|
@ -27,10 +36,6 @@ namespace Templates.Test.Helpers
|
||||||
.Perform();
|
.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)
|
public static void Click(this IWebDriver driver, ISearchContext searchContext, string cssSelector)
|
||||||
{
|
{
|
||||||
|
|
@ -47,21 +52,11 @@ namespace Templates.Test.Helpers
|
||||||
return webElement.FindElement(By.XPath(".."));
|
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)
|
public static IWebElement FindElement(this IWebDriver driver, ISearchContext searchContext, string cssSelector, int timeoutSeconds)
|
||||||
{
|
{
|
||||||
return FindElement(driver, searchContext, By.CssSelector(cssSelector), 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)
|
public static IWebElement FindElement(this IWebDriver driver, ISearchContext searchContext, By by, int timeoutSeconds)
|
||||||
{
|
{
|
||||||
return new WebDriverWait(driver, TimeSpan.FromSeconds(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)
|
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));
|
.Until(driver => driver.Url.Contains(expectedUrl, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WaitForElement(this IWebDriver browser, string expectedElementCss)
|
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);
|
.Until(driver => driver.FindElements(By.CssSelector(expectedElementCss)).Count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WaitForText(this IWebDriver browser, string cssSelector, string expectedText)
|
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 => {
|
.Until(driver => {
|
||||||
try
|
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>
|
<Project>
|
||||||
<Target Name="GenerateTestProps" BeforeTargets="CoreCompile">
|
<Target
|
||||||
|
Name="GenerateTestProps"
|
||||||
|
BeforeTargets="CoreCompile"
|
||||||
|
DependsOnTargets="PrepareForTest"
|
||||||
|
Condition="$(DesignTimeBuild) != true">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PropsProperties>
|
<PropsProperties>
|
||||||
RestoreSources=$([MSBuild]::Escape("$(RestoreSources);$(ArtifactsShippingPackagesDir);$(ArtifactsNonShippingPackagesDir)"));
|
RestoreSources=$([MSBuild]::Escape("$(RestoreSources);$(ArtifactsShippingPackagesDir);$(ArtifactsNonShippingPackagesDir)"));
|
||||||
|
|
@ -12,8 +16,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Sdk_GenerateFileFromTemplate
|
<Sdk_GenerateFileFromTemplate
|
||||||
TemplateFile="$(MSBuildThisFileDirectory)TemplateTests.props.in"
|
TemplateFile="$(MSBuildThisFileDirectory)\TemplateTests.props.in"
|
||||||
Properties="$(PropsProperties)"
|
Properties="$(PropsProperties)"
|
||||||
OutputPath="$(OutputPath)TemplateTests.props" />
|
OutputPath="$(TestTemplateTestsProps)" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// 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.
|
// 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;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -11,71 +13,152 @@ namespace Templates.Test
|
||||||
{
|
{
|
||||||
public MvcTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
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]
|
[Theory]
|
||||||
[InlineData(null)]
|
[InlineData(null)]
|
||||||
[InlineData("F#")]
|
[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);
|
var createResult = await Project.RunDotNetNewAsync("mvc", language: languageOverride);
|
||||||
Project.AssertDirectoryExists("Extensions", false);
|
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||||
Project.AssertFileExists("urlRewrite.config", false);
|
|
||||||
Project.AssertFileExists("Controllers/AccountController.cs", false);
|
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 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(".db", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents);
|
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents);
|
Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents);
|
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", 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))
|
Assert.False(
|
||||||
{
|
aspNetProcess.Process.HasExited,
|
||||||
aspNetProcess.AssertOk("/");
|
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||||
aspNetProcess.AssertOk("/Home/Privacy");
|
|
||||||
}
|
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]
|
[Theory]
|
||||||
[InlineData(true)]
|
[InlineData(true)]
|
||||||
[InlineData(false)]
|
[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);
|
var createResult = await Project.RunDotNetNewAsync("mvc", auth: "Individual", useLocalDB: useLocalDB);
|
||||||
Project.AssertFileExists("urlRewrite.config", false);
|
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||||
Project.AssertFileExists("Controllers/AccountController.cs", false);
|
|
||||||
|
|
||||||
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)
|
if (!useLocalDB)
|
||||||
{
|
{
|
||||||
Assert.Contains(".db", projectFileContents);
|
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");
|
Project.AssertEmptyMigration("mvc");
|
||||||
|
|
||||||
foreach (var publish in new[] { false, true })
|
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||||
{
|
{
|
||||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
await aspNetProcess.AssertOk("/");
|
||||||
{
|
await aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||||
aspNetProcess.AssertOk("/");
|
await aspNetProcess.AssertOk("/Home/Privacy");
|
||||||
aspNetProcess.AssertOk("/Identity/Account/Login");
|
|
||||||
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>
|
<SkipTests Condition="'$(RunTemplateTests)' != 'true'">true</SkipTests>
|
||||||
<!-- https://github.com/aspnet/AspNetCore/issues/6857 -->
|
<!-- https://github.com/aspnet/AspNetCore/issues/6857 -->
|
||||||
<BuildHelixPayload>false</BuildHelixPayload>
|
<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>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -45,10 +51,81 @@
|
||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
|
||||||
<_Parameter1>TestPackageRestorePath</_Parameter1>
|
<_Parameter1>TestPackageRestorePath</_Parameter1>
|
||||||
<_Parameter2>$([MSBuild]::EnsureTrailingSlash('$(RepositoryRoot)'))obj\template-restore\</_Parameter2>
|
<_Parameter2>$(TestPackageRestorePath)</_Parameter2>
|
||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
</ItemGroup>
|
</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 -->
|
<!-- Shared testing infrastructure for running E2E tests using selenium -->
|
||||||
<Import Project="$(SharedSourceRoot)E2ETesting\E2ETesting.targets" />
|
<Import Project="$(SharedSourceRoot)E2ETesting\E2ETesting.targets" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
// 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.
|
// 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.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
|
using OpenQA.Selenium;
|
||||||
using Templates.Test.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
@ -18,25 +16,52 @@ namespace Templates.Test
|
||||||
{
|
{
|
||||||
public RazorComponentsTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output) : base(browserFixture, output)
|
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")]
|
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8244")]
|
||||||
public void RazorComponentsTemplateWorks()
|
public async Task RazorComponentsTemplateWorksAsync()
|
||||||
{
|
{
|
||||||
Project.RunDotNetNew("razorcomponents");
|
Project = await ProjectFactory.GetOrCreateProject("razorcomponents", Output);
|
||||||
TestApplication(publish: false);
|
|
||||||
TestApplication(publish: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TestApplication(bool publish)
|
var createResult = await Project.RunDotNetNewAsync("razorcomponents");
|
||||||
{
|
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
|
||||||
|
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())
|
if (BrowserFixture.IsHostAutomationSupported())
|
||||||
{
|
{
|
||||||
aspNetProcess.VisitInBrowser(Browser);
|
aspNetProcess.VisitInBrowser(Browser);
|
||||||
|
|
@ -67,7 +92,7 @@ namespace Templates.Test
|
||||||
var counterDisplay = Browser.FindElement("h1 + p");
|
var counterDisplay = Browser.FindElement("h1 + p");
|
||||||
Assert.Equal("Current count: 0", counterDisplay.Text);
|
Assert.Equal("Current count: 0", counterDisplay.Text);
|
||||||
Browser.Click(counterComponent, "button");
|
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
|
// Can navigate to the 'fetch data' page
|
||||||
Browser.Click(By.PartialLinkText("Fetch data"));
|
Browser.Click(By.PartialLinkText("Fetch data"));
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Testing.xunit;
|
using System.IO;
|
||||||
using ProjectTemplates.Tests.Helpers;
|
using System.Threading.Tasks;
|
||||||
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -12,63 +13,130 @@ namespace Templates.Test
|
||||||
{
|
{
|
||||||
public RazorPagesTemplateTest(ProjectFactoryFixture projectFactory, ITestOutputHelper output)
|
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]
|
[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(".db", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents);
|
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents);
|
Assert.DoesNotContain("Microsoft.VisualStudio.Web.CodeGeneration.Design", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents);
|
Assert.DoesNotContain("Microsoft.EntityFrameworkCore.Tools.DotNet", projectFileContents);
|
||||||
Assert.DoesNotContain("Microsoft.Extensions.SecretManager.Tools", 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))
|
Assert.False(
|
||||||
{
|
aspNetProcess.Process.HasExited,
|
||||||
aspNetProcess.AssertOk("/");
|
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||||
aspNetProcess.AssertOk("/Privacy");
|
|
||||||
}
|
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]
|
[Theory]
|
||||||
[InlineData(false)]
|
[InlineData(false)]
|
||||||
[InlineData(true)]
|
[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)
|
if (!useLocalDB)
|
||||||
{
|
{
|
||||||
Assert.Contains(".db", projectFileContents);
|
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");
|
Project.AssertEmptyMigration("razorpages");
|
||||||
|
|
||||||
foreach (var publish in new[] { false, true })
|
using (var aspNetProcess = Project.StartBuiltProjectAsync())
|
||||||
{
|
{
|
||||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
await aspNetProcess.AssertOk("/");
|
||||||
{
|
await aspNetProcess.AssertOk("/Identity/Account/Login");
|
||||||
aspNetProcess.AssertOk("/");
|
await aspNetProcess.AssertOk("/Privacy");
|
||||||
aspNetProcess.AssertOk("/Identity/Account/Login");
|
|
||||||
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.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.E2ETesting;
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
using ProjectTemplates.Tests.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -13,8 +14,16 @@ namespace Templates.Test.SpaTemplateTest
|
||||||
public AngularTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
|
public AngularTemplateTest(ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output)
|
||||||
: base(projectFactory, browserFixture, output) { }
|
: base(projectFactory, browserFixture, output) { }
|
||||||
|
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore-Internal/issues/1854")]
|
[Fact]
|
||||||
public void AngularTemplate_Works()
|
public Task AngularTemplate_Works()
|
||||||
=> SpaTemplateImpl("angular");
|
=> 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.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.E2ETesting;
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
using ProjectTemplates.Tests.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -15,8 +16,8 @@ namespace Templates.Test.SpaTemplateTest
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/7377")]
|
[Fact]
|
||||||
public void ReactReduxTemplate_Works_NetCore()
|
public Task ReactReduxTemplate_Works_NetCore()
|
||||||
=> SpaTemplateImpl("reactredux");
|
=> SpaTemplateImplAsync("reactredux", "reactredux",useLocalDb: false, usesAuth: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.E2ETesting;
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
using ProjectTemplates.Tests.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
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.")]
|
[Fact]
|
||||||
public void ReactTemplate_Works_NetCore()
|
public Task ReactTemplate_Works_NetCore()
|
||||||
=> SpaTemplateImpl("react");
|
=> 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.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.E2ETesting;
|
using System;
|
||||||
using OpenQA.Selenium;
|
|
||||||
using ProjectTemplates.Tests.Helpers;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using OpenQA.Selenium;
|
||||||
using Templates.Test.Helpers;
|
using Templates.Test.Helpers;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
@ -14,7 +16,6 @@ using Xunit.Abstractions;
|
||||||
#if EDGE
|
#if EDGE
|
||||||
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
|
[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
|
||||||
#endif
|
#endif
|
||||||
[assembly: TestFramework("Microsoft.AspNetCore.E2ETesting.XunitTestFrameworkWithAssemblyFixture", "ProjectTemplates.Tests")]
|
|
||||||
namespace Templates.Test.SpaTemplateTest
|
namespace Templates.Test.SpaTemplateTest
|
||||||
{
|
{
|
||||||
public class SpaTemplateTestBase : BrowserTestBase
|
public class SpaTemplateTestBase : BrowserTestBase
|
||||||
|
|
@ -22,49 +23,150 @@ namespace Templates.Test.SpaTemplateTest
|
||||||
public SpaTemplateTestBase(
|
public SpaTemplateTestBase(
|
||||||
ProjectFactoryFixture projectFactory, BrowserFixture browserFixture, ITestOutputHelper output) : base(browserFixture, output)
|
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',
|
// 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
|
// 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.
|
// 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
|
var createResult = await Project.RunDotNetNewAsync(template, auth: usesAuth ? "Individual" : null, language: null, useLocalDb);
|
||||||
// templates it's at the project root. Strictly speaking we shouldn't have
|
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
|
||||||
// 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
|
// We shouldn't have to do the NPM restore in tests because it should happen
|
||||||
// installs run concurrently which otherwise causes errors when tests run
|
// automatically at build time, but by doing it up front we can avoid having
|
||||||
// in parallel.
|
// multiple NPM installs run concurrently which otherwise causes errors when
|
||||||
|
// tests run in parallel.
|
||||||
var clientAppSubdirPath = Path.Combine(Project.TemplateOutputDir, "ClientApp");
|
var clientAppSubdirPath = Path.Combine(Project.TemplateOutputDir, "ClientApp");
|
||||||
Assert.True(File.Exists(Path.Combine(clientAppSubdirPath, "package.json")), "Missing a package.json");
|
Assert.True(File.Exists(Path.Combine(clientAppSubdirPath, "package.json")), "Missing a package.json");
|
||||||
|
|
||||||
Npm.RestoreWithRetry(Output, clientAppSubdirPath);
|
var projectFileContents = ReadFile(Project.TemplateOutputDir, $"{Project.ProjectName}.csproj");
|
||||||
Npm.Test(Output, clientAppSubdirPath);
|
if (usesAuth && !useLocalDb)
|
||||||
|
|
||||||
TestApplication(publish: false);
|
|
||||||
TestApplication(publish: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TestApplication(bool publish)
|
|
||||||
{
|
|
||||||
using (var aspNetProcess = Project.StartAspNetProcess(publish))
|
|
||||||
{
|
{
|
||||||
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())
|
if (BrowserFixture.IsHostAutomationSupported())
|
||||||
{
|
{
|
||||||
aspNetProcess.VisitInBrowser(Browser);
|
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");
|
Browser.WaitForElement("ul");
|
||||||
// <title> element gets project ID injected into it during template execution
|
// <title> element gets project ID injected into it during template execution
|
||||||
|
|
@ -85,16 +187,40 @@ namespace Templates.Test.SpaTemplateTest
|
||||||
Browser.Click(counterComponent, "button");
|
Browser.Click(counterComponent, "button");
|
||||||
Assert.Equal("1", counterComponent.GetText("strong"));
|
Assert.Equal("1", counterComponent.GetText("strong"));
|
||||||
|
|
||||||
// Can navigate to the 'fetch data' page
|
if (visitFetchData)
|
||||||
Browser.Click(By.PartialLinkText("Fetch data"));
|
{
|
||||||
Browser.WaitForUrl("fetch-data");
|
// Can navigate to the 'fetch data' page
|
||||||
Assert.Equal("Weather forecast", Browser.GetText("h1"));
|
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
|
// Asynchronously loads and displays the table of weather forecasts
|
||||||
var fetchDataComponent = Browser.FindElement("h1").Parent();
|
var fetchDataComponent = Browser.FindElement("h1").Parent();
|
||||||
Browser.WaitForElement("table>tbody>tr");
|
Browser.WaitForElement("table>tbody>tr");
|
||||||
var table = Browser.FindElement(fetchDataComponent, "table", timeoutSeconds: 5);
|
var table = Browser.FindElement(fetchDataComponent, "table", timeoutSeconds: 5);
|
||||||
Assert.Equal(5, table.FindElements(By.CssSelector("tbody tr")).Count);
|
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.
|
// 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.
|
// 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;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
|
@ -11,23 +12,53 @@ namespace Templates.Test
|
||||||
{
|
{
|
||||||
public WebApiTemplateTest(ProjectFactoryFixture factoryFixture, ITestOutputHelper output)
|
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]
|
[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))
|
Assert.False(
|
||||||
{
|
aspNetProcess.Process.HasExited,
|
||||||
aspNetProcess.AssertOk("/api/values");
|
ErrorMessages.GetFailedProcessMessageOrEmpty("Run built project", Project, aspNetProcess.Process));
|
||||||
aspNetProcess.AssertNotFound("/");
|
|
||||||
}
|
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;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using OpenQA.Selenium.Chrome;
|
using OpenQA.Selenium.Chrome;
|
||||||
using OpenQA.Selenium.Remote;
|
using OpenQA.Selenium.Remote;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
using Xunit.Sdk;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.E2ETesting
|
namespace Microsoft.AspNetCore.E2ETesting
|
||||||
{
|
{
|
||||||
public class BrowserFixture : IDisposable
|
public class BrowserFixture : IDisposable
|
||||||
{
|
{
|
||||||
|
private RemoteWebDriver _browser;
|
||||||
|
private RemoteLogs _logs;
|
||||||
|
|
||||||
public BrowserFixture(IMessageSink diagnosticsMessageSink)
|
public BrowserFixture(IMessageSink diagnosticsMessageSink)
|
||||||
{
|
{
|
||||||
DiagnosticsMessageSink = 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; private set; }
|
||||||
|
|
||||||
public ILogs Logs { get; }
|
|
||||||
|
|
||||||
public IMessageSink DiagnosticsMessageSink { get; }
|
public IMessageSink DiagnosticsMessageSink { get; }
|
||||||
|
|
||||||
|
|
@ -79,10 +52,81 @@ namespace Microsoft.AspNetCore.E2ETesting
|
||||||
|
|
||||||
public void Dispose()
|
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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using OpenQA.Selenium;
|
using OpenQA.Selenium;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
@ -9,23 +10,46 @@ using Xunit.Abstractions;
|
||||||
namespace Microsoft.AspNetCore.E2ETesting
|
namespace Microsoft.AspNetCore.E2ETesting
|
||||||
{
|
{
|
||||||
[CaptureSeleniumLogs]
|
[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<ILogs> _logs = new AsyncLocal<ILogs>();
|
||||||
private static readonly AsyncLocal<ITestOutputHelper> _output = new AsyncLocal<ITestOutputHelper>();
|
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 ILogs Logs => _logs.Value;
|
||||||
|
|
||||||
public static ITestOutputHelper Output => _output.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)
|
public override void After(MethodInfo methodUnderTest)
|
||||||
{
|
{
|
||||||
var browser = BrowserTestBase.Browser;
|
var browser = BrowserTestBase.BrowserAccessor;
|
||||||
var logs = BrowserTestBase.Logs;
|
var logs = BrowserTestBase.Logs;
|
||||||
var output = BrowserTestBase.Output;
|
var output = BrowserTestBase.Output;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_DefaultProjectFilter>$(MSBuildProjectDirectory)\..\..</_DefaultProjectFilter>
|
<_DefaultProjectFilter>$(MSBuildProjectDirectory)\..\..</_DefaultProjectFilter>
|
||||||
<DefaultItemExcludes>$(DefaultItemExcludes);node_modules\**</DefaultItemExcludes>
|
<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>
|
<SeleniumE2ETestsSupported Condition="'$(SeleniumE2ETestsSupported)' == '' and '$(TargetArchitecture)' != 'arm' and '$(OS)' == 'Windows_NT'">true</SeleniumE2ETestsSupported>
|
||||||
<EnforcePrerequisites Condition="'$(SeleniumE2ETestsSupported)' == 'true' and '$(EnforcePrerequisites)' == ''">true</EnforcePrerequisites>
|
<EnforcePrerequisites Condition="'$(SeleniumE2ETestsSupported)' == 'true' and '$(EnforcePrerequisites)' == ''">true</EnforcePrerequisites>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,4 +102,15 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</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>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,93 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.E2ETesting;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.E2ETesting
|
namespace Microsoft.AspNetCore.E2ETesting
|
||||||
{
|
{
|
||||||
class SeleniumStandaloneServer
|
public class SeleniumStandaloneServer : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
|
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1);
|
||||||
private static Lazy<SeleniumStandaloneServer> _instance = new Lazy<SeleniumStandaloneServer>(() => new SeleniumStandaloneServer());
|
|
||||||
|
|
||||||
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();
|
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
|
var psi = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
|
|
@ -42,9 +104,34 @@ namespace Microsoft.AspNetCore.E2ETesting
|
||||||
psi.Arguments = $"/c npm {psi.Arguments}";
|
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.OutputDataReceived += LogOutput;
|
||||||
process.ErrorDataReceived += LogOutput;
|
process.ErrorDataReceived += LogOutput;
|
||||||
|
|
||||||
|
|
@ -52,72 +139,120 @@ namespace Microsoft.AspNetCore.E2ETesting
|
||||||
process.BeginErrorReadLine();
|
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.
|
// 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) =>
|
AppDomain.CurrentDomain.ProcessExit += (sender, args) => ProcessCleanup(process, pidFilePath);
|
||||||
{
|
|
||||||
if (!process.HasExited)
|
|
||||||
{
|
|
||||||
process.KillTree(TimeSpan.FromSeconds(10));
|
|
||||||
process.Dispose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Log
|
||||||
void LogOutput(object sender, DataReceivedEventArgs e)
|
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 response = await httpClient.GetAsync(uri);
|
||||||
};
|
if (response.StatusCode == HttpStatusCode.OK)
|
||||||
|
{
|
||||||
var retries = 0;
|
output = null;
|
||||||
while (retries++ < 30)
|
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
|
try
|
||||||
{
|
{
|
||||||
// Wait in intervals instead of indefinitely to prevent thread starvation.
|
if (pidFilePath != null && File.Exists(pidFilePath))
|
||||||
while (!waitForStart.TimeoutAfter(Timeout).Wait(1000))
|
|
||||||
{
|
{
|
||||||
|
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()
|
static int FindAvailablePort()
|
||||||
{
|
{
|
||||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||||
|
|
@ -132,5 +267,16 @@ namespace Microsoft.AspNetCore.E2ETesting
|
||||||
listener.Stop();
|
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
|
// XUnit assertions, but hooked into Selenium's polling mechanism
|
||||||
|
|
||||||
public class WaitAssert
|
public static class WaitAssert
|
||||||
{
|
{
|
||||||
private readonly static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(3);
|
private readonly static TimeSpan DefaultTimeout = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
public static void Equal<T>(T expected, Func<T> actual)
|
public static void Equal<T>(this IWebDriver driver, T expected, Func<T> actual)
|
||||||
=> WaitAssertCore(() => Assert.Equal(expected, actual()));
|
=> WaitAssertCore(driver, () => Assert.Equal(expected, actual()));
|
||||||
|
|
||||||
public static void True(Func<bool> actual)
|
public static void True(this IWebDriver driver, Func<bool> actual)
|
||||||
=> WaitAssertCore(() => Assert.True(actual()));
|
=> WaitAssertCore(driver, () => Assert.True(actual()));
|
||||||
|
|
||||||
public static void True(Func<bool> actual, TimeSpan timeout)
|
public static void True(this IWebDriver driver, Func<bool> actual, TimeSpan timeout)
|
||||||
=> WaitAssertCore(() => Assert.True(actual()), timeout);
|
=> WaitAssertCore(driver, () => Assert.True(actual()), timeout);
|
||||||
|
|
||||||
public static void False(Func<bool> actual)
|
public static void False(this IWebDriver driver, Func<bool> actual)
|
||||||
=> WaitAssertCore(() => Assert.False(actual()));
|
=> WaitAssertCore(driver, () => Assert.False(actual()));
|
||||||
|
|
||||||
public static void Contains(string expectedSubstring, Func<string> actualString)
|
public static void Contains(this IWebDriver driver, string expectedSubstring, Func<string> actualString)
|
||||||
=> WaitAssertCore(() => Assert.Contains(expectedSubstring, actualString()));
|
=> WaitAssertCore(driver, () => Assert.Contains(expectedSubstring, actualString()));
|
||||||
|
|
||||||
public static void Collection<T>(Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors)
|
public static void Collection<T>(this IWebDriver driver, Func<IEnumerable<T>> actualValues, params Action<T>[] elementInspectors)
|
||||||
=> WaitAssertCore(() => Assert.Collection(actualValues(), elementInspectors));
|
=> WaitAssertCore(driver, () => Assert.Collection(actualValues(), elementInspectors));
|
||||||
|
|
||||||
public static void Empty(Func<IEnumerable> actualValues)
|
public static void Empty(this IWebDriver driver, Func<IEnumerable> actualValues)
|
||||||
=> WaitAssertCore(() => Assert.Empty(actualValues()));
|
=> WaitAssertCore(driver, () => Assert.Empty(actualValues()));
|
||||||
|
|
||||||
public static void Single(Func<IEnumerable> actualValues)
|
public static void Single(this IWebDriver driver, Func<IEnumerable> actualValues)
|
||||||
=> WaitAssertCore(() => Assert.Single(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)
|
if (timeout == default)
|
||||||
{
|
{
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.E2ETesting
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
new WebDriverWait(BrowserTestBase.Browser, timeout).Until(_ =>
|
new WebDriverWait(driver, timeout).Until(_ =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,24 @@ namespace Microsoft.AspNetCore.E2ETesting
|
||||||
// Find all the AssemblyFixtureAttributes on the test assembly
|
// Find all the AssemblyFixtureAttributes on the test assembly
|
||||||
Aggregator.Run(() =>
|
Aggregator.Run(() =>
|
||||||
{
|
{
|
||||||
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly).Assembly
|
var fixturesAttributes = ((IReflectionAssemblyInfo)TestAssembly.Assembly)
|
||||||
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
|
.Assembly
|
||||||
.Cast<AssemblyFixtureAttribute>()
|
.GetCustomAttributes(typeof(AssemblyFixtureAttribute), false)
|
||||||
.ToList();
|
.Cast<AssemblyFixtureAttribute>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
// Instantiate all the fixtures
|
// Instantiate all the fixtures
|
||||||
foreach (var fixtureAttribute in fixturesAttributes)
|
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();
|
return base.BeforeTestAssemblyFinishedAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task<RunSummary> RunTestCollectionAsync(IMessageBus messageBus,
|
protected override Task<RunSummary> RunTestCollectionAsync(
|
||||||
ITestCollection testCollection,
|
IMessageBus messageBus,
|
||||||
IEnumerable<IXunitTestCase> testCases,
|
ITestCollection testCollection,
|
||||||
CancellationTokenSource cancellationTokenSource)
|
IEnumerable<IXunitTestCase> testCases,
|
||||||
|
CancellationTokenSource cancellationTokenSource)
|
||||||
=> new XunitTestCollectionRunnerWithAssemblyFixture(
|
=> new XunitTestCollectionRunnerWithAssemblyFixture(
|
||||||
_assemblyFixtureMappings,
|
_assemblyFixtureMappings,
|
||||||
testCollection,
|
testCollection,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue