Skip to content

Commit cded21a

Browse files
authoredMar 1, 2022
core(fr): separate audit phase for flows (#13623)
1 parent ceb03cb commit cded21a

12 files changed

+2601
-2683
lines changed
 

‎lighthouse-core/fraggle-rock/api.js

+44-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*/
66
'use strict';
77

8-
const {snapshot} = require('./gather/snapshot-runner.js');
9-
const {startTimespan} = require('./gather/timespan-runner.js');
10-
const {navigation} = require('./gather/navigation-runner.js');
8+
const {snapshotGather} = require('./gather/snapshot-runner.js');
9+
const {startTimespanGather} = require('./gather/timespan-runner.js');
10+
const {navigationGather} = require('./gather/navigation-runner.js');
11+
const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
12+
const Runner = require('../runner.js');
1113
const UserFlow = require('./user-flow.js');
1214

1315
/**
@@ -18,9 +20,48 @@ async function startFlow(page, options) {
1820
return new UserFlow(page, options);
1921
}
2022

23+
/**
24+
* @param {Parameters<navigationGather>} params
25+
* @return {Promise<LH.RunnerResult|undefined>}
26+
*/
27+
async function navigation(...params) {
28+
const gatherResult = await navigationGather(...params);
29+
return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
30+
}
31+
32+
/**
33+
* @param {Parameters<snapshotGather>} params
34+
* @return {Promise<LH.RunnerResult|undefined>}
35+
*/
36+
async function snapshot(...params) {
37+
const gatherResult = await snapshotGather(...params);
38+
return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
39+
}
40+
41+
/**
42+
* @param {Parameters<startTimespanGather>} params
43+
* @return {Promise<{endTimespan: () => Promise<LH.RunnerResult|undefined>}>}
44+
*/
45+
async function startTimespan(...params) {
46+
const {endTimespanGather} = await startTimespanGather(...params);
47+
const endTimespan = async () => {
48+
const gatherResult = await endTimespanGather();
49+
return Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
50+
};
51+
return {endTimespan};
52+
}
53+
54+
/**
55+
* @param {LH.FlowResult} flowResult
56+
*/
57+
async function generateFlowReport(flowResult) {
58+
return generateFlowReportHtml(flowResult);
59+
}
60+
2161
module.exports = {
2262
snapshot,
2363
startTimespan,
2464
navigation,
2565
startFlow,
66+
generateFlowReport,
2667
};

‎lighthouse-core/fraggle-rock/gather/navigation-runner.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,9 @@ async function _cleanup({requestedUrl, driver, config}) {
290290
/**
291291
* @param {LH.NavigationRequestor} requestor
292292
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
293-
* @return {Promise<LH.RunnerResult|undefined>}
293+
* @return {Promise<LH.Gatherer.FRGatherResult>}
294294
*/
295-
async function navigation(requestor, options) {
295+
async function navigationGather(requestor, options) {
296296
const {page, configContext = {}} = options;
297297
log.setLevel(configContext.logLevel || 'error');
298298

@@ -325,11 +325,11 @@ async function navigation(requestor, options) {
325325
},
326326
runnerOptions
327327
);
328-
return Runner.audit(artifacts, runnerOptions);
328+
return {artifacts, runnerOptions};
329329
}
330330

331331
module.exports = {
332-
navigation,
332+
navigationGather,
333333
_setup,
334334
_setupNavigation,
335335
_navigate,

‎lighthouse-core/fraggle-rock/gather/snapshot-runner.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ const {
1616
const {initializeConfig} = require('../config/config.js');
1717
const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
1818

19-
/** @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options */
20-
async function snapshot(options) {
19+
/**
20+
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
21+
* @return {Promise<LH.Gatherer.FRGatherResult>}
22+
*/
23+
async function snapshotGather(options) {
2124
const {configContext = {}} = options;
2225
log.setLevel(configContext.logLevel || 'error');
2326

@@ -57,9 +60,9 @@ async function snapshot(options) {
5760
},
5861
runnerOptions
5962
);
60-
return Runner.audit(artifacts, runnerOptions);
63+
return {artifacts, runnerOptions};
6164
}
6265

6366
module.exports = {
64-
snapshot,
67+
snapshotGather,
6568
};

‎lighthouse-core/fraggle-rock/gather/timespan-runner.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
1919

2020
/**
2121
* @param {{page: import('puppeteer').Page, config?: LH.Config.Json, configContext?: LH.Config.FRContext}} options
22-
* @return {Promise<{endTimespan(): Promise<LH.RunnerResult|undefined>}>}
22+
* @return {Promise<{endTimespanGather(): Promise<LH.Gatherer.FRGatherResult>}>}
2323
*/
24-
async function startTimespan(options) {
24+
async function startTimespanGather(options) {
2525
const {configContext = {}} = options;
2626
log.setLevel(configContext.logLevel || 'error');
2727

@@ -52,7 +52,7 @@ async function startTimespan(options) {
5252
await collectPhaseArtifacts({phase: 'startSensitiveInstrumentation', ...phaseOptions});
5353

5454
return {
55-
async endTimespan() {
55+
async endTimespanGather() {
5656
const finalUrl = await driver.url();
5757
phaseOptions.url = finalUrl;
5858

@@ -72,11 +72,11 @@ async function startTimespan(options) {
7272
},
7373
runnerOptions
7474
);
75-
return Runner.audit(artifacts, runnerOptions);
75+
return {artifacts, runnerOptions};
7676
},
7777
};
7878
}
7979

8080
module.exports = {
81-
startTimespan,
81+
startTimespanGather,
8282
};

‎lighthouse-core/fraggle-rock/user-flow.js

+54-43
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
'use strict';
77

88
const {generateFlowReportHtml} = require('../../report/generator/report-generator.js');
9-
const {snapshot} = require('./gather/snapshot-runner.js');
10-
const {startTimespan} = require('./gather/timespan-runner.js');
11-
const {navigation} = require('./gather/navigation-runner.js');
9+
const {snapshotGather} = require('./gather/snapshot-runner.js');
10+
const {startTimespanGather} = require('./gather/timespan-runner.js');
11+
const {navigationGather} = require('./gather/navigation-runner.js');
12+
const Runner = require('../runner.js');
1213

13-
/** @typedef {Parameters<snapshot>[0]} FrOptions */
14+
/** @typedef {Parameters<snapshotGather>[0]} FrOptions */
1415
/** @typedef {Omit<FrOptions, 'page'> & {name?: string}} UserFlowOptions */
1516
/** @typedef {Omit<FrOptions, 'page'> & {stepName?: string}} StepOptions */
17+
/** @typedef {{gatherResult: LH.Gatherer.FRGatherResult, name: string}} StepArtifact */
1618

1719
class UserFlow {
1820
/**
@@ -24,8 +26,8 @@ class UserFlow {
2426
this.options = {page, ...options};
2527
/** @type {string|undefined} */
2628
this.name = options?.name;
27-
/** @type {LH.FlowResult.Step[]} */
28-
this.steps = [];
29+
/** @type {StepArtifact[]} */
30+
this.stepArtifacts = [];
2931
}
3032

3133
/**
@@ -38,12 +40,12 @@ class UserFlow {
3840
}
3941

4042
/**
41-
* @param {LH.Result} lhr
43+
* @param {LH.Artifacts} artifacts
4244
* @return {string}
4345
*/
44-
_getDefaultStepName(lhr) {
45-
const shortUrl = this._shortenUrl(lhr.finalUrl);
46-
switch (lhr.gatherMode) {
46+
_getDefaultStepName(artifacts) {
47+
const shortUrl = this._shortenUrl(artifacts.URL.finalUrl);
48+
switch (artifacts.GatherContext.gatherMode) {
4749
case 'navigation':
4850
return `Navigation report (${shortUrl})`;
4951
case 'timespan':
@@ -66,7 +68,8 @@ class UserFlow {
6668
}
6769

6870
// On repeat navigations, we want to disable storage reset by default (i.e. it's not a cold load).
69-
const isSubsequentNavigation = this.steps.some(step => step.lhr.gatherMode === 'navigation');
71+
const isSubsequentNavigation = this.stepArtifacts
72+
.some(step => step.gatherResult.artifacts.GatherContext.gatherMode === 'navigation');
7073
if (isSubsequentNavigation) {
7174
if (settingsOverrides.disableStorageReset === undefined) {
7275
settingsOverrides.disableStorageReset = true;
@@ -84,85 +87,93 @@ class UserFlow {
8487
* @param {StepOptions=} stepOptions
8588
*/
8689
async navigate(requestor, stepOptions) {
87-
if (this.currentTimespan) throw Error('Timespan already in progress');
90+
if (this.currentTimespan) throw new Error('Timespan already in progress');
8891

89-
const result = await navigation(
92+
const gatherResult = await navigationGather(
9093
requestor,
9194
this._getNextNavigationOptions(stepOptions)
9295
);
93-
if (!result) throw Error('Navigation returned undefined');
9496

9597
const providedName = stepOptions?.stepName;
96-
this.steps.push({
97-
lhr: result.lhr,
98-
name: providedName || this._getDefaultStepName(result.lhr),
98+
this.stepArtifacts.push({
99+
gatherResult,
100+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
99101
});
100102

101-
return result;
103+
return gatherResult;
102104
}
103105

104106
/**
105107
* @param {StepOptions=} stepOptions
106108
*/
107109
async startTimespan(stepOptions) {
108-
if (this.currentTimespan) throw Error('Timespan already in progress');
110+
if (this.currentTimespan) throw new Error('Timespan already in progress');
109111

110112
const options = {...this.options, ...stepOptions};
111-
const timespan = await startTimespan(options);
113+
const timespan = await startTimespanGather(options);
112114
this.currentTimespan = {timespan, options};
113115
}
114116

115117
async endTimespan() {
116-
if (!this.currentTimespan) throw Error('No timespan in progress');
118+
if (!this.currentTimespan) throw new Error('No timespan in progress');
117119

118120
const {timespan, options} = this.currentTimespan;
119-
const result = await timespan.endTimespan();
121+
const gatherResult = await timespan.endTimespanGather();
120122
this.currentTimespan = undefined;
121-
if (!result) throw Error('Timespan returned undefined');
122123

123124
const providedName = options?.stepName;
124-
this.steps.push({
125-
lhr: result.lhr,
126-
name: providedName || this._getDefaultStepName(result.lhr),
125+
this.stepArtifacts.push({
126+
gatherResult,
127+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
127128
});
128129

129-
return result;
130+
return gatherResult;
130131
}
131132

132133
/**
133134
* @param {StepOptions=} stepOptions
134135
*/
135136
async snapshot(stepOptions) {
136-
if (this.currentTimespan) throw Error('Timespan already in progress');
137+
if (this.currentTimespan) throw new Error('Timespan already in progress');
137138

138139
const options = {...this.options, ...stepOptions};
139-
const result = await snapshot(options);
140-
if (!result) throw Error('Snapshot returned undefined');
140+
const gatherResult = await snapshotGather(options);
141141

142142
const providedName = stepOptions?.stepName;
143-
this.steps.push({
144-
lhr: result.lhr,
145-
name: providedName || this._getDefaultStepName(result.lhr),
143+
this.stepArtifacts.push({
144+
gatherResult,
145+
name: providedName || this._getDefaultStepName(gatherResult.artifacts),
146146
});
147147

148-
return result;
148+
return gatherResult;
149149
}
150150

151151
/**
152-
* @return {LH.FlowResult}
152+
* @returns {Promise<LH.FlowResult>}
153153
*/
154-
getFlowResult() {
155-
if (!this.steps.length) throw Error('Need at least one step before getting the flow result');
156-
const url = new URL(this.steps[0].lhr.finalUrl);
157-
const name = this.name || `User flow (${url.hostname})`;
158-
return {steps: this.steps, name};
154+
async createFlowResult() {
155+
if (!this.stepArtifacts.length) {
156+
throw new Error('Need at least one step before getting the result');
157+
}
158+
const url = new URL(this.stepArtifacts[0].gatherResult.artifacts.URL.finalUrl);
159+
const flowName = this.name || `User flow (${url.hostname})`;
160+
161+
/** @type {LH.FlowResult['steps']} */
162+
const steps = [];
163+
for (const {gatherResult, name} of this.stepArtifacts) {
164+
const result = await Runner.audit(gatherResult.artifacts, gatherResult.runnerOptions);
165+
if (!result) throw new Error(`Step "${name}" did not return a result`);
166+
steps.push({lhr: result.lhr, name});
167+
}
168+
169+
return {steps, name: flowName};
159170
}
160171

161172
/**
162-
* @return {string}
173+
* @return {Promise<string>}
163174
*/
164-
generateReport() {
165-
const flowResult = this.getFlowResult();
175+
async generateReport() {
176+
const flowResult = await this.createFlowResult();
166177
return generateFlowReportHtml(flowResult);
167178
}
168179
}

‎lighthouse-core/scripts/update-flow-fixtures.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import waitForExpect from 'wait-for-expect';
1313
import puppeteer from 'puppeteer';
1414

1515
import {LH_ROOT} from '../../root.js';
16-
import UserFlow from '../fraggle-rock/user-flow.js';
16+
import api from '../fraggle-rock/api.js';
1717

1818

1919
/** @param {puppeteer.Page} page */
@@ -54,7 +54,7 @@ async function waitForImagesToLoad(page) {
5454

5555
try {
5656
const page = await browser.newPage();
57-
const flow = new UserFlow(page);
57+
const flow = await api.startFlow(page);
5858

5959
await flow.navigate('https://www.mikescerealshack.co');
6060

@@ -70,15 +70,15 @@ async function waitForImagesToLoad(page) {
7070

7171
await flow.navigate('https://www.mikescerealshack.co/corrections');
7272

73-
const flowResult = flow.getFlowResult();
73+
const flowResult = await flow.createFlowResult();
7474

7575
fs.writeFileSync(
7676
`${LH_ROOT}/lighthouse-core/test/fixtures/fraggle-rock/reports/sample-flow-result.json`,
7777
JSON.stringify(flowResult, null, 2)
7878
);
7979

8080
if (process.argv.includes('--view')) {
81-
const htmlReport = flow.generateReport();
81+
const htmlReport = await api.generateFlowReport(flowResult);
8282
const filepath = `${LH_ROOT}/dist/sample-reports/flow-report/index.html`;
8383
fs.writeFileSync(filepath, htmlReport);
8484
open(filepath);

‎lighthouse-core/test/fixtures/fraggle-rock/reports/sample-flow-result.json

+2,338-2,556
Large diffs are not rendered by default.

‎lighthouse-core/test/fraggle-rock/gather/navigation-runner-test.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -527,9 +527,8 @@ describe('NavigationRunner', () => {
527527
it('should throw on invalid URL', async () => {
528528
const runnerActual = jest.requireActual('../../../runner.js');
529529
mockRunner.gather.mockImplementation(runnerActual.gather);
530-
mockRunner.audit.mockImplementation(runnerActual.audit);
531530

532-
const navigatePromise = runner.navigation(
531+
const navigatePromise = runner.navigationGather(
533532
'',
534533
{page: mockDriver._page.asPage()}
535534
);
@@ -545,7 +544,7 @@ describe('NavigationRunner', () => {
545544
};
546545

547546
const configContext = {settingsOverrides};
548-
await runner.navigation(
547+
await runner.navigationGather(
549548
'http://example.com',
550549
{
551550
page: mockDriver._page.asPage(),

‎lighthouse-core/test/fraggle-rock/gather/snapshot-runner-test.js

+9-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jest.mock('../../../fraggle-rock/gather/driver.js', () =>
2424
mockDriverModule(() => mockDriver.asDriver())
2525
);
2626

27-
const {snapshot} = require('../../../fraggle-rock/gather/snapshot-runner.js');
27+
const {snapshotGather} = require('../../../fraggle-rock/gather/snapshot-runner.js');
2828

2929
describe('Snapshot Runner', () => {
3030
/** @type {ReturnType<typeof createMockPage>} */
@@ -64,16 +64,16 @@ describe('Snapshot Runner', () => {
6464
});
6565

6666
it('should connect to the page and run', async () => {
67-
await snapshot({page, config});
67+
await snapshotGather({page, config});
6868
expect(mockDriver.connect).toHaveBeenCalled();
6969
expect(mockRunner.gather).toHaveBeenCalled();
70-
expect(mockRunner.audit).toHaveBeenCalled();
70+
expect(mockRunner.audit).not.toHaveBeenCalled();
7171
});
7272

7373
it('should collect base artifacts', async () => {
7474
mockDriver.url.mockResolvedValue('https://lighthouse.example.com/');
7575

76-
await snapshot({page, config});
76+
await snapshotGather({page, config});
7777
const artifacts = await mockRunner.gather.mock.calls[0][0]();
7878
expect(artifacts).toMatchObject({
7979
fetchTime: expect.any(String),
@@ -82,7 +82,7 @@ describe('Snapshot Runner', () => {
8282
});
8383

8484
it('should collect snapshot artifacts', async () => {
85-
await snapshot({page, config});
85+
await snapshotGather({page, config});
8686
const artifacts = await mockRunner.gather.mock.calls[0][0]();
8787
expect(artifacts).toMatchObject({A: 'Artifact A', B: 'Artifact B'});
8888
expect(gathererA.getArtifact).toHaveBeenCalled();
@@ -98,7 +98,7 @@ describe('Snapshot Runner', () => {
9898
};
9999

100100
const configContext = {settingsOverrides};
101-
await snapshot({page, config, configContext});
101+
await snapshotGather({page, config, configContext});
102102

103103
expect(mockRunner.gather.mock.calls[0][1]).toMatchObject({
104104
config: {
@@ -108,7 +108,7 @@ describe('Snapshot Runner', () => {
108108
});
109109

110110
it('should not invoke instrumentation methods', async () => {
111-
await snapshot({page, config});
111+
await snapshotGather({page, config});
112112
await mockRunner.gather.mock.calls[0][0]();
113113
expect(gathererA.startInstrumentation).not.toHaveBeenCalled();
114114
expect(gathererA.startSensitiveInstrumentation).not.toHaveBeenCalled();
@@ -119,7 +119,7 @@ describe('Snapshot Runner', () => {
119119
it('should skip timespan artifacts', async () => {
120120
gathererB.meta.supportedModes = ['timespan'];
121121

122-
await snapshot({page, config});
122+
await snapshotGather({page, config});
123123
const artifacts = await mockRunner.gather.mock.calls[0][0]();
124124
expect(artifacts).toMatchObject({A: 'Artifact A'});
125125
expect(artifacts).not.toHaveProperty('B');
@@ -132,7 +132,7 @@ describe('Snapshot Runner', () => {
132132
// @ts-expect-error - the default fixture was defined as one without dependencies.
133133
gathererB.meta.dependencies = {ImageElements: dependencySymbol};
134134

135-
await snapshot({page, config});
135+
await snapshotGather({page, config});
136136
const artifacts = await mockRunner.gather.mock.calls[0][0]();
137137
expect(artifacts).toMatchObject({A: 'Artifact A', B: 'Artifact B'});
138138
expect(gathererB.getArtifact.mock.calls[0][0]).toMatchObject({

‎lighthouse-core/test/fraggle-rock/gather/timespan-runner-test.js

+22-22
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jest.mock('../../../fraggle-rock/gather/driver.js', () =>
2626
mockDriverModule(() => mockDriver.asDriver())
2727
);
2828

29-
const {startTimespan} = require('../../../fraggle-rock/gather/timespan-runner.js');
29+
const {startTimespanGather} = require('../../../fraggle-rock/gather/timespan-runner.js');
3030

3131
describe('Timespan Runner', () => {
3232
/** @type {ReturnType<typeof createMockPage>} */
@@ -67,36 +67,36 @@ describe('Timespan Runner', () => {
6767
});
6868

6969
it('should connect to the page and run', async () => {
70-
const timespan = await startTimespan({page, config});
71-
await timespan.endTimespan();
70+
const timespan = await startTimespanGather({page, config});
71+
await timespan.endTimespanGather();
7272
expect(mockDriver.connect).toHaveBeenCalled();
7373
expect(mockRunner.gather).toHaveBeenCalled();
74-
expect(mockRunner.audit).toHaveBeenCalled();
74+
expect(mockRunner.audit).not.toHaveBeenCalled();
7575
});
7676

7777
it('should prepare the target', async () => {
78-
const timespan = await startTimespan({page, config});
78+
const timespan = await startTimespanGather({page, config});
7979
expect(mockSubmodules.prepareMock.prepareTargetForTimespanMode).toHaveBeenCalled();
80-
await timespan.endTimespan();
80+
await timespan.endTimespanGather();
8181
});
8282

8383
it('should invoke startInstrumentation', async () => {
84-
const timespan = await startTimespan({page, config});
84+
const timespan = await startTimespanGather({page, config});
8585
expect(gathererA.startInstrumentation).toHaveBeenCalled();
8686
expect(gathererB.startInstrumentation).toHaveBeenCalled();
8787
expect(gathererA.startSensitiveInstrumentation).toHaveBeenCalled();
8888
expect(gathererB.startSensitiveInstrumentation).toHaveBeenCalled();
89-
await timespan.endTimespan();
89+
await timespan.endTimespanGather();
9090
});
9191

9292
it('should collect base artifacts', async () => {
9393
mockDriver.url.mockResolvedValue('https://start.example.com/');
9494

95-
const timespan = await startTimespan({page, config});
95+
const timespan = await startTimespanGather({page, config});
9696

9797
mockDriver.url.mockResolvedValue('https://end.example.com/');
9898

99-
await timespan.endTimespan();
99+
await timespan.endTimespanGather();
100100
const artifacts = await mockRunner.gather.mock.calls[0][0]();
101101
expect(artifacts).toMatchObject({
102102
fetchTime: expect.any(String),
@@ -115,8 +115,8 @@ describe('Timespan Runner', () => {
115115
};
116116

117117
const configContext = {settingsOverrides};
118-
const timespan = await startTimespan({page, config, configContext});
119-
await timespan.endTimespan();
118+
const timespan = await startTimespanGather({page, config, configContext});
119+
await timespan.endTimespanGather();
120120

121121
expect(mockRunner.gather.mock.calls[0][1]).toMatchObject({
122122
config: {
@@ -126,8 +126,8 @@ describe('Timespan Runner', () => {
126126
});
127127

128128
it('should invoke stop instrumentation', async () => {
129-
const timespan = await startTimespan({page, config});
130-
await timespan.endTimespan();
129+
const timespan = await startTimespanGather({page, config});
130+
await timespan.endTimespanGather();
131131
await mockRunner.gather.mock.calls[0][0]();
132132
expect(gathererA.stopSensitiveInstrumentation).toHaveBeenCalled();
133133
expect(gathererB.stopSensitiveInstrumentation).toHaveBeenCalled();
@@ -136,8 +136,8 @@ describe('Timespan Runner', () => {
136136
});
137137

138138
it('should collect timespan artifacts', async () => {
139-
const timespan = await startTimespan({page, config});
140-
await timespan.endTimespan();
139+
const timespan = await startTimespanGather({page, config});
140+
await timespan.endTimespanGather();
141141
const artifacts = await mockRunner.gather.mock.calls[0][0]();
142142
expect(artifacts).toMatchObject({A: 'Artifact A', B: 'Artifact B'});
143143
});
@@ -146,8 +146,8 @@ describe('Timespan Runner', () => {
146146
const artifactError = new Error('BEFORE_TIMESPAN_ERROR');
147147
gathererA.startInstrumentation.mockRejectedValue(artifactError);
148148

149-
const timespan = await startTimespan({page, config});
150-
await timespan.endTimespan();
149+
const timespan = await startTimespanGather({page, config});
150+
await timespan.endTimespanGather();
151151
const artifacts = await mockRunner.gather.mock.calls[0][0]();
152152
expect(artifacts).toMatchObject({A: artifactError, B: 'Artifact B'});
153153
expect(gathererA.stopInstrumentation).not.toHaveBeenCalled();
@@ -157,8 +157,8 @@ describe('Timespan Runner', () => {
157157
it('should skip snapshot artifacts', async () => {
158158
gathererB.meta.supportedModes = ['snapshot'];
159159

160-
const timespan = await startTimespan({page, config});
161-
await timespan.endTimespan();
160+
const timespan = await startTimespanGather({page, config});
161+
await timespan.endTimespanGather();
162162
const artifacts = await mockRunner.gather.mock.calls[0][0]();
163163
expect(artifacts).toMatchObject({A: 'Artifact A'});
164164
expect(artifacts).not.toHaveProperty('B');
@@ -172,8 +172,8 @@ describe('Timespan Runner', () => {
172172
// @ts-expect-error - the default fixture was defined as one without dependencies.
173173
gathererB.meta.dependencies = {ImageElements: dependencySymbol};
174174

175-
const timespan = await startTimespan({page, config});
176-
await timespan.endTimespan();
175+
const timespan = await startTimespanGather({page, config});
176+
await timespan.endTimespanGather();
177177
const artifacts = await mockRunner.gather.mock.calls[0][0]();
178178
expect(artifacts).toMatchObject({A: 'Artifact A', B: 'Artifact B'});
179179
expect(gathererB.getArtifact.mock.calls[0][0]).toMatchObject({

‎lighthouse-core/test/fraggle-rock/user-flow-test.js

+104-30
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,64 @@
77

88
/* eslint-env jest */
99

10-
const snapshotModule = {snapshot: jest.fn()};
10+
const {createMockPage, mockRunnerModule} = require('./gather/mock-driver.js');
11+
12+
const snapshotModule = {snapshotGather: jest.fn()};
1113
jest.mock('../../fraggle-rock/gather/snapshot-runner.js', () => snapshotModule);
12-
const navigationModule = {navigation: jest.fn()};
14+
const navigationModule = {navigationGather: jest.fn()};
1315
jest.mock('../../fraggle-rock/gather/navigation-runner.js', () => navigationModule);
14-
const timespanModule = {startTimespan: jest.fn()};
16+
const timespanModule = {startTimespanGather: jest.fn()};
1517
jest.mock('../../fraggle-rock/gather/timespan-runner.js', () => timespanModule);
1618

17-
const {createMockPage} = require('./gather/mock-driver.js');
19+
const mockRunner = mockRunnerModule();
20+
1821
const UserFlow = require('../../fraggle-rock/user-flow.js');
1922

2023
describe('UserFlow', () => {
2124
let mockPage = createMockPage();
2225

2326
beforeEach(() => {
2427
mockPage = createMockPage();
25-
const lhr = {finalUrl: 'https://www.example.com'};
2628

27-
snapshotModule.snapshot.mockReset();
28-
snapshotModule.snapshot.mockResolvedValue({lhr: {...lhr, gatherMode: 'snapshot'}});
29+
mockRunner.reset();
30+
31+
snapshotModule.snapshotGather.mockReset();
32+
snapshotModule.snapshotGather.mockResolvedValue({
33+
artifacts: {
34+
URL: {finalUrl: 'https://www.example.com'},
35+
GatherContext: {gatherMode: 'snapshot'},
36+
},
37+
runnerOptions: {
38+
config: {},
39+
computedCache: new Map(),
40+
},
41+
});
2942

30-
navigationModule.navigation.mockReset();
31-
navigationModule.navigation.mockResolvedValue({lhr: {...lhr, gatherMode: 'navigation'}});
43+
navigationModule.navigationGather.mockReset();
44+
navigationModule.navigationGather.mockResolvedValue({
45+
artifacts: {
46+
URL: {finalUrl: 'https://www.example.com'},
47+
GatherContext: {gatherMode: 'navigation'},
48+
},
49+
runnerOptions: {
50+
config: {},
51+
computedCache: new Map(),
52+
},
53+
});
3254

33-
const timespanLhr = {...lhr, gatherMode: 'timespan'};
34-
const timespan = {endTimespan: jest.fn().mockResolvedValue({lhr: timespanLhr})};
35-
timespanModule.startTimespan.mockReset();
36-
timespanModule.startTimespan.mockResolvedValue(timespan);
55+
const timespanGatherResult = {
56+
artifacts: {
57+
URL: {finalUrl: 'https://www.example.com'},
58+
GatherContext: {gatherMode: 'timespan'},
59+
},
60+
runnerOptions: {
61+
config: {},
62+
computedCache: new Map(),
63+
},
64+
};
65+
const timespan = {endTimespanGather: jest.fn().mockResolvedValue(timespanGatherResult)};
66+
timespanModule.startTimespanGather.mockReset();
67+
timespanModule.startTimespanGather.mockResolvedValue(timespan);
3768
});
3869

3970
describe('.navigate()', () => {
@@ -53,11 +84,11 @@ describe('UserFlow', () => {
5384

5485
await flow.navigate('https://example.com/3');
5586

56-
expect(navigationModule.navigation).toHaveBeenCalledTimes(3);
57-
expect(flow.steps).toMatchObject([
58-
{name: 'My Step', lhr: {finalUrl: 'https://www.example.com'}},
59-
{name: 'Navigation report (www.example.com/)', lhr: {finalUrl: 'https://www.example.com'}},
60-
{name: 'Navigation report (www.example.com/)', lhr: {finalUrl: 'https://www.example.com'}},
87+
expect(navigationModule.navigationGather).toHaveBeenCalledTimes(3);
88+
expect(flow.stepArtifacts).toMatchObject([
89+
{name: 'My Step'},
90+
{name: 'Navigation report (www.example.com/)'},
91+
{name: 'Navigation report (www.example.com/)'},
6192
]);
6293
});
6394

@@ -77,8 +108,9 @@ describe('UserFlow', () => {
77108
await flow.navigate('https://example.com/4', {configContext: configContextExplicit});
78109

79110
// Check that we have the property set.
80-
expect(navigationModule.navigation).toHaveBeenCalledTimes(4);
81-
const [[, call1], [, call2], [, call3], [, call4]] = navigationModule.navigation.mock.calls;
111+
expect(navigationModule.navigationGather).toHaveBeenCalledTimes(4);
112+
const [[, call1], [, call2], [, call3], [, call4]] =
113+
navigationModule.navigationGather.mock.calls;
82114
expect(call1).not.toHaveProperty('configContext.settingsOverrides.disableStorageReset');
83115
expect(call2).toHaveProperty('configContext.settingsOverrides.disableStorageReset');
84116
expect(call3).toHaveProperty('configContext.settingsOverrides.disableStorageReset');
@@ -105,8 +137,8 @@ describe('UserFlow', () => {
105137
await flow.navigate('https://example.com/3', {configContext: configContextExplicit});
106138

107139
// Check that we have the property set.
108-
expect(navigationModule.navigation).toHaveBeenCalledTimes(3);
109-
const [[, call1], [, call2], [, call3]] = navigationModule.navigation.mock.calls;
140+
expect(navigationModule.navigationGather).toHaveBeenCalledTimes(3);
141+
const [[, call1], [, call2], [, call3]] = navigationModule.navigationGather.mock.calls;
110142
expect(call1).toHaveProperty('configContext.skipAboutBlank');
111143
expect(call2).toHaveProperty('configContext.skipAboutBlank');
112144
expect(call3).toHaveProperty('configContext.skipAboutBlank');
@@ -136,10 +168,10 @@ describe('UserFlow', () => {
136168
await flow.startTimespan();
137169
await flow.endTimespan();
138170

139-
expect(timespanModule.startTimespan).toHaveBeenCalledTimes(2);
140-
expect(flow.steps).toMatchObject([
141-
{name: 'My Timespan', lhr: {finalUrl: 'https://www.example.com'}},
142-
{name: 'Timespan report (www.example.com/)', lhr: {finalUrl: 'https://www.example.com'}},
171+
expect(timespanModule.startTimespanGather).toHaveBeenCalledTimes(2);
172+
expect(flow.stepArtifacts).toMatchObject([
173+
{name: 'My Timespan'},
174+
{name: 'Timespan report (www.example.com/)'},
143175
]);
144176
});
145177
});
@@ -164,11 +196,53 @@ describe('UserFlow', () => {
164196
await flow.snapshot({stepName: 'My Snapshot'});
165197
await flow.snapshot();
166198

167-
expect(snapshotModule.snapshot).toHaveBeenCalledTimes(2);
168-
expect(flow.steps).toMatchObject([
169-
{name: 'My Snapshot', lhr: {finalUrl: 'https://www.example.com'}},
170-
{name: 'Snapshot report (www.example.com/)', lhr: {finalUrl: 'https://www.example.com'}},
199+
expect(snapshotModule.snapshotGather).toHaveBeenCalledTimes(2);
200+
expect(flow.stepArtifacts).toMatchObject([
201+
{name: 'My Snapshot'},
202+
{name: 'Snapshot report (www.example.com/)'},
171203
]);
172204
});
173205
});
206+
207+
describe('.getFlowResult', () => {
208+
it('should throw if no flow steps have been run', async () => {
209+
const flow = new UserFlow(mockPage.asPage());
210+
const flowResultPromise = flow.createFlowResult();
211+
await expect(flowResultPromise).rejects.toThrow(/Need at least one step/);
212+
});
213+
214+
it('should get flow result', async () => {
215+
mockRunner.audit.mockImplementation(artifacts => ({
216+
lhr: {
217+
finalUrl: artifacts.URL.finalUrl,
218+
gatherMode: artifacts.GatherContext.gatherMode,
219+
},
220+
}));
221+
const flow = new UserFlow(mockPage.asPage());
222+
223+
await flow.navigate('https://www.example.com/');
224+
await flow.startTimespan({stepName: 'My Timespan'});
225+
await flow.endTimespan();
226+
await flow.snapshot({stepName: 'My Snapshot'});
227+
228+
const flowResult = await flow.createFlowResult();
229+
expect(flowResult).toMatchObject({
230+
steps: [
231+
{
232+
lhr: {finalUrl: 'https://www.example.com', gatherMode: 'navigation'},
233+
name: 'Navigation report (www.example.com/)',
234+
},
235+
{
236+
lhr: {finalUrl: 'https://www.example.com', gatherMode: 'timespan'},
237+
name: 'My Timespan',
238+
},
239+
{
240+
lhr: {finalUrl: 'https://www.example.com', gatherMode: 'snapshot'},
241+
name: 'My Snapshot',
242+
},
243+
],
244+
name: 'User flow (www.example.com)',
245+
});
246+
});
247+
});
174248
});

‎types/gatherer.d.ts

+8
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ declare module Gatherer {
6262
settings: Config.Settings;
6363
}
6464

65+
interface FRGatherResult {
66+
artifacts: Artifacts;
67+
runnerOptions: {
68+
config: Config.FRConfig;
69+
computedCache: Map<string, ArbitraryEqualityMap>
70+
}
71+
}
72+
6573
interface PassContext {
6674
gatherMode: 'navigation';
6775
/** The url of the currently loaded page. If the main document redirects, this will be updated to keep track. */

0 commit comments

Comments
 (0)
Please sign in to comment.