@wdio/lighthouse-service 9.0.0-alpha.321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-MIT +20 -0
- package/README.md +258 -0
- package/build/auditor.d.ts +54 -0
- package/build/auditor.d.ts.map +1 -0
- package/build/auditor.js +147 -0
- package/build/commands.d.ts +86 -0
- package/build/commands.d.ts.map +1 -0
- package/build/commands.js +212 -0
- package/build/constants.d.ts +95 -0
- package/build/constants.d.ts.map +1 -0
- package/build/constants.js +150 -0
- package/build/gatherer/devtools.d.ts +19 -0
- package/build/gatherer/devtools.d.ts.map +1 -0
- package/build/gatherer/devtools.js +12 -0
- package/build/gatherer/pwa.d.ts +28 -0
- package/build/gatherer/pwa.d.ts.map +1 -0
- package/build/gatherer/pwa.js +66 -0
- package/build/gatherer/trace.d.ts +61 -0
- package/build/gatherer/trace.d.ts.map +1 -0
- package/build/gatherer/trace.js +233 -0
- package/build/handler/network.d.ts +45 -0
- package/build/handler/network.d.ts.map +1 -0
- package/build/handler/network.js +133 -0
- package/build/index.d.ts +50 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +126 -0
- package/build/lighthouse/cri.d.ts +23 -0
- package/build/lighthouse/cri.d.ts.map +1 -0
- package/build/lighthouse/cri.js +29 -0
- package/build/scripts/collectMetaElements.d.ts +8 -0
- package/build/scripts/collectMetaElements.d.ts.map +1 -0
- package/build/scripts/collectMetaElements.js +37 -0
- package/build/scripts/registerPerformanceObserverInPage.d.ts +12 -0
- package/build/scripts/registerPerformanceObserverInPage.d.ts.map +1 -0
- package/build/scripts/registerPerformanceObserverInPage.js +23 -0
- package/build/types.d.ts +168 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +1 -0
- package/build/utils.d.ts +23 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +64 -0
- package/package.json +66 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import logger from '@wdio/logger';
|
|
2
|
+
import NetworkHandler from './handler/network.js';
|
|
3
|
+
import { CLICK_TRANSITION, DEFAULT_THROTTLE_STATE, DEFAULT_TRACING_CATEGORIES, NETWORK_STATES } from './constants.js';
|
|
4
|
+
import { sumByKey } from './utils.js';
|
|
5
|
+
import DevtoolsGatherer from './gatherer/devtools.js';
|
|
6
|
+
import Auditor from './auditor.js';
|
|
7
|
+
import PWAGatherer from './gatherer/pwa.js';
|
|
8
|
+
import TraceGatherer from './gatherer/trace.js';
|
|
9
|
+
const log = logger('@wdio/lighthouse-service:CommandHandler');
|
|
10
|
+
const TRACE_COMMANDS = ['click', 'navigateTo', 'url'];
|
|
11
|
+
function isCDPSessionOnMessageObject(data) {
|
|
12
|
+
return (data !== null &&
|
|
13
|
+
typeof data === 'object' &&
|
|
14
|
+
Object.prototype.hasOwnProperty.call(data, 'params') &&
|
|
15
|
+
Object.prototype.hasOwnProperty.call(data, 'method'));
|
|
16
|
+
}
|
|
17
|
+
export default class CommandHandler {
|
|
18
|
+
_session;
|
|
19
|
+
_page;
|
|
20
|
+
_driver;
|
|
21
|
+
_options;
|
|
22
|
+
_browser;
|
|
23
|
+
_isTracing = false;
|
|
24
|
+
_networkHandler;
|
|
25
|
+
_traceEvents;
|
|
26
|
+
_shouldRunPerformanceAudits = false;
|
|
27
|
+
_cacheEnabled;
|
|
28
|
+
_cpuThrottling;
|
|
29
|
+
_networkThrottling;
|
|
30
|
+
_formFactor;
|
|
31
|
+
_traceGatherer;
|
|
32
|
+
_devtoolsGatherer;
|
|
33
|
+
_pwaGatherer;
|
|
34
|
+
constructor(_session, _page, _driver, _options, _browser) {
|
|
35
|
+
this._session = _session;
|
|
36
|
+
this._page = _page;
|
|
37
|
+
this._driver = _driver;
|
|
38
|
+
this._options = _options;
|
|
39
|
+
this._browser = _browser;
|
|
40
|
+
this._networkHandler = new NetworkHandler(_session);
|
|
41
|
+
this._traceGatherer = new TraceGatherer(_session, _page, _driver);
|
|
42
|
+
this._pwaGatherer = new PWAGatherer(_session, _page, _driver);
|
|
43
|
+
_session.on('Page.loadEventFired', this._traceGatherer.onLoadEventFired.bind(this._traceGatherer));
|
|
44
|
+
_session.on('Page.frameNavigated', this._traceGatherer.onFrameNavigated.bind(this._traceGatherer));
|
|
45
|
+
_page.on('requestfailed', this._traceGatherer.onFrameLoadFail.bind(this._traceGatherer));
|
|
46
|
+
this._pwaGatherer = new PWAGatherer(_session, _page, _driver);
|
|
47
|
+
/**
|
|
48
|
+
* register browser commands
|
|
49
|
+
*/
|
|
50
|
+
const commands = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(fnName => fnName !== 'constructor' && !fnName.startsWith('_'));
|
|
51
|
+
commands.forEach(fnName => _browser.addCommand(fnName, this[fnName].bind(this)));
|
|
52
|
+
this._devtoolsGatherer = new DevtoolsGatherer();
|
|
53
|
+
_session.on('*', this._propagateWSEvents.bind(this));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Start tracing the browser. You can optionally pass in custom tracing categories and the
|
|
57
|
+
* sampling frequency.
|
|
58
|
+
*/
|
|
59
|
+
startTracing({ categories = DEFAULT_TRACING_CATEGORIES, path, screenshots = true } = {}) {
|
|
60
|
+
if (this._isTracing) {
|
|
61
|
+
throw new Error('browser is already being traced');
|
|
62
|
+
}
|
|
63
|
+
this._isTracing = true;
|
|
64
|
+
this._traceEvents = undefined;
|
|
65
|
+
return this._page.tracing.start({ categories, path, screenshots });
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Stop tracing the browser.
|
|
69
|
+
*/
|
|
70
|
+
async endTracing() {
|
|
71
|
+
if (!this._isTracing) {
|
|
72
|
+
throw new Error('No tracing was initiated, call `browser.startTracing()` first');
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const traceBuffer = await this._page.tracing.stop();
|
|
76
|
+
if (!traceBuffer) {
|
|
77
|
+
throw new Error('No tracebuffer captured');
|
|
78
|
+
}
|
|
79
|
+
this._traceEvents = JSON.parse(traceBuffer.toString('utf8'));
|
|
80
|
+
this._isTracing = false;
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
throw new Error(`Couldn't parse trace events: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
return this._traceEvents;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the tracelogs that was captured within the tracing period.
|
|
89
|
+
* You can use this command to store the trace logs on the file system to analyse the trace
|
|
90
|
+
* via Chrome DevTools interface.
|
|
91
|
+
*/
|
|
92
|
+
getTraceLogs() {
|
|
93
|
+
return this._traceEvents;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Returns page weight information of the last page load.
|
|
97
|
+
*/
|
|
98
|
+
getPageWeight() {
|
|
99
|
+
const requestTypes = Object.values(this._networkHandler.requestTypes).filter(Boolean);
|
|
100
|
+
const pageWeight = sumByKey(requestTypes, 'size');
|
|
101
|
+
const transferred = sumByKey(requestTypes, 'encoded');
|
|
102
|
+
const requestCount = sumByKey(requestTypes, 'count');
|
|
103
|
+
return { pageWeight, transferred, requestCount, details: this._networkHandler.requestTypes };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* set flag to run performance audits for page transitions
|
|
107
|
+
*/
|
|
108
|
+
enablePerformanceAudits({ networkThrottling, cpuThrottling, cacheEnabled, formFactor } = DEFAULT_THROTTLE_STATE) {
|
|
109
|
+
if (!NETWORK_STATES[networkThrottling]) {
|
|
110
|
+
throw new Error(`Network throttling profile "${networkThrottling}" is unknown, choose between ${Object.keys(NETWORK_STATES).join(', ')}`);
|
|
111
|
+
}
|
|
112
|
+
if (typeof cpuThrottling !== 'number') {
|
|
113
|
+
throw new Error(`CPU throttling rate needs to be typeof number but was "${typeof cpuThrottling}"`);
|
|
114
|
+
}
|
|
115
|
+
this._networkThrottling = networkThrottling;
|
|
116
|
+
this._cpuThrottling = cpuThrottling;
|
|
117
|
+
this._cacheEnabled = Boolean(cacheEnabled);
|
|
118
|
+
this._formFactor = formFactor;
|
|
119
|
+
this._shouldRunPerformanceAudits = true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* custom command to disable performance audits
|
|
123
|
+
*/
|
|
124
|
+
disablePerformanceAudits() {
|
|
125
|
+
this._shouldRunPerformanceAudits = false;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* helper method to set throttling profile
|
|
129
|
+
*/
|
|
130
|
+
async setThrottlingProfile(networkThrottling = DEFAULT_THROTTLE_STATE.networkThrottling, cpuThrottling = DEFAULT_THROTTLE_STATE.cpuThrottling, cacheEnabled = DEFAULT_THROTTLE_STATE.cacheEnabled) {
|
|
131
|
+
if (!this._page || !this._session) {
|
|
132
|
+
throw new Error('No page or session has been captured yet');
|
|
133
|
+
}
|
|
134
|
+
await this._page.setCacheEnabled(Boolean(cacheEnabled));
|
|
135
|
+
await this._session.send('Emulation.setCPUThrottlingRate', { rate: cpuThrottling });
|
|
136
|
+
await this._session.send('Network.emulateNetworkConditions', NETWORK_STATES[networkThrottling]);
|
|
137
|
+
}
|
|
138
|
+
async checkPWA(auditsToBeRun) {
|
|
139
|
+
const auditor = new Auditor();
|
|
140
|
+
const artifacts = await this._pwaGatherer.gatherData();
|
|
141
|
+
return auditor._auditPWA(artifacts, auditsToBeRun);
|
|
142
|
+
}
|
|
143
|
+
_propagateWSEvents(data) {
|
|
144
|
+
if (!isCDPSessionOnMessageObject(data)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this._devtoolsGatherer?.onMessage(data);
|
|
148
|
+
const method = data.method || 'event';
|
|
149
|
+
try {
|
|
150
|
+
// can fail due to "Cannot convert a Symbol value to a string"
|
|
151
|
+
log.debug(`cdp event: ${method} with params ${JSON.stringify(data.params)}`);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// ignore
|
|
155
|
+
}
|
|
156
|
+
if (this._browser) {
|
|
157
|
+
this._browser.emit(method, data.params);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async _initCommand() {
|
|
161
|
+
/**
|
|
162
|
+
* enable domains for client
|
|
163
|
+
*/
|
|
164
|
+
await Promise.all(['Page', 'Network', 'Runtime'].map((domain) => Promise.all([
|
|
165
|
+
this._session?.send(`${domain}.enable`)
|
|
166
|
+
])));
|
|
167
|
+
}
|
|
168
|
+
_beforeCmd(commandName, params) {
|
|
169
|
+
const isCommandNavigation = ['url', 'navigateTo'].some(cmdName => cmdName === commandName);
|
|
170
|
+
if (!this._shouldRunPerformanceAudits || !this._traceGatherer || this._traceGatherer.isTracing || !TRACE_COMMANDS.includes(commandName)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* set browser profile
|
|
175
|
+
*/
|
|
176
|
+
this.setThrottlingProfile(this._networkThrottling, this._cpuThrottling, this._cacheEnabled);
|
|
177
|
+
const url = isCommandNavigation
|
|
178
|
+
? params[0]
|
|
179
|
+
: CLICK_TRANSITION;
|
|
180
|
+
return this._traceGatherer.startTracing(url);
|
|
181
|
+
}
|
|
182
|
+
_afterCmd(commandName) {
|
|
183
|
+
if (!this._traceGatherer || !this._traceGatherer.isTracing || !TRACE_COMMANDS.includes(commandName)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* update custom commands once tracing finishes
|
|
188
|
+
*/
|
|
189
|
+
this._traceGatherer.once('tracingComplete', (traceEvents) => {
|
|
190
|
+
const auditor = new Auditor(traceEvents, this._devtoolsGatherer?.getLogs(), this._formFactor);
|
|
191
|
+
auditor.updateCommands(this._browser);
|
|
192
|
+
});
|
|
193
|
+
this._traceGatherer.once('tracingError', (err) => {
|
|
194
|
+
const auditor = new Auditor();
|
|
195
|
+
auditor.updateCommands(this._browser, /* istanbul ignore next */ () => {
|
|
196
|
+
throw new Error(`Couldn't capture performance due to: ${err.message}`);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
log.info(`Wait until tracing for command ${commandName} finishes`);
|
|
201
|
+
/**
|
|
202
|
+
* wait until tracing stops
|
|
203
|
+
*/
|
|
204
|
+
this._traceGatherer?.once('tracingFinished', async () => {
|
|
205
|
+
log.info('Disable throttling');
|
|
206
|
+
await this.setThrottlingProfile('online', 0, true);
|
|
207
|
+
log.info('continuing with next WebDriver command');
|
|
208
|
+
resolve();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* performance tracing categories
|
|
3
|
+
*/
|
|
4
|
+
export declare const DEFAULT_TRACING_CATEGORIES: string[];
|
|
5
|
+
/**
|
|
6
|
+
* ignored urls in request logger
|
|
7
|
+
*/
|
|
8
|
+
export declare const IGNORED_URLS: readonly ["data:,", "about:", "chrome-extension://"];
|
|
9
|
+
export declare const FRAME_LOAD_START_TIMEOUT = 2000;
|
|
10
|
+
export declare const TRACING_TIMEOUT = 15000;
|
|
11
|
+
export declare const MAX_TRACE_WAIT_TIME = 45000;
|
|
12
|
+
export declare const DEFAULT_NETWORK_THROTTLING_STATE: "online";
|
|
13
|
+
export declare const DEFAULT_FORM_FACTOR: "desktop";
|
|
14
|
+
export declare const UNSUPPORTED_ERROR_MESSAGE: string;
|
|
15
|
+
export declare const NETWORK_STATES: {
|
|
16
|
+
offline: {
|
|
17
|
+
offline: boolean;
|
|
18
|
+
latency: number;
|
|
19
|
+
downloadThroughput: number;
|
|
20
|
+
uploadThroughput: number;
|
|
21
|
+
};
|
|
22
|
+
GPRS: {
|
|
23
|
+
offline: boolean;
|
|
24
|
+
downloadThroughput: number;
|
|
25
|
+
uploadThroughput: number;
|
|
26
|
+
latency: number;
|
|
27
|
+
};
|
|
28
|
+
'Regular 2G': {
|
|
29
|
+
offline: boolean;
|
|
30
|
+
downloadThroughput: number;
|
|
31
|
+
uploadThroughput: number;
|
|
32
|
+
latency: number;
|
|
33
|
+
};
|
|
34
|
+
'Good 2G': {
|
|
35
|
+
offline: boolean;
|
|
36
|
+
downloadThroughput: number;
|
|
37
|
+
uploadThroughput: number;
|
|
38
|
+
latency: number;
|
|
39
|
+
};
|
|
40
|
+
'Regular 3G': {
|
|
41
|
+
offline: boolean;
|
|
42
|
+
latency: any;
|
|
43
|
+
downloadThroughput: number;
|
|
44
|
+
uploadThroughput: number;
|
|
45
|
+
};
|
|
46
|
+
'Good 3G': {
|
|
47
|
+
offline: boolean;
|
|
48
|
+
latency: any;
|
|
49
|
+
downloadThroughput: number;
|
|
50
|
+
uploadThroughput: number;
|
|
51
|
+
};
|
|
52
|
+
'Regular 4G': {
|
|
53
|
+
offline: boolean;
|
|
54
|
+
downloadThroughput: number;
|
|
55
|
+
uploadThroughput: number;
|
|
56
|
+
latency: number;
|
|
57
|
+
};
|
|
58
|
+
DSL: {
|
|
59
|
+
offline: boolean;
|
|
60
|
+
downloadThroughput: number;
|
|
61
|
+
uploadThroughput: number;
|
|
62
|
+
latency: number;
|
|
63
|
+
};
|
|
64
|
+
Wifi: {
|
|
65
|
+
offline: boolean;
|
|
66
|
+
downloadThroughput: number;
|
|
67
|
+
uploadThroughput: number;
|
|
68
|
+
latency: number;
|
|
69
|
+
};
|
|
70
|
+
online: {
|
|
71
|
+
offline: boolean;
|
|
72
|
+
latency: number;
|
|
73
|
+
downloadThroughput: number;
|
|
74
|
+
uploadThroughput: number;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
export declare const CLICK_TRANSITION = "click transition";
|
|
78
|
+
export declare const DEFAULT_THROTTLE_STATE: {
|
|
79
|
+
readonly networkThrottling: "online" | "offline" | "GPRS" | "Regular 2G" | "Good 2G" | "Regular 3G" | "Good 3G" | "Regular 4G" | "DSL" | "Wifi";
|
|
80
|
+
readonly cpuThrottling: 0;
|
|
81
|
+
readonly cacheEnabled: false;
|
|
82
|
+
readonly formFactor: "desktop";
|
|
83
|
+
};
|
|
84
|
+
export declare const NETWORK_RECORDER_EVENTS: readonly ["Network.requestWillBeSent", "Network.requestServedFromCache", "Network.responseReceived", "Network.dataReceived", "Network.loadingFinished", "Network.loadingFailed", "Network.resourceChangedPriority"];
|
|
85
|
+
export declare const PWA_AUDITS: {
|
|
86
|
+
readonly isInstallable: any;
|
|
87
|
+
readonly serviceWorker: any;
|
|
88
|
+
readonly splashScreen: any;
|
|
89
|
+
readonly themedOmnibox: any;
|
|
90
|
+
readonly contentWith: any;
|
|
91
|
+
readonly viewport: any;
|
|
92
|
+
readonly appleTouchIcon: any;
|
|
93
|
+
readonly maskableIcon: any;
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,eAAO,MAAM,0BAA0B,UAyCtC,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,sDAIf,CAAA;AAEV,eAAO,MAAM,wBAAwB,OAAO,CAAA;AAC5C,eAAO,MAAM,eAAe,QAAQ,CAAA;AACpC,eAAO,MAAM,mBAAmB,QAAQ,CAAA;AACxC,eAAO,MAAM,gCAAgC,UAAoB,CAAA;AACjE,eAAO,MAAM,mBAAmB,WAAqB,CAAA;AACrD,eAAO,MAAM,yBAAyB,QAIrC,CAAA;AAED,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+D1B,CAAA;AAED,eAAO,MAAM,gBAAgB,qBAAqB,CAAA;AAClD,eAAO,MAAM,sBAAsB;;;;;CAKzB,CAAA;AAEV,eAAO,MAAM,uBAAuB,qNAQ1B,CAAA;AAEV,eAAO,MAAM,UAAU;;;;;;;;;CASb,CAAA"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import InstallableManifest from 'lighthouse/lighthouse-core/audits/installable-manifest.js';
|
|
2
|
+
import ServiceWorker from 'lighthouse/lighthouse-core/audits/service-worker.js';
|
|
3
|
+
import SplashScreen from 'lighthouse/lighthouse-core/audits/splash-screen.js';
|
|
4
|
+
import ThemedOmnibox from 'lighthouse/lighthouse-core/audits/themed-omnibox.js';
|
|
5
|
+
import ContentWidth from 'lighthouse/lighthouse-core/audits/content-width.js';
|
|
6
|
+
import Viewport from 'lighthouse/lighthouse-core/audits/viewport.js';
|
|
7
|
+
import AppleTouchIcon from 'lighthouse/lighthouse-core/audits/apple-touch-icon.js';
|
|
8
|
+
import MaskableIcon from 'lighthouse/lighthouse-core/audits/maskable-icon.js';
|
|
9
|
+
import { throttling } from 'lighthouse/lighthouse-core/config/constants.js';
|
|
10
|
+
/**
|
|
11
|
+
* performance tracing categories
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_TRACING_CATEGORIES = [
|
|
14
|
+
// Exclude default categories. We'll be selective to minimize trace size
|
|
15
|
+
'-*',
|
|
16
|
+
// Used instead of 'toplevel' in Chrome 71+
|
|
17
|
+
'disabled-by-default-lighthouse',
|
|
18
|
+
// Used for Cumulative Layout Shift metric
|
|
19
|
+
'loading',
|
|
20
|
+
// All compile/execute events are captured by parent events in devtools.timeline..
|
|
21
|
+
// But the v8 category provides some nice context for only <0.5% of the trace size
|
|
22
|
+
'v8',
|
|
23
|
+
// Same situation here. This category is there for RunMicrotasks only, but with other teams
|
|
24
|
+
// accidentally excluding microtasks, we don't want to assume a parent event will always exist
|
|
25
|
+
'v8.execute',
|
|
26
|
+
// For extracting UserTiming marks/measures
|
|
27
|
+
'blink.user_timing',
|
|
28
|
+
// Not mandatory but not used much
|
|
29
|
+
'blink.console',
|
|
30
|
+
// Most of the events we need are from these two categories
|
|
31
|
+
'devtools.timeline',
|
|
32
|
+
'disabled-by-default-devtools.timeline',
|
|
33
|
+
// Up to 450 (https://goo.gl/rBfhn4) JPGs added to the trace
|
|
34
|
+
'disabled-by-default-devtools.screenshot',
|
|
35
|
+
// This doesn't add its own events, but adds a `stackTrace` property to devtools.timeline events
|
|
36
|
+
'disabled-by-default-devtools.timeline.stack',
|
|
37
|
+
// Additional categories used by devtools. Not used by Lighthouse, but included to facilitate
|
|
38
|
+
// loading traces from Lighthouse into the Performance panel.
|
|
39
|
+
'disabled-by-default-devtools.timeline.frame',
|
|
40
|
+
'latencyInfo',
|
|
41
|
+
// CPU sampling profiler data only enabled for debugging purposes
|
|
42
|
+
// 'disabled-by-default-v8.cpu_profiler',
|
|
43
|
+
// 'disabled-by-default-v8.cpu_profiler.hires',
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* ignored urls in request logger
|
|
47
|
+
*/
|
|
48
|
+
export const IGNORED_URLS = [
|
|
49
|
+
'data:,', // empty pages
|
|
50
|
+
'about:', // new tabs
|
|
51
|
+
'chrome-extension://' // all chrome extensions
|
|
52
|
+
];
|
|
53
|
+
export const FRAME_LOAD_START_TIMEOUT = 2000;
|
|
54
|
+
export const TRACING_TIMEOUT = 15000;
|
|
55
|
+
export const MAX_TRACE_WAIT_TIME = 45000;
|
|
56
|
+
export const DEFAULT_NETWORK_THROTTLING_STATE = 'online';
|
|
57
|
+
export const DEFAULT_FORM_FACTOR = 'desktop';
|
|
58
|
+
export const UNSUPPORTED_ERROR_MESSAGE = ('Can\'t connect to Chrome DevTools! The @wdio/lighthouse-service currently only supports Chrome and Chromium!\n\n' +
|
|
59
|
+
'Given that cloud vendors don\'t expose access to the Chrome DevTools Protocol this service also usually only works when ' +
|
|
60
|
+
'running tests locally or through a Selenium Grid (https://www.selenium.dev/documentation/grid/) v4 or higher.');
|
|
61
|
+
export const NETWORK_STATES = {
|
|
62
|
+
offline: {
|
|
63
|
+
offline: true,
|
|
64
|
+
latency: 0,
|
|
65
|
+
downloadThroughput: 0,
|
|
66
|
+
uploadThroughput: 0
|
|
67
|
+
},
|
|
68
|
+
GPRS: {
|
|
69
|
+
offline: false,
|
|
70
|
+
downloadThroughput: 50 * 1024 / 8,
|
|
71
|
+
uploadThroughput: 20 * 1024 / 8,
|
|
72
|
+
latency: 500
|
|
73
|
+
},
|
|
74
|
+
'Regular 2G': {
|
|
75
|
+
offline: false,
|
|
76
|
+
downloadThroughput: 250 * 1024 / 8,
|
|
77
|
+
uploadThroughput: 50 * 1024 / 8,
|
|
78
|
+
latency: 300
|
|
79
|
+
},
|
|
80
|
+
'Good 2G': {
|
|
81
|
+
offline: false,
|
|
82
|
+
downloadThroughput: 450 * 1024 / 8,
|
|
83
|
+
uploadThroughput: 150 * 1024 / 8,
|
|
84
|
+
latency: 150
|
|
85
|
+
},
|
|
86
|
+
'Regular 3G': {
|
|
87
|
+
offline: false,
|
|
88
|
+
latency: throttling.mobileRegular3G.requestLatencyMs,
|
|
89
|
+
// DevTools expects throughput in bytes per second rather than kbps
|
|
90
|
+
downloadThroughput: Math.floor(throttling.mobileRegular3G.downloadThroughputKbps * 1024 / 8),
|
|
91
|
+
uploadThroughput: Math.floor(throttling.mobileRegular3G.uploadThroughputKbps * 1024 / 8)
|
|
92
|
+
},
|
|
93
|
+
'Good 3G': {
|
|
94
|
+
offline: false,
|
|
95
|
+
latency: throttling.mobileSlow4G.requestLatencyMs,
|
|
96
|
+
// DevTools expects throughput in bytes per second rather than kbps
|
|
97
|
+
downloadThroughput: Math.floor(throttling.mobileSlow4G.downloadThroughputKbps * 1024 / 8),
|
|
98
|
+
uploadThroughput: Math.floor(throttling.mobileSlow4G.uploadThroughputKbps * 1024 / 8)
|
|
99
|
+
},
|
|
100
|
+
'Regular 4G': {
|
|
101
|
+
offline: false,
|
|
102
|
+
downloadThroughput: 4 * 1024 * 1024 / 8,
|
|
103
|
+
uploadThroughput: 3 * 1024 * 1024 / 8,
|
|
104
|
+
latency: 20
|
|
105
|
+
},
|
|
106
|
+
'DSL': {
|
|
107
|
+
offline: false,
|
|
108
|
+
downloadThroughput: 2 * 1024 * 1024 / 8,
|
|
109
|
+
uploadThroughput: 1 * 1024 * 1024 / 8,
|
|
110
|
+
latency: 5
|
|
111
|
+
},
|
|
112
|
+
'Wifi': {
|
|
113
|
+
offline: false,
|
|
114
|
+
downloadThroughput: 30 * 1024 * 1024 / 8,
|
|
115
|
+
uploadThroughput: 15 * 1024 * 1024 / 8,
|
|
116
|
+
latency: 2
|
|
117
|
+
},
|
|
118
|
+
online: {
|
|
119
|
+
offline: false,
|
|
120
|
+
latency: 0,
|
|
121
|
+
downloadThroughput: -1,
|
|
122
|
+
uploadThroughput: -1
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
export const CLICK_TRANSITION = 'click transition';
|
|
126
|
+
export const DEFAULT_THROTTLE_STATE = {
|
|
127
|
+
networkThrottling: DEFAULT_NETWORK_THROTTLING_STATE,
|
|
128
|
+
cpuThrottling: 0,
|
|
129
|
+
cacheEnabled: false,
|
|
130
|
+
formFactor: DEFAULT_FORM_FACTOR
|
|
131
|
+
};
|
|
132
|
+
export const NETWORK_RECORDER_EVENTS = [
|
|
133
|
+
'Network.requestWillBeSent',
|
|
134
|
+
'Network.requestServedFromCache',
|
|
135
|
+
'Network.responseReceived',
|
|
136
|
+
'Network.dataReceived',
|
|
137
|
+
'Network.loadingFinished',
|
|
138
|
+
'Network.loadingFailed',
|
|
139
|
+
'Network.resourceChangedPriority'
|
|
140
|
+
];
|
|
141
|
+
export const PWA_AUDITS = {
|
|
142
|
+
isInstallable: InstallableManifest,
|
|
143
|
+
serviceWorker: ServiceWorker,
|
|
144
|
+
splashScreen: SplashScreen,
|
|
145
|
+
themedOmnibox: ThemedOmnibox,
|
|
146
|
+
contentWith: ContentWidth,
|
|
147
|
+
viewport: Viewport,
|
|
148
|
+
appleTouchIcon: AppleTouchIcon,
|
|
149
|
+
maskableIcon: MaskableIcon
|
|
150
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface CDPSessionOnMessageObject {
|
|
2
|
+
id?: number;
|
|
3
|
+
method: string;
|
|
4
|
+
params: Record<string, unknown>;
|
|
5
|
+
error: {
|
|
6
|
+
message: string;
|
|
7
|
+
data: any;
|
|
8
|
+
};
|
|
9
|
+
result?: any;
|
|
10
|
+
}
|
|
11
|
+
export default class DevtoolsGatherer {
|
|
12
|
+
private _logs;
|
|
13
|
+
onMessage(msgObj: CDPSessionOnMessageObject): void;
|
|
14
|
+
/**
|
|
15
|
+
* retrieve logs and clean cache
|
|
16
|
+
*/
|
|
17
|
+
getLogs(): CDPSessionOnMessageObject[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=devtools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../../src/gatherer/devtools.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,yBAAyB;IACtC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE;QACH,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,GAAG,CAAC;KACb,CAAC;IACF,MAAM,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACjC,OAAO,CAAC,KAAK,CAAkC;IAE/C,SAAS,CAAE,MAAM,EAAE,yBAAyB;IAI5C;;OAEG;IACH,OAAO;CAGV"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CDPSession } from 'puppeteer-core/lib/esm/puppeteer/api/CDPSession.js';
|
|
2
|
+
import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js';
|
|
3
|
+
import type { GathererDriver } from '../types.js';
|
|
4
|
+
export default class PWAGatherer {
|
|
5
|
+
private _session;
|
|
6
|
+
private _page;
|
|
7
|
+
private _driver;
|
|
8
|
+
private _frGatherer;
|
|
9
|
+
private _networkRecorder;
|
|
10
|
+
private _networkRecords;
|
|
11
|
+
constructor(_session: CDPSession, _page: Page, _driver: GathererDriver);
|
|
12
|
+
gatherData(): Promise<{
|
|
13
|
+
URL: {
|
|
14
|
+
requestedUrl: string;
|
|
15
|
+
finalUrl: string;
|
|
16
|
+
};
|
|
17
|
+
WebAppManifest: any;
|
|
18
|
+
InstallabilityErrors: any;
|
|
19
|
+
MetaElements: any;
|
|
20
|
+
ViewportDimensions: any;
|
|
21
|
+
ServiceWorker: {
|
|
22
|
+
versions: any;
|
|
23
|
+
registrations: any;
|
|
24
|
+
};
|
|
25
|
+
LinkElements: any;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=pwa.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pwa.d.ts","sourceRoot":"","sources":["../../src/gatherer/pwa.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oDAAoD,CAAA;AACpF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,8CAA8C,CAAA;AAIxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,CAAC,OAAO,OAAO,WAAW;IAMxB,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,OAAO;IAPnB,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,eAAe,CAAY;gBAGvB,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,IAAI,EACX,OAAO,EAAE,cAAc;IAsB7B,UAAU;;;;;;;;;;;;;;;CA4BnB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import FRGatherer from 'lighthouse/lighthouse-core/fraggle-rock/gather/session.js';
|
|
2
|
+
import pageFunctions from 'lighthouse/lighthouse-core/lib/page-functions.js';
|
|
3
|
+
import NetworkRecorder from 'lighthouse/lighthouse-core/lib/network-recorder.js';
|
|
4
|
+
import InstallabilityErrors from 'lighthouse/lighthouse-core/gather/gatherers/installability-errors.js';
|
|
5
|
+
import WebAppManifest from 'lighthouse/lighthouse-core/gather/gatherers/web-app-manifest.js';
|
|
6
|
+
import LinkElements from 'lighthouse/lighthouse-core/gather/gatherers/link-elements.js';
|
|
7
|
+
import ViewportDimensions from 'lighthouse/lighthouse-core/gather/gatherers/viewport-dimensions.js';
|
|
8
|
+
import serviceWorkers from 'lighthouse/lighthouse-core/gather/driver/service-workers.js';
|
|
9
|
+
import collectMetaElements from '../scripts/collectMetaElements.js';
|
|
10
|
+
import { NETWORK_RECORDER_EVENTS } from '../constants.js';
|
|
11
|
+
export default class PWAGatherer {
|
|
12
|
+
_session;
|
|
13
|
+
_page;
|
|
14
|
+
_driver;
|
|
15
|
+
_frGatherer;
|
|
16
|
+
_networkRecorder;
|
|
17
|
+
_networkRecords = [];
|
|
18
|
+
constructor(_session, _page, _driver) {
|
|
19
|
+
this._session = _session;
|
|
20
|
+
this._page = _page;
|
|
21
|
+
this._driver = _driver;
|
|
22
|
+
this._frGatherer = new FRGatherer(this._session);
|
|
23
|
+
/**
|
|
24
|
+
* setup network recorder
|
|
25
|
+
*/
|
|
26
|
+
this._networkRecorder = new NetworkRecorder();
|
|
27
|
+
NETWORK_RECORDER_EVENTS.forEach((method) => {
|
|
28
|
+
this._session.on(method, (params) => this._networkRecorder.dispatch({ method, params }));
|
|
29
|
+
});
|
|
30
|
+
/**
|
|
31
|
+
* clean up network records after every page load
|
|
32
|
+
*/
|
|
33
|
+
this._page.on('load', () => {
|
|
34
|
+
this._networkRecords = this._networkRecorder.getRawRecords();
|
|
35
|
+
delete this._networkRecorder;
|
|
36
|
+
this._networkRecorder = new NetworkRecorder();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async gatherData() {
|
|
40
|
+
const pageUrl = await this._page?.url();
|
|
41
|
+
const passContext = {
|
|
42
|
+
url: pageUrl,
|
|
43
|
+
driver: this._driver
|
|
44
|
+
};
|
|
45
|
+
const loadData = {
|
|
46
|
+
networkRecords: this._networkRecords
|
|
47
|
+
};
|
|
48
|
+
const linkElements = new LinkElements();
|
|
49
|
+
const viewportDimensions = new ViewportDimensions();
|
|
50
|
+
const { registrations } = await serviceWorkers.getServiceWorkerRegistrations(this._frGatherer);
|
|
51
|
+
const { versions } = await serviceWorkers.getServiceWorkerVersions(this._frGatherer);
|
|
52
|
+
return {
|
|
53
|
+
URL: { requestedUrl: pageUrl, finalUrl: pageUrl },
|
|
54
|
+
WebAppManifest: await WebAppManifest.getWebAppManifest(this._frGatherer, pageUrl),
|
|
55
|
+
InstallabilityErrors: await InstallabilityErrors.getInstallabilityErrors(this._frGatherer),
|
|
56
|
+
MetaElements: await this._driver.evaluate(collectMetaElements, {
|
|
57
|
+
args: [],
|
|
58
|
+
useIsolation: true,
|
|
59
|
+
deps: [pageFunctions.getElementsInDocument],
|
|
60
|
+
}),
|
|
61
|
+
ViewportDimensions: await viewportDimensions.afterPass(passContext),
|
|
62
|
+
ServiceWorker: { versions, registrations },
|
|
63
|
+
LinkElements: await linkElements.afterPass(passContext, loadData)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import type { Protocol } from 'devtools-protocol';
|
|
4
|
+
import type { TraceEvent } from '@tracerbench/trace-event';
|
|
5
|
+
import type { HTTPRequest } from 'puppeteer-core/lib/esm/puppeteer/api/HTTPRequest.js';
|
|
6
|
+
import type { CDPSession } from 'puppeteer-core/lib/esm/puppeteer/api/CDPSession.js';
|
|
7
|
+
import type { Page } from 'puppeteer-core/lib/esm/puppeteer/api/Page.js';
|
|
8
|
+
import type { GathererDriver } from '../types.js';
|
|
9
|
+
export interface Trace {
|
|
10
|
+
traceEvents: TraceEvent[];
|
|
11
|
+
frameId?: string;
|
|
12
|
+
loaderId?: string;
|
|
13
|
+
pageUrl?: string;
|
|
14
|
+
traceStart?: number;
|
|
15
|
+
traceEnd?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface WaitPromise {
|
|
18
|
+
promise: Promise<any>;
|
|
19
|
+
cancel: Function;
|
|
20
|
+
}
|
|
21
|
+
export default class TraceGatherer extends EventEmitter {
|
|
22
|
+
private _session;
|
|
23
|
+
private _page;
|
|
24
|
+
private _driver;
|
|
25
|
+
private _failingFrameLoadIds;
|
|
26
|
+
private _pageLoadDetected;
|
|
27
|
+
private _networkListeners;
|
|
28
|
+
private _frameId?;
|
|
29
|
+
private _loaderId?;
|
|
30
|
+
private _pageUrl?;
|
|
31
|
+
private _networkStatusMonitor;
|
|
32
|
+
private _networkMonitor;
|
|
33
|
+
private _protocolSession;
|
|
34
|
+
private _trace?;
|
|
35
|
+
private _traceStart?;
|
|
36
|
+
private _clickTraceTimeout?;
|
|
37
|
+
private _waitConditionPromises;
|
|
38
|
+
constructor(_session: CDPSession, _page: Page, _driver: GathererDriver);
|
|
39
|
+
startTracing(url: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* store frame id of frames that are being traced
|
|
42
|
+
*/
|
|
43
|
+
onFrameNavigated(msgObj: Protocol.Page.FrameNavigatedEvent): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* once the page load event has fired, we can grab some performance
|
|
46
|
+
* metrics and timing
|
|
47
|
+
*/
|
|
48
|
+
onLoadEventFired(): Promise<void>;
|
|
49
|
+
onFrameLoadFail(request: HTTPRequest): void;
|
|
50
|
+
get isTracing(): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* once tracing has finished capture trace logs into memory
|
|
53
|
+
*/
|
|
54
|
+
completeTracing(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* clear tracing states and emit tracingFinished
|
|
57
|
+
*/
|
|
58
|
+
finishTracing(): void;
|
|
59
|
+
waitForMaxTimeout(maxWaitForLoadedMs?: number): Promise<() => Promise<void>>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=trace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/gatherer/trace.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAO1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,0BAA0B,CAAA;AAC1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qDAAqD,CAAA;AACtF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oDAAoD,CAAA;AACpF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,8CAA8C,CAAA;AASxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,MAAM,WAAW,KAAK;IAClB,WAAW,EAAE,UAAU,EAAE,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB;AAiBD,MAAM,WAAW,WAAW;IACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;IACrB,MAAM,EAAE,QAAQ,CAAA;CACnB;AAED,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY;IAgBtC,OAAO,CAAC,QAAQ;IAAc,OAAO,CAAC,KAAK;IAAQ,OAAO,CAAC,OAAO;IAf/E,OAAO,CAAC,oBAAoB,CAAe;IAC3C,OAAO,CAAC,iBAAiB,CAAQ;IACjC,OAAO,CAAC,iBAAiB,CAA4C;IAErE,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,qBAAqB,CAAwB;IACrD,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,gBAAgB,CAAyB;IACjD,OAAO,CAAC,MAAM,CAAC,CAAO;IACtB,OAAO,CAAC,WAAW,CAAC,CAAQ;IAC5B,OAAO,CAAC,kBAAkB,CAAC,CAAgB;IAC3C,OAAO,CAAC,sBAAsB,CAAsB;gBAE/B,QAAQ,EAAE,UAAU,EAAU,KAAK,EAAE,IAAI,EAAU,OAAO,EAAE,cAAc;IAWzF,YAAY,CAAE,GAAG,EAAE,MAAM;IAwC/B;;OAEG;IACG,gBAAgB,CAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,mBAAmB;IAsDjE;;;OAGG;IACG,gBAAgB;IA+BtB,eAAe,CAAE,OAAO,EAAE,WAAW;IAQrC,IAAI,SAAS,YAEZ;IAED;;OAEG;IACG,eAAe;IAiDrB;;OAEG;IACH,aAAa;IAoBb,iBAAiB,CAAE,kBAAkB,SAAsB;CAQ9D"}
|