| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js'; |
| import {getFirstOrError, getInsightOrError} from '../../testing/InsightHelpers.js'; |
| import {TraceLoader} from '../../testing/TraceLoader.js'; |
| import * as Trace from '../trace/trace.js'; |
| |
| describeWithEnvironment('TraceModel', function() { |
| it('dispatches an end event when the trace is done', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const events: string[] = []; |
| |
| model.addEventListener(Trace.TraceModel.ModelUpdateEvent.eventName, (evt: Event) => { |
| const updateEvent = evt as Trace.TraceModel.ModelUpdateEvent; |
| events.push(updateEvent.data.type); |
| }); |
| |
| await TraceLoader.rawEvents(this, 'basic.json.gz').then(events => model.parse(events)); |
| assert.isOk(events.includes('PROGRESS_UPDATE')); |
| assert.isOk(events.includes('COMPLETE')); |
| }); |
| |
| it('supports parsing a generic trace that has no browser specific details', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const file1 = await TraceLoader.rawEvents(this, 'generic-about-tracing.json.gz'); |
| await model.parse(file1); |
| assert.strictEqual(model.size(), 1); |
| }); |
| |
| it('supports being given a set of handlers to run and will run just those and the Meta handler', async function() { |
| const model = new Trace.TraceModel.Model({ |
| Animations: Trace.Handlers.ModelHandlers.Animations, |
| } as Trace.Handlers.Types.Handlers); |
| const file1 = await TraceLoader.rawEvents(this, 'animation.json.gz'); |
| await model.parse(file1); |
| assert.deepEqual(Object.keys(model.parsedTrace(0)?.data || {}), ['Meta', 'Animations']); |
| }); |
| |
| it('supports parsing multiple traces', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const file1 = await TraceLoader.rawEvents(this, 'basic.json.gz'); |
| const file2 = await TraceLoader.rawEvents(this, 'slow-interaction-keydown.json.gz'); |
| |
| await model.parse(file1); |
| assert.strictEqual(model.lastTraceIndex(), 0); |
| model.resetProcessor(); |
| await model.parse(file2); |
| assert.strictEqual(model.lastTraceIndex(), 1); |
| model.resetProcessor(); |
| |
| assert.strictEqual(model.size(), 2); |
| assert.isNotNull(model.parsedTrace(0)); |
| assert.isNotNull(model.parsedTrace(1)); |
| }); |
| |
| it('supports deleting traces', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const file1 = await TraceLoader.rawEvents(this, 'basic.json.gz'); |
| const file2 = await TraceLoader.rawEvents(this, 'slow-interaction-keydown.json.gz'); |
| |
| await model.parse(file1); |
| model.resetProcessor(); |
| await model.parse(file2); |
| model.resetProcessor(); |
| |
| // Test only one trace is deleted. |
| assert.strictEqual(model.size(), 2); |
| model.deleteTraceByIndex(0); |
| assert.strictEqual(model.size(), 1); |
| assert.isNotNull(model.parsedTrace(0)); |
| |
| model.deleteTraceByIndex(0); |
| assert.strictEqual(model.size(), 0); |
| assert.isNull(model.parsedTrace(0)); |
| }); |
| |
| it('names traces using their origin and defaults to "Trace n" when no origin is found', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const traceFiles = [ |
| await TraceLoader.rawEvents(this, 'threejs-gpu.json.gz'), |
| await TraceLoader.rawEvents(this, 'web-dev.json.gz'), |
| // Process the previous trace again to test the trace sequencing |
| await TraceLoader.rawEvents(this, 'web-dev.json.gz'), |
| await TraceLoader.rawEvents(this, 'multiple-navigations.json.gz'), |
| await TraceLoader.rawEvents(this, 'missing-url.json.gz'), |
| ]; |
| for (const traceFile of traceFiles) { |
| await model.parse(traceFile); |
| model.resetProcessor(); |
| } |
| const expectedResults = [ |
| 'threejs.org (1)', |
| 'web.dev (1)', |
| 'web.dev (2)', |
| 'google.com (1)', |
| 'Trace 5', |
| ]; |
| assert.deepEqual(model.getRecordingsAvailable(), expectedResults); |
| }); |
| |
| it('supports overriding modifications in metadata', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const file1 = await TraceLoader.rawEvents(this, 'basic.json.gz'); |
| await model.parse(file1); |
| |
| // Make sure there are no modifications before any are added |
| assert.isUndefined(model.parsedTrace(0)?.metadata.modifications); |
| |
| const initialBreadcrumb = { |
| window: { |
| max: 0 as Trace.Types.Timing.Micro, |
| min: 10 as Trace.Types.Timing.Micro, |
| range: 10 as Trace.Types.Timing.Micro, |
| }, |
| child: null, |
| }; |
| |
| const entriesModifications = { |
| hiddenEntries: ['r-1', 'r-2', 'r-3'], |
| expandableEntries: ['r-4'], |
| } as Trace.Types.File.Modifications['entriesModifications']; |
| |
| const annotations = { |
| entryLabels: [ |
| { |
| entry: 'r-4', |
| label: 'entry label', |
| }, |
| ], |
| labelledTimeRanges: [ |
| { |
| bounds: { |
| min: Trace.Types.Timing.Micro(0), |
| max: Trace.Types.Timing.Micro(10), |
| range: Trace.Types.Timing.Micro(10), |
| }, |
| label: 'range label', |
| }, |
| ], |
| linksBetweenEntries: [{ |
| entryFrom: 'r-10', |
| entryTo: 'r-11', |
| }], |
| } as Trace.Types.File.Modifications['annotations']; |
| |
| const modifications = { |
| entriesModifications, |
| initialBreadcrumb, |
| annotations, |
| }; |
| |
| model.overrideModifications(0, modifications); |
| // Make sure metadata contains overwritten modifications |
| assert.strictEqual(model.parsedTrace(0)?.metadata.modifications, modifications); |
| }); |
| |
| it('supports a custom time formatter for insights', async function() { |
| const model = Trace.TraceModel.Model.createWithAllHandlers(); |
| const file1 = await TraceLoader.rawEvents(this, 'many-redirects.json.gz'); |
| await model.parse(file1, { |
| insightTimeFormatters: { |
| milli() { |
| return 'FAKE-MILLI-TIME-FORMATTER'; |
| }, |
| } |
| }); |
| const result = model.parsedTrace(); |
| assert.isOk(result); |
| assert.isOk(result.insights); |
| const insight = getInsightOrError( |
| 'DocumentLatency', result.insights, getFirstOrError(result.data.Meta.navigationsByNavigationId.values())); |
| assert.include(insight.data?.checklist.noRedirects.label ?? '', 'FAKE-MILLI-TIME-FORMATTER'); |
| }); |
| }); |