webdriverio 4.14.0 → 4.14.4

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 (35) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/build/lib/commands/deleteCookie.js +1 -1
  3. package/build/lib/commands/getCookie.js +1 -1
  4. package/build/lib/commands/leftClick.js +2 -0
  5. package/build/lib/commands/middleClick.js +2 -0
  6. package/build/lib/commands/moveToObject.js +4 -37
  7. package/build/lib/commands/rightClick.js +2 -0
  8. package/build/lib/commands/setCookie.js +10 -2
  9. package/build/lib/helpers/detectSeleniumBackend.js +13 -1
  10. package/build/lib/helpers/findElementStrategy.js +3 -4
  11. package/build/lib/helpers/findMoveToCoordinates.js +42 -0
  12. package/build/lib/helpers/handleMouseButtonProtocol.js +34 -1
  13. package/build/lib/helpers/utilities.js +20 -1
  14. package/build/lib/helpers/wdio.conf.ejs +6 -0
  15. package/build/lib/protocol/execute.js +4 -1
  16. package/build/lib/protocol/moveTo.js +35 -1
  17. package/build/lib/utils/BaseReporter.js +16 -1
  18. package/build/lib/utils/ConfigParser.js +41 -19
  19. package/build/lib/utils/ReporterStats.js +7 -0
  20. package/build/lib/utils/RequestHandler.js +13 -1
  21. package/build/lib/webdriverio.js +0 -1
  22. package/build/package.json +1 -1
  23. package/docs/guide/getstarted/boilerplate.md +26 -0
  24. package/docs/guide/testrunner/configurationfile.md +12 -6
  25. package/docs/guide/testrunner/organizesuite.md +6 -0
  26. package/lib/commands/deleteCookie.js +1 -1
  27. package/lib/commands/getCookie.js +1 -1
  28. package/lib/commands/leftClick.js +2 -0
  29. package/lib/commands/middleClick.js +2 -0
  30. package/lib/commands/moveToObject.js +4 -37
  31. package/lib/commands/rightClick.js +2 -0
  32. package/lib/commands/setCookie.js +10 -2
  33. package/lib/protocol/execute.js +5 -2
  34. package/lib/protocol/moveTo.js +27 -1
  35. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,4 +1,21 @@
1
1
  # CHANGELOG
