Commit c368d9b6 authored by John Chen's avatar John Chen Committed by Commit Bot

Reland "[ChromeDriver] Handle pauses in Perform Actions"

This is a reland of 70ffd447

Original change was reverted due to test flakiness on Mac, caused by:
* Pause time often goes above the upper limit of 300 ms, sometime more than
  double the limit.
* Occasionally the first event is missing.

The following changes are made to improve test reliability:
* Remove the check of an upper limit for the pause time. With the wide range
  of actual pause time, there appears to be no feasible upper limit to use.
* The first significant event is now pointer down instead of key down, to
  ensure that the target element receives focus. The previous dependence on
  autofocus attribute appears to be unreliable.
* Change mouse button from 1 (middle) to 0 (left), as the middle mouse button
  triggers paste on some platforms. (It was a typo.)

Original change's description:
> [ChromeDriver] Handle pauses in Perform Actions
>
> W3C spec allows the app to specify pauses within Perform Actions command
> (https://w3c.github.io/webdriver/#dfn-dispatch-actions). Updating
> ChromeDriver to handle these pauses.
>
> Bug: chromedriver:1897
> Change-Id: I533179f4a9f2216bfacf0f4fdb539c8f898bed07
> Reviewed-on: https://chromium-review.googlesource.com/c/1392281
> Commit-Queue: John Chen <johnchen@chromium.org>
> Reviewed-by: Caleb Rouleau <crouleau@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#619506}

Bug: chromedriver:1897
Change-Id: I38122bdc01965345421429b1d2f45374395b0cc7
Reviewed-on: https://chromium-review.googlesource.com/c/1394786Reviewed-by: default avatarCaleb Rouleau <crouleau@chromium.org>
Commit-Queue: John Chen <johnchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#619717}
parent f32d2098
...@@ -777,6 +777,74 @@ class ChromeDriverTest(ChromeDriverBaseTestWithWebServer): ...@@ -777,6 +777,74 @@ class ChromeDriverTest(ChromeDriverBaseTestWithWebServer):
self._driver.PerformActions(actions) self._driver.PerformActions(actions)
self.assertEquals(1, len(self._driver.FindElements('tag name', 'br'))) self.assertEquals(1, len(self._driver.FindElements('tag name', 'br')))
def testActionsPause(self):
self._driver.Load(self.GetHttpUrlForFile('/chromedriver/empty.html'))
self._driver.ExecuteScript(
'''
document.body.innerHTML
= "<input type='text' autofocus style='width:100px; height:100px'>";
window.events = [];
const input = document.getElementsByTagName("input")[0];
const listener
= e => window.events.push({type: e.type, time: e.timeStamp});
input.addEventListener("keydown", listener);
input.addEventListener("keyup", listener);
input.addEventListener("mousedown", listener);
''')
# Actions on 3 devices, across 6 ticks, with 200 ms pause at ticks 1 to 4.
# Tick "key" device "pointer" device "none" device
# 0 move
# 1 pause 200 ms pointer down pause 100 ms
# 2 "a" key down pointer up pause 200 ms
# 3 "a" key up pause 200 ms
# 4 "b" key down move 200 ms
# 5 "b" key up
actions = {'actions': [
{
'type': 'key',
'id': 'key',
'actions': [
{'type': 'pause'},
{'type': 'pause', 'duration': 200},
{'type': 'keyDown', 'value': 'a'},
{'type': 'keyUp', 'value': 'a'},
{'type': 'keyDown', 'value': 'b'},
{'type': 'keyUp', 'value': 'b'},
]
},
{
'type': 'pointer',
'id': 'mouse',
'actions': [
{'type': 'pointerMove', 'x': 50, 'y': 50},
{'type': 'pointerDown', 'button': 0},
{'type': 'pointerUp', 'button': 0},
{'type': 'pause', 'duration': 200},
{'type': 'pointerMove', 'duration': 200, 'x': 10, 'y': 10},
]
},
{
'type': 'none',
'id': 'none',
'actions': [
{'type': 'pause'},
{'type': 'pause', 'duration': 100},
{'type': 'pause', 'duration': 200},
]
}
]}
self._driver.PerformActions(actions)
events = self._driver.ExecuteScript('return window.events')
expected_events = ['mousedown', 'keydown', 'keyup', 'keydown', 'keyup']
self.assertEquals(len(expected_events), len(events))
for i in range(len(events)):
self.assertEqual(expected_events[i], events[i]['type'])
if i > 0:
elapsed_time = events[i]['time'] - events[i-1]['time']
self.assertGreaterEqual(elapsed_time, 200)
def testPageLoadStrategyIsNormalByDefault(self): def testPageLoadStrategyIsNormalByDefault(self):
self.assertEquals('normal', self.assertEquals('normal',
self._driver.capabilities['pageLoadStrategy']) self._driver.capabilities['pageLoadStrategy'])
......
...@@ -355,6 +355,39 @@ Status ProcessPauseAction(const base::DictionaryValue* action_item, ...@@ -355,6 +355,39 @@ Status ProcessPauseAction(const base::DictionaryValue* action_item,
return Status(kOk); return Status(kOk);
} }
// Implements "compute the tick duration" algorithm from W3C spec
// (https://w3c.github.io/webdriver/#dfn-computing-the-tick-duration).
// For convenience, this function computes durations of all ticks, while the
// original algorithm computes duration of one tick.
void ComputeTickDurations(std::vector<int>* tick_durations,
const base::ListValue& actions_list) {
for (size_t i = 0; i < actions_list.GetSize(); i++) {
const base::DictionaryValue* action_sequence = nullptr;
actions_list.GetDictionary(i, &action_sequence);
const base::ListValue* actions = nullptr;
action_sequence->GetList("actions", &actions);
std::string type;
action_sequence->GetString("sourceType", &type);
for (size_t j = 0; j < actions->GetSize(); j++) {
const base::DictionaryValue* action = nullptr;
actions->GetDictionary(j, &action);
std::string subtype;
action->GetString("subtype", &subtype);
if (subtype == "pause" ||
(type == "pointer" && subtype == "pointerMove")) {
if (j >= tick_durations->size())
tick_durations->resize(j + 1);
int duration = 0;
GetOptionalInt(action, "duration", &duration);
if (duration > (*tick_durations)[j])
(*tick_durations)[j] = duration;
}
}
}
}
Status ElementInViewCenter(Session* session, Status ElementInViewCenter(Session* session,
WebView* web_view, WebView* web_view,
std::string element_id, std::string element_id,
...@@ -1327,9 +1360,12 @@ Status ExecutePerformActions(Session* session, ...@@ -1327,9 +1360,12 @@ Status ExecutePerformActions(Session* session,
} }
} }
std::vector<int> tick_durations;
ComputeTickDurations(&tick_durations, actions_list);
size_t max_list_length = size_t max_list_length =
std::max(std::max(longest_mouse_list_size, longest_touch_list_size), std::max({longest_mouse_list_size, longest_touch_list_size,
longest_key_list_size); longest_key_list_size, tick_durations.size()});
std::map<std::string, gfx::Point> element_center_point; std::map<std::string, gfx::Point> element_center_point;
for (size_t i = 0; i < max_list_length; i++) { for (size_t i = 0; i < max_list_length; i++) {
std::list<MouseEvent> dispatch_mouse_events; std::list<MouseEvent> dispatch_mouse_events;
...@@ -1399,6 +1435,10 @@ Status ExecutePerformActions(Session* session, ...@@ -1399,6 +1435,10 @@ Status ExecutePerformActions(Session* session,
if (status.IsError()) if (status.IsError())
return status; return status;
} }
if (i < tick_durations.size() && tick_durations[i] > 0) {
base::PlatformThread::Sleep(
base::TimeDelta::FromMilliseconds(tick_durations[i]));
}
} }
return Status(kOk); return Status(kOk);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment