Dev exception notifications (#14636)

Blazor dev exception notification
This commit is contained in:
Ryan Brandenburg 2019-10-09 16:37:57 -07:00 committed by GitHub
parent f3b0fbe207
commit a68c961a58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 256 additions and 6 deletions

View File

@ -96,6 +96,25 @@ app {
color: red;
}
#error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@media (max-width: 767.98px) {
.main .top-row {
display: none;

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
@ -8,9 +9,16 @@
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/site.css" rel="stylesheet" />
</head>
<body>
<app>Loading...</app>
<div id="error-ui">
An unhandled error has occurred.
<a href class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View File

@ -12,4 +12,10 @@
<div class="content px-4">
@Body
</div>
</div>
<div id="error-ui">
An unhandled error has occurred.
<a class="reload">Reload</a>
<a class="dismiss">X</a>
</div>

View File

@ -111,6 +111,23 @@ app {
color: red;
}
#error-ui {
background: lightyellow;
position: fixed;
border: "1px solid";
border-color: black;
width: 100%;
bottom: 0;
left: 0;
z-index: 1000;
}
#error-ui .dismiss {
position: absolute;
right: 5px;
top: 5px;
}
@media (max-width: 767.98px) {
.main .top-row {
display: none;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,6 +2,7 @@ import '@dotnet/jsinterop';
import './GlobalExports';
import * as signalR from '@aspnet/signalr';
import { MessagePackHubProtocol } from '@aspnet/signalr-protocol-msgpack';
import { showErrorNotification } from './BootErrors';
import { shouldAutoStart } from './BootCommon';
import { RenderQueue } from './Platform/Circuits/RenderQueue';
import { ConsoleLogger } from './Platform/Logging/Loggers';
@ -106,6 +107,7 @@ async function initializeConnection(options: BlazorOptions, logger: Logger, circ
connection.on('JS.Error', error => {
renderingFailed = true;
unhandledError(connection, error, logger);
showErrorNotification();
});
window['Blazor']._internal.forceCloseConnection = () => connection.stop();

View File

@ -0,0 +1,30 @@
let hasFailed = false;
export async function showErrorNotification() {
let errorUi = document.querySelector('#error-ui') as HTMLElement;
if (errorUi) {
errorUi.style.display = 'block';
}
if (!hasFailed) {
hasFailed = true;
const errorUiReloads = document.querySelectorAll<HTMLElement>('#error-ui .reload');
errorUiReloads.forEach(reload => {
reload.onclick = function (e) {
location.reload();
e.preventDefault();
};
});
let errorUiDismiss = document.querySelectorAll<HTMLElement>('#error-ui .dismiss');
errorUiDismiss.forEach(dismiss => {
dismiss.onclick = function (e) {
const errorUi = document.querySelector<HTMLElement>('#error-ui');
if (errorUi) {
errorUi.style.display = 'none';
}
e.preventDefault();
};
});
}
}

View File

@ -1,6 +1,7 @@
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
import { getFileNameFromUrl } from '../Url';
import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger';
import { showErrorNotification } from '../../BootErrors';
const assemblyHandleCache: { [assemblyName: string]: number } = {};
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
@ -232,7 +233,11 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
const suppressMessages = ['DEBUGGING ENABLED'];
module.print = line => (suppressMessages.indexOf(line) < 0 && console.log(`WASM: ${line}`));
module.printErr = line => console.error(`WASM: ${line}`);
module.printErr = line => {
console.error(`WASM: ${line}`);
showErrorNotification();
};
module.preRun = [];
module.postRun = [];
module.preloadPlugins = [];

View File

@ -0,0 +1,67 @@
// 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.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
{
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
public class ErrorNotificationClientSideTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
{
public ErrorNotificationClientSideTest(
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
}
protected override void InitializeAsyncCore()
{
// On WebAssembly, page reloads are expensive so skip if possible
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
Browser.MountTestComponent<ErrorComponent>();
Browser.Exists(By.Id("error-ui"));
Browser.Exists(By.TagName("button"));
}
[Fact]
public void ShowsErrorNotification_OnError_Dismiss()
{
var errorUi = Browser.FindElement(By.Id("error-ui"));
Assert.Equal("none", errorUi.GetCssValue("display"));
var causeErrorButton = Browser.FindElement(By.TagName("button"));
causeErrorButton.Click();
Browser.Exists(By.CssSelector("#error-ui[style='display: block;']"), TimeSpan.FromSeconds(10));
var reload = Browser.FindElement(By.ClassName("reload"));
reload.Click();
Browser.DoesNotExist(By.TagName("button"));
}
[Fact]
public void ShowsErrorNotification_OnError_Reload()
{
var causeErrorButton = Browser.Exists(By.TagName("button"));
var errorUi = Browser.FindElement(By.Id("error-ui"));
Assert.Equal("none", errorUi.GetCssValue("display"));
causeErrorButton.Click();
Browser.Exists(By.CssSelector("#error-ui[style='display: block;']"));
var dismiss = Browser.FindElement(By.ClassName("dismiss"));
dismiss.Click();
Browser.Exists(By.CssSelector("#error-ui[style='display: none;']"));
}
}
}

View File

@ -0,0 +1,25 @@
// 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 BasicTestApp;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.Components.E2ETest.ServerExecutionTests;
using Microsoft.AspNetCore.E2ETesting;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
{
[Collection("ErrorNotification")] // When the clientside and serverside tests run together it seems to cause failures, possibly due to connection lose on exception.
public class ErrorNotificationServerSideTest : ErrorNotificationClientSideTest
{
public ErrorNotificationServerSideTest(
BrowserFixture browserFixture,
ToggleExecutionModeServerFixture<Program> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture.WithServerExecution(), output)
{
}
}
}

View File

@ -0,0 +1,16 @@
<div>
<h2>Error throwing button</h2>
<p>
<button @onclick="@(IncrementCount)">Click me</button>
</p>
</div>
@code {
int currentCount = 0;
void IncrementCount()
{
currentCount++;
throw new NotImplementedException("Doing crazy things!");
}
}

View File

@ -1,6 +1,6 @@
@using Microsoft.AspNetCore.Components.Rendering
<div id="test-selector">
Select test:
Select test:
<select id="test-selector-select" @bind=SelectedComponentTypeName>
<option value="none">Choose...</option>
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
@ -20,6 +20,7 @@
<option value="BasicTestApp.DispatchingComponent">Dispatching to sync context</option>
<option value="BasicTestApp.DuplicateAttributesComponent">Duplicate attributes</option>
<option value="BasicTestApp.ElementRefComponent">Element ref component</option>
<option value="BasicTestApp.ErrorComponent">Error throwing</option>
<option value="BasicTestApp.EventBubblingComponent">Event bubbling</option>
<option value="BasicTestApp.EventCallbackTest.EventCallbackCases">EventCallback</option>
<option value="BasicTestApp.EventCasesComponent">Event cases</option>
@ -82,6 +83,12 @@
@((RenderFragment)RenderSelectedComponent)
</app>
<div id="error-ui">
An unhandled error has occurred.
<a href class='reload'>Reload</a>
<a class='dismiss' style="cursor: pointer;">🗙</a>
</div>
@code {
string SelectedComponentTypeName { get; set; } = "none";

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Basic test app</title>
@ -9,6 +10,7 @@
<!-- Used by ExternalContentPackage -->
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
</head>
<body>
<root>Loading...</root>
@ -31,4 +33,5 @@
<!-- Used by ExternalContentPackage -->
<script src="_content/TestContentPackage/prompt.js"></script>
</body>
</html>

View File

@ -6,6 +6,14 @@
outline: 1px solid red;
}
#error-ui {
display: none;
}
#error-ui dismiss {
cursor: pointer;
}
.validation-message {
color: red;
}

View File

@ -17,6 +17,17 @@
@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))
</app>
<div id="error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
</body>
</html>

View File

@ -111,6 +111,25 @@ app {
color: red;
}
#error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
@media (max-width: 767.98px) {
.main .top-row {
display: none;

View File

@ -47,6 +47,13 @@ namespace Microsoft.AspNetCore.E2ETesting
public static IWebElement Exists(this IWebDriver driver, By finder)
=> Exists(driver, finder, default);
public static void DoesNotExist(this IWebDriver driver, By finder, TimeSpan timeout = default)
=> WaitAssertCore(driver, () =>
{
var elements = driver.FindElements(finder);
Assert.Empty(elements);
}, timeout);
public static IWebElement Exists(this IWebDriver driver, By finder, TimeSpan timeout)
=> WaitAssertCore(driver, () =>
{