Skip to content

Commit 9e35c06

Browse files
committed
Add beginnings of a injected wrappers for the interactions API. This requires
the mouse and keyboard state to be tracked outside of the atoms and injected with each call. The new device state will be in returned in the response object's value property. This has been tested on OS X 10.8.5 against Safari 6.1, Chrome 33, Firefox 25, and Opera 12.
1 parent f2a7eb8 commit 9e35c06

File tree

6 files changed

+240
-19
lines changed

6 files changed

+240
-19
lines changed

javascript/atoms/keyboard.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ goog.require('goog.userAgent');
4040
* A keyboard that provides atomic typing actions.
4141
*
4242
* @constructor
43-
* @param {{pressed: !Array.<!bot.Keyboard.Key>,
44-
currentPos: number}=} opt_state Optional keyboard state.
43+
* @param {bot.Keyboard.State=} opt_state Optional keyboard state.
4544
* @extends {bot.Device}
4645
*/
4746
bot.Keyboard = function(opt_state) {
@@ -63,12 +62,20 @@ bot.Keyboard = function(opt_state) {
6362
this.setKeyPressed_(/** @type {!bot.Keyboard.Key} */ (key), true);
6463
}, this);
6564

66-
this.currentPos_ = opt_state['currentPos'];
65+
this.currentPos_ = opt_state['currentPos'] || 0;
6766
}
6867
};
6968
goog.inherits(bot.Keyboard, bot.Device);
7069

7170

