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 2 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
5 changes: 5 additions & 0 deletions app/main/appium-method-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ export default class AppiumMethodHandler {
}

async fetchElement (strategy, selector) {
const start = process.hrtime.bigint();
wswebcreation marked this conversation as resolved.
Show resolved Hide resolved
let element = await this.driver.elementOrNull(strategy, selector);
const end = process.hrtime.bigint();
const executionTime = Math.round(Number(end - start) / 1000000);

if (element === null) {
return {};
}
Expand All @@ -84,6 +88,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>);
}
}
33 changes: 29 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,19 @@ class SelectedElement extends Component {
dataIndex: 'selector',
key: 'selector',
render: selectedElementTableCell
}, {
title: t('Time'),
dataIndex: 'time',
key: 'time',
render: selectedElementTableCell
}];

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

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

// 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 +156,39 @@ 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('getTimes')}>
<Button
disabled={isDisabled}
id='btnGetTimings'
onClick={() => getFindElementsTimes(findDataSource)}
>
{t('Get Times')}
wswebcreation marked this conversation as resolved.
Show resolved Hide resolved
</Button>
</Tooltip>
<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
2 changes: 2 additions & 0 deletions assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@
"Value": "Value",
"Find By": "Find By",
"Selector": "Selector",
"Time": "Time (ms)",
"getTimes": "Get the time 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