2
+ ## unreleased
3
+ * wdio changes
4
+ * add W3C actions protocol fallback for when moveTo protocol returns an error
5
+ * add W3C actions protocol fallback for when leftClick, middleClick or rightClick command / buttonDown, buttonPress, or buttonUp protocol returns an error
6
+
7
+ ## v4.14.2 (2019-01-11)
8
+ * bugfix:
9
+ * fix css-id selector
10
+ * fix a nullref exception on network failures where response had no body
11
+
12
+ ## v4.14.1 (2018-11-30)
13
+ * global:
14
+ * add config `region` that allows to specify the SauceLabs datacenter region
15
+ * wdio changes:
16
+ * Allowing to specify a feature file with a scenario line number (#3019)
17
+ * bugfixes:
18
+ * round values for touchMove command (#3039)
2
19
 
3
20
  ## v4.14.0 (2018-10-23)
4
21
  * global:
@@ -53,7 +53,7 @@ var deleteCookie = function deleteCookie(name) {
53
53
  name = null;
54
54
  }
55
55
 
56
- return this.cookie('DELETE', name);
56
+ return this.cookie('DELETE', typeof name === 'string' ? encodeURIComponent(encodeURIComponent(name)) : name);
57
57
  };
58
58
 
59
59
  exports.default = deleteCookie;
@@ -49,7 +49,7 @@ var getCookie = function getCookie(name) {
49
49
 
50
50
  if (typeof name === 'string') {
51
51
  return res.value.filter(function (cookie) {
52
- return cookie.name === name;
52
+ return cookie.name === encodeURIComponent(encodeURIComponent(name));
53
53
  })[0] || null;
54
54
  }
55
55
 
@@ -17,6 +17,8 @@ var leftClick = function leftClick(selector, xoffset, yoffset) {
17
17
  * Apply left click on an element. If selector is not provided, click on the last
18
18
  * moved-to location.
19
19
  *
20
+ * Uses JSONWireframe buttonPress protocol with W3C actions protocol as fallback.
21
+ *
20
22
  * @alias browser.leftClick
21
23
  * @param {String} selector element to click on
22
24
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element.
@@ -17,6 +17,8 @@ var middleClick = function middleClick(selector, xoffset, yoffset) {
17
17
  * Apply middle click on an element. If selector is not provided, click on the last
18
18
  * moved-to location.
19
19
  *
20
+ * Uses JSONWireframe buttonPress protocol with W3C actions protocol as fallback.
21
+ *
20
22
  * @alias browser.middleClick
21
23
  * @param {String} selector element to click on
22
24
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element.
@@ -9,41 +9,6 @@ var _ErrorHandler = require('../utils/ErrorHandler');
9
9
  var moveToObject = function moveToObject(selector, xoffset, yoffset) {
10
10
  var _this = this;
11
11
 
12
- /**
13
- * check for offset params
14
- */
15
- var hasOffsetParams = true;
16
- if (typeof xoffset !== 'number' && typeof yoffset !== 'number') {
17
- hasOffsetParams = false;
18
- }
19
-
20
- if (this.isMobile) {
21
- return this.element(selector).then(function (res) {
22
- /**
23
- * check if element was found and throw error if not
24
- */
25
- if (!res.value) {
26
- throw new _ErrorHandler.RuntimeError(7);
27
- }
28
-
29
- return _this.elementIdSize(res.value.ELEMENT).then(function (size) {
30
- return _this.elementIdLocation(res.value.ELEMENT).then(function (location) {
31
- return { size, location };
32
- });
33
- });
34
- }).then(function (res) {
35
- var x = res.location.value.x + res.size.value.width / 2;
36
- var y = res.location.value.y + res.size.value.height / 2;
37
-
38
- if (hasOffsetParams) {
39
- x = res.location.value.x + xoffset;
40
- y = res.location.value.y + yoffset;
41
- }
42
-
43
- return _this.touchMove(x, y);
44
- });
45
- }
46
-
47
12
  return this.element(selector).then(function (res) {
48
13
  /**
49
14
  * check if element was found and throw error if not
@@ -60,14 +25,16 @@ var moveToObject = function moveToObject(selector, xoffset, yoffset) {
60
25
  * offset, the mouse will be moved to the center of the element. If the element is not
61
26
  * visible, it will be scrolled into view.
62
27
  *
28
+ * Uses JSONWireframe moveTo protocol with W3C actions protocol fallback.
29
+ * Uses touchMove protocol for mobile.
30
+ *
63
31
  * @alias browser.moveToObject
64
32
  * @param {String} selector element to move to
65
33
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
66
34
  * @param {Number} yoffset Y offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
67
- * @uses protocol/element, protocol/elementIdLocation
35
+ * @uses protocol/element, protocol/elementIdLocation, protocol/elementIdSize
68
36
  * @type action
69
37
  *
70
38
  */
71
-
72
39
  exports.default = moveToObject;
73
40
  module.exports = exports['default'];
@@ -17,6 +17,8 @@ var rightClick = function rightClick(selector, xoffset, yoffset) {
17
17
  * Apply right click on an element. If selector is not provided, click on the last
18
18
  * moved-to location.
19
19
  *
20
+ * Uses JSONWireframe buttonPress protocol with W3C actions protocol as fallback.
21
+ *
20
22
  * @alias browser.rightClick
21
23
  * @param {String} selector element to click on
22
24
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element.
@@ -4,17 +4,25 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
 
7
+ var _assign = require('babel-runtime/core-js/object/assign');
8
+
9
+ var _assign2 = _interopRequireDefault(_assign);
10
+
7
11
  var _ErrorHandler = require('../utils/ErrorHandler');
8
12
 
13
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
+
9
15
  var setCookie = function setCookie(cookieObj) {
16
+ var name = cookieObj.name;
10
17
  /*!
11
18
  * parameter check
12
19
  */
13
- if (typeof cookieObj !== 'object') {
20
+
21
+ if (typeof cookieObj !== 'object' || !name || typeof name !== 'string') {
14
22
  throw new _ErrorHandler.CommandError('Please specify a cookie object to set (see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object for documentation.');
15
23
  }
16
24
 
17
- return this.cookie('POST', cookieObj);
25
+ return this.cookie('POST', (0, _assign2.default)({}, cookieObj, { name: encodeURIComponent(encodeURIComponent(name)) }));
18
26
  }; /**
19
27
  *
20
28
  * Sets a [cookie](https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object)
@@ -6,6 +6,15 @@ Object.defineProperty(exports, "__esModule", {
6
6
  var DEFAULT_HOST = '127.0.0.1';
7
7
  var DEFAULT_PORT = 4444;
8
8
 
9
+ var REGION_MAPPING = {
10
+ 'eu': 'eu-central-1'
11
+ };
12
+
13
+ function getSauceEndpoint(region) {
14
+ var dc = region ? (REGION_MAPPING[region] || region) + '.' : '';
15
+ return `${dc}saucelabs.com`;
16
+ }
17
+
9
18
  /**
10
19
  * helper to detect the Selenium backend according to given capabilities
11
20
  */
@@ -25,6 +34,7 @@ var detectSeleniumBackend = function detectSeleniumBackend(capabilities) {
25
34
  */
26
35
  if (!capabilities.user || !capabilities.key) {
27
36
  return {
37
+ protocol: 'http',
28
38
  host: DEFAULT_HOST,
29
39
  port: DEFAULT_PORT
30
40
  };
@@ -36,6 +46,7 @@ var detectSeleniumBackend = function detectSeleniumBackend(capabilities) {
36
46
  */
37
47
  if (capabilities.key.length === 20) {
38
48
  return {
49
+ protocol: 'http',
39
50
  host: 'hub.browserstack.com',
40
51
  port: 80
41
52
  };
@@ -47,6 +58,7 @@ var detectSeleniumBackend = function detectSeleniumBackend(capabilities) {
47
58
  */
48
59
  if (capabilities.key.length === 32) {
49
60
  return {
61
+ protocol: 'http',
50
62
  host: 'hub.testingbot.com',
51
63
  port: 80
52
64
  };
@@ -58,7 +70,7 @@ var detectSeleniumBackend = function detectSeleniumBackend(capabilities) {
58
70
  */
59
71
  return {
60
72
  protocol: 'https',
61
- host: 'ondemand.saucelabs.com',
73
+ host: capabilities.host || `ondemand.${getSauceEndpoint(capabilities.region)}`,
62
74
  port: 443
63
75
  };
64
76
  };
@@ -48,11 +48,10 @@ var findStrategy = function findStrategy() {
48
48
  }
49
49
 
50
50
  // check value type
51
- // use id strategy if value starts with # and doesnt contain any other CSS selector-relevant character
52
- // regex to match ids from http://stackoverflow.com/questions/18938390/regex-to-match-ids-in-a-css-file
51
+ // use css selector for hash instead of by.id
52
+ // https://github.com/webdriverio/webdriverio/issues/2780
53
53
  if (value.search(/^#-?[_a-zA-Z]+[_a-zA-Z0-9-]*$/) > -1) {
54
- using = 'id';
55
- value = value.slice(1);
54
+ using = 'css selector';
56
55
 
57
56
  // use xPath strategy if value starts with //
58
57
  } else if (value.indexOf('/') === 0 || value.indexOf('(') === 0 || value.indexOf('../') === 0 || value.indexOf('./') === 0 || value.indexOf('*/') === 0) {
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ /**
7
+ * call must be scoped to the webdriverio client
8
+ *
9
+ * @param {String} element Opaque ID assigned to the element to move to, as described in the WebElement JSON Object.
10
+ * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
11
+ * @param {Number} yoffset Y offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
12
+ * @uses protocol/elementIdLocation, protocol/elementIdSize
13
+ */
14
+ var findMoveToCoordinates = function findMoveToCoordinates(element, xoffset, yoffset) {
15
+ var _this = this;
16
+
17
+ /**
18
+ * check for offset params
19
+ */
20
+ xoffset = typeof xoffset === 'number' ? xoffset : 0;
21
+ yoffset = typeof yoffset === 'number' ? yoffset : 0;
22
+
23
+ return this.elementIdSize(element).then(function (size) {
24
+ return _this.elementIdLocation(element).then(function (location) {
25
+ var x = location.value.x + size.value.width / 2;
26
+ var y = location.value.y + size.value.height / 2;
27
+
28
+ if (xoffset > 0 || yoffset > 0) {
29
+ x = location.value.x + xoffset;
30
+ y = location.value.y + yoffset;
31
+ }
32
+
33
+ return {
34
+ x: Math.round(x),
35
+ y: Math.round(y)
36
+ };
37
+ });
38
+ });
39
+ };
40
+
41
+ exports.default = findMoveToCoordinates;
42
+ module.exports = exports['default'];
@@ -3,20 +3,53 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+
7
+ var _utilities = require('../helpers/utilities');
8
+
6
9
  var BUTTON_ENUM = {
7
10
  left: 0,
8
11
  middle: 1,
9
12
  right: 2
13
+ };
14
+
15
+ var REQUEST_PATH_ENUM = {
16
+ down: '/session/:sessionId/buttondown',
17
+ press: '/session/:sessionId/click',
18
+ up: '/session/:sessionId/buttonup'
10
19
 
11
20
  /**
12
21
  * call must be scoped to the webdriverio client
13
22
  */
14
23
  };var handleMouseButtonProtocol = function handleMouseButtonProtocol(requestPath, button) {
24
+ var _this = this;
25
+
15
26
  if (typeof button !== 'number') {
16
27
  button = BUTTON_ENUM[button || 'left'];
17
28
  }
18
29
 
19
- return this.requestHandler.create(requestPath, { button: button });
30
+ return this.requestHandler.create(requestPath, { button: button }).catch(function (err) {
31
+ /**
32
+ * jsonwire command not supported try webdriver endpoint
33
+ */
34
+ if ((0, _utilities.isUnknownCommand)(err)) {
35
+ var actions = [{ type: 'pointerDown', button: button }, { type: 'pointerUp', button: button }];
36
+
37
+ if (requestPath === REQUEST_PATH_ENUM['down']) {
38
+ actions = [{ type: 'pointerDown', button: button }];
39
+ } else if (requestPath === REQUEST_PATH_ENUM['up']) {
40
+ actions = [{ type: 'pointerUp', button: button }];
41
+ }
42
+
43
+ return _this.actions([{
44
+ type: 'pointer',
45
+ id: 'mouse',
46
+ parameters: { pointerType: 'mouse' },
47
+ actions
48
+ }]);
49
+ }
50
+
51
+ return err;
52
+ });
20
53
  };
21
54
 
22
55
  exports.default = handleMouseButtonProtocol;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.isSuccessfulResponse = isSuccessfulResponse;
7
7
  exports.isUnknownCommand = isUnknownCommand;
8
+ exports.isUnknownError = isUnknownError;
8
9
  exports.formatHostname = formatHostname;
9
10
 
10
11
  var _net = require('net');
@@ -70,7 +71,7 @@ function isUnknownCommand(err) {
70
71
  /**
71
72
  * when running browser driver directly
72
73
  */
73
- if (err.message.match(/Invalid Command Method/) || err.message.match(/did not match a known command/) || err.message.match(/unknown command/) || err.message.match(/Driver info: driver\.version: unknown/) || err.message.match(/Method has not yet been implemented/) || err.message.match(/did not map to a valid resource/)) {
74
+ if (err.message.match(/Command not found/) || err.message.match(/Invalid Command Method/) || err.message.match(/did not match a known command/) || err.message.match(/unknown command/) || err.message.match(/Driver info: driver\.version: unknown/) || err.message.match(/Method has not yet been implemented/) || err.message.match(/did not map to a valid resource/)) {
74
75
  return true;
75
76
  }
76
77
 
@@ -84,6 +85,24 @@ function isUnknownCommand(err) {
84
85
  return false;
85
86
  }
86
87
 
88
+ /**
89
+ * Checks if the error message is an unknown error
90
+ * https://w3c.github.io/webdriver/#dfn-unknown-error
91
+ * @param {Error} err
92
+ * @return {Boolean} Whether the error message is an "Unknown Error"
93
+ */
94
+ function isUnknownError(err) {
95
+ if (!err || typeof err !== 'object') {
96
+ return false;
97
+ }
98
+
99
+ if (err.message.match(/Unknown error/i)) {
100
+ return true;
101
+ }
102
+
103
+ return false;
104
+ }
105
+
87
106
  /**
88
107
  * Prepare the hostname to properly use IPv6 address
89
108
  * @param {string} hostname
@@ -25,6 +25,12 @@ exports.config = {
25
25
  //
26
26
  user: process.env.<%= answers.env_user %>,
27
27
  key: process.env.<%= answers.env_key %>,
28
+ //
29
+ // If you run your tests on SauceLabs you can specify the region you want to run your tests
30
+ // in via the `region` property. You can either provide the full region name or the short handle:
31
+ // us: us-west-1 (default)
32
+ // eu: eu-central-1
33
+ // region: 'eu', // for eu-central-1
28
34
  <% }
29
35
  if(answers.backend.indexOf('In the cloud') > -1) { %>
30
36
  <% } %>
@@ -73,8 +73,11 @@ function execute() {
73
73
  return this.requestHandler.create('/session/:sessionId/execute', { script, args }).catch(function (err) {
74
74
  /**
75
75
  * jsonwire command not supported try webdriver endpoint
76
+ * Note: MicrosoftWebDriver returns "UnknownError" when receiving
77
+ * a jsonwire command in W3C Mode
78
+ * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/19917561/
76
79
  */
77
- if ((0, _utilities.isUnknownCommand)(err)) {
80
+ if ((0, _utilities.isUnknownCommand)(err) || (0, _utilities.isUnknownError)(err)) {
78
81
  return _this.requestHandler.create('/session/:sessionId/execute/sync', { script, args });
79
82
  }
80
83
 
@@ -12,6 +12,8 @@ exports.default = moveTo;
12
12
 
13
13
  var _ErrorHandler = require('../utils/ErrorHandler');
14
14
 
15
+ var _utilities = require('../helpers/utilities');
16
+
15
17
  var _eventSimulator = require('../scripts/eventSimulator');
16
18
 
17
19
  var _eventSimulator2 = _interopRequireDefault(_eventSimulator);
@@ -20,9 +22,15 @@ var _deprecationWarning = require('../helpers/deprecationWarning');
20
22
 
21
23
  var _deprecationWarning2 = _interopRequireDefault(_deprecationWarning);
22
24
 
25
+ var _findMoveToCoordinates = require('../helpers/findMoveToCoordinates');
26
+
27
+ var _findMoveToCoordinates2 = _interopRequireDefault(_findMoveToCoordinates);
28
+
23
29
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
24
30
 
25
31
  function moveTo(element, xoffset, yoffset) {
32
+ var _this = this;
33
+
26
34
  var data = {};
27
35
 
28
36
  if (typeof element === 'string') {
@@ -37,6 +45,12 @@ function moveTo(element, xoffset, yoffset) {
37
45
  data.yoffset = yoffset;
38
46
  }
39
47
 
48
+ if (this.isMobile) {
49
+ return _findMoveToCoordinates2.default.call(this, element, xoffset, yoffset).then(function (coordinates) {
50
+ return _this.touchMove(coordinates.x, coordinates.y);
51
+ });
52
+ }
53
+
40
54
  /**
41
55
  * if no attribute is set, throw error
42
56
  */
@@ -61,7 +75,27 @@ function moveTo(element, xoffset, yoffset) {
61
75
  }, { ELEMENT: element }, target.x, target.y);
62
76
  }
63
77
 
64
- return this.requestHandler.create('/session/:sessionId/moveto', data);
78
+ return this.requestHandler.create('/session/:sessionId/moveto', data).catch(function (err) {
79
+ /**
80
+ * jsonwire command not supported try w3c protocol endpoint
81
+ */
82
+ if ((0, _utilities.isUnknownCommand)(err)) {
83
+ return _findMoveToCoordinates2.default.call(_this, element, xoffset, yoffset).then(function (coordinates) {
84
+ return _this.actions([{
85
+ type: 'pointer',
86
+ id: 'mouse',
87
+ actions: [{
88
+ type: 'pointerMove',
89
+ duration: 0,
90
+ x: coordinates.x,
91
+ y: coordinates.y
92
+ }]
93
+ }]);
94
+ });
95
+ }
96
+
97
+ throw err;
98
+ });
65
99
  } /**
66
100
  *
67
101
  * Move the mouse by an offset of the specified element. If no element is specified,
@@ -265,7 +265,22 @@ var BaseReporter = function (_events$EventEmitter) {
265
265
  }
266
266
 
267
267
  /**
268
- * Outut the given failures as a list
268
+ * Output the given passes as a list
269
+ */
270
+
271
+ }, {
272
+ key: 'listPasses',
273
+ value: function listPasses() {
274
+ console.log();
275
+ this.stats.getPasses().forEach(function (test, i) {
276
+ var fmt = this.color('green', '%s) %s:\n') + this.color('green', '%s') + this.color('bright yellow', '%s');
277
+ var title = typeof test.fullTitle !== 'undefined' ? test.fullTitle : typeof test.parent !== 'undefined' ? test.parent + ' ' + test.title : test.title;
278
+ console.log(fmt, i + 1, title, test.nonerr.message, test.runningBrowser);
279
+ });
280
+ }
281
+
282
+ /**
283
+ * Output the given failures as a list
269
284
  */
270
285
 
271
286
  }, {
@@ -24,21 +24,21 @@ var _createClass2 = require('babel-runtime/helpers/createClass');
24
24
 
25
25
  var _createClass3 = _interopRequireDefault(_createClass2);
26
26
 
27
- var _fs = require('fs');
27
+ var _deepmerge = require('deepmerge');
28
28
 
29
- var _fs2 = _interopRequireDefault(_fs);
29
+ var _deepmerge2 = _interopRequireDefault(_deepmerge);
30
30
 
31
- var _path = require('path');
31
+ var _fs = require('fs');
32
32
 
33
- var _path2 = _interopRequireDefault(_path);
33
+ var _fs2 = _interopRequireDefault(_fs);
34
34
 
35
35
  var _glob = require('glob');
36
36
 
37
37
  var _glob2 = _interopRequireDefault(_glob);
38
38
 
39
- var _deepmerge = require('deepmerge');
39
+ var _path = require('path');
40
40
 
41
- var _deepmerge2 = _interopRequireDefault(_deepmerge);
41
+ var _path2 = _interopRequireDefault(_path);
42
42
 
43
43
  var _detectSeleniumBackend = require('../helpers/detectSeleniumBackend');
44
44
 
@@ -114,6 +114,8 @@ var DEFAULT_CONFIGS = {
114
114
  };
115
115
  var FILE_EXTENSIONS = ['.js', '.ts', '.feature', '.coffee', '.es6'];
116
116
 
117
+ var FEATURE_FILE_SPEC_REGEX = /^(.+\.feature):[0-9]+$/;
118
+
117
119
  var ConfigParser = function () {
118
120
  function ConfigParser() {
119
121
  (0, _classCallCheck3.default)(this, ConfigParser);
@@ -420,7 +422,18 @@ var ConfigParser = function () {
420
422
  var filePathList = ConfigParser.getFilePaths(config);
421
423
 
422
424
  var _loop = function _loop(file) {
423
- if (_fs2.default.existsSync(file) && _fs2.default.lstatSync(file).isFile()) {
425
+ var match = file.match(FEATURE_FILE_SPEC_REGEX);
426
+ var filename = void 0;
427
+ /*
428
+ * check whether a file is a feature file specifying a scenario by its line number
429
+ * if this is the case extract filename part
430
+ */
431
+ if (match) {
432
+ filename = match[1];
433
+ } else {
434
+ filename = file;
435
+ }
436
+ if (_fs2.default.existsSync(filename) && _fs2.default.lstatSync(filename).isFile()) {
424
437
  filesToFilter.add(_path2.default.resolve(process.cwd(), file));
425
438
  return 'continue';
426
439
  }
@@ -495,18 +508,27 @@ var ConfigParser = function () {
495
508
  for (var _iterator6 = (0, _getIterator3.default)(patterns), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
496
509
  var pattern = _step6.value;
497
510
 
498
- var filenames = _glob2.default.sync(pattern);
499
-
500
- filenames = filenames.filter(function (filename) {
501
- return FILE_EXTENSIONS.indexOf(_path2.default.extname(filename)) !== -1;
502
- });
503
-
504
- filenames = filenames.map(function (filename) {
505
- return _path2.default.isAbsolute(filename) ? _path2.default.normalize(filename) : _path2.default.resolve(process.cwd(), filename);
506
- });
507
-
508
- if (filenames.length === 0 && !omitWarnings) {
509
- console.warn('pattern', pattern, 'did not match any file');
511
+ var filenames = void 0;
512
+ /*
513
+ * check whether a pattern is a feature file specifying a scenario by its line number
514
+ * if this is the case don't glob the pattern
515
+ */
516
+ if (pattern.match(FEATURE_FILE_SPEC_REGEX)) {
517
+ filenames = [_path2.default.isAbsolute(pattern) ? _path2.default.normalize(pattern) : _path2.default.resolve(process.cwd(), pattern)];
518
+ } else {
519
+ filenames = _glob2.default.sync(pattern);
520
+
521
+ filenames = filenames.filter(function (filename) {
522
+ return FILE_EXTENSIONS.indexOf(_path2.default.extname(filename)) !== -1;
523
+ });
524
+
525
+ filenames = filenames.map(function (filename) {
526
+ return _path2.default.isAbsolute(filename) ? _path2.default.normalize(filename) : _path2.default.resolve(process.cwd(), filename);
527
+ });
528
+
529
+ if (filenames.length === 0 && !omitWarnings) {
530
+ console.warn('pattern', pattern, 'did not match any file');
531
+ }
510
532
  }
511
533
 
512
534
  files = (0, _deepmerge2.default)(files, filenames, MERGE_OPTIONS);
@@ -195,12 +195,18 @@ var ReporterStats = function (_RunnableStats6) {
195
195
  failures: 0
196
196
  };
197
197
  this.failures = [];
198
+ this.passes = [];
198
199
  }
199
200
  }, {
200
201
  key: 'getCounts',
201
202
  value: function getCounts() {
202
203
  return this.counts;
203
204
  }
205
+ }, {
206
+ key: 'getPasses',
207
+ value: function getPasses() {
208
+ return this.passes;
209
+ }
204
210
  }, {
205
211
  key: 'getFailures',
206
212
  value: function getFailures() {
@@ -414,6 +420,7 @@ var ReporterStats = function (_RunnableStats6) {
414
420
  value: function testPass(runner) {
415
421
  this.getTestStats(runner).state = 'pass';
416
422
  this.counts.passes++;
423
+ this.passes.push(runner);
417
424
  }
418
425
  }, {
419
426
  key: 'testPending',
@@ -126,6 +126,8 @@ var RequestHandler = function () {
126
126
  newOptions.qs = this.defaultOptions.queryParams;
127
127
  }
128
128
 
129
+ this.defaultOptions.protocol = this.defaultOptions.protocol || 'http';
130
+
129
131
  newOptions.uri = _url2.default.parse(this.defaultOptions.protocol + '://' + (0, _utilities.formatHostname)(this.defaultOptions.hostname) + ':' + this.defaultOptions.port + (requestOptions.gridCommand ? this.gridApiStartPath : this.startPath) + requestOptions.path.replace(':sessionId', this.sessionID || ''));
130
132
 
131
133
  // send authentication credentials only when creating new session
@@ -277,7 +279,7 @@ var RequestHandler = function () {
277
279
  }
278
280
 
279
281
  if (fullRequestOptions.gridCommand) {
280
- if (body.success) {
282
+ if (body && body.success) {
281
283
  return resolve({ body, response });
282
284
  }
283
285
 
@@ -316,6 +318,16 @@ var RequestHandler = function () {
316
318
  return reject(new _ErrorHandler.RuntimeError(error));
317
319
  }
318
320
 
321
+ // IEServer webdriver bug where the error is put into the Allow header
322
+ // https://github.com/SeleniumHQ/selenium/issues/6828
323
+ if (response && response.statusCode === 405) {
324
+ var allowHeader = response.headers && response.headers.allow;
325
+ var _err = new _ErrorHandler.RuntimeError(allowHeader);
326
+ if ((0, _utilities.isUnknownCommand)(_err)) {
327
+ return reject(_err);
328
+ }
329
+ }
330
+
319
331
  if (retryCount >= totalRetryCount) {
320
332
  var message = 'Couldn\'t connect to selenium server';
321
333
  var status = -1;
@@ -113,7 +113,6 @@ var WebdriverIO = function WebdriverIO(args, modifier) {
113
113
  * merge default options with given user options
114
114
  */
115
115
  var options = (0, _deepmerge2.default)({
116
- protocol: 'http',
117
116
  waitforTimeout: 1000,
118
117
  waitforInterval: 500,
119
118
  coloredLogs: true,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "webdriverio",
3
3
  "description": "A Node.js bindings implementation for the W3C WebDriver protocol",
4
- "version": "4.14.0",
4
+ "version": "4.14.4",
5
5
  "homepage": "http://webdriver.io",
6
6
  "author": "Camilo Tapia <camilo.tapia@gmail.com>",
7
7
  "repository": {
@@ -136,3 +136,29 @@ Boilerplate repo for quick set up of WebdriverIO test scripts with TravisCI, Sau
136
136
  - Local notifications
137
137
  - ESLint using Semistandard style
138
138
  - WebdriverIO tuned Gitignore file
139
+
140
+ ## [webdriverio/appium-boilerplate](https://github.com/webdriverio/appium-boilerplate/)
141
+
142
+ Boilerplate project to run Appium tests together with WebdriverIO for:
143
+
144
+ - iOS/Android Native Apps
145
+ - iOS/Android Hybrid Apps
146
+ - Android Chrome and iOS Safari browser
147
+
148
+ The boilerplate holds the following things
149
+ - Framework: Jasmine
150
+ - Features:
151
+ - Configs for:
152
+ - iOS and Android app
153
+ - iOS and Android browsers
154
+ - Helpers for:
155
+ - WebView
156
+ - Gestures
157
+ - Native alerts
158
+ - Pickers
159
+ - Tests examples for:
160
+ - WebView
161
+ - Login
162
+ - Forms
163
+ - Swipe
164
+ - Browsers
@@ -37,6 +37,12 @@ exports.config = {
37
37
  user: 'webdriverio',
38
38
  key: 'xxxxxxxxxxxxxxxx-xxxxxx-xxxxx-xxxxxxxxx',
39
39
  //
40
+ // If you run your tests on SauceLabs you can specify the region you want to run your tests
41
+ // in via the `region` property. You can either provide the full region name or the short handle:
42
+ // us: us-west-1 (default)
43
+ // eu: eu-central-1
44
+ region: 'eu', // for eu-central-1
45
+ //
40
46
  // ==================
41
47
  // Specify Test Files
42
48
  // ==================
@@ -82,8 +88,8 @@ exports.config = {
82
88
  chromeOptions: {
83
89
  // to run chrome headless the following flags are required
84
90
  // (see https://developers.google.com/web/updates/2017/04/headless-chrome)
85
- // args: ['--headless', '--disable-gpu'],
86
- }
91
+ // args: ['--headless', '--disable-gpu'],
92
+ }
87
93
  }, {
88
94
  // maxInstances can get overwritten per capability. So if you have an in house Selenium
89
95
  // grid with only 5 firefox instance available you can make sure that not more than
@@ -142,9 +148,9 @@ exports.config = {
142
148
  // Saves a screenshot to a given path if a command fails.
143
149
  screenshotPath: 'shots',
144
150
  //
145
- // Set a base URL in order to shorten url command calls. If your `url` parameter starts
146
- // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
147
- // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
151
+ // Set a base URL in order to shorten url command calls. If your `url` parameter starts
152
+ // with `/`, the base url gets prepended, not including the path portion of your baseUrl.
153
+ // If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url
148
154
  // gets prepended directly.
149
155
  baseUrl: 'http://localhost:8080',
150
156
  //
@@ -257,7 +263,7 @@ exports.config = {
257
263
  * @param {Array.<String>} specs List of spec file paths that are to be run
258
264
  */
259
265
  beforeSession: function (config, capabilities, specs) {
260
- },
266
+ },
261
267
  /**
262
268
  * Gets executed before test execution begins. At this point you can access to all global
263
269
  * variables like `browser`. It is the perfect place to define custom commands.
@@ -111,6 +111,12 @@ or run multiple specs at once:
111
111
  $ wdio wdio.conf.js --spec ./test/specs/signup.js,./test/specs/forgot-password.js
112
112
  ```
113
113
 
114
+ In case of a feature (Cucumber) you can specify a single scenario by adding a line number:
115
+
116
+ ```sh
117
+ $ wdio wdio.conf.js --spec ./test/specs/login.feature:6
118
+ ```
119
+
114
120
  If the spec passed in is not a path to a spec file, it is used as a filter for the spec file names defined in your configuration file. To run all specs with the word 'dialog' in the spec file names, you could use:
115
121
 
116
122
  ```sh
@@ -48,7 +48,7 @@ let deleteCookie = function (name) {
48
48
  name = null
49
49
  }
50
50
 
51
- return this.cookie('DELETE', name)
51
+ return this.cookie('DELETE', typeof name === 'string' ? encodeURIComponent(encodeURIComponent((name))) : name)
52
52
  }
53
53
 
54
54
  export default deleteCookie
@@ -43,7 +43,7 @@ let getCookie = function (name) {
43
43
  res.value = res.value || []
44
44
 
45
45
  if (typeof name === 'string') {
46
- return res.value.filter((cookie) => cookie.name === name)[0] || null
46
+ return res.value.filter((cookie) => cookie.name === encodeURIComponent(encodeURIComponent(name)))[0] || null
47
47
  }
48
48
 
49
49
  return res.value || (typeof name === 'string' ? null : [])
@@ -3,6 +3,8 @@
3
3
  * Apply left click on an element. If selector is not provided, click on the last
4
4
  * moved-to location.
5
5
  *
6
+ * Uses JSONWireframe buttonPress protocol with W3C actions protocol as fallback.
7
+ *
6
8
  * @alias browser.leftClick
7
9
  * @param {String} selector element to click on
8
10
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element.
@@ -3,6 +3,8 @@
3
3
  * Apply middle click on an element. If selector is not provided, click on the last
4
4
  * moved-to location.
5
5
  *
6
+ * Uses JSONWireframe buttonPress protocol with W3C actions protocol as fallback.
7
+ *
6
8
  * @alias browser.middleClick
7
9
  * @param {String} selector element to click on
8
10
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element.
@@ -4,53 +4,20 @@
4
4
  * offset, the mouse will be moved to the center of the element. If the element is not
5
5
  * visible, it will be scrolled into view.
6
6
  *
7
+ * Uses JSONWireframe moveTo protocol with W3C actions protocol fallback.
8
+ * Uses touchMove protocol for mobile.
9
+ *
7
10
  * @alias browser.moveToObject
8
11
  * @param {String} selector element to move to
9
12
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
10
13
  * @param {Number} yoffset Y offset to move to, relative to the top-left corner of the element. If not specified, the mouse will move to the middle of the element.
11
- * @uses protocol/element, protocol/elementIdLocation
14
+ * @uses protocol/element, protocol/elementIdLocation, protocol/elementIdSize
12
15
  * @type action
13
16
  *
14
17
  */
15
-
16
18
  import { RuntimeError } from '../utils/ErrorHandler'
17
19
 
18
20
  let moveToObject = function (selector, xoffset, yoffset) {
19
- /**
20
- * check for offset params
21
- */
22
- var hasOffsetParams = true
23
- if (typeof xoffset !== 'number' && typeof yoffset !== 'number') {
24
- hasOffsetParams = false
25
- }
26
-
27
- if (this.isMobile) {
28
- return this.element(selector).then((res) => {
29
- /**
30
- * check if element was found and throw error if not
31
- */
32
- if (!res.value) {
33
- throw new RuntimeError(7)
34
- }
35
-
36
- return this.elementIdSize(res.value.ELEMENT).then((size) =>
37
- this.elementIdLocation(res.value.ELEMENT).then((location) => {
38
- return { size, location }
39
- })
40
- )
41
- }).then((res) => {
42
- let x = res.location.value.x + (res.size.value.width / 2)
43
- let y = res.location.value.y + (res.size.value.height / 2)
44
-
45
- if (hasOffsetParams) {
46
- x = res.location.value.x + xoffset
47
- y = res.location.value.y + yoffset
48
- }
49
-
50
- return this.touchMove(x, y)
51
- })
52
- }
53
-
54
21
  return this.element(selector).then((res) => {
55
22
  /**
56
23
  * check if element was found and throw error if not
@@ -3,6 +3,8 @@
3
3
  * Apply right click on an element. If selector is not provided, click on the last
4
4
  * moved-to location.
5
5
  *
6
+ * Uses JSONWireframe buttonPress protocol with W3C actions protocol as fallback.
7
+ *
6
8
  * @alias browser.rightClick
7
9
  * @param {String} selector element to click on
8
10
  * @param {Number} xoffset X offset to move to, relative to the top-left corner of the element.
@@ -32,14 +32,22 @@
32
32
  import { CommandError } from '../utils/ErrorHandler'
33
33
 
34
34
  let setCookie = function (cookieObj) {
35
+ const { name } = cookieObj
35
36
  /*!
36
37
  * parameter check
37
38
  */
38
- if (typeof cookieObj !== 'object') {
39
+ if (typeof cookieObj !== 'object' || !name || typeof name !== 'string') {
39
40
  throw new CommandError('Please specify a cookie object to set (see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object for documentation.')
40
41
  }
41
42
 
42
- return this.cookie('POST', cookieObj)
43
+ return this.cookie(
44
+ 'POST',
45
+ Object.assign(
46
+ {},
47
+ cookieObj,
48
+ { name: encodeURIComponent(encodeURIComponent(name)) }
49
+ )
50
+ )
43
51
  }
44
52
 
45
53
  export default setCookie
@@ -36,7 +36,7 @@
36
36
  */
37
37
 
38
38
  import { ProtocolError } from '../utils/ErrorHandler'
39
- import { isUnknownCommand } from '../helpers/utilities'
39
+ import { isUnknownCommand, isUnknownError } from '../helpers/utilities'
40
40
 
41
41
  export default function execute (...args) {
42
42
  let script = args.shift()
@@ -59,8 +59,11 @@ export default function execute (...args) {
59
59
  return this.requestHandler.create('/session/:sessionId/execute', { script, args }).catch((err) => {
60
60
  /**
61
61
  * jsonwire command not supported try webdriver endpoint
62
+ * Note: MicrosoftWebDriver returns "UnknownError" when receiving
63
+ * a jsonwire command in W3C Mode
64
+ * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/19917561/
62
65
  */
63
- if (isUnknownCommand(err)) {
66
+ if (isUnknownCommand(err) || isUnknownError(err)) {
64
67
  return this.requestHandler.create('/session/:sessionId/execute/sync', { script, args })
65
68
  }
66
69
 
@@ -19,8 +19,10 @@
19
19
  */
20
20
 
21
21
  import { ProtocolError } from '../utils/ErrorHandler'
22
+ import { isUnknownCommand } from '../helpers/utilities'
22
23
  import eventSimulator from '../scripts/eventSimulator'
23
24
  import deprecate from '../helpers/deprecationWarning'
25
+ import findMoveToCoordinates from '../helpers/findMoveToCoordinates'
24
26
 
25
27
  export default function moveTo (element, xoffset, yoffset) {
26
28
  let data = {}
@@ -37,6 +39,10 @@ export default function moveTo (element, xoffset, yoffset) {
37
39
  data.yoffset = yoffset
38
40
  }
39
41
 
42
+ if (this.isMobile) {
43
+ return findMoveToCoordinates.call(this, element, xoffset, yoffset).then(coordinates => this.touchMove(coordinates.x, coordinates.y))
44
+ }
45
+
40
46
  /**
41
47
  * if no attribute is set, throw error
42
48
  */
@@ -67,5 +73,25 @@ export default function moveTo (element, xoffset, yoffset) {
67
73
  }, { ELEMENT: element }, target.x, target.y)
68
74
  }
69
75
 
70
- return this.requestHandler.create('/session/:sessionId/moveto', data)
76
+ return this.requestHandler.create('/session/:sessionId/moveto', data).catch(err => {
77
+ /**
78
+ * jsonwire command not supported try w3c protocol endpoint
79
+ */
80
+ if (isUnknownCommand(err)) {
81
+ return findMoveToCoordinates.call(this, element, xoffset, yoffset).then((coordinates) => {
82
+ return this.actions([{
83
+ type: 'pointer',
84
+ id: 'mouse',
85
+ actions: [{
86
+ type: 'pointerMove',
87
+ duration: 0,
88
+ x: coordinates.x,
89
+ y: coordinates.y
90
+ }]
91
+ }])
92
+ })
93
+ }
94
+
95
+ throw err
96
+ })
71
97
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "webdriverio",
3
3
  "description": "A Node.js bindings implementation for the W3C WebDriver protocol",
4
- "version": "4.14.0",
4
+ "version": "4.14.4",
5
5
  "homepage": "http://webdriver.io",
6
6
  "author": "Camilo Tapia <camilo.tapia@gmail.com>",
7
7
  "repository": {