Skip to content
This repository has been archived by the owner on Apr 25, 2023. It is now read-only.

feat: add locator timings #1448

Merged
merged 4 commits into from
Jul 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/main/appium-method-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Bluebird from 'bluebird';
import wd from 'wd';
import log from 'electron-log';
import _ from 'lodash';
import { timing } from 'appium-support';
import { SCREENSHOT_INTERACTION_MODE } from '../renderer/components/Inspector/shared';
import {getWebviewStatusAddressBarHeight, parseSource, setHtmlElementAttributes} from './webviewHelpers';

Expand Down Expand Up @@ -64,7 +65,11 @@ export default class AppiumMethodHandler {
}

async fetchElement (strategy, selector) {
const timer = new timing.Timer().start();
let element = await this.driver.elementOrNull(strategy, selector);
const duration = timer.getDuration();
const executionTime = Math.round(duration.asMilliSeconds);

if (element === null) {
return {};
}
Expand All @@ -84,6 +89,7 @@ export default class AppiumMethodHandler {
strategy,
selector,
id,
executionTime,
};
}

Expand Down
23 changes: 23 additions & 0 deletions app/renderer/actions/Inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export const SET_LOCATOR_TEST_STRATEGY = 'SET_LOCATOR_TEST_STRATEGY';
export const SET_LOCATOR_TEST_VALUE = 'SET_LOCATOR_TEST_VALUE';
export const SEARCHING_FOR_ELEMENTS = 'SEARCHING_FOR_ELEMENTS';
export const SEARCHING_FOR_ELEMENTS_COMPLETED = 'SEARCHING_FOR_ELEMENTS_COMPLETED';
export const GET_FIND_ELEMENTS_TIMES = 'GET_FIND_ELEMENTS_TIMES';
export const GET_FIND_ELEMENTS_TIMES_COMPLETED = 'GET_FIND_ELEMENTS_TIMES_COMPLETED';
export const SET_LOCATOR_TEST_ELEMENT = 'SET_LOCATOR_TEST_ELEMENT';
export const CLEAR_SEARCH_RESULTS = 'CLEAR_SEARCH_RESULTS';
export const ADD_ASSIGNED_VAR_CACHE = 'ADD_ASSIGNED_VAR_CACHE';
Expand Down Expand Up @@ -367,6 +369,27 @@ export function searchForElement (strategy, selector) {
};
}

/**
* Get all the find element times based on the find data source
*/
export function getFindElementsTimes (findDataSource) {
return async (dispatch) => {
dispatch({type: GET_FIND_ELEMENTS_TIMES});
try {
const findElementsExecutionTimes = [];
for (const element of findDataSource) {
const {find, selector} = element;
const {executionTime} = await callClientMethod({strategy: find, selector});
findElementsExecutionTimes.push({find, key: find, selector, time: executionTime});
}
dispatch({type: GET_FIND_ELEMENTS_TIMES_COMPLETED, findElementsExecutionTimes});
} catch (error) {
dispatch({type: GET_FIND_ELEMENTS_TIMES_COMPLETED});
showError(error, 10);
}
};
}

export function findAndAssign (strategy, selector, variableName, isArray) {
return (dispatch, getState) => {
const {assignedVarCache} = getState().inspector;
Expand Down
32 changes: 17 additions & 15 deletions app/renderer/components/Inspector/Inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ export default class Inspector extends Component {
const {screenshot, screenshotError, selectedElement = {},
applyClientMethod, quitSession, isRecording, showRecord, startRecording,
pauseRecording, showLocatorTestModal,
screenshotInteractionMode,
screenshotInteractionMode, isFindingElementsTimes,
selectedInteractionMode, selectInteractionMode,
showKeepAlivePrompt, keepSessionAlive, sourceXML, t} = this.props;
const {path} = selectedElement;
Expand Down Expand Up @@ -215,19 +215,21 @@ export default class Inspector extends Component {
</ButtonGroup>
</div>;

return <div className={InspectorStyles['inspector-container']}>
{controls}
{main}
<Modal
title={t('Session Inactive')}
visible={showKeepAlivePrompt}
onOk={() => keepSessionAlive()}
onCancel={() => quitSession()}
okText={t('Keep Session Running')}
cancelText={t('Quit Session')}
>
<p>{t('Your session is about to expire')}</p>
</Modal>
</div>;
return (<Spin spinning={isFindingElementsTimes} key="main">
<div className={InspectorStyles['inspector-container']}>
{controls}
{main}
<Modal
title={t('Session Inactive')}
visible={showKeepAlivePrompt}
onOk={() => keepSessionAlive()}
onCancel={() => quitSession()}
okText={t('Keep Session Running')}
cancelText={t('Quit Session')}
>
<p>{t('Your session is about to expire')}</p>
</Modal>
</div>
</Spin>);
}
}
34 changes: 30 additions & 4 deletions app/renderer/components/Inspector/SelectedElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class SelectedElement extends Component {
contexts,
currentContext,
setFieldValue,
getFindElementsTimes,
findElementsExecutionTimes,
isFindingElementsTimes,
sendKeys,
selectedElement,
sendKeysModalVisible,
Expand All @@ -69,6 +72,7 @@ class SelectedElement extends Component {
t,
} = this.props;
const {attributes, xpath} = selectedElement;
const isDisabled = !elementId || isFindingElementsTimes;

// Get the columns for the attributes table
let attributeColumns = [{
Expand Down Expand Up @@ -106,13 +110,29 @@ class SelectedElement extends Component {
dataIndex: 'selector',
key: 'selector',
render: selectedElementTableCell
}, {
title: t('Time'),
dataIndex: 'time',
key: 'time',
render: selectedElementTableCell
}];

const getTimeButton = (<Tooltip title={t('getTimes')}>
<Button
disabled={isDisabled}
id='btnGetTimings'
onClick={() => getFindElementsTimes(findDataSource)}
>
{t('Get Timing')}
</Button>
</Tooltip>);

// Get the data for the strategies table
let findDataSource = _.toPairs(getLocators(attributes, sourceXML)).map(([key, selector]) => ({
key,
selector,
find: key,
time: getTimeButton,
}));

// If XPath is the only provided data source, warn the user about it's brittleness
Expand All @@ -127,9 +147,15 @@ class SelectedElement extends Component {
key: 'xpath',
find: 'xpath',
selector: xpath,
time: getTimeButton,
});
}

// Replace table data with table data that has the times
if (findElementsExecutionTimes.length > 0) {
findDataSource = findElementsExecutionTimes;
}

return <div>
{elementInteractionsNotAvailable && <Row type={ROW.FLEX} gutter={10}>
<Col>
Expand All @@ -140,30 +166,30 @@ class SelectedElement extends Component {
<Col>
<ButtonGroup size="small">
<Button
disabled={!elementId}
disabled={isDisabled}
icon={!elementInteractionsNotAvailable && !elementId && <LoadingOutlined/>}
id='btnTapElement'
onClick={() => applyClientMethod({methodName: 'click', elementId})}
>
{t('Tap')}
</Button>
<Button
disabled={!elementId}
disabled={isDisabled}
id='btnSendKeysToElement'
onClick={() => showSendKeysModal()}
>
{t('Send Keys')}
</Button>
<Button
disabled={!elementId}
disabled={isDisabled}
id='btnClearElement'
onClick={() => applyClientMethod({methodName: 'clear', elementId})}
>
{t('Clear')}
</Button>
<Tooltip title={t('Copy Attributes to Clipboard')}>
<Button
disabled={!elementId}
disabled={isDisabled}
id='btnCopyAttributes'
icon={<CopyOutlined/>}
onClick={() => clipboard.writeText(JSON.stringify(dataSource))}/>
Expand Down
21 changes: 20 additions & 1 deletion app/renderer/reducers/Inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SET_SOURCE_AND_SCREENSHOT, QUIT_SESSION_REQUESTED, QUIT_SESSION_DONE,
SEARCHING_FOR_ELEMENTS, SEARCHING_FOR_ELEMENTS_COMPLETED, SET_LOCATOR_TEST_ELEMENT, CLEAR_SEARCH_RESULTS,
ADD_ASSIGNED_VAR_CACHE, CLEAR_ASSIGNED_VAR_CACHE, SET_SCREENSHOT_INTERACTION_MODE,
SET_SWIPE_START, SET_SWIPE_END, CLEAR_SWIPE_ACTION, SET_SEARCHED_FOR_ELEMENT_BOUNDS, CLEAR_SEARCHED_FOR_ELEMENT_BOUNDS,
PROMPT_KEEP_ALIVE, HIDE_PROMPT_KEEP_ALIVE,
PROMPT_KEEP_ALIVE, HIDE_PROMPT_KEEP_ALIVE, GET_FIND_ELEMENTS_TIMES, GET_FIND_ELEMENTS_TIMES_COMPLETED,
SELECT_ACTION_GROUP, SELECT_SUB_ACTION_GROUP,
SELECT_INTERACTION_MODE, ENTERING_ACTION_ARGS, SET_ACTION_ARG, REMOVE_ACTION, SET_CONTEXT
} from '../actions/Inspector';
Expand Down Expand Up @@ -37,6 +37,8 @@ const INITIAL_STATE = {
selectedSubActionGroup: null,
selectedInteractionMode: INTERACTION_MODE.SOURCE,
pendingAction: null,
findElementsExecutionTimes: [],
isFindingElementsTimes: false,
};

/**
Expand Down Expand Up @@ -66,6 +68,7 @@ export default function inspector (state = INITIAL_STATE, action) {
screenshotError: action.screenshotError,
windowSize: action.windowSize,
windowSizeError: action.windowSizeError,
findElementsExecutionTimes: [],
};

case QUIT_SESSION_REQUESTED:
Expand Down Expand Up @@ -93,6 +96,7 @@ export default function inspector (state = INITIAL_STATE, action) {
selectedElement: findElementByPath(action.path, state.source),
selectedElementPath: action.path,
elementInteractionsNotAvailable: false,
findElementsExecutionTimes: [],
};

case UNSELECT_ELEMENT:
Expand All @@ -111,6 +115,7 @@ export default function inspector (state = INITIAL_STATE, action) {
selectedElementId: action.elementId,
selectedElementVariableName: action.variableName,
selectedElementVariableType: action.variableType,
findElementsExecutionTimes: [],
};

case SET_INTERACTIONS_NOT_AVAILABLE:
Expand Down Expand Up @@ -150,6 +155,7 @@ export default function inspector (state = INITIAL_STATE, action) {
return {
...state,
expandedPaths: action.paths,
findElementsExecutionTimes: [],
};

case SHOW_SEND_KEYS_MODAL:
Expand Down Expand Up @@ -269,6 +275,19 @@ export default function inspector (state = INITIAL_STATE, action) {
isSearchingForElements: false,
};

case GET_FIND_ELEMENTS_TIMES:
return {
...state,
isFindingElementsTimes: true,
};

case GET_FIND_ELEMENTS_TIMES_COMPLETED:
return {
...state,
findElementsExecutionTimes: action.findElementsExecutionTimes,
isFindingElementsTimes: false,
};

case SET_LOCATOR_TEST_ELEMENT:
return {
...state,
Expand Down
3 changes: 3 additions & 0 deletions assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@
"Value": "Value",
"Find By": "Find By",
"Selector": "Selector",
"Time": "Time (ms)",
"Get Timing": "Get Timing",
"getTimes": "Get the timing of each element locator strategy to see which one is the fastest and most efficient. See http://appium.io/docs/en/writing-running-appium/finding-elements/ for more info",
"Interactions are not available for this element": "Interactions are not available for this element",
"usingXPathNotRecommended": "Using XPath locators is not recommended and can lead to fragile tests. Ask your development team to provide unique accessibility locators instead!",
"usingSwitchContextRecommended": "Webview context(s) detected; certain elements might only be accessible after switching to a webview context, which must be done in an Appium script. See http://appium.io/docs/en/writing-running-appium/web/hybrid/ for more information",
Expand Down