browsertime 14.4.0 → 14.7.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/bin/browsertime.js +1 -1
  3. package/lib/android/index.js +1 -1
  4. package/lib/chrome/har.js +4 -4
  5. package/lib/chrome/longTaskMetrics.js +1 -1
  6. package/lib/chrome/speedline.js +2 -2
  7. package/lib/chrome/webdriver/builder.js +1 -1
  8. package/lib/chrome/webdriver/setupChromiumOptions.js +7 -2
  9. package/lib/connectivity/trafficShapeParser.js +2 -2
  10. package/lib/connectivity/tsProxy.js +3 -3
  11. package/lib/core/engine/command/measure.js +16 -10
  12. package/lib/core/engine/command/mouse/clickAndHold.js +146 -0
  13. package/lib/core/engine/command/mouse/contextClick.js +61 -0
  14. package/lib/core/engine/command/mouse/doubleClick.js +76 -0
  15. package/lib/core/engine/command/mouse/index.js +9 -0
  16. package/lib/core/engine/command/mouse/mouseMove.js +81 -0
  17. package/lib/core/engine/command/mouse/singleClick.js +79 -0
  18. package/lib/core/engine/command/switch.js +2 -8
  19. package/lib/core/engine/iteration.js +5 -6
  20. package/lib/core/engine/run.js +2 -2
  21. package/lib/core/seleniumRunner.js +23 -22
  22. package/lib/core/webdriver/index.js +1 -1
  23. package/lib/edge/webdriver/builder.js +1 -1
  24. package/lib/extensionserver/setup.js +1 -1
  25. package/lib/firefox/geckoProfiler.js +2 -3
  26. package/lib/firefox/getHAR.js +1 -1
  27. package/lib/firefox/webdriver/builder.js +12 -7
  28. package/lib/firefox/webdriver/firefox.js +6 -7
  29. package/lib/safari/webdriver/builder.js +1 -1
  30. package/lib/support/browserScript.js +1 -1
  31. package/lib/support/cli.js +12 -0
  32. package/lib/support/engineUtils.js +1 -1
  33. package/lib/support/getViewPort.js +1 -1
  34. package/lib/support/har/index.js +13 -14
  35. package/lib/support/pathToFolder.js +4 -10
  36. package/lib/support/preURL.js +1 -1
  37. package/lib/support/setResourceTimingBufferSize.js +1 -1
  38. package/lib/support/statistics.js +1 -1
  39. package/lib/support/stop.js +1 -1
  40. package/lib/support/storageManager.js +5 -10
  41. package/lib/video/postprocessing/finetune/addTextToVideo.js +1 -1
  42. package/lib/video/postprocessing/finetune/convertFps.js +1 -1
  43. package/lib/video/postprocessing/finetune/getFont.js +1 -1
  44. package/lib/video/postprocessing/finetune/getTimingMetrics.js +5 -4
  45. package/lib/video/postprocessing/finetune/index.js +1 -1
  46. package/lib/video/postprocessing/finetune/removeOrange.js +1 -1
  47. package/lib/video/postprocessing/visualmetrics/extraMetrics.js +1 -1
  48. package/lib/video/postprocessing/visualmetrics/getVideoMetrics.js +1 -1
  49. package/lib/video/screenRecording/desktop/ffmpegRecorder.js +3 -3
  50. package/lib/video/screenRecording/desktop/osx/getSPDisplaysDataType.js +1 -1
  51. package/lib/video/screenRecording/desktop/osx/getScreen.js +1 -1
  52. package/lib/video/screenRecording/firefox/firefoxWindowRecorder.js +2 -2
  53. package/lib/video/screenRecording/setOrangeBackground.js +1 -1
  54. package/package.json +9 -9
  55. package/lib/core/engine/command/mouse.js +0 -314
package/CHANGELOG.md CHANGED
@@ -1,8 +1,36 @@
1
1
  # Browsertime changelog (we do [semantic versioning](https://semver.org))
2
2
 
3
+
4
+ ## 14.7.0 - 2021-10-20
5
+ ### Added
6
+ * Updated to Chromedriver 95. Added Chrome 95 in the Docker container.
7
+
8
+ ## 14.6.1 - 2021-10-18
9
+ ### Fixed
10
+ * If loading the URL fails (we get an exception, the browser crashes etc) make sure we stop the video correctly so it can be used [#1657](https://github.com/sitespeedio/browsertime/pull/1657).
11
+
12
+
13
+ ## 14.6.0 - 2021-10-13
14
+ ### Added
15
+ * Updated to Selenium 4.0.0 [#1655](https://github.com/sitespeedio/browsertime/pull/1655).
16
+ * You can skip setting Browsertime default Firefox preferences with `--firefox.noDefaultPrefs`, thank you [Gregory Mierzwinski](https://github.com/gmierz) for PR [#1650](https://github.com/sitespeedio/browsertime/pull/1650).
17
+ * You can skip setting Browsertime default Chrome options with `--chrome.noDefaultOptions`, fixed in [#1651](https://github.com/sitespeedio/browsertime/pull/1651).
18
+ * Updated to Firefox 93 in the Docker container.
19
+
20
+ ### Fixed
21
+ * Updated eslint and prettier dependencies [#1652](https://github.com/sitespeedio/browsertime/pull/1652).
22
+ * Log Firefox preference and Chrome args on debug level [#1654](https://github.com/sitespeedio/browsertime/pull/1654)
23
+ ## 14.5.0 - 2021-09-30
24
+ ### Added
25
+ * Added CSS selector support for mouse commands [#1645](https://github.com/sitespeedio/browsertime/pull/1645)
26
+
27
+ ### Fixed
28
+ * Internal refactoring of the mouse command [#1646](https://github.com/sitespeedio/browsertime/pull/1646) and [#1647](https://github.com/sitespeedio/browsertime/pull/1647).
29
+
3
30
  ## 14.4.0 - 2021-09-22
4
31
  ### Added
5
- * Updated to Chromedriver 94 and Chrome 94 in the Docker container [#1643](https://github.com/sitespeedio/browsertime/pull/1643).
32
+ * Updated to Ch
33
+ romedriver 94 and Chrome 94 in the Docker container [#1643](https://github.com/sitespeedio/browsertime/pull/1643).
6
34
  ## 14.3.0 - 2021-09-16
7
35
  ### Added
8
36
  * Update to Edgedriver 93. Update to Edge 93 in the Docker container [#1641](https://github.com/sitespeedio/browsertime/pull/1641).
@@ -11,7 +11,7 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const log = require('intel').getLogger('browsertime');
13
13
  const engineUtils = require('../lib/support/engineUtils');
14
- const AsyncFunction = Object.getPrototypeOf(async function() {}).constructor;
14
+ const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
15
15
  const delay = ms => new Promise(res => setTimeout(res, ms));
16
16
 
17
17
  async function parseUserScripts(scripts) {
@@ -91,7 +91,7 @@ class Android {
91
91
  const transfer = await this.client.pull(this.id, sourcePath);
92
92
 
93
93
  return new Promise((resolve, reject) => {
94
- transfer.on('end', function() {
94
+ transfer.on('end', function () {
95
95
  resolve();
96
96
  });
97
97
  transfer.on('error', reject);
package/lib/chrome/har.js CHANGED
@@ -5,7 +5,7 @@ const perflogParser = require('chrome-har');
5
5
  const harBuilder = require('../support/har');
6
6
  const { Type } = require('selenium-webdriver').logging;
7
7
 
8
- module.exports = async function(
8
+ module.exports = async function (
9
9
  runner,
10
10
  result,
11
11
  index,
@@ -38,9 +38,9 @@ module.exports = async function(
38
38
  await cdpClient.setResponseBodies(har);
39
39
  }
40
40
 
41
- const versionInfo = (await cdpClient.send(
42
- 'Browser.getVersion'
43
- )).product.split('/');
41
+ const versionInfo = (
42
+ await cdpClient.send('Browser.getVersion')
43
+ ).product.split('/');
44
44
  const info = {
45
45
  name: versionInfo[0],
46
46
  version: versionInfo[1]
@@ -2,7 +2,7 @@
2
2
 
3
3
  const get = require('lodash.get');
4
4
 
5
- module.exports = function(result, options) {
5
+ module.exports = function (result, options) {
6
6
  let totalDurationFirstPaint = 0;
7
7
  let totalDurationFirstContentFulPaint = 0;
8
8
  let totalDurationAfterLoadEventEnd = 0;
@@ -18,13 +18,13 @@ function pad(n) {
18
18
  * Use SpeedLine to analyse the Chrome trace log to get Visual Metrics.
19
19
  * Not as good as using a video but maybe usable in some cases.
20
20
  */
21
- module.exports = async function(trace, result, index) {
21
+ module.exports = async function (trace, result, index) {
22
22
  try {
23
23
  const navStart = trace.traceEvents.filter(
24
24
  task =>
25
25
  task.cat === 'blink.user_timing' && task.name === 'navigationStart'
26
26
  );
27
- navStart.sort(function(a, b) {
27
+ navStart.sort(function (a, b) {
28
28
  return a.ts - b.ts;
29
29
  });
30
30
  log.debug('Get Speedline result from the trace');
@@ -14,7 +14,7 @@ let hasConfiguredChromeDriverService = false;
14
14
  * @param builder
15
15
  * @param {Object} options the options for a web driver.
16
16
  */
17
- module.exports.configureBuilder = function(builder, baseDir, options) {
17
+ module.exports.configureBuilder = function (builder, baseDir, options) {
18
18
  const chromeConfig = options.chrome || {};
19
19
 
20
20
  if (!hasConfiguredChromeDriverService) {
@@ -5,7 +5,7 @@ const getViewPort = require('../../support/getViewPort');
5
5
  const util = require('../../support/util');
6
6
  const log = require('intel').getLogger('browsertime.chrome');
7
7
 
8
- module.exports = function(seleniumOptions, browserOptions, options, baseDir) {
8
+ module.exports = function (seleniumOptions, browserOptions, options, baseDir) {
9
9
  // Fixing save password popup, only on Desktop
10
10
  if (!options.android) {
11
11
  seleniumOptions.setUserPreferences({
@@ -109,10 +109,15 @@ module.exports = function(seleniumOptions, browserOptions, options, baseDir) {
109
109
  '--remote-debugging-port=' + options.devToolsPort
110
110
  );
111
111
  } else {
112
- seleniumOptions.addArguments(defaultChromeOptions);
112
+ if (!browserOptions.noDefaultOptions) {
113
+ seleniumOptions.addArguments(defaultChromeOptions);
114
+ } else {
115
+ log.info('Skip setting default options for Chrome');
116
+ }
113
117
  }
114
118
 
115
119
  if (browserOptions.args) {
120
+ log.debug('Set Chrome args %j', browserOptions.args);
116
121
  seleniumOptions.addArguments(browserOptions.args);
117
122
  }
118
123
 
@@ -104,7 +104,7 @@ const legacyProfiles = {
104
104
  };
105
105
 
106
106
  module.exports = {
107
- parseTrafficShapeConfig: function(options) {
107
+ parseTrafficShapeConfig: function (options) {
108
108
  if (options.connectivity && options.connectivity.profile === 'custom') {
109
109
  return {
110
110
  down: options.connectivity.down,
@@ -133,7 +133,7 @@ module.exports = {
133
133
  return null;
134
134
  }
135
135
  },
136
- getProfiles: function() {
136
+ getProfiles: function () {
137
137
  return newProfiles;
138
138
  }
139
139
  };
@@ -14,7 +14,7 @@ const TS_PROXY_SCRIPT_PATH = path.join(
14
14
  );
15
15
 
16
16
  function getStartupCriteriaListener(resolve, reject) {
17
- return function(data) {
17
+ return function (data) {
18
18
  const logLine = data.toString();
19
19
  if (logLine.startsWith('Started Socks5 proxy server on')) {
20
20
  log.debug('Started TSproxy');
@@ -65,12 +65,12 @@ class TSProxy {
65
65
  const tsProxyProcess = this.tsProxyProcess;
66
66
 
67
67
  if (this.logVerbose) {
68
- tsProxyProcess.stderr.on('data', function(data) {
68
+ tsProxyProcess.stderr.on('data', function (data) {
69
69
  log.verbose(data.toString().trim());
70
70
  });
71
71
  }
72
72
 
73
- return new Promise(function(resolve, reject) {
73
+ return new Promise(function (resolve, reject) {
74
74
  tsProxyProcess.stdout.on(
75
75
  'data',
76
76
  getStartupCriteriaListener(resolve, reject)
@@ -96,6 +96,13 @@ class Measure {
96
96
  }
97
97
  }
98
98
 
99
+ async _stopVideo(url) {
100
+ if (this.recordVideo && !this.options.videoParams.debug) {
101
+ await this.video.stop(url);
102
+ this.videos.push(this.video);
103
+ }
104
+ }
105
+
99
106
  _error(message) {
100
107
  // If we already are measuring a page, associate the error woth that page
101
108
  if (this.areWeMeasuring === true) {
@@ -223,6 +230,9 @@ class Measure {
223
230
  this.numberOfMeasuredPages++;
224
231
  this.numberOfVisitedPages++;
225
232
  this.engineDelegate.failing(url);
233
+ // If we got an error we still should stop the video to make sure we can use it to
234
+ // try to understand what went wrong
235
+ await this._stopVideo(url);
226
236
  throw e;
227
237
  }
228
238
  } else {
@@ -261,14 +271,12 @@ class Measure {
261
271
  // we also need to add that to the meta data so that the correct folder is created
262
272
  if (this.result[this.numberOfMeasuredPages].alias) {
263
273
  if (this.options.urlMetaData) {
264
- this.options.urlMetaData[url] = this.result[
265
- this.numberOfMeasuredPages
266
- ].alias;
274
+ this.options.urlMetaData[url] =
275
+ this.result[this.numberOfMeasuredPages].alias;
267
276
  } else {
268
277
  this.options.urlMetaData = {};
269
- this.options.urlMetaData[url] = this.result[
270
- this.numberOfMeasuredPages
271
- ].alias;
278
+ this.options.urlMetaData[url] =
279
+ this.result[this.numberOfMeasuredPages].alias;
272
280
  }
273
281
  }
274
282
 
@@ -276,10 +284,8 @@ class Measure {
276
284
  await this.storageManager.createSubDataDir(
277
285
  path.join(pathToFolder(url, this.options))
278
286
  );
279
- if (this.recordVideo && !this.options.videoParams.debug) {
280
- await this.video.stop(url);
281
- this.videos.push(this.video);
282
- }
287
+ await this._stopVideo(url);
288
+
283
289
  const alias = this.options.urlMetaData
284
290
  ? this.options.urlMetaData[url]
285
291
  : undefined || this.result[this.numberOfMeasuredPages].alias;
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ const log = require('intel').getLogger('browsertime.command.mouse');
4
+ const webdriver = require('selenium-webdriver');
5
+
6
+ class ClickAndHold {
7
+ constructor(browser) {
8
+ this.driver = browser.getDriver();
9
+ this.actions = this.driver.actions({ async: true });
10
+ }
11
+
12
+ /**
13
+ * Click and hold an element that matches a XPath selector.
14
+ * @param {string} xpath
15
+ * @returns {Promise} Promise object represents when mouse is pressed.
16
+ * @throws Will throw an error if the element is not found.
17
+ */
18
+ async byXpath(xpath) {
19
+ try {
20
+ const element = await this.driver.findElement(webdriver.By.xpath(xpath));
21
+ return this.actions.move({ origin: element }).press().perform();
22
+ } catch (e) {
23
+ log.error('Could not click and hold on element with xpath %s', xpath);
24
+ log.verbose(e);
25
+ throw Error('Could not click and hold on element with xpath ' + xpath);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Click and hold an element that matches a CSS selector.
31
+ * @param {string} selector
32
+ * @returns {Promise} Promise object represents when mouse is pressed.
33
+ * @throws Will throw an error if the element is not found.
34
+ */
35
+ async bySelector(selector) {
36
+ try {
37
+ const element = await this.driver.findElement(webdriver.By.css(selector));
38
+ return this.actions.move({ origin: element }).press().perform();
39
+ } catch (e) {
40
+ log.error(
41
+ 'Could not click and hold on element with selector %s',
42
+ selector
43
+ );
44
+ log.verbose(e);
45
+ throw Error(
46
+ 'Could not click and hold on element with selector ' + selector
47
+ );
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Click and hold an element at the cursor's position.
53
+ * @returns {Promise} Promise object represents when mouse is pressed.
54
+ * @throws Will throw an error if action cannot be performed.
55
+ */
56
+ async atCursor() {
57
+ try {
58
+ return this.actions
59
+ .move({ origin: webdriver.Origin.POINTER })
60
+ .press()
61
+ .perform();
62
+ } catch (e) {
63
+ log.error('Could not click and hold at cursor');
64
+ log.verbose(e);
65
+ throw Error('Could not click and hold at cursor');
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Click and hold an element at the given coordinates.
71
+ * @param {integer} xPos
72
+ * @param {integer} yPos
73
+ * @returns {Promise} Promise object represents when mouse is pressed.
74
+ * @throws Will throw an error if action cannot be performed.
75
+ */
76
+ async atPosition(xPos, yPos) {
77
+ try {
78
+ return this.actions
79
+ .move({ x: xPos, y: yPos, origin: webdriver.Origin.VIEWPORT })
80
+ .press()
81
+ .perform();
82
+ } catch (e) {
83
+ log.error('Could not click and hold at position (%d,%d)', xPos, yPos);
84
+ log.verbose(e);
85
+ throw Error(
86
+ 'Could not click and hold at position (' + xPos + ',' + yPos + ')'
87
+ );
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Release mouse on element that matches the specified Xpath.
93
+ * @param {string} xpath
94
+ * @returns {Promise} Promise object represents when mouse is released.
95
+ * @throws Will throw an error if action cannot be performed.
96
+ */
97
+ async releaseAtXpath(xpath) {
98
+ try {
99
+ const element = await this.driver.findElement(webdriver.By.xpath(xpath));
100
+ return this.actions.move({ origin: element }).release().perform();
101
+ } catch (e) {
102
+ log.error('Could not release on xpath %s', xpath);
103
+ log.verbose(e);
104
+ throw Error('Could not release on xpath ' + xpath);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Release mouse on element that matches the specified CSS selector.
110
+ * @param {string} selector
111
+ * @returns {Promise} Promise object represents when mouse is released.
112
+ * @throws Will throw an error if action cannot be performed.
113
+ */
114
+ async releaseAtSelector(selector) {
115
+ try {
116
+ const element = await this.driver.findElement(webdriver.By.css(selector));
117
+ return this.actions.move({ origin: element }).release().perform();
118
+ } catch (e) {
119
+ log.error('Could not release on selector %s', selector);
120
+ log.verbose(e);
121
+ throw Error('Could not release on selector ' + selector);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Release mouse at specified coordinates.
127
+ * @param {integer} xPos
128
+ * @param {integer} yPos
129
+ * @returns {Promise} Promise object represents when mouse is released.
130
+ * @throws Will throw an error if action cannot be performed.
131
+ */
132
+ async releaseAtPosition(xPos, yPos) {
133
+ try {
134
+ return this.actions
135
+ .move({ x: xPos, y: yPos, origin: webdriver.Origin.VIEWPORT })
136
+ .release()
137
+ .perform();
138
+ } catch (e) {
139
+ log.error('Could not release at position (%d,%d)', xPos, yPos);
140
+ log.verbose(e);
141
+ throw Error('Could not release at position (' + xPos + ',' + yPos + ')');
142
+ }
143
+ }
144
+ }
145
+
146
+ module.exports = ClickAndHold;
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const log = require('intel').getLogger('browsertime.command.mouse');
4
+ const webdriver = require('selenium-webdriver');
5
+
6
+ class ContextClick {
7
+ constructor(browser) {
8
+ this.driver = browser.getDriver();
9
+ this.actions = this.driver.actions({ async: true });
10
+ }
11
+
12
+ /**
13
+ * Perform ContextClick on an element that matches a XPath selector.
14
+ * @param {string} xpath
15
+ * @returns {Promise} Promise object represents when context click occurs.
16
+ * @throws Will throw an error if the element is not found
17
+ */
18
+ async byXpath(xpath) {
19
+ try {
20
+ const element = await this.driver.findElement(webdriver.By.xpath(xpath));
21
+ return this.actions.contextClick(element).perform();
22
+ } catch (e) {
23
+ log.error('Could not context click on element with xpath %s', xpath);
24
+ log.verbose(e);
25
+ throw Error('Could not context click on element with xpath ' + xpath);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Perform ContextClick on an element that matches a CSS selector.
31
+ * @param {string} css selector
32
+ * @returns {Promise} Promise object represents when context click occurs.
33
+ * @throws Will throw an error if the element is not found
34
+ */
35
+ async bySelector(selector) {
36
+ try {
37
+ const element = await this.driver.findElement(webdriver.By.css(selector));
38
+ return this.actions.contextClick(element).perform();
39
+ } catch (e) {
40
+ log.error('Could not context click on element with css %s', selector);
41
+ log.verbose(e);
42
+ throw Error('Could not context click on element with css ' + selector);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Perform ContextClick at the cursor's position.
48
+ * @returns {Promise} Promise object represents when context click occurs.
49
+ * @throws Will throw an error if context click cannot be performed.
50
+ */
51
+ async atCursor() {
52
+ try {
53
+ return this.actions.contextClick().perform();
54
+ } catch (e) {
55
+ log.error('Could not perform context click');
56
+ log.verbose(e);
57
+ throw Error('Could not perform context click');
58
+ }
59
+ }
60
+ }
61
+ module.exports = ContextClick;
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const log = require('intel').getLogger('browsertime.command.mouse');
4
+ const webdriver = require('selenium-webdriver');
5
+
6
+ class DoubleClick {
7
+ constructor(browser, pageCompleteCheck) {
8
+ this.browser = browser;
9
+ this.actions = this.browser.getDriver().actions({ async: true });
10
+ this.pageCompleteCheck = pageCompleteCheck;
11
+ }
12
+
13
+ /**
14
+ * Perform mouse double click on an element matches a XPath selector.
15
+ * @param {string} xpath
16
+ * @returns {Promise} Promise object represents when the element has been double clicked.
17
+ * @throws Will throw an error if the element is not found.
18
+ */
19
+ async byXpath(xpath, options) {
20
+ try {
21
+ const element = await this.browser
22
+ .getDriver()
23
+ .findElement(webdriver.By.xpath(xpath));
24
+ await this.actions.doubleClick(element).perform();
25
+ if (options && 'wait' in options && options.wait === true) {
26
+ return this.browser.extraWait(this.pageCompleteCheck);
27
+ }
28
+ } catch (e) {
29
+ log.error('Could not double click on element with xpath %s', xpath);
30
+ log.verbose(e);
31
+ throw Error('Could not double click on element with xpath ' + xpath);
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Perform mouse double click on an element matches a CSS selector.
37
+ * @param {string} selector
38
+ * @returns {Promise} Promise object represents when the element has been double clicked.
39
+ * @throws Will throw an error if the element is not found.
40
+ */
41
+ async bySelector(selector, options) {
42
+ try {
43
+ const element = await this.browser
44
+ .getDriver()
45
+ .findElement(webdriver.By.css(selector));
46
+ await this.actions.doubleClick(element).perform();
47
+ if (options && 'wait' in options && options.wait === true) {
48
+ return this.browser.extraWait(this.pageCompleteCheck);
49
+ }
50
+ } catch (e) {
51
+ log.error('Could not double click on element with xpath %s', selector);
52
+ log.verbose(e);
53
+ throw Error('Could not double click on element with xpath ' + selector);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Perform mouse double click at the cursor's position.
59
+ * @returns {Promise} Promise object represents when double click occurs.
60
+ * @throws Will throw an error if double click cannot be performed.
61
+ */
62
+ async atCursor(options) {
63
+ try {
64
+ await this.actions.doubleClick().perform();
65
+ if (options && 'wait' in options && options.wait === true) {
66
+ return this.browser.extraWait(this.pageCompleteCheck);
67
+ }
68
+ } catch (e) {
69
+ log.error('Could not perform double click');
70
+ log.verbose(e);
71
+ throw Error('Could not perform double click');
72
+ }
73
+ }
74
+ }
75
+
76
+ module.exports = DoubleClick;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ SingleClick: require('./singleClick'),
5
+ DoubleClick: require('./doubleClick'),
6
+ ClickAndHold: require('./clickAndHold'),
7
+ ContextClick: require('./contextClick'),
8
+ MouseMove: require('./mouseMove')
9
+ };
@@ -0,0 +1,81 @@
1
+ 'use strict';
2
+
3
+ const log = require('intel').getLogger('browsertime.command.mouse');
4
+ const webdriver = require('selenium-webdriver');
5
+
6
+ class MouseMove {
7
+ constructor(browser) {
8
+ this.driver = browser.getDriver();
9
+ this.actions = this.driver.actions({ async: true });
10
+ }
11
+
12
+ /**
13
+ * Move mouse to an element that matches a XPath selector.
14
+ * @param {string} xpath
15
+ * @returns {Promise} Promise object represents when the mouse has moved
16
+ * @throws Will throw an error if the element is not found
17
+ */
18
+ async byXpath(xpath) {
19
+ try {
20
+ const element = await this.driver.findElement(webdriver.By.xpath(xpath));
21
+ return this.actions.move({ origin: element }).perform();
22
+ } catch (e) {
23
+ log.error('Could not find element by xpath %s', xpath);
24
+ log.verbose(e);
25
+ throw Error('Could not find element by xpath ' + xpath);
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Move mouse to an element that matches a CSS selector.
31
+ * @param {string} selector
32
+ * @returns {Promise} Promise object represents when the mouse has moved
33
+ * @throws Will throw an error if the element is not found
34
+ */
35
+ async bySelector(selector) {
36
+ try {
37
+ const element = await this.driver.findElement(webdriver.By.css(selector));
38
+ return this.actions.move({ origin: element }).perform();
39
+ } catch (e) {
40
+ log.error('Could not find element by selector %s', selector);
41
+ log.verbose(e);
42
+ throw Error('Could not find element by selector ' + selector);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Move mouse to a position
48
+ * @param {number} xPos, {number} yPos
49
+ * @returns {Promise} Promise object represents when the mouse has moved
50
+ * @throws Will throw an error if the element is not found
51
+ */
52
+ async toPosition(xPos, yPos) {
53
+ try {
54
+ return this.actions.move({ x: xPos, y: yPos }).perform();
55
+ } catch (e) {
56
+ log.error('Could not move mouse to position.');
57
+ log.verbose(e);
58
+ throw Error('Could not move mouse to position.');
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Move mouse by an offset
64
+ * @param {number} xOffset, {number} yOffset
65
+ * @returns {Promise} Promise object represents when the mouse has moved
66
+ * @throws Will throw an error if the element is not found
67
+ */
68
+ async byOffset(xOffset, yOffset) {
69
+ try {
70
+ return this.actions
71
+ .move({ x: xOffset, y: yOffset, origin: webdriver.Origin.POINTER })
72
+ .perform();
73
+ } catch (e) {
74
+ log.error('Could not move mouse by offset');
75
+ log.verbose(e);
76
+ throw Error('Could not move mouse by offset');
77
+ }
78
+ }
79
+ }
80
+
81
+ module.exports = MouseMove;