diff --git a/app/main/appium-method-handler.js b/app/main/appium-method-handler.js
index f4ac4db61..5dfe7f426 100644
--- a/app/main/appium-method-handler.js
+++ b/app/main/appium-method-handler.js
@@ -1,5 +1,6 @@
import Bluebird from 'bluebird';
import _ from 'lodash';
+import wd from 'wd';
export default class AppiumMethodHandler {
constructor (driver) {
@@ -80,7 +81,20 @@ export default class AppiumMethodHandler {
async executeMethod (methodName, args = []) {
let res = {};
- if (methodName !== 'source' && methodName !== 'screenshot') {
+
+ // Specially handle the tap and swipe method
+ if (methodName === 'tap') {
+ res = await (new wd.TouchAction(this.driver))
+ .tap({x: args[0], y: args[1]})
+ .perform();
+ } else if (methodName === 'swipe') {
+ const [startX, startY, endX, endY] = args;
+ res = await (new wd.TouchAction(this.driver))
+ .press({x: startX, y: startY})
+ .moveTo({x: endX, y: endY})
+ .release()
+ .perform();
+ } else if (methodName !== 'source' && methodName !== 'screenshot') {
res = await this.driver[methodName].apply(this.driver, args);
}
@@ -94,7 +108,7 @@ export default class AppiumMethodHandler {
res,
};
}
-
+
async _getSourceAndScreenshot () {
let source, sourceError, screenshot, screenshotError;
try {
diff --git a/app/main/appium.js b/app/main/appium.js
index d6ef9c1ed..70d71856e 100644
--- a/app/main/appium.js
+++ b/app/main/appium.js
@@ -278,7 +278,7 @@ function connectClientMethodListener () {
} = data;
let renderer = evt.sender;
- let driver = appiumHandlers[renderer.id];
+ let methodHandler = appiumHandlers[renderer.id];
try {
if (methodName === 'quit') {
@@ -296,18 +296,18 @@ function connectClientMethodListener () {
if (methodName) {
if (elementId) {
console.log(`Handling client method request with method '${methodName}', args ${JSON.stringify(args)} and elementId ${elementId}`);
- res = await driver.executeElementCommand(elementId, methodName, args);
+ res = await methodHandler.executeElementCommand(elementId, methodName, args);
} else {
console.log(`Handling client method request with method '${methodName}' and args ${JSON.stringify(args)}`);
- res = await driver.executeMethod(methodName, args);
+ res = await methodHandler.executeMethod(methodName, args);
}
} else if (strategy && selector) {
if (fetchArray) {
console.log(`Fetching elements with selector '${selector}' and strategy ${strategy}`);
- res = await driver.fetchElements(strategy, selector);
+ res = await methodHandler.fetchElements(strategy, selector);
} else {
console.log(`Fetching an element with selector '${selector}' and strategy ${strategy}`);
- res = await driver.fetchElement(strategy, selector);
+ res = await methodHandler.fetchElement(strategy, selector);
}
}
@@ -324,7 +324,7 @@ function connectClientMethodListener () {
renderer.send('appium-session-done', e);
}
console.log('Caught an exception: ', e);
- renderer.send('appium-client-command-response-error', {e, uuid});
+ renderer.send('appium-client-command-response-error', {e: e.message, uuid});
}
});
}
diff --git a/app/renderer/actions/Inspector.js b/app/renderer/actions/Inspector.js
index 4d3e25122..44397f0ea 100644
--- a/app/renderer/actions/Inspector.js
+++ b/app/renderer/actions/Inspector.js
@@ -47,6 +47,10 @@ export const ADD_ASSIGNED_VAR_CACHE = 'ADD_ASSIGNED_VAR_CACHE';
export const CLEAR_ASSIGNED_VAR_CACHE = 'CLEAR_ASSIGNED_VAR_CACHE';
export const SET_SCREENSHOT_INTERACTION_MODE = 'SET_SCREENSHOT_INTERACTION_MODE';
+export const SET_SWIPE_START = 'SET_SWIPE_START';
+export const SET_SWIPE_END = 'SET_SWIPE_END';
+export const CLEAR_SWIPE_ACTION = 'CLEAR_SWIPE_ACTION';
+
// Attributes on nodes that we know are unique to the node
const uniqueAttributes = [
'name',
@@ -375,4 +379,22 @@ export function selectScreenshotInteractionMode (screenshotInteractionMode) {
return (dispatch) => {
dispatch({type: SET_SCREENSHOT_INTERACTION_MODE, screenshotInteractionMode });
};
+}
+
+export function setSwipeStart (swipeStartX, swipeStartY) {
+ return (dispatch) => {
+ dispatch({type: SET_SWIPE_START, swipeStartX, swipeStartY});
+ };
+}
+
+export function setSwipeEnd (swipeEndX, swipeEndY) {
+ return (dispatch) => {
+ dispatch({type: SET_SWIPE_END, swipeEndX, swipeEndY});
+ };
+}
+
+export function clearSwipeAction () {
+ return (dispatch) => {
+ dispatch({type: CLEAR_SWIPE_ACTION});
+ };
}
\ No newline at end of file
diff --git a/app/renderer/actions/shared.js b/app/renderer/actions/shared.js
index 622ecec99..4da6dc8ae 100644
--- a/app/renderer/actions/shared.js
+++ b/app/renderer/actions/shared.js
@@ -27,7 +27,7 @@ if (ipcRenderer) {
const {e, uuid} = resp;
let promise = clientMethodPromises[uuid];
if (promise) {
- promise.reject(e);
+ promise.reject(new Error(e));
delete clientMethodPromises[uuid];
}
});
diff --git a/app/renderer/components/Inspector/Actions.js b/app/renderer/components/Inspector/Actions.js
index 13992d9d9..aadbfd1a6 100644
--- a/app/renderer/components/Inspector/Actions.js
+++ b/app/renderer/components/Inspector/Actions.js
@@ -1,5 +1,5 @@
import React, { Component } from 'react';
-import { Button, Radio, Icon } from 'antd';
+import { Radio } from 'antd';
/**
@@ -8,8 +8,8 @@ import { Button, Radio, Icon } from 'antd';
export default class Actions extends Component {
handleScreenshotInteractionChange (e) {
- const {selectScreenshotInteractionMode} = this.props;
-
+ const {selectScreenshotInteractionMode, clearSwipeAction} = this.props;
+ clearSwipeAction(); // When the action changes, reset the swipe action
selectScreenshotInteractionMode(e.target.value);
}
diff --git a/app/renderer/components/Inspector/HighlighterRects.js b/app/renderer/components/Inspector/HighlighterRects.js
new file mode 100644
index 000000000..35d98a728
--- /dev/null
+++ b/app/renderer/components/Inspector/HighlighterRects.js
@@ -0,0 +1,142 @@
+import React, { Component } from 'react';
+import ReactDOM from 'react-dom';
+import { debounce } from 'lodash';
+import HighlighterRect from './HighlighterRect';
+import B from 'bluebird';
+import { parseCoordinates } from './shared';
+
+/**
+ * Shows screenshot of running application and divs that highlight the elements' bounding boxes
+ */
+export default class HighlighterRects extends Component {
+
+ constructor (props) {
+ super(props);
+ this.state = {
+ scaleRatio: 1,
+ };
+ this.updateScaleRatio = debounce(this.updateScaleRatio.bind(this), 1000);
+ }
+
+ /**
+ * Calculates the ratio that the image is being scaled by
+ */
+ updateScaleRatio () {
+ const screenshotEl = this.props.containerEl.querySelector('img');
+
+ // now update scale ratio
+ const {x1, x2} = parseCoordinates(this.props.source.children[0].children[0]);
+ this.setState({
+ scaleRatio: (x2 - x1) / screenshotEl.offsetWidth
+ });
+
+ }
+
+ async handleScreenshotClick () {
+ const {screenshotInteractionMode, applyClientMethod,
+ swipeStart, swipeEnd, setSwipeStart, setSwipeEnd} = this.props;
+ const {x, y} = this.state;
+
+ if (screenshotInteractionMode === 'tap') {
+ applyClientMethod({
+ methodName: 'tap',
+ args: [x, y],
+ });
+ } else if (screenshotInteractionMode === 'swipe') {
+ if (!swipeStart) {
+ setSwipeStart(x, y);
+ } else if (!swipeEnd) {
+ setSwipeEnd(x, y);
+ await B.delay(500); // Wait a second to do the swipe so user can see the SVG line
+ await this.handleDoSwipe();
+ }
+ }
+ }
+
+ handleMouseMove (e) {
+ const {screenshotInteractionMode} = this.props;
+ const {scaleRatio} = this.state;
+
+ if (screenshotInteractionMode !== 'select') {
+ const offsetX = e.nativeEvent.offsetX;
+ const offsetY = e.nativeEvent.offsetY;
+ const x = offsetX * scaleRatio;
+ const y = offsetY * scaleRatio;
+ this.setState({
+ ...this.state,
+ x: Math.round(x),
+ y: Math.round(y),
+ });
+ }
+ }
+
+ handleMouseOut () {
+ this.setState({
+ ...this.state,
+ x: null,
+ y: null,
+ });
+ }
+
+ async handleDoSwipe () {
+ const {swipeStart, swipeEnd, clearSwipeAction, applyClientMethod} = this.props;
+ await applyClientMethod({
+ methodName: 'swipe',
+ args: [swipeStart.x, swipeStart.y, swipeEnd.x - swipeStart.x, swipeEnd.y - swipeStart.y],
+ });
+ clearSwipeAction();
+ }
+
+ componentDidMount () {
+ // When DOM is ready, calculate the image scale ratio and re-calculate it whenever the window is resized
+ this.updateScaleRatio();
+ window.addEventListener('resize', this.updateScaleRatio);
+ }
+
+ componentWillUnmount () {
+ window.removeEventListener('resize', this.updateScaleRatio);
+ }
+
+ render () {
+ const {source, screenshotInteractionMode, containerEl} = this.props;
+ const {scaleRatio} = this.state;
+
+ // Recurse through the 'source' JSON and render a highlighter rect for each element
+ const highlighterRects = [];
+
+ let highlighterXOffset = 0;
+ if (containerEl) {
+ const screenshotEl = containerEl.querySelector('img');
+ highlighterXOffset = screenshotEl.getBoundingClientRect().left -
+ containerEl.getBoundingClientRect().left;
+ }
+
+ // TODO: Refactor this into a separate component
+ let recursive = (element, zIndex = 0) => {
+ if (!element) {
+ return;
+ }
+ highlighterRects.push(
X: {x}
+Y: {y}
+Click swipe start
} + {swipeStart && !swipeEnd &&Click swipe end
} +