Begin E2E testing for BasicTestApp and component rendering in browser

This commit is contained in:
Steve Sanderson 2018-01-04 17:35:58 +00:00
parent ce04fde7bd
commit dfd6c4a1c2
14 changed files with 151 additions and 20 deletions

View File

@ -1,6 +1,7 @@
import { platform } from './Environment';
import { getAssemblyNameFromUrl } from './Platform/DotNet';
import './Rendering/Renderer';
import './GlobalExports';
async function boot() {
// Read startup config from the <script> element that's importing this file

View File

@ -0,0 +1,13 @@
import { platform } from './Environment'
import { registerFunction } from './RegisteredFunction';
// This file defines an export that, when the library is loaded in a browser via a
// <script> element, will be attached to the global namespace
const blazorInstance = {
platform: platform,
registerFunction: registerFunction
};
if (typeof window !== 'undefined') {
window['Blazor'] = blazorInstance;
}

View File

@ -18,11 +18,15 @@ export function registerFunction(identifier: string, implementation: Function) {
registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson: System_String[]) => {
const identifierJsString = platform.toJavaScriptString(identifier);
if (!(registeredFunctions && registeredFunctions.hasOwnProperty(identifierJsString))) {
throw new Error(`Could not find registered function with name "${identifier}".`);
throw new Error(`Could not find registered function with name "${identifierJsString}".`);
}
const funcInstance = registeredFunctions[identifierJsString];
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
const result = funcInstance.apply(null, args);
const resultJson = JSON.stringify(result);
return platform.toDotNetString(resultJson);
if (result !== null && result !== undefined) {
const resultJson = JSON.stringify(result);
return platform.toDotNetString(resultJson);
} else {
return null;
}
});

View File

@ -29,10 +29,21 @@ namespace Microsoft.Blazor.Build.Core.FileSystem
private static string GetIndexHtmlContents(string htmlTemplate, string assemblyName, IEnumerable<IFileInfo> binFiles)
{
// TODO: Consider parsing the HTML properly so for example we don't insert into
// the wrong place if there was also '</body>' in a JavaScript string literal
// TODO: Instead of inserting the script as the first element in <body>,
// consider either:
// [1] Inserting it just before the first <script> in the <body>, so that
// developers can still put other <script> elems after (and therefore
// reference Blazor JS APIs from them) but we don't block the page
// rendering while fetching that script. Note that adding async/defer
// alone isn't enough because that doesn't help older browsers that
// don't suppor them.
// [2] Or possibly better, don't insert the <script> magically at all.
// Instead, just insert a block of configuration data at the top of
// <body> (e.g., <script type='blazor-config'>{ ...json... }</script>)
// and then let the developer manually place the tag that loads blazor.js
// wherever they want (adding their own async/defer if they want).
return htmlTemplate
.Replace("</body>", CreateBootMarkup(assemblyName, binFiles) + "\n</body>");
.Replace("<body>", "<body>\n" + CreateBootMarkup(assemblyName, binFiles));
}
private static string CreateBootMarkup(string assemblyName, IEnumerable<IFileInfo> binFiles)

View File

