[Blazor] Fix publish with different combinations of SWA base paths (#28539)

Description
#28017

In 5.0.1 we fixed a publish scenario where Blazor webassembly wasn't respecting the StaticWebAssetBasePath defined for the project. Unfortunately the logic for handling the path composition was not robust enough and while it fixed the publish output layout, it introduced an integration issue later on at runtime.

The current fix makes the handling of the path more tolerant to initial and final slashes and includes a new end to end test that validates this scenario.

Customer Impact
Customers can't host blazor applications outside the root path (/)

Regression?
Yes. Worked in 3.2

Risk
Low.

We've included additional tests and E2E automation to verify this case.

Validation
 Automated
 Manual
This commit is contained in:
Javier Calvarro Nelson 2020-12-10 22:49:49 +01:00 committed by GitHub
parent a8646e3022
commit b7abe1ed90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 252 additions and 26 deletions

View File

@ -1503,7 +1503,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.App.Un
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Localization", "Localization", "{3D34C81F-2CB5-459E-87E9-0CC04757A2A0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GlobalizationWasmApp", "src\Components\test\testassets\GlobalizationWasmApp\GlobalizationWasmApp.csproj", "{04CFE286-6D32-41EF-8887-4B5F8086A365}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GlobalizationWasmApp", "src\Components\test\testassets\GlobalizationWasmApp\GlobalizationWasmApp.csproj", "{04CFE286-6D32-41EF-8887-4B5F8086A365}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Localization.Abstractions", "src\Localization\Abstractions\src\Microsoft.Extensions.Localization.Abstractions.csproj", "{FEF97646-9BC9-4D1B-A939-784D915C18A4}"
EndProject
@ -1539,6 +1539,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sdk", "Sdk", "{E83B0BCC-A8E
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostingStartup", "src\SiteExtensions\Sdk\HostingStartup\HostingStartup.csproj", "{5D6F99C5-D292-4459-B8BD-8E4AD42E1B21}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomBasePathApp", "src\Components\WebAssembly\testassets\CustomBasePathApp\CustomBasePathApp.csproj", "{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -7199,18 +7201,6 @@ Global
{BAD47859-95DF-4C8F-9AF7-C48B68F478A1}.Release|x64.Build.0 = Release|Any CPU
{BAD47859-95DF-4C8F-9AF7-C48B68F478A1}.Release|x86.ActiveCfg = Release|Any CPU
{BAD47859-95DF-4C8F-9AF7-C48B68F478A1}.Release|x86.Build.0 = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x64.ActiveCfg = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x64.Build.0 = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x86.ActiveCfg = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x86.Build.0 = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|Any CPU.Build.0 = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x64.ActiveCfg = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x64.Build.0 = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x86.ActiveCfg = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x86.Build.0 = Release|Any CPU
{010A9638-F20E-4FE6-A186-85732BFC9CB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{010A9638-F20E-4FE6-A186-85732BFC9CB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{010A9638-F20E-4FE6-A186-85732BFC9CB0}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -7223,6 +7213,18 @@ Global
{010A9638-F20E-4FE6-A186-85732BFC9CB0}.Release|x64.Build.0 = Release|Any CPU
{010A9638-F20E-4FE6-A186-85732BFC9CB0}.Release|x86.ActiveCfg = Release|Any CPU
{010A9638-F20E-4FE6-A186-85732BFC9CB0}.Release|x86.Build.0 = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x64.ActiveCfg = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x64.Build.0 = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x86.ActiveCfg = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Debug|x86.Build.0 = Debug|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|Any CPU.Build.0 = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x64.ActiveCfg = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x64.Build.0 = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x86.ActiveCfg = Release|Any CPU
{04CFE286-6D32-41EF-8887-4B5F8086A365}.Release|x86.Build.0 = Release|Any CPU
{FEF97646-9BC9-4D1B-A939-784D915C18A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FEF97646-9BC9-4D1B-A939-784D915C18A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FEF97646-9BC9-4D1B-A939-784D915C18A4}.Debug|x64.ActiveCfg = Debug|Any CPU
@ -7367,6 +7369,18 @@ Global
{5D6F99C5-D292-4459-B8BD-8E4AD42E1B21}.Release|x64.Build.0 = Release|Any CPU
{5D6F99C5-D292-4459-B8BD-8E4AD42E1B21}.Release|x86.ActiveCfg = Release|Any CPU
{5D6F99C5-D292-4459-B8BD-8E4AD42E1B21}.Release|x86.Build.0 = Release|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Debug|x64.ActiveCfg = Debug|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Debug|x64.Build.0 = Debug|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Debug|x86.ActiveCfg = Debug|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Debug|x86.Build.0 = Debug|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Release|Any CPU.Build.0 = Release|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Release|x64.ActiveCfg = Release|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Release|x64.Build.0 = Release|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Release|x86.ActiveCfg = Release|Any CPU
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -8117,10 +8131,10 @@ Global
{37329855-01B8-4B03-9765-1A941B06E43C} = {8C15FD04-7F90-43FC-B488-023432FE3CE1}
{D3246226-BC1A-47F1-8E3E-C3380A8F13FB} = {8C15FD04-7F90-43FC-B488-023432FE3CE1}
{B06ADD57-E855-4D8C-85DC-B323509AE540} = {898F7E0B-1671-42CB-9DFB-689AFF212ED3}
{04CFE286-6D32-41EF-8887-4B5F8086A365} = {6126DCE4-9692-4EE2-B240-C65743572995}
{BAD47859-95DF-4C8F-9AF7-C48B68F478A1} = {A4C26078-B6D8-4FD8-87A6-7C15A3482038}
{010A9638-F20E-4FE6-A186-85732BFC9CB0} = {A4C26078-B6D8-4FD8-87A6-7C15A3482038}
{3D34C81F-2CB5-459E-87E9-0CC04757A2A0} = {017429CC-C5FB-48B4-9C46-034E29EE2F06}
{04CFE286-6D32-41EF-8887-4B5F8086A365} = {6126DCE4-9692-4EE2-B240-C65743572995}
{FEF97646-9BC9-4D1B-A939-784D915C18A4} = {3D34C81F-2CB5-459E-87E9-0CC04757A2A0}
{839CE175-E0D9-43B9-9FA8-F32C47E7F56B} = {3D34C81F-2CB5-459E-87E9-0CC04757A2A0}
{50BF2926-7435-4F4B-88A9-3D0EDEB67FC8} = {3D34C81F-2CB5-459E-87E9-0CC04757A2A0}
@ -8138,6 +8152,7 @@ Global
{545751D5-71FC-4889-A3A0-BBD731DBA18A} = {56B45580-B089-424E-A847-A6115D591950}
{E83B0BCC-A8E0-4FBD-BE51-9A533C9CB972} = {DFC4F588-B4B4-484B-AB93-B36721374AD3}
{5D6F99C5-D292-4459-B8BD-8E4AD42E1B21} = {E83B0BCC-A8E0-4FBD-BE51-9A533C9CB972}
{E2461809-D2EA-436D-B5C3-8A9EE0A283B8} = {7D2B0799-A634-42AC-AE77-5D167BA51389}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

View File

@ -29,6 +29,7 @@
"src\\Components\\WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj",
"src\\Components\\WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj",
"src\\Components\\WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj",
"src\\Components\\WebAssembly\\testassets\\CustomBasePathApp\\CustomBasePathApp.csproj",
"src\\Components\\WebAssembly\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"src\\Components\\WebAssembly\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
"src\\Components\\WebAssembly\\testassets\\StandaloneApp\\StandaloneApp.csproj",
@ -48,4 +49,4 @@
"src\\Components\\test\\testassets\\TestServer\\Components.TestServer.csproj"
]
}
}
}

View File

@ -252,14 +252,18 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
VerifyCompression(result, blazorPublishDirectory);
}
[Fact]
public async Task Publish_WithStaticWebBasePathWorks()
[Theory]
[InlineData("different-path")]
[InlineData("/different-path")]
[InlineData("different-path/")]
[InlineData("/different-path/")]
public async Task Publish_WithStaticWebBasePathWorks(string basePath)
{
// Arrange
using var project = ProjectDirectory.Create("blazorwasm", "razorclasslibrary");
project.AddProjectFileContent(
@"<PropertyGroup>
<StaticWebAssetBasePath>different-path/</StaticWebAssetBasePath>
$@"<PropertyGroup>
<StaticWebAssetBasePath>{basePath}</StaticWebAssetBasePath>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");
@ -300,14 +304,18 @@ namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
staticWebAssetsBasePath: "different-path");
}
[Fact]
public async Task Publish_Hosted_WithStaticWebBasePathWorks()
[Theory]
[InlineData("different-path")]
[InlineData("/different-path")]
[InlineData("different-path/")]
[InlineData("/different-path/")]
public async Task Publish_Hosted_WithStaticWebBasePathWorks(string basePath)
{
using var project = ProjectDirectory.Create("blazorhosted", additionalProjects: new[] { "blazorwasm", "razorclasslibrary", });
var wasmProject = project.GetSibling("blazorwasm");
wasmProject.AddProjectFileContent(
@"<PropertyGroup>
<StaticWebAssetBasePath>different-path/</StaticWebAssetBasePath>
$@"<PropertyGroup>
<StaticWebAssetBasePath>{basePath}</StaticWebAssetBasePath>
</PropertyGroup>");
var result = await MSBuildProcessManager.DotnetMSBuild(project, "Publish");

View File

@ -457,7 +457,7 @@ Copyright (c) .NET Foundation. All rights reserved.
</JoinItems>
<PropertyGroup>
<_BlazorPublishOutputPath Condition="'$(StaticWebAssetBasePath)' != '/'">wwwroot\$(StaticWebAssetBasePath.Replace('/', '\'))</_BlazorPublishOutputPath>
<_BlazorPublishOutputPath Condition="'$(StaticWebAssetBasePath)' != '/'">wwwroot\$(StaticWebAssetBasePath.Replace('/', '\').Trim('\'))\</_BlazorPublishOutputPath>
<_BlazorPublishOutputPath Condition="'$(StaticWebAssetBasePath)' == '/'">wwwroot\</_BlazorPublishOutputPath>
<_BlazorFrameworkPublishPath>$(_BlazorPublishOutputPath)_framework\</_BlazorFrameworkPublishPath>
</PropertyGroup>

View File

@ -0,0 +1 @@
Image from custom path: <img src="img/red-square.png" />

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<StaticWebAssetBasePath>/app</StaticWebAssetBasePath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.WebAssembly" />
<Reference Include="System.Net.Http.Json" />
</ItemGroup>
<!-- A bit of msbuild magic to support reference resolver tests -->
<Target Name="CreateReferenceHintPathsList" AfterTargets="ResolveAssemblyReferences">
<ItemGroup>
<_BclFile Include="$(ComponentsWebAssemblyBaseClassLibraryPath)*" />
<_BclFile Include="$(ComponentsWebAssemblyBaseClassLibraryFacadesPath)*" />
<_BclFile Include="$(ComponentsWebAssemblyFrameworkPath)*" />
</ItemGroup>
<WriteLinesToFile Lines="@(ReferencePath);@(ReferenceDependencyPaths)" File="$(IntermediateOutputPath)referenceHints.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
<WriteLinesToFile Lines="@(_BclFile)" File="$(IntermediateOutputPath)bclLocations.txt" WriteOnlyWhenDifferent="true" Overwrite="true" />
<ItemGroup>
<EmbeddedResource Include="$(IntermediateOutputPath)bclLocations.txt" Link="bclLocations.txt" Type="Non-Resx" />
<EmbeddedResource Include="$(IntermediateOutputPath)referenceHints.txt" Link="referenceHints.txt" Type="Non-Resx" />
</ItemGroup>
</Target>
</Project>

View File

@ -0,0 +1,22 @@
// 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.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace CustomBasePathApp
{
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
await builder.Build().RunAsync();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- Forcing the device width here so that our automated tests work consistently on mobile browsers. -->
<meta name="viewport" content="width=1024">
<title>App loaded on custom path</title>
<base href="/app/" />
</head>
<body>
<app>Loading...</app>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CustomBasePathApp\CustomBasePathApp.csproj" />
<ProjectReference Include="..\HostedInAspNet.Client\HostedInAspNet.Client.csproj" />
</ItemGroup>

View File

@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -10,6 +11,13 @@ namespace HostedInAspNet.Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
@ -20,6 +28,8 @@ namespace HostedInAspNet.Server
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, BootResourceRequestLog bootResourceRequestLog)
{
var mapAlternativePathApp = Configuration.GetValue<bool>("UseAlternativeBasePath");
var mapAllApps = Configuration.GetValue<bool>("MapAllApps");
app.Use((context, next) =>
{
// This is used by E2E tests to verify that the correct resources were fetched,
@ -39,14 +49,31 @@ namespace HostedInAspNet.Server
app.UseWebAssemblyDebugging();
}
app.UseBlazorFrameworkFiles();
if (mapAllApps || mapAlternativePathApp)
{
app.UseBlazorFrameworkFiles("/app");
}
if (mapAllApps || !mapAlternativePathApp)
{
app.UseBlazorFrameworkFiles();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapFallbackToFile("index.html");
if (mapAllApps || mapAlternativePathApp)
{
endpoints.MapFallbackToFile("/app/{**slug:nonfile}", "app/index.html");
}
if (mapAllApps || !mapAlternativePathApp)
{
endpoints.MapFallbackToFile("index.html");
}
});
}
}

View File

@ -0,0 +1,45 @@
// 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.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class HostedInAlternativeBasePathTest : ServerTestBase<AspNetSiteServerFixture>
{
public HostedInAlternativeBasePathTest(
BrowserFixture browserFixture,
AspNetSiteServerFixture serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
serverFixture.AdditionalArguments.AddRange(new[] { "--UseAlternativeBasePath", "true" });
serverFixture.BuildWebHostMethod = HostedInAspNet.Server.Program.BuildWebHost;
serverFixture.Environment = AspNetEnvironment.Development;
}
protected override void InitializeAsyncCore()
{
Navigate("/app/", noReload: true);
WaitUntilLoaded();
}
[Fact]
public void CanLoadBlazorAppFromSubPath()
{
Assert.Equal("App loaded on custom path", Browser.Title);
Assert.Equal(0, Browser.GetBrowserLogs(LogLevel.Severe).Count);
}
private void WaitUntilLoaded()
{
var app = Browser.Exists(By.TagName("app"));
Browser.NotEqual("Loading...", () => app.Text);
}
}
}

View File

@ -0,0 +1,62 @@
// 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.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
public class MultipleHostedAppTest: ServerTestBase<AspNetSiteServerFixture>
{
public MultipleHostedAppTest(
BrowserFixture browserFixture,
AspNetSiteServerFixture serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
serverFixture.AdditionalArguments.AddRange(new[] { "--MapAllApps", "true" });
serverFixture.BuildWebHostMethod = HostedInAspNet.Server.Program.BuildWebHost;
serverFixture.Environment = AspNetEnvironment.Development;
}
protected override void InitializeAsyncCore()
{
Navigate("/", noReload: true);
WaitUntilLoaded();
}
[Fact]
public void CanLoadBlazorAppFromSubPath()
{
Navigate("/app/");
WaitUntilLoaded();
Assert.Equal("App loaded on custom path", Browser.Title);
Assert.Equal(0, Browser.GetBrowserLogs(LogLevel.Severe).Count);
}
[Fact]
public void HasTitle()
{
Assert.Equal("Sample Blazor app", Browser.Title);
}
[Fact]
public void ServesStaticAssetsFromClientAppWebRoot()
{
var javascriptExecutor = (IJavaScriptExecutor)Browser;
var bootstrapTooltipType = javascriptExecutor
.ExecuteScript("return window.customJsWasLoaded;");
Assert.True((bool)bootstrapTooltipType);
}
private void WaitUntilLoaded()
{
var app = Browser.Exists(By.TagName("app"));
Browser.NotEqual("Loading...", () => app.Text);
}
}
}