browsertime 26.3.1 → 27.0.0
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/CHANGELOG.md +71 -2
- package/README.md +1 -1
- package/browserscripts/timings/softNavigations.js +94 -0
- package/lib/chrome/har.js +36 -5
- package/lib/chrome/settings/chromeAndroidOptions.js +2 -1
- package/lib/chrome/webdriver/chromium.js +39 -0
- package/lib/chrome/webdriver/setupChromiumOptions.js +86 -31
- package/lib/core/engine/collector.js +340 -317
- package/lib/core/engine/command/addText.js +93 -12
- package/lib/core/engine/command/click.js +239 -139
- package/lib/core/engine/command/commandHelper.js +45 -0
- package/lib/core/engine/command/cookie.js +85 -0
- package/lib/core/engine/command/element.js +37 -2
- package/lib/core/engine/command/javaScript.js +14 -24
- package/lib/core/engine/command/measure/screenshots.js +114 -0
- package/lib/core/engine/command/measure/video.js +85 -0
- package/lib/core/engine/command/measure.js +104 -180
- package/lib/core/engine/command/mouse/clickAndHold.js +6 -3
- package/lib/core/engine/command/mouse/contextClick.js +56 -4
- package/lib/core/engine/command/mouse/doubleClick.js +49 -1
- package/lib/core/engine/command/mouse/mouseMove.js +50 -1
- package/lib/core/engine/command/mouse/singleClick.js +165 -129
- package/lib/core/engine/command/navigation.js +6 -6
- package/lib/core/engine/command/select.js +95 -3
- package/lib/core/engine/command/selectorParser.js +58 -0
- package/lib/core/engine/command/set.js +55 -1
- package/lib/core/engine/command/wait.js +41 -2
- package/lib/core/engine/commands.js +550 -42
- package/lib/core/engine/index.js +206 -150
- package/lib/core/engine/iteration.js +269 -137
- package/lib/firefox/networkManager.js +22 -30
- package/lib/firefox/webdriver/builder.js +14 -0
- package/lib/firefox/webdriver/firefox.js +1 -1
- package/lib/safari/har.js +131 -0
- package/lib/safari/native/ios-capture +0 -0
- package/lib/safari/native/ios-capture.m +340 -0
- package/lib/safari/safariInspectorClient.js +322 -0
- package/lib/safari/webdriver/safari.js +303 -13
- package/lib/safari/webkitToCdpAdapter.js +241 -0
- package/lib/support/cli.js +13 -0
- package/lib/support/engineUtils.js +16 -5
- package/lib/support/getViewPort.js +5 -0
- package/lib/support/har/index.js +41 -3
- package/lib/support/usbPower.js +2 -3
- package/lib/video/postprocessing/finetune/addTextToVideo.js +4 -0
- package/lib/video/postprocessing/finetune/getFont.js +3 -3
- package/lib/video/postprocessing/finetune/getTimingMetrics.js +7 -0
- package/lib/video/postprocessing/finetune/index.js +26 -9
- package/lib/video/screenRecording/ios/iosDeviceRecorder.js +109 -0
- package/lib/video/screenRecording/recorder.js +2 -2
- package/lib/video/screenRecording/setOrangeBackground.js +3 -0
- package/package.json +12 -12
- package/types/core/engine/command/addText.d.ts +35 -6
- package/types/core/engine/command/addText.d.ts.map +1 -1
- package/types/core/engine/command/click.d.ts +87 -16
- package/types/core/engine/command/click.d.ts.map +1 -1
- package/types/core/engine/command/commandHelper.d.ts +16 -0
- package/types/core/engine/command/commandHelper.d.ts.map +1 -0
- package/types/core/engine/command/cookie.d.ts +65 -0
- package/types/core/engine/command/cookie.d.ts.map +1 -0
- package/types/core/engine/command/element.d.ts +19 -1
- package/types/core/engine/command/element.d.ts.map +1 -1
- package/types/core/engine/command/javaScript.d.ts +4 -0
- package/types/core/engine/command/javaScript.d.ts.map +1 -1
- package/types/core/engine/command/measure/screenshots.d.ts +15 -0
- package/types/core/engine/command/measure/screenshots.d.ts.map +1 -0
- package/types/core/engine/command/measure/video.d.ts +26 -0
- package/types/core/engine/command/measure/video.d.ts.map +1 -0
- package/types/core/engine/command/measure.d.ts +59 -33
- package/types/core/engine/command/measure.d.ts.map +1 -1
- package/types/core/engine/command/mouse/clickAndHold.d.ts.map +1 -1
- package/types/core/engine/command/mouse/contextClick.d.ts +28 -4
- package/types/core/engine/command/mouse/contextClick.d.ts.map +1 -1
- package/types/core/engine/command/mouse/doubleClick.d.ts +24 -4
- package/types/core/engine/command/mouse/doubleClick.d.ts.map +1 -1
- package/types/core/engine/command/mouse/mouseMove.d.ts +30 -5
- package/types/core/engine/command/mouse/mouseMove.d.ts.map +1 -1
- package/types/core/engine/command/mouse/singleClick.d.ts +50 -13
- package/types/core/engine/command/mouse/singleClick.d.ts.map +1 -1
- package/types/core/engine/command/navigation.d.ts +3 -3
- package/types/core/engine/command/select.d.ts +45 -8
- package/types/core/engine/command/select.d.ts.map +1 -1
- package/types/core/engine/command/selectorParser.d.ts +20 -0
- package/types/core/engine/command/selectorParser.d.ts.map +1 -0
- package/types/core/engine/command/set.d.ts +34 -7
- package/types/core/engine/command/set.d.ts.map +1 -1
- package/types/core/engine/command/wait.d.ts +29 -6
- package/types/core/engine/command/wait.d.ts.map +1 -1
- package/types/core/engine/commands.d.ts +296 -28
- package/types/core/engine/commands.d.ts.map +1 -1
- package/types/support/engineUtils.d.ts.map +1 -1
- package/types/support/getViewPort.d.ts.map +1 -1
- package/types/support/usbPower.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/addTextToVideo.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/getTimingMetrics.d.ts.map +1 -1
- package/types/video/postprocessing/finetune/index.d.ts.map +1 -1
- package/types/video/screenRecording/ios/iosDeviceRecorder.d.ts +28 -0
- package/types/video/screenRecording/ios/iosDeviceRecorder.d.ts.map +1 -0
- package/types/video/screenRecording/recorder.d.ts +2 -2
- package/types/video/screenRecording/recorder.d.ts.map +1 -1
- package/types/video/screenRecording/setOrangeBackground.d.ts.map +1 -1
- package/types/video/video.d.ts +1 -1
- package/types/video/video.d.ts.map +1 -1
- package/visualmetrics/visualmetrics-portable.py +136 -136
- package/lib/video/screenRecording/ios/convertToMp4.js +0 -22
- package/lib/video/screenRecording/ios/iosRecorder.js +0 -68
|
@@ -2,9 +2,7 @@ import path from 'node:path';
|
|
|
2
2
|
import { getLogger } from '@sitespeed.io/log';
|
|
3
3
|
import merge from 'lodash.merge';
|
|
4
4
|
import { timestamp as _timestamp } from '../../../support/engineUtils.js';
|
|
5
|
-
import { Video } from '../../../video/video.js';
|
|
6
5
|
import { pathToFolder } from '../../../support/pathToFolder.js';
|
|
7
|
-
import { setOrangeBackground } from '../../../video/screenRecording/setOrangeBackground.js';
|
|
8
6
|
import {
|
|
9
7
|
filterAllowlisted,
|
|
10
8
|
filterBlocklisted
|
|
@@ -12,9 +10,8 @@ import {
|
|
|
12
10
|
import { isAndroidConfigured, Android } from '../../../android/index.js';
|
|
13
11
|
import { TCPDump } from '../../../support/tcpdump.js';
|
|
14
12
|
import { getProperty } from '../../../support/util.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
const delay = ms => new Promise(res => setTimeout(res, ms));
|
|
13
|
+
import { MeasureScreenshots } from './measure/screenshots.js';
|
|
14
|
+
import { MeasureVideo } from './measure/video.js';
|
|
18
15
|
const log = getLogger('browsertime.command.measure');
|
|
19
16
|
|
|
20
17
|
// In some cases we could have one alias mapped to multiple URLs
|
|
@@ -38,7 +35,7 @@ function getNewResult() {
|
|
|
38
35
|
* @hideconstructor
|
|
39
36
|
*/
|
|
40
37
|
export class Measure {
|
|
41
|
-
constructor(
|
|
38
|
+
constructor({
|
|
42
39
|
browser,
|
|
43
40
|
index,
|
|
44
41
|
pageCompleteCheck,
|
|
@@ -52,7 +49,7 @@ export class Measure {
|
|
|
52
49
|
context,
|
|
53
50
|
screenshotManager,
|
|
54
51
|
options
|
|
55
|
-
) {
|
|
52
|
+
}) {
|
|
56
53
|
/**
|
|
57
54
|
* @private
|
|
58
55
|
*/
|
|
@@ -77,22 +74,10 @@ export class Measure {
|
|
|
77
74
|
* @private
|
|
78
75
|
*/
|
|
79
76
|
this.options = options;
|
|
80
|
-
/**
|
|
81
|
-
* @private
|
|
82
|
-
*/
|
|
83
|
-
this.screenshotManager = screenshotManager;
|
|
84
77
|
/**
|
|
85
78
|
* @private
|
|
86
79
|
*/
|
|
87
80
|
this.storageManager = storageManager;
|
|
88
|
-
/**
|
|
89
|
-
* @private
|
|
90
|
-
*/
|
|
91
|
-
this.recordVideo = options.visualMetrics || options.video;
|
|
92
|
-
/**
|
|
93
|
-
* @private
|
|
94
|
-
*/
|
|
95
|
-
this.videos = videos;
|
|
96
81
|
/**
|
|
97
82
|
* @private
|
|
98
83
|
*/
|
|
@@ -132,64 +117,22 @@ export class Measure {
|
|
|
132
117
|
/**
|
|
133
118
|
* @private
|
|
134
119
|
*/
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
120
|
+
this.measureVideo = new MeasureVideo(
|
|
121
|
+
storageManager,
|
|
122
|
+
browser,
|
|
123
|
+
videos,
|
|
124
|
+
options
|
|
139
125
|
);
|
|
140
126
|
/**
|
|
141
127
|
* @private
|
|
142
128
|
*/
|
|
143
|
-
this.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
this.DESKTOP_DELAY_TIME = getProperty(
|
|
148
|
-
options,
|
|
149
|
-
'orangeDesktopDelayTime',
|
|
150
|
-
800
|
|
129
|
+
this.screenshots = new MeasureScreenshots(
|
|
130
|
+
browser,
|
|
131
|
+
screenshotManager,
|
|
132
|
+
options
|
|
151
133
|
);
|
|
152
134
|
}
|
|
153
135
|
|
|
154
|
-
/**
|
|
155
|
-
* Have a consistent way of starting the video
|
|
156
|
-
* @private
|
|
157
|
-
*/
|
|
158
|
-
async _startVideo(numberOfMeasuredPages, index) {
|
|
159
|
-
this.video = new Video(this.storageManager, this.options, this.browser);
|
|
160
|
-
|
|
161
|
-
if (this.options.firefox && this.options.firefox.windowRecorder) {
|
|
162
|
-
// The Firefox window recorder will only record subsequent
|
|
163
|
-
// changes after recording has begun. So the orange frame needs
|
|
164
|
-
// to come after we start recording.
|
|
165
|
-
await this.video.record(numberOfMeasuredPages, index);
|
|
166
|
-
await setOrangeBackground(this.browser.getDriver(), this.options);
|
|
167
|
-
} else {
|
|
168
|
-
await setOrangeBackground(this.browser.getDriver(), this.options);
|
|
169
|
-
await this.video.record(numberOfMeasuredPages, index);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Give ffmpeg/video on phone time to settle
|
|
173
|
-
if (isAndroidConfigured(this.options)) {
|
|
174
|
-
return delay(this.ANDROID_DELAY_TIME);
|
|
175
|
-
} else if (this.options.safari && this.options.safari.useSimulator) {
|
|
176
|
-
return delay(this.IOS_DELAY_TIME);
|
|
177
|
-
} else {
|
|
178
|
-
return delay(this.DESKTOP_DELAY_TIME);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Have a consistent way of starting stopping the video
|
|
184
|
-
* @private
|
|
185
|
-
*/
|
|
186
|
-
async _stopVideo(url) {
|
|
187
|
-
if (this.recordVideo && !this.options.videoParams.debug) {
|
|
188
|
-
await this.video.stop(url);
|
|
189
|
-
this.videos.push(this.video);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
136
|
/**
|
|
194
137
|
* Have a consistent way of adding an error
|
|
195
138
|
* @private
|
|
@@ -322,8 +265,16 @@ export class Measure {
|
|
|
322
265
|
.executeScript('window.performance.clearResourceTimings();');
|
|
323
266
|
}
|
|
324
267
|
|
|
325
|
-
|
|
326
|
-
|
|
268
|
+
// Record current soft navigation count so browser scripts
|
|
269
|
+
// only collect entries from this measurement
|
|
270
|
+
await this.browser
|
|
271
|
+
.getDriver()
|
|
272
|
+
.executeScript(
|
|
273
|
+
'window.__bt_softNavCount = (performance.getEntriesByType("soft-navigation") || []).length;'
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (this.measureVideo.shouldRecord()) {
|
|
277
|
+
await this.measureVideo.start(this.numberOfMeasuredPages, this.index);
|
|
327
278
|
// if we do not have the URL, we should remove the orange and
|
|
328
279
|
// start meausuring because the user themself will navigate to
|
|
329
280
|
// the URL that will be tested.
|
|
@@ -363,7 +314,7 @@ export class Measure {
|
|
|
363
314
|
this.engineDelegate.failing(url);
|
|
364
315
|
// If we got an error we still should stop the video to make sure we can use it to
|
|
365
316
|
// try to understand what went wrong
|
|
366
|
-
await this.
|
|
317
|
+
await this.measureVideo.stop(url);
|
|
367
318
|
throw error;
|
|
368
319
|
}
|
|
369
320
|
} else {
|
|
@@ -387,7 +338,7 @@ export class Measure {
|
|
|
387
338
|
// Remove the last result
|
|
388
339
|
this.result.pop();
|
|
389
340
|
|
|
390
|
-
await this.
|
|
341
|
+
await this.measureVideo.stop();
|
|
391
342
|
|
|
392
343
|
if (this.options.tcpdump) {
|
|
393
344
|
await this.tcpDump.stop();
|
|
@@ -395,6 +346,31 @@ export class Measure {
|
|
|
395
346
|
|
|
396
347
|
return;
|
|
397
348
|
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
setClick(click) {
|
|
354
|
+
this._click = click;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Starts a measurement, clicks on an element matching the given CSS selector,
|
|
359
|
+
* waits for the page complete check, and stops the measurement. This is
|
|
360
|
+
* a shortcut for the common pattern of measure.start/click/measure.stop.
|
|
361
|
+
*
|
|
362
|
+
* @async
|
|
363
|
+
* @param {string} alias - The friendly name for this measurement.
|
|
364
|
+
* @param {string} selector - The CSS selector of the element to click.
|
|
365
|
+
* @returns {Promise} A promise that resolves with the collected metrics data.
|
|
366
|
+
* @throws {Error} Throws an error if the element is not found or the measurement fails.
|
|
367
|
+
*/
|
|
368
|
+
async clickAndMeasure(alias, selector) {
|
|
369
|
+
await this.start(alias);
|
|
370
|
+
await this._click.bySelectorAndWait(selector);
|
|
371
|
+
return this.stop();
|
|
372
|
+
}
|
|
373
|
+
|
|
398
374
|
/**
|
|
399
375
|
* Stops the measurement process, collects metrics, and handles any post-measurement tasks.
|
|
400
376
|
* It finalizes the URL being tested, manages any URL-specific metadata, stops any ongoing video recordings,
|
|
@@ -442,7 +418,7 @@ export class Measure {
|
|
|
442
418
|
await this.storageManager.createSubDataDir(
|
|
443
419
|
path.join(pathToFolder(url, this.options))
|
|
444
420
|
);
|
|
445
|
-
await this.
|
|
421
|
+
await this.measureVideo.stop(url);
|
|
446
422
|
|
|
447
423
|
const alias = this.options.urlMetaData
|
|
448
424
|
? this.options.urlMetaData[url]
|
|
@@ -473,7 +449,7 @@ export class Measure {
|
|
|
473
449
|
await this.tcpDump.stop();
|
|
474
450
|
}
|
|
475
451
|
|
|
476
|
-
return this.
|
|
452
|
+
return this._collect(url);
|
|
477
453
|
}
|
|
478
454
|
|
|
479
455
|
/**
|
|
@@ -515,125 +491,39 @@ export class Measure {
|
|
|
515
491
|
}
|
|
516
492
|
|
|
517
493
|
/**
|
|
518
|
-
*
|
|
519
494
|
* @private
|
|
520
495
|
*/
|
|
521
|
-
|
|
522
|
-
// This stops collecting trace logs in Chrome etc
|
|
523
|
-
// await this.engineDelegate.beforeCollect();
|
|
524
|
-
|
|
525
|
-
log.verbose('Collecting metrics');
|
|
526
|
-
|
|
527
|
-
// If we have an alias for the URL, use that URL
|
|
496
|
+
_resolveAlias(url) {
|
|
528
497
|
if (
|
|
529
498
|
this.result[this.numberOfMeasuredPages] &&
|
|
530
499
|
this.result[this.numberOfMeasuredPages].alias
|
|
531
500
|
) {
|
|
532
501
|
const alias = this.result[this.numberOfMeasuredPages].alias;
|
|
533
502
|
if (aliasAndUrl[alias]) {
|
|
534
|
-
|
|
535
|
-
} else {
|
|
536
|
-
aliasAndUrl[alias] = url;
|
|
503
|
+
return aliasAndUrl[alias];
|
|
537
504
|
}
|
|
505
|
+
aliasAndUrl[alias] = url;
|
|
538
506
|
}
|
|
507
|
+
return url;
|
|
508
|
+
}
|
|
539
509
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
this.video.getRecordingStartTime()
|
|
547
|
-
) {
|
|
510
|
+
/**
|
|
511
|
+
* @private
|
|
512
|
+
*/
|
|
513
|
+
_attachVideoMetadata() {
|
|
514
|
+
const metadata = this.measureVideo.getRecordingMetadata();
|
|
515
|
+
if (metadata) {
|
|
548
516
|
this.result[this.numberOfMeasuredPages].recordingStartTime =
|
|
549
|
-
|
|
517
|
+
metadata.recordingStartTime;
|
|
550
518
|
this.result[this.numberOfMeasuredPages].timeToFirstFrame =
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (this.options.screenshot) {
|
|
555
|
-
log.info('Take after page complete check screenshot');
|
|
556
|
-
try {
|
|
557
|
-
const screenshot = await this.browser.takeScreenshot(url);
|
|
558
|
-
await this.screenshotManager.save(
|
|
559
|
-
'afterPageCompleteCheck',
|
|
560
|
-
screenshot,
|
|
561
|
-
url,
|
|
562
|
-
this.index
|
|
563
|
-
);
|
|
564
|
-
} catch {
|
|
565
|
-
// not getting screenshots shouldn't result in a failed test.
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const supportLS = await this.browser.runScript(
|
|
570
|
-
`
|
|
571
|
-
const supported = PerformanceObserver.supportedEntryTypes;
|
|
572
|
-
if (!supported || supported.indexOf('layout-shift') === -1) {
|
|
573
|
-
return false;
|
|
574
|
-
} else return true;
|
|
575
|
-
`,
|
|
576
|
-
'SUPPORT_LS'
|
|
577
|
-
);
|
|
578
|
-
|
|
579
|
-
if (this.options.screenshotLS && supportLS) {
|
|
580
|
-
log.info('Take cumulative layout shift screenshot');
|
|
581
|
-
await this.browser.runScript(highlightLS, 'HIGHLIGHT_LS', {
|
|
582
|
-
color: this.options.screenshotLSColor || 'red',
|
|
583
|
-
limit: this.options.screenshotLSLimit || 0.01
|
|
584
|
-
});
|
|
585
|
-
const screenshot = await this.browser.takeScreenshot(url);
|
|
586
|
-
await this.screenshotManager.save(
|
|
587
|
-
'layoutShift',
|
|
588
|
-
screenshot,
|
|
589
|
-
url,
|
|
590
|
-
this.index
|
|
591
|
-
);
|
|
592
|
-
const lsScriptClean = `
|
|
593
|
-
const c = document.getElementById("browsertime-ls");
|
|
594
|
-
if (c) c.remove();
|
|
595
|
-
`;
|
|
596
|
-
await this.browser.runScript(lsScriptClean, 'CLEAN_LS');
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
const supportLCP = await this.browser.runScript(
|
|
600
|
-
`
|
|
601
|
-
const supported = PerformanceObserver.supportedEntryTypes;
|
|
602
|
-
if (!supported || supported.indexOf('largest-contentful-paint') === -1) {
|
|
603
|
-
return false;
|
|
604
|
-
} else return true;
|
|
605
|
-
`,
|
|
606
|
-
'SUPPORT_LCP'
|
|
607
|
-
);
|
|
608
|
-
|
|
609
|
-
if (this.options.screenshotLCP && supportLCP) {
|
|
610
|
-
log.info('Take largest contentful paint screenshot');
|
|
611
|
-
try {
|
|
612
|
-
const lcpScriptClean = `
|
|
613
|
-
const c = document.getElementById("browsertime-lcp");
|
|
614
|
-
if (c) c.remove();
|
|
615
|
-
`;
|
|
616
|
-
const message = await this.browser.runScript(
|
|
617
|
-
highlightLargestContentfulPaint,
|
|
618
|
-
'HIGHLIGHT_LCP',
|
|
619
|
-
this.options.screenshotLCPColor || 'red'
|
|
620
|
-
);
|
|
621
|
-
if (message != '') {
|
|
622
|
-
log.info(message);
|
|
623
|
-
}
|
|
624
|
-
const screenshot = await this.browser.takeScreenshot(url);
|
|
625
|
-
await this.screenshotManager.save(
|
|
626
|
-
'largestContentfulPaint',
|
|
627
|
-
screenshot,
|
|
628
|
-
url,
|
|
629
|
-
this.index
|
|
630
|
-
);
|
|
631
|
-
await this.browser.runScript(lcpScriptClean, 'CLEAN_LCP');
|
|
632
|
-
} catch (error) {
|
|
633
|
-
log.error('Could not get LCP screenshot', error);
|
|
634
|
-
}
|
|
519
|
+
metadata.timeToFirstFrame;
|
|
635
520
|
}
|
|
521
|
+
}
|
|
636
522
|
|
|
523
|
+
/**
|
|
524
|
+
* @private
|
|
525
|
+
*/
|
|
526
|
+
async _collectBrowserScripts() {
|
|
637
527
|
const syncScripts = this.scriptsByCategory
|
|
638
528
|
? await this.browser.runScripts(this.scriptsByCategory)
|
|
639
529
|
: {},
|
|
@@ -646,7 +536,12 @@ export class Measure {
|
|
|
646
536
|
syncScripts,
|
|
647
537
|
asyncScripts
|
|
648
538
|
);
|
|
539
|
+
}
|
|
649
540
|
|
|
541
|
+
/**
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
_filterUserTimings() {
|
|
650
545
|
// Some sites has crazy amount of user timings:
|
|
651
546
|
// strip them if you want
|
|
652
547
|
if (this.options.userTimingAllowList) {
|
|
@@ -664,7 +559,12 @@ export class Measure {
|
|
|
664
559
|
this.options.userTimingBlockList
|
|
665
560
|
);
|
|
666
561
|
}
|
|
562
|
+
}
|
|
667
563
|
|
|
564
|
+
/**
|
|
565
|
+
* @private
|
|
566
|
+
*/
|
|
567
|
+
async _collectAndroidProcessTime() {
|
|
668
568
|
if (isAndroidConfigured(this.options) && this.options.processStartTime) {
|
|
669
569
|
const packageName =
|
|
670
570
|
this.options.browser === 'firefox'
|
|
@@ -685,10 +585,34 @@ export class Measure {
|
|
|
685
585
|
].browserScripts.browser.processStartTime = processStartTime;
|
|
686
586
|
}
|
|
687
587
|
}
|
|
588
|
+
}
|
|
688
589
|
|
|
590
|
+
/**
|
|
591
|
+
* @private
|
|
592
|
+
*/
|
|
593
|
+
async _runPostURLScripts() {
|
|
689
594
|
for (const postURLScript of this.postURLScripts) {
|
|
690
595
|
await postURLScript(this.context);
|
|
691
596
|
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* @private
|
|
601
|
+
*/
|
|
602
|
+
async _collect(url) {
|
|
603
|
+
log.verbose('Collecting metrics');
|
|
604
|
+
|
|
605
|
+
url = this._resolveAlias(url);
|
|
606
|
+
this.result[this.numberOfMeasuredPages].url = url;
|
|
607
|
+
|
|
608
|
+
this._attachVideoMetadata();
|
|
609
|
+
await this.screenshots.afterPageCompleteCheck(url, this.index);
|
|
610
|
+
await this.screenshots.layoutShift(url, this.index);
|
|
611
|
+
await this.screenshots.largestContentfulPaint(url, this.index);
|
|
612
|
+
await this._collectBrowserScripts();
|
|
613
|
+
this._filterUserTimings();
|
|
614
|
+
await this._collectAndroidProcessTime();
|
|
615
|
+
await this._runPostURLScripts();
|
|
692
616
|
|
|
693
617
|
if (this.options.tcpdump) {
|
|
694
618
|
await this.tcpDump.mv(url, this.index);
|
|
@@ -117,7 +117,8 @@ export class ClickAndHold {
|
|
|
117
117
|
async releaseAtXpath(xpath) {
|
|
118
118
|
try {
|
|
119
119
|
const element = await this.driver.findElement(By.xpath(xpath));
|
|
120
|
-
|
|
120
|
+
await this.actions.move({ origin: element }).release().perform();
|
|
121
|
+
return this.actions.clear();
|
|
121
122
|
} catch (error) {
|
|
122
123
|
log.error('Could not release on xpath %s', xpath);
|
|
123
124
|
log.verbose(error);
|
|
@@ -136,7 +137,8 @@ export class ClickAndHold {
|
|
|
136
137
|
async releaseAtSelector(selector) {
|
|
137
138
|
try {
|
|
138
139
|
const element = await this.driver.findElement(By.css(selector));
|
|
139
|
-
|
|
140
|
+
await this.actions.move({ origin: element }).release().perform();
|
|
141
|
+
return this.actions.clear();
|
|
140
142
|
} catch (error) {
|
|
141
143
|
log.error('Could not release on selector %s', selector);
|
|
142
144
|
log.verbose(error);
|
|
@@ -155,10 +157,11 @@ export class ClickAndHold {
|
|
|
155
157
|
*/
|
|
156
158
|
async releaseAtPosition(xPos, yPos) {
|
|
157
159
|
try {
|
|
158
|
-
|
|
160
|
+
await this.actions
|
|
159
161
|
.move({ x: xPos, y: yPos, origin: Origin.VIEWPORT })
|
|
160
162
|
.release()
|
|
161
163
|
.perform();
|
|
164
|
+
return this.actions.clear();
|
|
162
165
|
} catch (error) {
|
|
163
166
|
log.error('Could not release at position (%d,%d)', xPos, yPos);
|
|
164
167
|
log.verbose(error);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
2
2
|
import { By } from 'selenium-webdriver';
|
|
3
|
+
import webdriver from 'selenium-webdriver';
|
|
4
|
+
import { executeCommand } from '../commandHelper.js';
|
|
5
|
+
import { parseSelector } from '../selectorParser.js';
|
|
3
6
|
const log = getLogger('browsertime.command.mouse');
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -9,7 +12,11 @@ const log = getLogger('browsertime.command.mouse');
|
|
|
9
12
|
* @hideconstructor
|
|
10
13
|
*/
|
|
11
14
|
export class ContextClick {
|
|
12
|
-
constructor(browser) {
|
|
15
|
+
constructor(browser, options) {
|
|
16
|
+
/**
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
this.browser = browser;
|
|
13
20
|
/**
|
|
14
21
|
* @private
|
|
15
22
|
*/
|
|
@@ -18,9 +25,49 @@ export class ContextClick {
|
|
|
18
25
|
* @private
|
|
19
26
|
*/
|
|
20
27
|
this.actions = this.driver.actions({ async: true });
|
|
28
|
+
/**
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
this.options = options;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
async _waitForElement(driver, locator) {
|
|
38
|
+
const timeout = this.options?.timeouts?.elementWait ?? 0;
|
|
39
|
+
if (timeout > 0) {
|
|
40
|
+
await driver.wait(webdriver.until.elementLocated(locator), timeout);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Performs a context click (right-click) on an element using a unified selector string.
|
|
46
|
+
*
|
|
47
|
+
* @async
|
|
48
|
+
* @param {string} selector - The selector string. CSS by default, or use a prefix.
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
51
|
+
*/
|
|
52
|
+
async run(selector) {
|
|
53
|
+
const { locator, description } = parseSelector(selector);
|
|
54
|
+
return executeCommand(
|
|
55
|
+
log,
|
|
56
|
+
'Could not context click on element %s',
|
|
57
|
+
description,
|
|
58
|
+
async () => {
|
|
59
|
+
const driver = this.browser.getDriver();
|
|
60
|
+
await this._waitForElement(driver, locator);
|
|
61
|
+
const element = await driver.findElement(locator);
|
|
62
|
+
await this.actions.contextClick(element).perform();
|
|
63
|
+
await this.actions.clear();
|
|
64
|
+
},
|
|
65
|
+
this.browser
|
|
66
|
+
);
|
|
21
67
|
}
|
|
22
68
|
|
|
23
69
|
/**
|
|
70
|
+
* @private
|
|
24
71
|
* Performs a context click (right-click) on an element that matches a given XPath selector.
|
|
25
72
|
*
|
|
26
73
|
* @async
|
|
@@ -31,7 +78,8 @@ export class ContextClick {
|
|
|
31
78
|
async byXpath(xpath) {
|
|
32
79
|
try {
|
|
33
80
|
const element = await this.driver.findElement(By.xpath(xpath));
|
|
34
|
-
|
|
81
|
+
await this.actions.contextClick(element).perform();
|
|
82
|
+
return this.actions.clear();
|
|
35
83
|
} catch (error) {
|
|
36
84
|
log.error('Could not context click on element with xpath %s', xpath);
|
|
37
85
|
log.verbose(error);
|
|
@@ -40,6 +88,7 @@ export class ContextClick {
|
|
|
40
88
|
}
|
|
41
89
|
|
|
42
90
|
/**
|
|
91
|
+
* @private
|
|
43
92
|
* Performs a context click (right-click) on an element that matches a given CSS selector.
|
|
44
93
|
*
|
|
45
94
|
* @async
|
|
@@ -50,7 +99,8 @@ export class ContextClick {
|
|
|
50
99
|
async bySelector(selector) {
|
|
51
100
|
try {
|
|
52
101
|
const element = await this.driver.findElement(By.css(selector));
|
|
53
|
-
|
|
102
|
+
await this.actions.contextClick(element).perform();
|
|
103
|
+
return this.actions.clear();
|
|
54
104
|
} catch (error) {
|
|
55
105
|
log.error('Could not context click on element with css %s', selector);
|
|
56
106
|
log.verbose(error);
|
|
@@ -61,6 +111,7 @@ export class ContextClick {
|
|
|
61
111
|
}
|
|
62
112
|
|
|
63
113
|
/**
|
|
114
|
+
* @private
|
|
64
115
|
* Performs a context click (right-click) at the current cursor position.
|
|
65
116
|
*
|
|
66
117
|
* @async
|
|
@@ -69,7 +120,8 @@ export class ContextClick {
|
|
|
69
120
|
*/
|
|
70
121
|
async atCursor() {
|
|
71
122
|
try {
|
|
72
|
-
|
|
123
|
+
await this.actions.contextClick().perform();
|
|
124
|
+
return this.actions.clear();
|
|
73
125
|
} catch (error) {
|
|
74
126
|
log.error('Could not perform context click');
|
|
75
127
|
log.verbose(error);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getLogger } from '@sitespeed.io/log';
|
|
2
2
|
import { By } from 'selenium-webdriver';
|
|
3
|
+
import webdriver from 'selenium-webdriver';
|
|
4
|
+
import { executeCommand } from '../commandHelper.js';
|
|
5
|
+
import { parseSelector } from '../selectorParser.js';
|
|
3
6
|
const log = getLogger('browsertime.command.mouse');
|
|
4
7
|
/**
|
|
5
8
|
* Provides functionality to perform a double-click action on elements in a web page.
|
|
@@ -8,7 +11,7 @@ const log = getLogger('browsertime.command.mouse');
|
|
|
8
11
|
* @hideconstructor
|
|
9
12
|
*/
|
|
10
13
|
export class DoubleClick {
|
|
11
|
-
constructor(browser, pageCompleteCheck) {
|
|
14
|
+
constructor(browser, pageCompleteCheck, options) {
|
|
12
15
|
/**
|
|
13
16
|
* @private
|
|
14
17
|
*/
|
|
@@ -21,9 +24,49 @@ export class DoubleClick {
|
|
|
21
24
|
* @private
|
|
22
25
|
*/
|
|
23
26
|
this.pageCompleteCheck = pageCompleteCheck;
|
|
27
|
+
/**
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
this.options = options;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
async _waitForElement(driver, locator) {
|
|
37
|
+
const timeout = this.options?.timeouts?.elementWait ?? 0;
|
|
38
|
+
if (timeout > 0) {
|
|
39
|
+
await driver.wait(webdriver.until.elementLocated(locator), timeout);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Performs a double click on an element using a unified selector string.
|
|
45
|
+
*
|
|
46
|
+
* @async
|
|
47
|
+
* @param {string} selector - The selector string. CSS by default, or use a prefix.
|
|
48
|
+
* @returns {Promise<void>}
|
|
49
|
+
* @throws {Error} Throws an error if the element is not found.
|
|
50
|
+
*/
|
|
51
|
+
async run(selector) {
|
|
52
|
+
const { locator, description } = parseSelector(selector);
|
|
53
|
+
return executeCommand(
|
|
54
|
+
log,
|
|
55
|
+
'Could not double click on element %s',
|
|
56
|
+
description,
|
|
57
|
+
async () => {
|
|
58
|
+
const driver = this.browser.getDriver();
|
|
59
|
+
await this._waitForElement(driver, locator);
|
|
60
|
+
const element = await driver.findElement(locator);
|
|
61
|
+
await this.actions.doubleClick(element).perform();
|
|
62
|
+
await this.actions.clear();
|
|
63
|
+
},
|
|
64
|
+
this.browser
|
|
65
|
+
);
|
|
24
66
|
}
|
|
25
67
|
|
|
26
68
|
/**
|
|
69
|
+
* @private
|
|
27
70
|
* Performs a mouse double-click on an element matching a given XPath selector.
|
|
28
71
|
*
|
|
29
72
|
* @async
|
|
@@ -38,6 +81,7 @@ export class DoubleClick {
|
|
|
38
81
|
.getDriver()
|
|
39
82
|
.findElement(By.xpath(xpath));
|
|
40
83
|
await this.actions.doubleClick(element).perform();
|
|
84
|
+
await this.actions.clear();
|
|
41
85
|
if (options && 'wait' in options && options.wait === true) {
|
|
42
86
|
return this.browser.extraWait(this.pageCompleteCheck);
|
|
43
87
|
}
|
|
@@ -49,6 +93,7 @@ export class DoubleClick {
|
|
|
49
93
|
}
|
|
50
94
|
|
|
51
95
|
/**
|
|
96
|
+
* @private
|
|
52
97
|
* Performs a mouse double-click on an element matching a given CSS selector.
|
|
53
98
|
*
|
|
54
99
|
* @async
|
|
@@ -63,6 +108,7 @@ export class DoubleClick {
|
|
|
63
108
|
.getDriver()
|
|
64
109
|
.findElement(By.css(selector));
|
|
65
110
|
await this.actions.doubleClick(element).perform();
|
|
111
|
+
await this.actions.clear();
|
|
66
112
|
if (options && 'wait' in options && options.wait === true) {
|
|
67
113
|
return this.browser.extraWait(this.pageCompleteCheck);
|
|
68
114
|
}
|
|
@@ -76,6 +122,7 @@ export class DoubleClick {
|
|
|
76
122
|
}
|
|
77
123
|
|
|
78
124
|
/**
|
|
125
|
+
* @private
|
|
79
126
|
* Performs a mouse double-click at the current cursor position.
|
|
80
127
|
*
|
|
81
128
|
* @async
|
|
@@ -86,6 +133,7 @@ export class DoubleClick {
|
|
|
86
133
|
async atCursor(options) {
|
|
87
134
|
try {
|
|
88
135
|
await this.actions.doubleClick().perform();
|
|
136
|
+
await this.actions.clear();
|
|
89
137
|
if (options && 'wait' in options && options.wait === true) {
|
|
90
138
|
return this.browser.extraWait(this.pageCompleteCheck);
|
|
91
139
|
}
|