Skip to content

Dev exception notifications #14636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
Expand All @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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>
17 changes: 17 additions & 0 deletions src/Components/Samples/BlazorServerApp/wwwroot/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/Components/Web.JS/dist/Release/blazor.server.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Components/Web.JS/dist/Release/blazor.webassembly.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/Components/Web.JS/src/Boot.Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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();
Expand Down
30 changes: 30 additions & 0 deletions src/Components/Web.JS/src/BootErrors.ts
Original file line number Diff line number Diff line change
@@ -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();
};
});
}
}
7 changes: 6 additions & 1 deletion src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts
Original file line number Diff line number Diff line change
@@ -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 } = {};
Expand Down Expand Up @@ -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 = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -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;']"));
}
}
}
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -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!");
}
}
9 changes: 8 additions & 1 deletion src/Components/test/testassets/BasicTestApp/Index.razor
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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>
Expand Down Expand Up @@ -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";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>Basic test app</title>
Expand All @@ -9,6 +10,7 @@
<!-- Used by ExternalContentPackage -->
<link href="_content/TestContentPackage/styles.css" rel="stylesheet" />
</head>

<body>
<root>Loading...</root>

Expand All @@ -31,4 +33,5 @@
<!-- Used by ExternalContentPackage -->
<script src="_content/TestContentPackage/prompt.js"></script>
</body>

</html>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@
outline: 1px solid red;
}

#error-ui {
display: none;
}

#error-ui dismiss {
cursor: pointer;
}

.validation-message {
color: red;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions src/Shared/E2ETesting/WaitAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ public static void Single(this IWebDriver driver, Func<IEnumerable> actualValues
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, () =>
{
Expand Down