@ -68,7 +68,7 @@ namespace Microsoft.Blazor.Server.Test
public void InsertsScriptTagReferencingAssemblyAndDependencies()
{
// Arrange
var htmlTemplate = "<html><body>Hello</body></html>";
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body></html>";
var dependencies = new IFileInfo[]
{
new TestFileInfo("System.Abc.dll"),
@ -80,7 +80,7 @@ namespace Microsoft.Blazor.Server.Test
// Act
var file = instance.GetFileInfo("/index.html");
var parsedHtml = new HtmlParser().Parse(ReadString(file));
var scriptElem = parsedHtml.Body.LastElementChild;
var scriptElem = parsedHtml.Body.FirstElementChild;
// Assert
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());

View File

@ -23,9 +23,7 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
$"No value was provided for {nameof(BuildWebHostMethod)}");
}
var sampleSitePath = Path.Combine(
FindSolutionDir(),
"samples",
var sampleSitePath = FindSampleOrTestSitePath(
BuildWebHostMethod.Method.DeclaringType.Assembly.GetName().Name);
return BuildWebHostMethod(new[]

View File

@ -11,9 +11,7 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
{
protected override IWebHost CreateWebHost()
{
var sampleSitePath = Path.Combine(
FindSolutionDir(),
"samples",
var sampleSitePath = FindSampleOrTestSitePath(
typeof(TProgram).Assembly.GetName().Name);
return DevHostServerProgram.BuildWebHost(new string[]

View File

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
@ -30,6 +31,19 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
Path.GetDirectoryName(typeof(ServerFixture).Assembly.Location));
}
protected static string FindSampleOrTestSitePath(string projectName)
{
var solutionDir = FindSolutionDir();
var possibleLocations = new[]
{
Path.Combine(solutionDir, "samples", projectName),
Path.Combine(solutionDir, "test", "testapps", projectName)
};
return possibleLocations.FirstOrDefault(Directory.Exists)
?? throw new ArgumentException($"Cannot find a sample or test site with name '{projectName}'.");
}
private static string FindClosestDirectoryContaining(
string filename,
string startDirectory)

View File

@ -23,10 +23,7 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
throw new InvalidOperationException($"No value was provided for {nameof(SampleSiteName)}");
}
var sampleSitePath = Path.Combine(
FindSolutionDir(),
"samples",
SampleSiteName);
var sampleSitePath = FindSampleOrTestSitePath(SampleSiteName);
return new WebHostBuilder()
.UseKestrel()

View File

@ -22,6 +22,7 @@
<ProjectReference Include="..\..\samples\MonoSanity\MonoSanity.csproj" />
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Blazor.DevHost\Microsoft.Blazor.DevHost.csproj" />
<ProjectReference Include="..\testapps\BasicTestApp\BasicTestApp.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,55 @@
// 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 BasicTestApp;
using Microsoft.Blazor.E2ETest.Infrastructure;
using Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
namespace Microsoft.Blazor.E2ETest.Tests
{
public class ComponentRenderingTest
: ServerTestBase<DevHostServerFixture<BasicTestApp.Program>>
{
public ComponentRenderingTest(BrowserFixture browserFixture, DevHostServerFixture<Program> serverFixture)
: base(browserFixture, serverFixture)
{
}
[Fact]
public void BasicTestAppCanBeServed()
{
Navigate("/", noReload: true);
Assert.Equal("Basic test app", Browser.Title);
}
[Fact]
public void CanRenderTextOnlyComponent()
{
Navigate("/", noReload: true);
MountTestComponent("BasicTestApp.TextOnlyComponent");
var appElement = Browser.FindElement(By.TagName("app"));
Assert.Equal("Hello from TextOnlyComponent", appElement.Text);
}
private void MountTestComponent(string componentTypeName)
{
WaitUntilDotNetRunningInBrowser();
((IJavaScriptExecutor)Browser).ExecuteScript(
$"mountTestComponent('{componentTypeName}')");
}
private void WaitUntilDotNetRunningInBrowser()
{
new WebDriverWait(Browser, TimeSpan.FromSeconds(30)).Until(driver =>
{
return ((IJavaScriptExecutor)driver)
.ExecuteScript("return window.isTestReady;");
});
}
}
}

View File

@ -1,15 +1,26 @@
// 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.Blazor.Browser;
using Microsoft.Blazor.Browser.Interop;
using Microsoft.Blazor.Components;
using System;
namespace BasicTestApp
{
class Program
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("BasicTestApp entrypoint invoked");
// Signal to tests that we're ready
RegisteredFunction.Invoke<object>("testReady");
}
public static void MountTestComponent(string componentTypeName)
{
var componentType = Type.GetType(componentTypeName);
var componentInstance = (IComponent)Activator.CreateInstance(componentType);
Renderer.Render(componentInstance, "app");
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Blazor.Components;
using Microsoft.Blazor.UITree;
namespace BasicTestApp
{
public class TextOnlyComponent : IComponent
{
public void Render(UITreeBuilder builder)
{
builder.AddText($"Hello from {nameof(TextOnlyComponent)}");
}
}
}

View File

@ -6,5 +6,17 @@
</head>
<body>
<app>Loading...</app>
<script>
// The client-side .NET code calls this when it is ready to be called from test code
// The Xunit test code polls until it sees the flag is set
Blazor.registerFunction('testReady', function () { window.isTestReady = true; });
// The Xunit test code calls this when setting up tests for specific components
function mountTestComponent(typeName) {
var method = Blazor.platform.findMethod('BasicTestApp', 'BasicTestApp', 'Program', 'MountTestComponent');
Blazor.platform.callMethod(method, null, [Blazor.platform.toDotNetString(typeName)]);
}
</script>
</body>
</html>