blob: e309c8c6acf1c8fd8dea2b5664ef942fe8fb5f62 [file] [log] [blame]
<!DOCTYPE html>
<!--
@license
Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
<meta charset="utf-8">
<title>gr-reporting</title>
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-services-es5-adapter.js"></script>
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
<script src="/components/wct-browser-legacy/browser.js"></script>
<script type="module">
import '../../test/common-test-setup.js';
import {GrReporting, DEFAULT_STARTUP_TIMERS, initErrorReporter} from './gr-reporting.js';
import {appContext} from '../app-context.js';
suite('gr-reporting tests', () => {
let service;
let sandbox;
let clock;
let fakePerformance;
const NOW_TIME = 100;
setup(() => {
sandbox = sinon.sandbox.create();
clock = sinon.useFakeTimers(NOW_TIME);
service = new GrReporting(appContext.flagsService);
service._baselines = Object.assign({}, DEFAULT_STARTUP_TIMERS);
sandbox.stub(service, 'reporter');
});
teardown(() => {
sandbox.restore();
clock.restore();
});
test('appStarted', () => {
fakePerformance = {
navigationStart: 1,
loadEventEnd: 2,
};
fakePerformance.toJSON = () => fakePerformance;
sinon.stub(service, 'performanceTiming',
{get() { return fakePerformance; }});
sandbox.stub(window.performance, 'now').returns(42);
service.appStarted();
assert.isTrue(
service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'App Started', 42
));
assert.isTrue(
service.reporter.calledWithExactly(
'timing-report', 'UI Latency', 'NavResTime - loadEventEnd',
fakePerformance.loadEventEnd - fakePerformance.navigationStart,
undefined, true)
);
});
test('WebComponentsReady', () => {
sandbox.stub(window.performance, 'now').returns(42);
service.timeEnd('WebComponentsReady');
assert.isTrue(service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'WebComponentsReady', 42
));
});
test('beforeLocationChanged', () => {
service._baselines['garbage'] = 'monster';
sandbox.stub(service, 'time');
service.beforeLocationChanged();
assert.isTrue(service.time.calledWithExactly('DashboardDisplayed'));
assert.isTrue(service.time.calledWithExactly('ChangeDisplayed'));
assert.isTrue(service.time.calledWithExactly('ChangeFullyLoaded'));
assert.isTrue(service.time.calledWithExactly('DiffViewDisplayed'));
assert.isTrue(service.time.calledWithExactly('FileListDisplayed'));
assert.isFalse(service._baselines.hasOwnProperty('garbage'));
});
test('changeDisplayed', () => {
sandbox.spy(service, 'timeEnd');
service.changeDisplayed();
assert.isFalse(service.timeEnd.calledWith('ChangeDisplayed'));
assert.isTrue(service.timeEnd.calledWith('StartupChangeDisplayed'));
service.changeDisplayed();
assert.isTrue(service.timeEnd.calledWith('ChangeDisplayed'));
});
test('changeFullyLoaded', () => {
sandbox.spy(service, 'timeEnd');
service.changeFullyLoaded();
assert.isFalse(
service.timeEnd.calledWithExactly('ChangeFullyLoaded'));
assert.isTrue(
service.timeEnd.calledWithExactly('StartupChangeFullyLoaded'));
service.changeFullyLoaded();
assert.isTrue(service.timeEnd.calledWithExactly('ChangeFullyLoaded'));
});
test('diffViewDisplayed', () => {
sandbox.spy(service, 'timeEnd');
service.diffViewDisplayed();
assert.isFalse(service.timeEnd.calledWith('DiffViewDisplayed'));
assert.isTrue(service.timeEnd.calledWith('StartupDiffViewDisplayed'));
service.diffViewDisplayed();
assert.isTrue(service.timeEnd.calledWith('DiffViewDisplayed'));
});
test('fileListDisplayed', () => {
sandbox.spy(service, 'timeEnd');
service.fileListDisplayed();
assert.isFalse(
service.timeEnd.calledWithExactly('FileListDisplayed'));
assert.isTrue(
service.timeEnd.calledWithExactly('StartupFileListDisplayed'));
service.fileListDisplayed();
assert.isTrue(service.timeEnd.calledWithExactly('FileListDisplayed'));
});
test('dashboardDisplayed', () => {
sandbox.spy(service, 'timeEnd');
service.dashboardDisplayed();
assert.isFalse(service.timeEnd.calledWith('DashboardDisplayed'));
assert.isTrue(service.timeEnd.calledWith('StartupDashboardDisplayed'));
service.dashboardDisplayed();
assert.isTrue(service.timeEnd.calledWith('DashboardDisplayed'));
});
test('dashboardDisplayed details', () => {
sandbox.spy(service, 'timeEnd');
sandbox.stub(window, 'performance', {
memory: {
usedJSHeapSize: 1024 * 1024,
},
measure: () => {},
now: () => { 42; },
});
service.reportRpcTiming('/changes/*~*/comments', 500);
service.dashboardDisplayed();
assert.isTrue(
service.timeEnd.calledWithExactly('StartupDashboardDisplayed',
{rpcList: [
{
anonymizedUrl: '/changes/*~*/comments',
elapsed: 500,
},
],
screenSize: {
width: window.screen.width,
height: window.screen.height,
},
viewport: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
},
usedJSHeapSizeMb: 1,
hiddenDurationMs: 0,
}
));
});
suite('hidden duration', () => {
let nowStub;
let visibilityStateStub;
const assertHiddenDurationsMs = hiddenDurationMs => {
service.dashboardDisplayed();
assert.isTrue(
service.timeEnd.calledWithMatch('StartupDashboardDisplayed',
{hiddenDurationMs}
));
};
setup(() => {
sandbox.spy(service, 'timeEnd');
nowStub = sandbox.stub(window.performance, 'now');
visibilityStateStub = {
value: value => {
Object.defineProperty(document, 'visibilityState',
{value, configurable: true});
},
};
});
test('starts in hidden', () => {
nowStub.returns(10);
visibilityStateStub.value('hidden');
service.onVisibilityChange();
nowStub.returns(15);
visibilityStateStub.value('visible');
service.onVisibilityChange();
assertHiddenDurationsMs(5);
});
test('full in hidden', () => {
nowStub.returns(10);
visibilityStateStub.value('hidden');
assertHiddenDurationsMs(10);
});
test('full in visible', () => {
nowStub.returns(10);
visibilityStateStub.value('visible');
assertHiddenDurationsMs(0);
});
test('accumulated', () => {
nowStub.returns(10);
visibilityStateStub.value('hidden');
service.onVisibilityChange();
nowStub.returns(15);
visibilityStateStub.value('visible');
service.onVisibilityChange();
nowStub.returns(20);
visibilityStateStub.value('hidden');
service.onVisibilityChange();
nowStub.returns(25);
assertHiddenDurationsMs(10);
});
test('reset after location change', () => {
nowStub.returns(10);
visibilityStateStub.value('hidden');
assertHiddenDurationsMs(10);
visibilityStateStub.value('visible');
nowStub.returns(15);
service.beforeLocationChanged();
service.timeEnd.reset();
service.dashboardDisplayed();
assert.isTrue(
service.timeEnd.calledWithMatch('DashboardDisplayed',
{hiddenDurationMs: 0}
));
});
});
test('time and timeEnd', () => {
const nowStub = sandbox.stub(window.performance, 'now').returns(0);
service.time('foo');
nowStub.returns(1);
service.time('bar');
nowStub.returns(2);
service.timeEnd('bar');
nowStub.returns(3);
service.timeEnd('foo');
assert.isTrue(service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'foo', 3
));
assert.isTrue(service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'bar', 1
));
});
test('timer object', () => {
const nowStub = sandbox.stub(window.performance, 'now').returns(100);
const timer = service.getTimer('foo-bar');
nowStub.returns(150);
timer.end();
assert.isTrue(service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'foo-bar', 50));
});
test('timer object double call', () => {
const timer = service.getTimer('foo-bar');
timer.end();
assert.isTrue(service.reporter.calledOnce);
assert.throws(() => {
timer.end();
}, 'Timer for "foo-bar" already ended.');
});
test('timer object maximum', () => {
const nowStub = sandbox.stub(window.performance, 'now').returns(100);
const timer = service.getTimer('foo-bar').withMaximum(100);
nowStub.returns(150);
timer.end();
assert.isTrue(service.reporter.calledOnce);
timer.reset();
nowStub.returns(260);
timer.end();
assert.isTrue(service.reporter.calledOnce);
});
test('recordDraftInteraction', () => {
const key = 'TimeBetweenDraftActions';
const nowStub = sandbox.stub(window.performance, 'now').returns(100);
const timingStub = sandbox.stub(service, '_reportTiming');
service.recordDraftInteraction();
assert.isFalse(timingStub.called);
nowStub.returns(200);
service.recordDraftInteraction();
assert.isTrue(timingStub.calledOnce);
assert.equal(timingStub.lastCall.args[0], key);
assert.equal(timingStub.lastCall.args[1], 100);
nowStub.returns(350);
service.recordDraftInteraction();
assert.isTrue(timingStub.calledTwice);
assert.equal(timingStub.lastCall.args[0], key);
assert.equal(timingStub.lastCall.args[1], 150);
nowStub.returns(370 + 2 * 60 * 1000);
service.recordDraftInteraction();
assert.isFalse(timingStub.calledThrice);
});
test('timeEndWithAverage', () => {
const nowStub = sandbox.stub(window.performance, 'now').returns(0);
nowStub.returns(1000);
service.time('foo');
nowStub.returns(1100);
service.timeEndWithAverage('foo', 'bar', 10);
assert.isTrue(service.reporter.calledTwice);
assert.isTrue(service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'foo', 100));
assert.isTrue(service.reporter.calledWithMatch(
'timing-report', 'UI Latency', 'bar', 10));
});
test('reportExtension', () => {
service.reportExtension('foo');
assert.isTrue(service.reporter.calledWithExactly(
'lifecycle', 'Extension detected', 'foo'
));
});
test('reportInteraction', () => {
service.reporter.restore();
sandbox.spy(service, '_reportEvent');
service.pluginsLoaded(); // so we don't cache
service.reportInteraction('button-click', {name: 'sendReply'});
assert.isTrue(service._reportEvent.getCall(2).calledWithMatch(
{
type: 'interaction',
name: 'button-click',
eventDetails: JSON.stringify({name: 'sendReply'}),
}
));
});
test('report start time', () => {
service.reporter.restore();
sandbox.stub(window.performance, 'now').returns(42);
sandbox.spy(service, '_reportEvent');
const dispatchStub = sandbox.spy(document, 'dispatchEvent');
service.pluginsLoaded();
service.time('timeAction');
service.timeEnd('timeAction');
assert.isTrue(service._reportEvent.getCall(2).calledWithMatch(
{
type: 'timing-report',
category: 'UI Latency',
name: 'timeAction',
value: 0,
eventStart: 42,
}
));
assert.equal(dispatchStub.getCall(2).args[0].detail.eventStart, 42);
});
suite('plugins', () => {
setup(() => {
service.reporter.restore();
sandbox.stub(service, '_reportEvent');
});
test('pluginsLoaded reports time', () => {
sandbox.stub(window.performance, 'now').returns(42);
service.pluginsLoaded();
assert.isTrue(service._reportEvent.calledWithMatch(
{
type: 'timing-report',
category: 'UI Latency',
name: 'PluginsLoaded',
value: 42,
}
));
});
test('pluginsLoaded reports plugins', () => {
service.pluginsLoaded(['foo', 'bar']);
assert.isTrue(service._reportEvent.calledWithMatch(
{
type: 'lifecycle',
category: 'Plugins installed',
eventDetails: JSON.stringify({pluginsList: ['foo', 'bar']}),
}
));
});
test('caches reports if plugins are not loaded', () => {
service.timeEnd('foo');
assert.isFalse(service._reportEvent.called);
});
test('reports if plugins are loaded', () => {
service.pluginsLoaded();
assert.isTrue(service._reportEvent.called);
});
test('reports if metrics plugin xyz is loaded', () => {
service.pluginLoaded('metrics-xyz');
assert.isTrue(service._reportEvent.called);
});
test('reports cached events preserving order', () => {
service.time('foo');
service.time('bar');
service.timeEnd('foo');
service.pluginsLoaded();
service.timeEnd('bar');
assert.isTrue(service._reportEvent.getCall(0).calledWithMatch(
{type: 'timing-report', category: 'UI Latency', name: 'foo'}
));
assert.isTrue(service._reportEvent.getCall(1).calledWithMatch(
{type: 'timing-report', category: 'UI Latency',
name: 'PluginsLoaded'}
));
assert.isTrue(service._reportEvent.getCall(2).calledWithMatch(
{type: 'lifecycle', category: 'Plugins installed'}
));
assert.isTrue(service._reportEvent.getCall(3).calledWithMatch(
{type: 'timing-report', category: 'UI Latency', name: 'bar'}
));
});
});
test('search', () => {
service.locationChanged('_handleSomeRoute');
assert.isTrue(service.reporter.calledWithExactly(
'nav-report', 'Location Changed', 'Page', '_handleSomeRoute'));
});
suite('exception logging', () => {
let fakeWindow;
let reporter;
const emulateThrow = function(msg, url, line, column, error) {
return fakeWindow.onerror(msg, url, line, column, error);
};
setup(() => {
reporter = service.reporter;
fakeWindow = {
handlers: {},
addEventListener(type, handler) {
this.handlers[type] = handler;
},
};
sandbox.stub(console, 'error');
Object.defineProperty(appContext, 'reportingService', {
get() {
return service;
},
});
const errorReporter = initErrorReporter(appContext);
errorReporter.catchErrors(fakeWindow);
});
test('is reported', () => {
const error = new Error('bar');
error.stack = undefined;
emulateThrow('bar', 'http://url', 4, 2, error);
assert.isTrue(reporter.calledWith('error', 'exception', 'bar'));
const payload = reporter.lastCall.args[3];
assert.deepEqual(payload, {
url: 'http://url',
line: 4,
column: 2,
error,
});
});
test('is reported with 3 lines of stack', () => {
const error = new Error('bar');
emulateThrow('bar', 'http://url', 4, 2, error);
const expectedStack = error.stack.split('\n').slice(0, 3)
.join('\n');
assert.isTrue(reporter.calledWith('error', 'exception',
expectedStack));
});
test('prevent default event handler', () => {
assert.isTrue(emulateThrow());
});
test('unhandled rejection', () => {
fakeWindow.handlers['unhandledrejection']({
reason: {
message: 'bar',
},
});
assert.isTrue(reporter.calledWith('error', 'exception', 'bar'));
});
});
});
</script>