71+
/**
72+
* Describes the current state of a keyboard.
73+
* @typedef {{pressed: !Array.<!bot.Keyboard.Key>,
74+
* currentPos: number}}
75+
*/
76+
bot.Keyboard.State;
77+
78+
7279
/**
7380
* Maps characters to (key,boolean) pairs, where the key generates the
7481
* character and the boolean is true when the shift must be pressed.
@@ -844,8 +851,7 @@ bot.Keyboard.prototype.moveCursor = function(element) {
844851
/**
845852
* Serialize the current state of the keyboard.
846853
*
847-
* @return {{pressed: !Array.<!bot.Keyboard.Key>, currentPos: number}} The
848-
* current keyboard state.
854+
* @return {bot.Keyboard.State} The current keyboard state.
849855
*/
850856
bot.Keyboard.prototype.getState = function() {
851857
// Need to use quoted literals here, so the compiler will not rename the

javascript/atoms/mouse.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ bot.Mouse = function(opt_state, opt_modifiersState, opt_eventEmitter) {
6868
this.hasEverInteracted_ = false;
6969

7070
if (opt_state) {
71-
this.buttonPressed_ = opt_state.buttonPressed;
71+
this.buttonPressed_ = opt_state.buttonPressed || null;
7272

7373
try {
7474
if (bot.dom.isElement(opt_state.elementPressed)) {
@@ -78,9 +78,12 @@ bot.Mouse = function(opt_state, opt_modifiersState, opt_eventEmitter) {
7878
this.buttonPressed_ = null;
7979
}
8080

81-
this.clientXY_ = opt_state.clientXY;
82-
this.nextClickIsDoubleClick_ = opt_state.nextClickIsDoubleClick;
83-
this.hasEverInteracted_ = opt_state.hasEverInteracted;
81+
this.clientXY_ = new goog.math.Coordinate(
82+
opt_state.clientXY.x,
83+
opt_state.clientXY.y);
84+
85+
this.nextClickIsDoubleClick_ = !!opt_state.nextClickIsDoubleClick;
86+
this.hasEverInteracted_ = !!opt_state.hasEverInteracted;
8487

8588
try {
8689
if (bot.dom.isElement(opt_state.element)) {

javascript/webdriver/atoms/fragments/inject/build.desc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,13 @@ js_fragment(name = "get_appcache_status",
174174
module = "webdriver.atoms.inject.storage.appcache",
175175
function = "webdriver.atoms.inject.storage.appcache.getStatus",
176176
deps = ["//javascript/webdriver/atoms/inject:deps"])
177+
178+
js_fragment(name = "mouse_click",
179+
module = "webdriver.atoms.inject.action",
180+
function = "webdriver.atoms.inject.action.mouseClick",
181+
deps = ["//javascript/webdriver/atoms/inject:deps"])
182+
183+
js_fragment(name = "send_keys_to_active_element",
184+
module = "webdriver.atoms.inject.action",
185+
function = "webdriver.atoms.inject.action.sendKeysToActiveElement",
186+
deps = ["//javascript/webdriver/atoms/inject:deps"])

javascript/webdriver/atoms/inject/action.js

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@
1919

2020
goog.provide('webdriver.atoms.inject.action');
2121

22-
goog.require('bot.Keyboard');
23-
goog.require('bot.Mouse');
2422
goog.require('bot.action');
2523
goog.require('webdriver.atoms.element');
2624
goog.require('webdriver.atoms.inject');
25+
goog.require('webdriver.atoms.inputs');
2726

2827

2928
/**
@@ -86,20 +85,72 @@ webdriver.atoms.inject.action.click = function (element, opt_window) {
8685
};
8786

8887

88+
/**
89+
* JSON representation of a {@link bot.Mouse.State} object.
90+
* @typedef {{buttonPressed: ?bot.Mouse.Button,
91+
* elementPressed: ?bot.inject.JsonElement,
92+
* clientXY: {x: number, y: number},
93+
* nextClickIsDoubleClick: boolean,
94+
* hasEverInteracted: boolean,
95+
* element: ?bot.inject.JsonElement}}
96+
*/
97+
webdriver.atoms.inject.action.JsonMouseState;
98+
99+
100+
/**
101+
* Clicks a mouse button.
102+
*
103+
* @param {bot.Mouse.Button} button The button to press.
104+
* @param {webdriver.atoms.inject.action.JsonMouseState=} opt_mouseState The
105+
* current state of the mouse.
106+
* @param {bot.inject.JsonWindow=} opt_window The window context for
107+
* the execution of the function.
108+
* @return {string} A stringified {@link bot.response.ResponseObject}. The
109+
* mouse's new state, as a
110+
* {@link webdriver.atoms.inject.action.JsonMouseState} will be included
111+
* as the response value.
112+
*/
113+
webdriver.atoms.inject.action.mouseClick = function(
114+
button, opt_mouseState, opt_window) {
115+
return webdriver.atoms.inject.action.executeActionFunction_(
116+
webdriver.atoms.inputs.mouseClick,
117+
[button, opt_mouseState], opt_window);
118+
};
119+
120+
121+
/**
122+
* Types a sequence of key strokes on the active element.
123+
* @param {!Array.<string>} keys The keys to type.
124+
* @param {bot.Keyboard.State=} opt_keyboardState The keyboard's state.
125+
* @param {bot.inject.JsonWindow=} opt_window The window context for
126+
* the execution of the function.
127+
* @return {string} A stringified {@link bot.response.ResponseObject}. The
128+
* keyboard's new state, as a {@link bot.Keyboard.State} will be included
129+
* as the response value.
130+
*/
131+
webdriver.atoms.inject.action.sendKeysToActiveElement = function(
132+
keys, opt_keyboardState, opt_window) {
133+
var persistModifiers = true;
134+
return webdriver.atoms.inject.action.executeActionFunction_(
135+
webdriver.atoms.inputs.sendKeys,
136+
[null, keys, opt_keyboardState, persistModifiers], opt_window);
137+
};
138+
139+
89140
/**
90141
* @param {!Function} fn The function to call.
91142
* @param {!Array.<*>} args An array of function arguments for the function.
92143
* @param {bot.inject.JsonWindow=} opt_window The window context for
93144
* the execution of the function.
94145
* @return {string} The serialized JSON wire protocol result of the function.
95146
*/
96-
webdriver.atoms.inject.action.executeActionFunction_ =
97-
function (fn, args, opt_window) {
147+
webdriver.atoms.inject.action.executeActionFunction_ = function (
148+
fn, args, opt_window) {
98149
var response;
99150
try {
100151
var targetWindow = webdriver.atoms.inject.getWindow(opt_window);
101-
var unwrappedArgs = /** @type {!Array} */(bot.inject.unwrapValue(args,
102-
targetWindow.document));
152+
var unwrappedArgs = /** @type {Array} */(bot.inject.unwrapValue(
153+
args, targetWindow.document));
103154
var functionResult = fn.apply(null, unwrappedArgs);
104155
response = bot.inject.wrapResponse(functionResult);
105156
} catch (ex) {

javascript/webdriver/atoms/inputs.js

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,10 @@ goog.require('webdriver.atoms.element');
3434
* @param {Element} element The element to send the keyboard input to, or
3535
* {@code null} to use the document's active element.
3636
* @param {!Array.<string>} keys The keys to type on the element.
37-
* @param {{currentPos: number, pressed: !Array.<!bot.Keyboard.Key>}=} opt_state
38-
* The predefined keyboard state to use.
37+
* @param {bot.Keyboard.State=} opt_state The predefined keyboard state to use.
3938
* @param {boolean=} opt_persistModifiers Whether modifier keys should remain
4039
* pressed when this function ends.
41-
* @return {{currentPos: number, pressed: !Array.<!bot.Keyboard.Key>}} The
42-
* keyboard state.
40+
* @return {bot.Keyboard.State} The keyboard state.
4341
*/
4442
webdriver.atoms.inputs.sendKeys = function(
4543
element, keys, opt_state, opt_persistModifiers) {
@@ -170,10 +168,34 @@ webdriver.atoms.inputs.doubleClick = function(opt_state) {
170168
*
171169
* @param {bot.Mouse.State=} opt_state The serialized state of the mouse.
172170
* @return {!bot.Mouse.State} The mouse state.
171+
* @deprecated Use {@link webdriver.atoms.inputs.mouseClick}.
173172
*/
174173
webdriver.atoms.inputs.rightClick = function(opt_state) {
175174
var mouse = new bot.Mouse(opt_state);
176175
mouse.pressButton(bot.Mouse.Button.RIGHT);
177176
mouse.releaseButton();
178177
return mouse.getState();
179178
};
179+
180+
181+
/**
182+
* Executes a mousedown/up with the given button at the current mouse
183+
* location.
184+
*
185+
* @param {bot.Mouse.Button} button The button to press.
186+
* @param {bot.Mouse.State=} opt_state The serialized state of the mouse.
187+
* @return {!bot.Mouse.State} The mouse state.
188+
*/
189+
webdriver.atoms.inputs.mouseClick = function(button, opt_state) {
190+
// If no target element is specified, try to find it from the
191+
// client (x, y) location. No, this is not exact.
192+
if (opt_state && opt_state.clientXY && !opt_state.element &&
193+
document.elementFromPoint) {
194+
opt_state.element = document.elementFromPoint(
195+
opt_state.clientXY.x, opt_state.clientXY.y);
196+
}
197+
var mouse = new bot.Mouse(opt_state);
198+
mouse.pressButton(button);
199+
mouse.releaseButton();
200+
return mouse.getState();
201+
};
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!DOCTYPE html>
2+
<title>interactions_test</title>
3+
<script src="../../test_bootstrap.js"></script>
4+
<script>
5+
goog.require('bot.Keyboard');
6+
goog.require('bot.Mouse');
7+
goog.require('bot.color');
8+
goog.require('bot.dom');
9+
goog.require('bot.inject.cache');
10+
goog.require('bot.json');
11+
goog.require('bot.response');
12+
goog.require('goog.array');
13+
goog.require('goog.testing.jsunit');
14+
goog.require('webdriver.Key');
15+
goog.require('webdriver.atoms.inject.action');
16+
</script>
17+
<style>
18+
#click-box {
19+
position: absolute;
20+
width: 75px;
21+
height: 75px;
22+
top: 50px;
23+
left: 50px;
24+
}
25+
</style>
26+
<body>
27+
<input id="focus-sync">
28+
<input id="text-input">
29+
<div id="click-box"></div>
30+
<script>
31+
var clickBox = document.getElementById('click-box');
32+
clickBox.style.background = 'red';
33+
clickBox.onclick = function(e) {
34+
e = e || window.event;
35+
clickBox.style.background = 'blue';
36+
if (e.clientX > 100) {
37+
clickBox.style.background = 'cyan';
38+
}
39+
if (e.clientY > 100) {
40+
clickBox.style.background = 'yellow';
41+
}
42+
};
43+
clickBox.oncontextmenu = function() {
44+
clickBox.style.background = 'green';
45+
return false;
46+
};
47+
48+
49+
function resetFocus() {
50+
var sink = document.getElementById('focus-sync');
51+
focusElement(sink);
52+
}
53+
54+
55+
function focusElement(el) {
56+
el.focus();
57+
assertEquals(el, bot.dom.getActiveElement(document));
58+
}
59+
60+
61+
function testSendKeysToActiveElement() {
62+
resetFocus();
63+
64+
var input = document.getElementById('text-input');
65+
focusElement(input);
66+
67+
var state = doType('foo');
68+
assertEquals('foo', input.value);
69+
70+
resetFocus();
71+
72+
state['pressed'].push(bot.Keyboard.Keys.SHIFT);
73+
state = doType('more', state);
74+
assertEquals('foo', input.value);
75+
76+
focusElement(input);
77+
doType(['bar', webdriver.Key.SHIFT, 'baz'], state);
78+
assertEquals('fooBARbaz', input.value);
79+
80+
function doType(keys, opt_state) {
81+
var res = webdriver.atoms.inject.action.sendKeysToActiveElement(
82+
keys, opt_state);
83+
res = bot.json.parse(res);
84+
bot.response.checkResponse(res);
85+
return res['value'];
86+
}
87+
}
88+
89+
function testMouseClick() {
90+
assertEquals('initial state is wrong', 'rgba(255, 0, 0, 1)', getColor());
91+
92+
var state = doClick(bot.Mouse.Button.LEFT, {
93+
clientXY: {x: 60, y: 60} // Click over the clickBox.
94+
});
95+
assertEquals('rgba(0, 0, 255, 1)', getColor());
96+
assertTrue(!!state['element']);
97+
assertEquals(
98+
clickBox, bot.inject.cache.getElement(state['element']['ELEMENT']));
99+
100+
state = doClick(bot.Mouse.Button.RIGHT, state);
101+
assertEquals('rgba(0, 128, 0, 1)', getColor());
102+
103+
state.clientXY.x += 41;
104+
assertEquals(101, state.clientXY.x);
105+
doClick(bot.Mouse.Button.LEFT, state);
106+
assertEquals('rgba(0, 255, 255, 1)', getColor());
107+
108+
state.clientXY.y += 41;
109+
assertEquals(101, state.clientXY.y);
110+
doClick(bot.Mouse.Button.LEFT, state);
111+
assertEquals('rgba(255, 255, 0, 1)', getColor());
112+
113+
state.clientXY = {x: 50, y: 50};
114+
doClick(bot.Mouse.Button.LEFT, state);
115+
assertEquals('rgba(0, 0, 255, 1)', getColor());
116+
117+
function doClick(button, state) {
118+
var res = webdriver.atoms.inject.action.mouseClick(button, state);
119+
res = bot.json.parse(res);
120+
bot.response.checkResponse(res);
121+
return res['value'];
122+
}
123+
124+
function getColor() {
125+
var value = bot.dom.getEffectiveStyle(clickBox, 'backgroundColor');
126+
return bot.color.standardizeColor('backgroundColor', value);
127+
}
128+
}
129+
</script>

0 commit comments

Comments
 (0)