Skip to content

Commit 5f9a060

Browse files
committed
feat: first implementation
add wrapper over wdio commands: "swipe", "touch", "dragAndDrop"
1 parent e55afc6 commit 5f9a060

File tree

22 files changed

+891
-1
lines changed

22 files changed

+891
-1
lines changed

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,59 @@ You can read more about hermione plugins [here](https://github.com/gemini-testin
1212
```bash
1313
npm install hermione-safari-commands
1414
```
15+
16+
## Usage
17+
18+
Plugin has following configuration:
19+
20+
* **enabled** (optional) `Boolean` – enable/disable the plugin, by default plugin is enabled;
21+
* **browsers** (required) `Object` - the list of browsers to use for wrap commands;
22+
* **commands** (required) `Array` - commands which will be wrapped.
23+
24+
Also there is ability to override plugin parameters by CLI options or environment variables
25+
(see [configparser](https://github.com/gemini-testing/configparser)).
26+
Use `hermione_safari_commands_` prefix for the environment variables and `--hermione-safari-commands-` for the cli options.
27+
28+
Add plugin to your `hermione` config file:
29+
30+
```js
31+
module.exports = {
32+
// ...
33+
system: {
34+
plugins: {
35+
'hermione-safari-commands': {
36+
enabled: true,
37+
browsers: {
38+
safari13: {
39+
commands: [
40+
'swipe',
41+
'touch',
42+
'dragAndDrop'
43+
]
44+
}
45+
}
46+
}
47+
}
48+
},
49+
//...
50+
}
51+
```
52+
53+
### Existing safari commands:
54+
55+
Wrappers over existing commands:
56+
* **swipe** - replaces wdio "swipe" in order to perform swipe by coordinates in native context;
57+
* **touch** - replaces wdio "touch" in order to perform touch click by coordinates in native context;
58+
* **dragAndDrop** - replaces wdio "dragAndDrop" in order to perform drag and drop elements by coordinates in native context.
59+
60+
## Testing
61+
62+
Run [mocha](http://mochajs.org) tests:
63+
```bash
64+
npm run test-unit
65+
```
66+
67+
Run [eslint](http://eslint.org) codestyle verification
68+
```bash
69+
npm run lint
70+
```
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict';
2+
3+
const {NATIVE_CONTEXT} = require('../constants');
4+
5+
exports.runInNativeContext = async function(browser, action, testCtx) {
6+
if (!testCtx.webViewContext) {
7+
const {value: contexts} = await browser.contexts();
8+
testCtx.webViewContext = contexts[1];
9+
}
10+
11+
await browser.context(NATIVE_CONTEXT);
12+
const result = await action.fn.call(browser, ...[].concat(action.args));
13+
await browser.context(testCtx.webViewContext);
14+
15+
return result;
16+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const {getTestContext} = require('./test-context');
4+
const {runInNativeContext} = require('./context-switcher');
5+
const {TOP_TOOLBAR} = require('../native-locators');
6+
7+
exports.getTopToolbarHeight = async (browser) => {
8+
const testCtx = getTestContext(browser.executionContext);
9+
10+
if (!testCtx.topToolbarHeight || testCtx.isVerticalSwipePerformed) {
11+
const {height} = await runInNativeContext(browser, {fn: browser.getElementSize, args: TOP_TOOLBAR}, testCtx);
12+
13+
testCtx.topToolbarHeight = height;
14+
testCtx.isVerticalSwipePerformed = false;
15+
}
16+
17+
return testCtx.topToolbarHeight;
18+
};
19+
20+
exports.getElemCoords = async (browser, selector) => {
21+
const [{width, height}, {x, y}] = await Promise.all([browser.getElementSize(selector), browser.getLocation(selector)]);
22+
const topToolbarHeight = await exports.getTopToolbarHeight(browser);
23+
24+
return {width, height, x, y: y + topToolbarHeight};
25+
};
26+
27+
exports.getElemCenterLocation = async (browser, selector) => {
28+
const {width, height, x, y} = await exports.getElemCoords(browser, selector);
29+
30+
return {
31+
x: x + width / 2,
32+
y: y + height / 2
33+
};
34+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
exports.getTestContext = (context) => {
4+
return context.type === 'hook' && /^"before each"/.test(context.title)
5+
? context.ctx.currentTest
6+
: context;
7+
};

lib/commands/dragAndDrop.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const {getElemCenterLocation} = require('../command-helpers/element-utils');
4+
const {WAIT_BETWEEN_ACTIONS_IN_MS} = require('../constants');
5+
6+
module.exports = (browser) => {
7+
browser.addCommand('dragAndDrop', async (srcSelector, destSelector) => {
8+
const {x: srcX, y: srcY} = await getElemCenterLocation(browser, srcSelector);
9+
const {x: destX, y: destY} = await getElemCenterLocation(browser, destSelector);
10+
11+
await browser.touchAction([
12+
{action: 'tap', x: srcX, y: srcY},
13+
{action: 'wait', ms: WAIT_BETWEEN_ACTIONS_IN_MS},
14+
{action: 'moveTo', x: destX, y: destY},
15+
{action: 'wait', ms: WAIT_BETWEEN_ACTIONS_IN_MS},
16+
'release'
17+
]);
18+
}, true);
19+
};

lib/commands/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
module.exports = {
4+
swipe: require('./swipe'),
5+
touch: require('./touch'),
6+
dragAndDrop: require('./dragAndDrop')
7+
};

lib/commands/swipe.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const {getTestContext} = require('../command-helpers/test-context');
5+
const {getElemCenterLocation} = require('../command-helpers/element-utils');
6+
const {WAIT_BETWEEN_ACTIONS_IN_MS} = require('../constants');
7+
8+
module.exports = (browser) => {
9+
browser.addCommand('swipe', async (selector, xOffset = 0, yOffset = 0, speed = WAIT_BETWEEN_ACTIONS_IN_MS) => {
10+
if (typeof selector === 'number') {
11+
throw new TypeError(
12+
'Method "swipe" does not implement the functionality "swipe(xspeed, yspeed)"' +
13+
' try to use "swipe(selector, xOffset, yOffset, speed)"'
14+
);
15+
}
16+
17+
if (!_.isNumber(xOffset) || !_.isNumber(yOffset) || !_.isNumber(speed)) {
18+
throw new TypeError(
19+
'Arguments "xOffset", "yOffset" and "speed" must be a numbers'
20+
);
21+
}
22+
23+
const {x, y} = await getElemCenterLocation(browser, selector);
24+
25+
await browser.touchAction([
26+
{action: 'press', x, y},
27+
{action: 'wait', ms: speed <= 0 ? WAIT_BETWEEN_ACTIONS_IN_MS : speed},
28+
{action: 'moveTo', x: x + xOffset, y: y + yOffset},
29+
'release'
30+
]);
31+
32+
const testCtx = getTestContext(browser.executionContext);
33+
testCtx.isVerticalSwipePerformed = Boolean(yOffset);
34+
}, true);
35+
};

lib/commands/touch.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
3+
const {getElemCenterLocation} = require('../command-helpers/element-utils');
4+
5+
module.exports = (browser) => {
6+
browser.addCommand('touch', async (selector) => {
7+
const {x, y} = await getElemCenterLocation(browser, selector);
8+
9+
await browser.touchAction([
10+
{action: 'tap', x, y}
11+
]);
12+
}, true);
13+
};

lib/config/defaults.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
module.exports = {
4+
enabled: true,
5+
commands: null
6+
};

lib/config/index.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const configParser = require('gemini-configparser');
5+
const defaults = require('./defaults');
6+
7+
const thr = (str) => {
8+
throw new TypeError(str);
9+
};
10+
11+
const {root, map, section, option} = configParser;
12+
13+
const ENV_PREFIX = 'hermione_safari_commands_';
14+
const CLI_PREFIX = '--hermione-safari-commands-';
15+
16+
const assertType = (name, validationFn, type) => {
17+
return (v) => !validationFn(v) && thr(`"${name}" option must be ${type}, but got ${typeof v}`);
18+
};
19+
20+
const assertBoolean = (name) => assertType(name, _.isBoolean, 'boolean');
21+
const assertArrayOfStrings = (value, name) => {
22+
if (!(_.isArray(value) && value.every(_.isString))) {
23+
throw new Error(`"${name}" must be an array of strings but got ${JSON.stringify(value)}`);
24+
}
25+
};
26+
27+
const getParser = () => {
28+
return root(section({
29+
enabled: option({
30+
defaultValue: defaults.enabled,
31+
parseEnv: JSON.parse,
32+
parseCli: JSON.parse,
33+
validate: assertBoolean('enabled')
34+
}),
35+
browsers: map(section({
36+
commands: option({
37+
defaultValue: defaults.commands,
38+
parseEnv: JSON.parse,
39+
parseCli: JSON.parse,
40+
validate: (value) => {
41+
_.isNull(value)
42+
? thr('Each browser must have "commands" option')
43+
: assertArrayOfStrings(value, 'commands');
44+
}
45+
})
46+
}))
47+
}), {envPrefix: ENV_PREFIX, cliPrefix: CLI_PREFIX});
48+
};
49+
50+
module.exports = (options) => {
51+
const {env, argv} = process;
52+
53+
return getParser()({options, env, argv});
54+
};

0 commit comments

Comments
 (0)