Begin E2E testing for BasicTestApp and component rendering in browser
This commit is contained in:
parent
ce04fde7bd
commit
dfd6c4a1c2
|
|
@ -1,6 +1,7 @@
|
||||||
import { platform } from './Environment';
|
import { platform } from './Environment';
|
||||||
import { getAssemblyNameFromUrl } from './Platform/DotNet';
|
import { getAssemblyNameFromUrl } from './Platform/DotNet';
|
||||||
import './Rendering/Renderer';
|
import './Rendering/Renderer';
|
||||||
|
import './GlobalExports';
|
||||||
|
|
||||||
async function boot() {
|
async function boot() {
|
||||||
// Read startup config from the <script> element that's importing this file
|
// Read startup config from the <script> element that's importing this 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;
|
||||||
|
}
|
||||||
|
|
@ -18,11 +18,15 @@ export function registerFunction(identifier: string, implementation: Function) {
|
||||||
registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson: System_String[]) => {
|
registerFunction('__blazor_InvokeJson', (identifier: System_String, ...argsJson: System_String[]) => {
|
||||||
const identifierJsString = platform.toJavaScriptString(identifier);
|
const identifierJsString = platform.toJavaScriptString(identifier);
|
||||||
if (!(registeredFunctions && registeredFunctions.hasOwnProperty(identifierJsString))) {
|
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 funcInstance = registeredFunctions[identifierJsString];
|
||||||
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
|
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json)));
|
||||||
const result = funcInstance.apply(null, args);
|
const result = funcInstance.apply(null, args);
|
||||||
const resultJson = JSON.stringify(result);
|
if (result !== null && result !== undefined) {
|
||||||
return platform.toDotNetString(resultJson);
|
const resultJson = JSON.stringify(result);
|
||||||
|
return platform.toDotNetString(resultJson);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,21 @@ namespace Microsoft.Blazor.Build.Core.FileSystem
|
||||||
|
|
||||||
private static string GetIndexHtmlContents(string htmlTemplate, string assemblyName, IEnumerable<IFileInfo> binFiles)
|
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
|
// TODO: Instead of inserting the script as the first element in <body>,
|
||||||
// the wrong place if there was also '</body>' in a JavaScript string literal
|
// 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
|
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)
|
private static string CreateBootMarkup(string assemblyName, IEnumerable<IFileInfo> binFiles)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ namespace Microsoft.Blazor.Server.Test
|
||||||
public void InsertsScriptTagReferencingAssemblyAndDependencies()
|
public void InsertsScriptTagReferencingAssemblyAndDependencies()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var htmlTemplate = "<html><body>Hello</body></html>";
|
var htmlTemplate = "<html><body><h1>Hello</h1>Some text</body></html>";
|
||||||
var dependencies = new IFileInfo[]
|
var dependencies = new IFileInfo[]
|
||||||
{
|
{
|
||||||
new TestFileInfo("System.Abc.dll"),
|
new TestFileInfo("System.Abc.dll"),
|
||||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.Blazor.Server.Test
|
||||||
// Act
|
// Act
|
||||||
var file = instance.GetFileInfo("/index.html");
|
var file = instance.GetFileInfo("/index.html");
|
||||||
var parsedHtml = new HtmlParser().Parse(ReadString(file));
|
var parsedHtml = new HtmlParser().Parse(ReadString(file));
|
||||||
var scriptElem = parsedHtml.Body.LastElementChild;
|
var scriptElem = parsedHtml.Body.FirstElementChild;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());
|
Assert.Equal("script", scriptElem.TagName.ToLowerInvariant());
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
|
||||||
$"No value was provided for {nameof(BuildWebHostMethod)}");
|
$"No value was provided for {nameof(BuildWebHostMethod)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var sampleSitePath = Path.Combine(
|
var sampleSitePath = FindSampleOrTestSitePath(
|
||||||
FindSolutionDir(),
|
|
||||||
"samples",
|
|
||||||
BuildWebHostMethod.Method.DeclaringType.Assembly.GetName().Name);
|
BuildWebHostMethod.Method.DeclaringType.Assembly.GetName().Name);
|
||||||
|
|
||||||
return BuildWebHostMethod(new[]
|
return BuildWebHostMethod(new[]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
|
||||||
{
|
{
|
||||||
protected override IWebHost CreateWebHost()
|
protected override IWebHost CreateWebHost()
|
||||||
{
|
{
|
||||||
var sampleSitePath = Path.Combine(
|
var sampleSitePath = FindSampleOrTestSitePath(
|
||||||
FindSolutionDir(),
|
|
||||||
"samples",
|
|
||||||
typeof(TProgram).Assembly.GetName().Name);
|
typeof(TProgram).Assembly.GetName().Name);
|
||||||
|
|
||||||
return DevHostServerProgram.BuildWebHost(new string[]
|
return DevHostServerProgram.BuildWebHost(new string[]
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
|
namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
|
||||||
|
|
@ -30,6 +31,19 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
|
||||||
Path.GetDirectoryName(typeof(ServerFixture).Assembly.Location));
|
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(
|
private static string FindClosestDirectoryContaining(
|
||||||
string filename,
|
string filename,
|
||||||
string startDirectory)
|
string startDirectory)
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@ namespace Microsoft.Blazor.E2ETest.Infrastructure.ServerFixtures
|
||||||
throw new InvalidOperationException($"No value was provided for {nameof(SampleSiteName)}");
|
throw new InvalidOperationException($"No value was provided for {nameof(SampleSiteName)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var sampleSitePath = Path.Combine(
|
var sampleSitePath = FindSampleOrTestSitePath(SampleSiteName);
|
||||||
FindSolutionDir(),
|
|
||||||
"samples",
|
|
||||||
SampleSiteName);
|
|
||||||
|
|
||||||
return new WebHostBuilder()
|
return new WebHostBuilder()
|
||||||
.UseKestrel()
|
.UseKestrel()
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
<ProjectReference Include="..\..\samples\MonoSanity\MonoSanity.csproj" />
|
<ProjectReference Include="..\..\samples\MonoSanity\MonoSanity.csproj" />
|
||||||
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
|
<ProjectReference Include="..\..\samples\StandaloneApp\StandaloneApp.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Microsoft.Blazor.DevHost\Microsoft.Blazor.DevHost.csproj" />
|
<ProjectReference Include="..\..\src\Microsoft.Blazor.DevHost\Microsoft.Blazor.DevHost.csproj" />
|
||||||
|
<ProjectReference Include="..\testapps\BasicTestApp\BasicTestApp.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -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;");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,26 @@
|
||||||
// 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.Blazor.Browser;
|
||||||
|
using Microsoft.Blazor.Browser.Interop;
|
||||||
|
using Microsoft.Blazor.Components;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace BasicTestApp
|
namespace BasicTestApp
|
||||||
{
|
{
|
||||||
class Program
|
public class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,5 +6,17 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app>Loading...</app>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue