feat(aio): report application errors to Google Analytics (#22011)
This is a basic implementation of error logging using the limited facilities provided by Google Analytics. Errors within the Angular app itself will be handled by a new `ReportingErrorHandler` service, which overrides and extends the built-in `ErrorHandler`. Further, errors outside the app, which arrive at `window.onerror` will also be reported to Google Analytics. Closes #21943 PR Close #22011
This commit is contained in:
parent
0b23573573
commit
eb0da530a7
@ -1,5 +1,5 @@
|
|||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule } from '@angular/core';
|
import { ErrorHandler, NgModule } from '@angular/core';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ import { TopMenuComponent } from 'app/layout/top-menu/top-menu.component';
|
|||||||
import { FooterComponent } from 'app/layout/footer/footer.component';
|
import { FooterComponent } from 'app/layout/footer/footer.component';
|
||||||
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
import { NavMenuComponent } from 'app/layout/nav-menu/nav-menu.component';
|
||||||
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
import { NavItemComponent } from 'app/layout/nav-item/nav-item.component';
|
||||||
|
import { ReportingErrorHandler } from 'app/shared/reporting-error-handler';
|
||||||
import { ScrollService } from 'app/shared/scroll.service';
|
import { ScrollService } from 'app/shared/scroll.service';
|
||||||
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
import { ScrollSpyService } from 'app/shared/scroll-spy.service';
|
||||||
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
import { SearchBoxComponent } from 'app/search/search-box/search-box.component';
|
||||||
@ -124,6 +125,7 @@ export const svgIconProviders = [
|
|||||||
providers: [
|
providers: [
|
||||||
Deployment,
|
Deployment,
|
||||||
DocumentService,
|
DocumentService,
|
||||||
|
{ provide: ErrorHandler, useClass: ReportingErrorHandler },
|
||||||
GaService,
|
GaService,
|
||||||
Logger,
|
Logger,
|
||||||
Location,
|
Location,
|
||||||
|
@ -30,6 +30,9 @@ export class GaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ga(...args: any[]) {
|
ga(...args: any[]) {
|
||||||
(this.window as any)['ga'](...args);
|
const gaFn = (this.window as any)['ga'];
|
||||||
|
if (gaFn) {
|
||||||
|
gaFn(...args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
aio/src/app/shared/reporting-error-handler.spec.ts
Normal file
64
aio/src/app/shared/reporting-error-handler.spec.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { ErrorHandler, ReflectiveInjector } from '@angular/core';
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { WindowToken } from 'app/shared/window';
|
||||||
|
import { AppModule } from 'app/app.module';
|
||||||
|
|
||||||
|
import { ReportingErrorHandler } from './reporting-error-handler';
|
||||||
|
|
||||||
|
describe('ReportingErrorHandler service', () => {
|
||||||
|
let handler: ReportingErrorHandler;
|
||||||
|
let superHandler: jasmine.Spy;
|
||||||
|
let onerrorSpy: jasmine.Spy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onerrorSpy = jasmine.createSpy('onerror');
|
||||||
|
superHandler = spyOn(ErrorHandler.prototype, 'handleError');
|
||||||
|
|
||||||
|
const injector = ReflectiveInjector.resolveAndCreate([
|
||||||
|
{ provide: ErrorHandler, useClass: ReportingErrorHandler },
|
||||||
|
{ provide: WindowToken, useFactory: () => ({ onerror: onerrorSpy }) }
|
||||||
|
]);
|
||||||
|
handler = injector.get(ErrorHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be registered on the AppModule', () => {
|
||||||
|
handler = TestBed.configureTestingModule({ imports: [AppModule] }).get(ErrorHandler);
|
||||||
|
expect(handler).toEqual(jasmine.any(ReportingErrorHandler));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleError', () => {
|
||||||
|
it('should call the super class handleError', () => {
|
||||||
|
const error = new Error();
|
||||||
|
handler.handleError(error);
|
||||||
|
expect(superHandler).toHaveBeenCalledWith(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should cope with the super handler throwing an error', () => {
|
||||||
|
const error = new Error('initial error');
|
||||||
|
superHandler.and.throwError('super handler error');
|
||||||
|
handler.handleError(error);
|
||||||
|
|
||||||
|
expect(onerrorSpy).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
// Error from super handler is reported first
|
||||||
|
expect(onerrorSpy.calls.argsFor(0)[0]).toEqual('super handler error');
|
||||||
|
expect(onerrorSpy.calls.argsFor(0)[4]).toEqual(jasmine.any(Error));
|
||||||
|
|
||||||
|
// Then error from initial exception
|
||||||
|
expect(onerrorSpy.calls.argsFor(1)[0]).toEqual('initial error');
|
||||||
|
expect(onerrorSpy.calls.argsFor(1)[4]).toEqual(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send an error object to window.onerror', () => {
|
||||||
|
const error = new Error('this is an error message');
|
||||||
|
handler.handleError(error);
|
||||||
|
expect(onerrorSpy).toHaveBeenCalledWith(error.message, undefined, undefined, undefined, error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send an error string to window.onerror', () => {
|
||||||
|
const error = 'this is an error message';
|
||||||
|
handler.handleError(error);
|
||||||
|
expect(onerrorSpy).toHaveBeenCalledWith(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
37
aio/src/app/shared/reporting-error-handler.ts
Normal file
37
aio/src/app/shared/reporting-error-handler.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ErrorHandler, Inject, Injectable } from '@angular/core';
|
||||||
|
import { WindowToken } from './window';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extend the default error handling to report errors to an external service - e.g Google Analytics.
|
||||||
|
*
|
||||||
|
* Errors outside the Angular application may also be handled by `window.onerror`.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ReportingErrorHandler extends ErrorHandler {
|
||||||
|
|
||||||
|
constructor(@Inject(WindowToken) private window: Window) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send error info to Google Analytics, in addition to the default handling.
|
||||||
|
* @param error Information about the error.
|
||||||
|
*/
|
||||||
|
handleError(error: string | Error) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
super.handleError(error);
|
||||||
|
} catch (e) {
|
||||||
|
this.reportError(e);
|
||||||
|
}
|
||||||
|
this.reportError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private reportError(error: string | Error) {
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
this.window.onerror(error);
|
||||||
|
} else {
|
||||||
|
this.window.onerror(error.message, undefined, undefined, undefined, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,38 @@
|
|||||||
</script>
|
</script>
|
||||||
<!-- End Google Analytics -->
|
<!-- End Google Analytics -->
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Report fatal errors to Google Analytics
|
||||||
|
window.onerror = function() {
|
||||||
|
ga('send', 'exception', {exDescription: formatError.apply(null, arguments), exFatal: true});
|
||||||
|
|
||||||
|
function formatError(msg, url, line, col, e) {
|
||||||
|
var stack;
|
||||||
|
msg = msg.replace(/^Error: /, '');
|
||||||
|
if (e) {
|
||||||
|
stack = e.stack
|
||||||
|
// strip the leading "Error: " from the stack trace
|
||||||
|
.replace(/^Error: /, '')
|
||||||
|
// strip the message from the stack trace, if present
|
||||||
|
.replace(msg + '\n', '')
|
||||||
|
// strip leading spaces
|
||||||
|
.replace(/^ +/gm, '')
|
||||||
|
// strip all leading "at " for each frame
|
||||||
|
.replace(/^at /gm, '')
|
||||||
|
// replace long urls with just the last segment: `filename:line:column`
|
||||||
|
.replace(/(?: \(|@)http.+\/([^/)]+)\)?(?:\n|$)/gm, '@$1\n')
|
||||||
|
// replace "eval code" in Edge
|
||||||
|
.replace(/ *\(eval code(:\d+:\d+)\)(?:\n|$)/gm, '@???$1\n')
|
||||||
|
} else {
|
||||||
|
line = line || '?';
|
||||||
|
col = col || '?';
|
||||||
|
stack = url + ':' + line + ':' + col;
|
||||||
|
}
|
||||||
|
return (msg + '\n' + stack).substr(0, 150);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
if (window.document.documentMode) {
|
if (window.document.documentMode) {
|
||||||
// polyfill IE11 in a blocking way
|
// polyfill IE11 in a blocking way
|
||||||
|
203
aio/tests/e2e/onerror.e2e-spec.ts
Normal file
203
aio/tests/e2e/onerror.e2e-spec.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import { browser } from 'protractor';
|
||||||
|
import { SitePage } from './app.po';
|
||||||
|
|
||||||
|
/* tslint:disable:max-line-length */
|
||||||
|
|
||||||
|
describe('onerror handler', function() {
|
||||||
|
let page: SitePage;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
page = new SitePage();
|
||||||
|
page.navigateTo('');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('(called without an error object) should call ga with a payload based on the message, url, row and column arguments', async () => {
|
||||||
|
const message1 = await callOnError('Error: some error message', 'some-file.js', 12, 3, undefined);
|
||||||
|
expect(message1).toEqual('some error message\nsome-file.js:12:3');
|
||||||
|
const message2 = await callOnError('Error: some error message', undefined, undefined, undefined, undefined);
|
||||||
|
expect(message2).toEqual('some error message\nnull:?:?');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called without an error object) should call ga with a payload that is no longer that 150 characters', async () => {
|
||||||
|
const message = await callOnError(
|
||||||
|
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' +
|
||||||
|
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz',
|
||||||
|
'some-file.js', 12, 3, undefined);
|
||||||
|
expect(message).toEqual(
|
||||||
|
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' +
|
||||||
|
'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called with a Firefox on android style error) should call ga with a payload based on the error object', async () => {
|
||||||
|
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
|
||||||
|
stack: `AppComponent@https://example.com/app/app.component.ts:31:29
|
||||||
|
createClass@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20
|
||||||
|
createDirectiveInstance@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37
|
||||||
|
createViewNodes@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53
|
||||||
|
createRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5
|
||||||
|
callWithDebugContext@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:39
|
||||||
|
debugCreateRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12
|
||||||
|
ComponentFactory_.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:37
|
||||||
|
ComponentFactoryBoundToModule.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:16
|
||||||
|
ApplicationRef.prototype.bootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:40
|
||||||
|
PlatformRef.prototype._moduleDoBootstrap/<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:74
|
||||||
|
PlatformRef.prototype._moduleDoBootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:13
|
||||||
|
PlatformRef.prototype.bootstrapModuleFactory/</</<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5417:21
|
||||||
|
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:392:17
|
||||||
|
forkInnerZoneWithAngularBehavior/zone._inner<.onInvoke@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4665:24
|
||||||
|
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:391:17
|
||||||
|
Zone.prototype.run@https://example.com/packages/zone.js@0.8.18/dist/zone.js:142:24
|
||||||
|
scheduleResolveOrReject/<@https://example.com/packages/zone.js@0.8.18/dist/zone.js:873:52
|
||||||
|
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:425:17
|
||||||
|
forkInnerZoneWithAngularBehavior/zone._inner<.onInvokeTask@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4656:24
|
||||||
|
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:424:17
|
||||||
|
Zone.prototype.runTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:192:28
|
||||||
|
drainMicroTaskQueue@https://example.com/packages/zone.js@0.8.18/dist/zone.js:602:25` });
|
||||||
|
|
||||||
|
expect(message).toEqual(`something terrible has happened. oh no. oh no.
|
||||||
|
AppComponent@app.component.ts:31:29
|
||||||
|
createClass@core.umd.js:12200:20
|
||||||
|
createDirectiveInstance@core.umd.j`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called with a Safari 11 style error) should call ga with a payload based on the error object', async () => {
|
||||||
|
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
|
||||||
|
stack: `AppComponent
|
||||||
|
createClass
|
||||||
|
createDirectiveInstance
|
||||||
|
createViewNodes
|
||||||
|
createRootView
|
||||||
|
callWithDebugContext
|
||||||
|
create
|
||||||
|
bootstrap
|
||||||
|
forEach@[native code]
|
||||||
|
_moduleDoBootstrap
|
||||||
|
|
||||||
|
onInvoke
|
||||||
|
run
|
||||||
|
|
||||||
|
onInvokeTask
|
||||||
|
runTask
|
||||||
|
drainMicroTaskQueue
|
||||||
|
promiseReactionJob@[native code]` });
|
||||||
|
|
||||||
|
expect(message).toEqual(`something terrible has happened. oh no. oh no.
|
||||||
|
AppComponent
|
||||||
|
createClass
|
||||||
|
createDirectiveInstance
|
||||||
|
createViewNodes
|
||||||
|
createRootView
|
||||||
|
callWithDebugContext
|
||||||
|
cr`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called with a Opera 50 style error) should call ga with a payload based on the error object', async () => {
|
||||||
|
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
|
||||||
|
stack: `Error: something terrible has happened. oh no. oh no.
|
||||||
|
at new AppComponent (https://example.com/app/app.component.ts:31:29)
|
||||||
|
at createClass (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20)
|
||||||
|
at createDirectiveInstance (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37)
|
||||||
|
at createViewNodes (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53)
|
||||||
|
at createRootView (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5)
|
||||||
|
at callWithDebugContext (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:42)
|
||||||
|
at Object.debugCreateRootView [as createRootView] (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12)
|
||||||
|
at ComponentFactory_.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:46)
|
||||||
|
at ComponentFactoryBoundToModule.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:29)
|
||||||
|
at ApplicationRef.bootstrap (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:57)` });
|
||||||
|
|
||||||
|
expect(message).toEqual(`something terrible has happened. oh no. oh no.
|
||||||
|
new AppComponent@app.component.ts:31:29
|
||||||
|
createClass@core.umd.js:12200:20
|
||||||
|
createDirectiveInstance@core.u`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called with a Chrome 64 style error) should call ga with a payload based on the error object', async () => {
|
||||||
|
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
|
||||||
|
stack: `Error: something terrible has happened. oh no. oh no.
|
||||||
|
at new AppComponent (https://example.com/app/app.component.ts:31:29)
|
||||||
|
at createClass (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20)
|
||||||
|
at createDirectiveInstance (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37)
|
||||||
|
at createViewNodes (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53)
|
||||||
|
at createRootView (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5)
|
||||||
|
at callWithDebugContext (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:42)
|
||||||
|
at Object.debugCreateRootView [as createRootView] (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12)
|
||||||
|
at ComponentFactory_.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:46)
|
||||||
|
at ComponentFactoryBoundToModule.create (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:29)
|
||||||
|
at ApplicationRef.bootstrap (https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:57)` });
|
||||||
|
|
||||||
|
expect(message).toEqual(`something terrible has happened. oh no. oh no.
|
||||||
|
new AppComponent@app.component.ts:31:29
|
||||||
|
createClass@core.umd.js:12200:20
|
||||||
|
createDirectiveInstance@core.u`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called with a Firefox 58 style error) should call ga with a payload based on the error object', async () => {
|
||||||
|
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
|
||||||
|
stack: `AppComponent@https://example.com/app/app.component.ts:31:29
|
||||||
|
createClass@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12200:20
|
||||||
|
createDirectiveInstance@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:12049:37
|
||||||
|
createViewNodes@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13487:53
|
||||||
|
createRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:13377:5
|
||||||
|
callWithDebugContext@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14778:39
|
||||||
|
debugCreateRootView@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:14079:12
|
||||||
|
ComponentFactory_.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:10998:37
|
||||||
|
ComponentFactoryBoundToModule.prototype.create@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:3958:16
|
||||||
|
ApplicationRef.prototype.bootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5769:40
|
||||||
|
PlatformRef.prototype._moduleDoBootstrap/<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:74
|
||||||
|
PlatformRef.prototype._moduleDoBootstrap@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5496:13
|
||||||
|
PlatformRef.prototype.bootstrapModuleFactory/</</<@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:5417:21
|
||||||
|
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:392:17
|
||||||
|
onInvoke@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4665:24
|
||||||
|
ZoneDelegate.prototype.invoke@https://example.com/packages/zone.js@0.8.18/dist/zone.js:391:17
|
||||||
|
Zone.prototype.run@https://example.com/packages/zone.js@0.8.18/dist/zone.js:142:24
|
||||||
|
scheduleResolveOrReject/<@https://example.com/packages/zone.js@0.8.18/dist/zone.js:873:52
|
||||||
|
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:425:17
|
||||||
|
onInvokeTask@https://example.com/packages/@angular/core@5.0.0/bundles/core.umd.js:4656:24
|
||||||
|
ZoneDelegate.prototype.invokeTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:424:17
|
||||||
|
Zone.prototype.runTask@https://example.com/packages/zone.js@0.8.18/dist/zone.js:192:28
|
||||||
|
drainMicroTaskQueue@https://example.com/packages/zone.js@0.8.18/dist/zone.js:602:25` });
|
||||||
|
|
||||||
|
expect(message).toEqual(`something terrible has happened. oh no. oh no.
|
||||||
|
AppComponent@app.component.ts:31:29
|
||||||
|
createClass@core.umd.js:12200:20
|
||||||
|
createDirectiveInstance@core.umd.j`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('(called with a Edge 16 style error) should call ga with a payload based on the error object', async () => {
|
||||||
|
const message = await callOnError('Error: something terrible has happened. oh no. oh no.', undefined, undefined, undefined, {
|
||||||
|
stack: `Error: something terrible has happened. oh no. oh no.
|
||||||
|
at AppComponent (eval code:31:21)
|
||||||
|
at createClass (eval code:12200:13)
|
||||||
|
at createDirectiveInstance (eval code:12049:5)
|
||||||
|
at createViewNodes (eval code:13487:21)
|
||||||
|
at createRootView (eval code:13377:5)
|
||||||
|
at callWithDebugContext (eval code:14778:9)
|
||||||
|
at debugCreateRootView (eval code:14079:5)
|
||||||
|
at ComponentFactory_.prototype.create (eval code:10998:9)
|
||||||
|
at ComponentFactoryBoundToModule.prototype.create (eval code:3958:9)
|
||||||
|
at ApplicationRef.prototype.bootstrap (eval code:5769:9)` });
|
||||||
|
|
||||||
|
expect(message).toEqual(`something terrible has happened. oh no. oh no.
|
||||||
|
AppComponent@???:31:21
|
||||||
|
createClass@???:12200:13
|
||||||
|
createDirectiveInstance@???:12049:5
|
||||||
|
createViewNodes@???`);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function callOnError(message, url, line, column, error) {
|
||||||
|
await browser.executeScript(function() {
|
||||||
|
// reset the ga queue
|
||||||
|
(window as any).ga.q.length = 0;
|
||||||
|
// post the error to the handler
|
||||||
|
window.onerror(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
|
||||||
|
}, message, url, line, column, error);
|
||||||
|
const gaCalls = await page.ga();
|
||||||
|
const exceptionCall = gaCalls.find(call => call[0] === 'send' && call[1] === 'exception');
|
||||||
|
if (exceptionCall) {
|
||||||
|
const payload = exceptionCall[2];
|
||||||
|
expect(payload.exFatal).toBe(true);
|
||||||
|
return payload.exDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user