selenium-webdriver 0.0.13 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- data/chrome/src/extension/background.js +31 -12
- data/chrome/src/extension/content_script.js +29 -28
- data/chrome/src/extension/manifest-nonwin.json +2 -1
- data/chrome/src/extension/manifest-win.json +2 -1
- data/chrome/src/extension/utils.js +16 -5
- data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +1 -2
- data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +60 -17
- data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +21 -5
- data/common/src/rb/lib/selenium/webdriver.rb +28 -15
- data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +5 -2
- data/common/src/rb/lib/selenium/webdriver/child_process.rb +1 -0
- data/common/src/rb/lib/selenium/webdriver/driver.rb +1 -1
- data/common/src/rb/lib/selenium/webdriver/element.rb +12 -2
- data/common/src/rb/lib/selenium/webdriver/keys.rb +6 -2
- data/common/src/rb/lib/selenium/webdriver/target_locator.rb +14 -1
- data/firefox/src/extension/components/nsCommandProcessor.js +78 -34
- data/firefox/src/extension/components/utils.js +1 -1
- data/firefox/src/extension/components/wrappedElement.js +35 -12
- data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +3 -2
- data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
- data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
- data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +8 -6
- data/remote/client/src/rb/lib/selenium/webdriver/remote/capabilities.rb +4 -4
- data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +3 -2
- metadata +2 -2
@@ -126,12 +126,9 @@ ChromeDriver.xmlHttpRequest = null;
|
|
126
126
|
|
127
127
|
/**
|
128
128
|
* URL to ping for commands.
|
129
|
-
* TODO(danielwh): Get this from the initial URL - see http://crbug.com/11547,
|
130
|
-
* the ChromeDriverInternals wiki page.
|
131
|
-
* There is a patch to fix this on the Downloads page of the Selenium project
|
132
129
|
* @type {string}
|
133
130
|
*/
|
134
|
-
ChromeDriver.xmlHttpRequestUrl =
|
131
|
+
ChromeDriver.xmlHttpRequestUrl = null;
|
135
132
|
|
136
133
|
|
137
134
|
/**
|
@@ -140,6 +137,12 @@ ChromeDriver.xmlHttpRequestUrl = "http://127.0.0.1:9700/chromeCommandExecutor";
|
|
140
137
|
ChromeDriver.requestSequenceNumber = 0;
|
141
138
|
|
142
139
|
|
140
|
+
/**
|
141
|
+
* @type {number}
|
142
|
+
*/
|
143
|
+
ChromeDriver.lastReceivedSequenceNumber = -2;
|
144
|
+
|
145
|
+
|
143
146
|
/**
|
144
147
|
* @type {number}
|
145
148
|
*/
|
@@ -189,6 +192,17 @@ resetCurrentlyWaitingOnContentScriptTime();
|
|
189
192
|
ChromeDriver.waitForContentScriptIncrement = 100;
|
190
193
|
|
191
194
|
chrome.extension.onConnect.addListener(function(port) {
|
195
|
+
if (ChromeDriver.xmlHttpRequestUrl == null) {
|
196
|
+
//This is the first content script, so is from the URL we need to connect to
|
197
|
+
ChromeDriver.xmlHttpRequestUrl = port.tab.url;
|
198
|
+
//Tell the ChromeCommandExecutor that we are here
|
199
|
+
sendResponseByXHR("", false);
|
200
|
+
return;
|
201
|
+
} else if (port.tab.url.indexOf(ChromeDriver.xmlHttpRequestUrl) == 0) {
|
202
|
+
//We have reloaded the xmlHttpRequest page. Ignore the connection.
|
203
|
+
return;
|
204
|
+
}
|
205
|
+
|
192
206
|
console.log("Connected to " + port.name);
|
193
207
|
// Note: The frameset port *always* connects before any frame port. After
|
194
208
|
// that, the order is in page loading time
|
@@ -296,9 +310,6 @@ chrome.extension.onConnect.addListener(function(port) {
|
|
296
310
|
});
|
297
311
|
});
|
298
312
|
|
299
|
-
//Tell the ChromeCommandExecutor that we are here
|
300
|
-
sendResponseByXHR({statusCode: 0}, false);
|
301
|
-
|
302
313
|
/**
|
303
314
|
* Sends the passed argument as the result of a command
|
304
315
|
* @param result object encapsulating result to send
|
@@ -498,8 +509,8 @@ function parseRequest(request) {
|
|
498
509
|
sendResponseToParsedRequest({statusCode: 0, value: []});
|
499
510
|
break;
|
500
511
|
}
|
501
|
-
|
502
|
-
|
512
|
+
// Falling through, as if we do have a page, we want to treat this like a
|
513
|
+
// normal request
|
503
514
|
case "deleteAllCookies":
|
504
515
|
case "deleteCookie":
|
505
516
|
if (hasNoPage()) {
|
@@ -507,8 +518,16 @@ function parseRequest(request) {
|
|
507
518
|
sendResponseToParsedRequest({statusCode: 0});
|
508
519
|
break;
|
509
520
|
}
|
510
|
-
|
511
|
-
|
521
|
+
// Falling through, as if we do have a page, we want to treat this like a
|
522
|
+
// normal request
|
523
|
+
case "executeScript":
|
524
|
+
if (hasNoPage()) {
|
525
|
+
console.log("Not got a page, but asked to execute script, so sending error 17");
|
526
|
+
sendResponseToParsedRequest({statusCode: 17, value: {message: 'Was not on a page, so could not execute javascript'}});
|
527
|
+
break;
|
528
|
+
}
|
529
|
+
// Falling through, as if we do have a page, we want to treat this like a
|
530
|
+
// normal request
|
512
531
|
default:
|
513
532
|
var sequenceNumber = ChromeDriver.requestSequenceNumber;
|
514
533
|
ChromeDriver.requestSequenceNumber++;
|
@@ -557,7 +576,7 @@ function parsePortMessage(message) {
|
|
557
576
|
if (!message || !message.response || !message.response.value ||
|
558
577
|
message.response.value.statusCode === undefined ||
|
559
578
|
message.response.value.statusCode == null ||
|
560
|
-
message.sequenceNumber === undefined) {
|
579
|
+
message.sequenceNumber === undefined || message.sequenceNumber < ChromeDriver.lastReceivedSequenceNumber) {
|
561
580
|
// Should only ever happen if we sent a bad request,
|
562
581
|
// or the content script is broken
|
563
582
|
console.log("Got invalid response from the content script.");
|
@@ -87,7 +87,7 @@ function parsePortMessage(message) {
|
|
87
87
|
|
88
88
|
if (element.click) {
|
89
89
|
console.log("click");
|
90
|
-
execute("try { arguments[0].click(); } catch(e){}", {type: "ELEMENT", value:
|
90
|
+
execute("try { arguments[0].click(); } catch(e){}", {type: "ELEMENT", value: addElementToInternalArray(element)});
|
91
91
|
}
|
92
92
|
response.value = {statusCode: 0};
|
93
93
|
break;
|
@@ -457,6 +457,9 @@ function getElement(plural, lookupBy, lookupValue, id) {
|
|
457
457
|
case "tag name":
|
458
458
|
elements = getElementsByXPath(root + "//" + lookupValue);
|
459
459
|
break;
|
460
|
+
case "css":
|
461
|
+
elements = parent.querySelectorAll(lookupValue);
|
462
|
+
break;
|
460
463
|
case "xpath":
|
461
464
|
if (root != "" && lookupValue[0] != "/") {
|
462
465
|
//Because xpath is relative to the parent, if there is a parent, and the lookup seems to be implied sub-lookup, we add a /
|
@@ -502,6 +505,10 @@ function getElement(plural, lookupBy, lookupValue, id) {
|
|
502
505
|
}
|
503
506
|
}
|
504
507
|
|
508
|
+
/**
|
509
|
+
* Adds the element to the internal element store, if it isn't already there.
|
510
|
+
* @return index of element in the array
|
511
|
+
*/
|
505
512
|
function addElementToInternalArray(element) {
|
506
513
|
for (var existingElement in ChromeDriverContentScript.internalElementArray) {
|
507
514
|
if (element == ChromeDriverContentScript.internalElementArray[existingElement]) {
|
@@ -545,23 +552,6 @@ function internalGetElement(elementIdAsString) {
|
|
545
552
|
}
|
546
553
|
}
|
547
554
|
|
548
|
-
/**
|
549
|
-
* Given an element, returning the index that can be used to locate it
|
550
|
-
*
|
551
|
-
* @param element the element to look up the internal ID of
|
552
|
-
* @return A positive integer on success or -1 otherwise
|
553
|
-
*/
|
554
|
-
function getElementId_(element) {
|
555
|
-
var length = ChromeDriverContentScript.internalElementArray.length;
|
556
|
-
for (var i = 0; i < length; i++) {
|
557
|
-
if (ChromeDriverContentScript.internalElementArray[i] === element) {
|
558
|
-
return i;
|
559
|
-
}
|
560
|
-
}
|
561
|
-
|
562
|
-
return -1;
|
563
|
-
}
|
564
|
-
|
565
555
|
/**
|
566
556
|
* Ensures the passed element is in view, so that the native click event can be sent
|
567
557
|
* @return object to send back to background page to trigger a native click
|
@@ -698,6 +688,18 @@ function selectElement(element) {
|
|
698
688
|
if (!oldValue) {
|
699
689
|
//TODO: Work out a way of firing events,
|
700
690
|
//now that synthesising them gives appendMessage errors
|
691
|
+
if (tagName == "option") {
|
692
|
+
var select = element;
|
693
|
+
while (select.parentNode != null && select.tagName.toLowerCase() != "select") {
|
694
|
+
select = select.parentNode;
|
695
|
+
}
|
696
|
+
if (select.tagName.toLowerCase() == "select") {
|
697
|
+
element = select;
|
698
|
+
} else {
|
699
|
+
//If we're not within a select element, fire the event from the option, and hope that it bubbles up
|
700
|
+
console.log("Falling back to event firing from option, not select element");
|
701
|
+
}
|
702
|
+
}
|
701
703
|
Utils.fireHtmlEvent(element, "change");
|
702
704
|
}
|
703
705
|
return {statusCode: 0};
|
@@ -717,10 +719,8 @@ function sendElementKeys(element, keys, elementId) {
|
|
717
719
|
if (oldFocusedElement != element) {
|
718
720
|
//TODO: Work out a way of firing events,
|
719
721
|
//now that synthesising them gives appendMessage errors
|
720
|
-
oldFocusedElement.blur();
|
721
|
-
Utils.
|
722
|
-
element.focus();
|
723
|
-
Utils.fireHtmlEvent(element, "focus");
|
722
|
+
Utils.fireHtmlEventAndConditionallyPerformAction(oldFocusedElement, "blur", function() {oldFocusedElement.blur();});
|
723
|
+
Utils.fireHtmlEventAndConditionallyPerformAction(element, "focus", function() {element.focus();});
|
724
724
|
}
|
725
725
|
return {statusCode: "no-op", keys: keys, elementId: elementId};
|
726
726
|
}
|
@@ -742,10 +742,10 @@ function sendElementNonNativeKeys(element, keys) {
|
|
742
742
|
function submitElement(element) {
|
743
743
|
while (element != null) {
|
744
744
|
if (element.tagName.toLowerCase() == "form") {
|
745
|
-
element.submit();
|
745
|
+
Utils.fireHtmlEventAndConditionallyPerformAction(element, "submit", function() {element.submit();});
|
746
746
|
return {statusCode: 0};
|
747
747
|
}
|
748
|
-
element = element.
|
748
|
+
element = element.parentNode;
|
749
749
|
}
|
750
750
|
return {statusCode: 12, value: {message: "Cannot submit an element not in a form"}};
|
751
751
|
}
|
@@ -765,7 +765,7 @@ function toggleElement(element) {
|
|
765
765
|
if (tagName == "option") {
|
766
766
|
var parent = element;
|
767
767
|
while (parent != null && parent.tagName.toLowerCase() != "select") {
|
768
|
-
parent = parent.
|
768
|
+
parent = parent.parentNode;
|
769
769
|
}
|
770
770
|
if (parent == null) {
|
771
771
|
throw {statusCode: 12, value: {message: "option tag had no select tag parent"}};
|
@@ -871,19 +871,20 @@ function parseWrappedArguments(argument) {
|
|
871
871
|
* We can't share objects between content script and page, so have to wrap up arguments as JSON
|
872
872
|
* @param script script to execute as a string
|
873
873
|
* @param passedArgs array of arguments to pass to the script
|
874
|
-
* @param callback function to call when the result is returned
|
874
|
+
* @param callback function to call when the result is returned. Passed a DOMAttrModified event which should be parsed as returnFromJavascriptInPage
|
875
|
+
* TODO: Make the callback be passed the parsed result.
|
875
876
|
*/
|
876
877
|
function execute_(script, passedArgs, callback) {
|
877
878
|
console.log("executing " + script + ", args: " + JSON.stringify(passedArgs));
|
878
879
|
var func = "function(){" + script + "}";
|
879
880
|
var args = [];
|
880
881
|
for (var i = 0; i < passedArgs.length; ++i) {
|
881
|
-
console.log("Parsing: " + passedArgs[i]);
|
882
|
+
console.log("Parsing: " + JSON.stringify(passedArgs[i]));
|
882
883
|
var value = parseWrappedArguments(passedArgs[i]);
|
883
884
|
if (value.success) {
|
884
885
|
args.push(value.value);
|
885
886
|
} else {
|
886
|
-
ChromeDriverContentScript.port.postMessage(value.value);
|
887
|
+
ChromeDriverContentScript.port.postMessage({response: value.value, sequenceNumber: ChromeDriverContentScript.currentSequenceNumber});
|
887
888
|
return;
|
888
889
|
}
|
889
890
|
}
|
@@ -11,7 +11,8 @@
|
|
11
11
|
{
|
12
12
|
"matches": ["http://*/*", "https://*/*", "file:///*"],
|
13
13
|
"js": ["utils.js", "content_script.js"],
|
14
|
-
"run_at": "document_end"
|
14
|
+
"run_at": "document_end",
|
15
|
+
"all_frames": true
|
15
16
|
}
|
16
17
|
],
|
17
18
|
"plugins": [{"path": "npchromedriver.dll", "public": true}],
|
@@ -174,9 +174,20 @@ Utils.getText = function(element) {
|
|
174
174
|
return text.slice(start, end);
|
175
175
|
};
|
176
176
|
|
177
|
-
|
177
|
+
/**
|
178
|
+
* Fires the event using Utils.fireEvent, and if the event returned true,
|
179
|
+
* perform callback, which will be passed on arguments
|
180
|
+
*/
|
181
|
+
Utils.fireHtmlEventAndConditionallyPerformAction = function(element, eventName, callback) {
|
182
|
+
Utils.fireHtmlEvent(element, eventName, function(evt) { if (JSON.parse(evt.newValue).value) { callback(); } });
|
183
|
+
};
|
184
|
+
|
185
|
+
Utils.fireHtmlEvent = function(element, eventName, callback) {
|
186
|
+
if (callback === undefined) {
|
187
|
+
callback = function() {};
|
188
|
+
}
|
178
189
|
var args = [
|
179
|
-
{type: "ELEMENT", value:
|
190
|
+
{type: "ELEMENT", value: addElementToInternalArray(element)},
|
180
191
|
{type: "STRING", value: eventName}
|
181
192
|
];
|
182
193
|
|
@@ -185,9 +196,9 @@ Utils.fireHtmlEvent = function(element, eventName) {
|
|
185
196
|
// http://code.google.com/p/chromium/issues/detail?id=29071
|
186
197
|
var script = "var e = document.createEvent('HTMLEvents'); "
|
187
198
|
+ "e.initEvent(arguments[1], true, true); "
|
188
|
-
+ "arguments[0].dispatchEvent(e);";
|
199
|
+
+ "return arguments[0].dispatchEvent(e);";
|
189
200
|
|
190
|
-
execute_(script, args,
|
201
|
+
execute_(script, args, callback);
|
191
202
|
};
|
192
203
|
|
193
204
|
|
@@ -197,7 +208,7 @@ Utils.fireMouseEventOn = function(element, eventName) {
|
|
197
208
|
|
198
209
|
Utils.triggerMouseEvent = function(element, eventType, clientX, clientY) {
|
199
210
|
var args = [
|
200
|
-
{type: "ELEMENT", value:
|
211
|
+
{type: "ELEMENT", value: addElementToInternalArray(element)},
|
201
212
|
{type: "STRING", value: eventType},
|
202
213
|
{type: "NUMBER", value: clientX},
|
203
214
|
{type: "NUMBER", value: clientY}
|
@@ -2,13 +2,16 @@ module Selenium
|
|
2
2
|
module WebDriver
|
3
3
|
module Chrome
|
4
4
|
class CommandExecutor
|
5
|
-
|
5
|
+
HTML_TEMPLATE = "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n%s"
|
6
|
+
JSON_TEMPLATE = "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n%s"
|
6
7
|
|
7
8
|
def initialize
|
8
|
-
@server = TCPServer.new("0.0.0.0",
|
9
|
+
@server = TCPServer.new("0.0.0.0", 0)
|
9
10
|
@queue = Queue.new
|
11
|
+
|
10
12
|
@accepted_any = false
|
11
13
|
@next_socket = nil
|
14
|
+
@listening = true
|
12
15
|
|
13
16
|
Thread.new { start_run_loop }
|
14
17
|
end
|
@@ -20,7 +23,7 @@ module Selenium
|
|
20
23
|
end
|
21
24
|
|
22
25
|
json = command.to_json
|
23
|
-
data =
|
26
|
+
data = JSON_TEMPLATE % [json.length, json]
|
24
27
|
|
25
28
|
@next_socket.write data
|
26
29
|
@next_socket.close
|
@@ -29,33 +32,47 @@ module Selenium
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def close
|
32
|
-
|
35
|
+
stop_listening
|
36
|
+
close_sockets
|
37
|
+
@server.close unless @server.closed?
|
33
38
|
rescue IOError
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def port
|
43
|
+
@server.addr[1]
|
44
|
+
end
|
45
|
+
|
46
|
+
def uri
|
47
|
+
"http://localhost:#{port}/chromeCommandExecutor"
|
34
48
|
end
|
35
49
|
|
36
50
|
private
|
37
51
|
|
38
52
|
def start_run_loop
|
39
|
-
|
53
|
+
while(@listening) do
|
40
54
|
socket = @server.accept
|
41
|
-
@queue << socket
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
|
56
|
+
if socket.read(1) == "G" # initial GET(s)
|
57
|
+
write_holding_page_to socket
|
58
|
+
else
|
59
|
+
if accepted_any?
|
60
|
+
@queue << socket
|
61
|
+
else
|
62
|
+
read_response(socket)
|
63
|
+
@accepted_any = true
|
64
|
+
end
|
46
65
|
end
|
47
|
-
|
48
|
-
@accepted_any ||= true
|
49
66
|
end
|
50
|
-
rescue IOError
|
51
|
-
raise
|
67
|
+
rescue IOError, Errno::EBADF
|
68
|
+
raise unless @server.closed?
|
52
69
|
end
|
53
70
|
|
54
71
|
def read_response(socket)
|
55
72
|
result = ''
|
56
|
-
|
57
73
|
seen_double_crlf = false
|
58
|
-
|
74
|
+
|
75
|
+
while line = next_line(socket)
|
59
76
|
seen_double_crlf = true if line.empty?
|
60
77
|
result << "#{line}\n" if seen_double_crlf
|
61
78
|
end
|
@@ -63,14 +80,40 @@ module Selenium
|
|
63
80
|
@next_socket = socket
|
64
81
|
|
65
82
|
result.strip!
|
66
|
-
# p result
|
67
|
-
result
|
68
83
|
end
|
69
84
|
|
70
85
|
def accepted_any?
|
71
86
|
@accepted_any
|
72
87
|
end
|
73
88
|
|
89
|
+
def close_sockets
|
90
|
+
@queue.pop.close until @queue.empty?
|
91
|
+
@next_socket.close if @next_socket
|
92
|
+
end
|
93
|
+
|
94
|
+
def stop_listening
|
95
|
+
@listening = false
|
96
|
+
end
|
97
|
+
|
98
|
+
def next_line(socket)
|
99
|
+
return if socket.closed?
|
100
|
+
input = socket.gets
|
101
|
+
|
102
|
+
raise Error::WebDriverError, "unexpected EOF from Chrome" if input.nil?
|
103
|
+
|
104
|
+
line = input.chomp
|
105
|
+
return if line == "EOResponse"
|
106
|
+
|
107
|
+
line
|
108
|
+
end
|
109
|
+
|
110
|
+
def write_holding_page_to(socket)
|
111
|
+
msg = %[<html><head><script type='text/javascript'>if (window.location.search == '') { setTimeout("window.location = window.location.href + '?reloaded'", 5000); }</script></head><body><p>ChromeDriver server started and connected. Please leave this tab open.</p></body></html>]
|
112
|
+
|
113
|
+
socket.write HTML_TEMPLATE % [msg.length, msg]
|
114
|
+
socket.close
|
115
|
+
end
|
116
|
+
|
74
117
|
end # CommandExecutor
|
75
118
|
end # Chrome
|
76
119
|
end # WebDriver
|
@@ -21,10 +21,10 @@ module Selenium
|
|
21
21
|
launcher
|
22
22
|
end
|
23
23
|
|
24
|
-
def launch
|
24
|
+
def launch(server_url)
|
25
25
|
create_extension
|
26
26
|
create_profile
|
27
|
-
launch_chrome
|
27
|
+
launch_chrome server_url
|
28
28
|
|
29
29
|
pid
|
30
30
|
end
|
@@ -54,7 +54,8 @@ module Selenium
|
|
54
54
|
touch "#{tmp_profile_dir}/First Run Dev"
|
55
55
|
end
|
56
56
|
|
57
|
-
def launch_chrome
|
57
|
+
def launch_chrome(server_url)
|
58
|
+
check_binary_exists
|
58
59
|
@process = ChildProcess.new Platform.wrap_in_quotes_if_necessary(binary_path),
|
59
60
|
"--load-extension=#{Platform.wrap_in_quotes_if_necessary tmp_extension_dir}",
|
60
61
|
"--user-data-dir=#{Platform.wrap_in_quotes_if_necessary tmp_profile_dir}",
|
@@ -62,10 +63,16 @@ module Selenium
|
|
62
63
|
"--disable-hang-monitor",
|
63
64
|
"--disable-popup-blocking",
|
64
65
|
"--disable-prompt-on-repost",
|
65
|
-
|
66
|
+
server_url
|
66
67
|
@process.start
|
67
68
|
end
|
68
69
|
|
70
|
+
def check_binary_exists
|
71
|
+
unless File.file?(binary_path)
|
72
|
+
raise Error::WebDriverError, "Could not find Chrome binary. Make sure Chrome is installed (OS: #{Platform.os})"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
69
76
|
def ext_path
|
70
77
|
@ext_path ||= "#{WebDriver.root}/chrome/src/extension"
|
71
78
|
end
|
@@ -95,7 +102,16 @@ module Selenium
|
|
95
102
|
end
|
96
103
|
|
97
104
|
def binary_path
|
98
|
-
@binary_path ||=
|
105
|
+
@binary_path ||= begin
|
106
|
+
possible_paths = [
|
107
|
+
"#{ENV['USERPROFILE']}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe",
|
108
|
+
"#{ENV['USERPROFILE']}\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe",
|
109
|
+
"#{Platform.home}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe",
|
110
|
+
"#{Platform.home}\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe",
|
111
|
+
]
|
112
|
+
|
113
|
+
possible_paths.find { |f| File.exist?(f) } || possible_paths.first
|
114
|
+
end
|
99
115
|
end
|
100
116
|
|
101
117
|
end
|
@@ -1,6 +1,34 @@
|
|
1
1
|
require "tmpdir"
|
2
2
|
require "fileutils"
|
3
3
|
|
4
|
+
def have_yajl?
|
5
|
+
require "yajl/json_gem"
|
6
|
+
true
|
7
|
+
rescue LoadError
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
def have_json?
|
12
|
+
require "json"
|
13
|
+
true
|
14
|
+
rescue LoadError
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
18
|
+
unless have_yajl? || have_json?
|
19
|
+
raise LoadError, <<-END
|
20
|
+
|
21
|
+
You need to require rubygems or install one of these gems:
|
22
|
+
|
23
|
+
yajl-ruby (best on MRI)
|
24
|
+
json
|
25
|
+
json-jruby (native JRuby)
|
26
|
+
json_pure (any platform)
|
27
|
+
|
28
|
+
END
|
29
|
+
end
|
30
|
+
|
31
|
+
|
4
32
|
require "selenium/webdriver/core_ext/dir"
|
5
33
|
require "selenium/webdriver/error"
|
6
34
|
require "selenium/webdriver/platform"
|
@@ -15,21 +43,6 @@ require "selenium/webdriver/bridge_helper"
|
|
15
43
|
require "selenium/webdriver/driver"
|
16
44
|
require "selenium/webdriver/element"
|
17
45
|
|
18
|
-
begin
|
19
|
-
require "json" # gem dependency
|
20
|
-
rescue LoadError => e
|
21
|
-
msg = Selenium::WebDriver::Platform.jruby? ? "jruby -S gem install json-jruby" : "gem install json"
|
22
|
-
|
23
|
-
|
24
|
-
raise LoadError, <<-END
|
25
|
-
#{e.message}
|
26
|
-
|
27
|
-
You need to install the json gem or (require rubygems):
|
28
|
-
#{msg}
|
29
|
-
END
|
30
|
-
end
|
31
|
-
|
32
|
-
|
33
46
|
module Selenium
|
34
47
|
module WebDriver
|
35
48
|
Point = Struct.new(:x, :y)
|
@@ -73,15 +73,25 @@ module Selenium
|
|
73
73
|
#
|
74
74
|
# Send keystrokes to this element
|
75
75
|
#
|
76
|
-
# @param [String, Symbol]
|
76
|
+
# @param [String, Symbol, Array]
|
77
|
+
#
|
78
|
+
# Examples:
|
79
|
+
#
|
80
|
+
# element.send_keys "foo" #=> value: 'foo'
|
81
|
+
# element.send_keys "tet", :arrow_left, "s" #=> value: 'test'
|
82
|
+
# element.send_keys [:control, 'a'], :space #=> value: ' '
|
77
83
|
#
|
78
84
|
# @see Keys::KEYS
|
79
85
|
#
|
80
86
|
|
81
87
|
def send_keys(*args)
|
82
88
|
args.each do |arg|
|
83
|
-
|
89
|
+
case arg
|
90
|
+
when Symbol
|
84
91
|
arg = Keys[arg]
|
92
|
+
when Array
|
93
|
+
arg = arg.map { |e| e.kind_of?(Symbol) ? Keys[e] : e }.join
|
94
|
+
arg << Keys[:null]
|
85
95
|
end
|
86
96
|
|
87
97
|
bridge.sendKeysToElement(@id, arg.to_s)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Selenium
|
2
4
|
module WebDriver
|
3
5
|
module Keys
|
@@ -5,13 +7,15 @@ module Selenium
|
|
5
7
|
#
|
6
8
|
# @see Element#send_keys
|
7
9
|
#
|
10
|
+
# http://www.google.com.au/search?&q=unicode+pua&btnG=Search
|
11
|
+
#
|
8
12
|
|
9
13
|
KEYS = {
|
10
14
|
# \x works on both 1.8.6/1.9
|
11
15
|
:null => "\xEE\x80\x80",
|
12
16
|
:cancel => "\xEE\x80\x81",
|
13
17
|
:help => "\xEE\x80\x82",
|
14
|
-
:
|
18
|
+
:backspace => "\xEE\x80\x83",
|
15
19
|
:tab => "\xEE\x80\x84",
|
16
20
|
:clear => "\xEE\x80\x85",
|
17
21
|
:return => "\xEE\x80\x86",
|
@@ -72,7 +76,7 @@ module Selenium
|
|
72
76
|
}
|
73
77
|
|
74
78
|
def self.[](key)
|
75
|
-
KEYS[key] || raise(Error::UnsupportedOperationError, "no such key #{
|
79
|
+
KEYS[key] || raise(Error::UnsupportedOperationError, "no such key #{key.inspect}")
|
76
80
|
end
|
77
81
|
|
78
82
|
end # Keys
|
@@ -21,9 +21,22 @@ module Selenium
|
|
21
21
|
#
|
22
22
|
# switch to the frame with the given id
|
23
23
|
#
|
24
|
+
# If given a block, this method will return to the original window after
|
25
|
+
# block execution.
|
26
|
+
#
|
27
|
+
# @param id
|
28
|
+
# A window handle
|
29
|
+
#
|
24
30
|
|
25
31
|
def window(id)
|
26
|
-
|
32
|
+
if block_given?
|
33
|
+
original = @bridge.getCurrentWindowHandle
|
34
|
+
@bridge.switchToWindow id
|
35
|
+
yield
|
36
|
+
@bridge.switchToWindow original
|
37
|
+
else
|
38
|
+
@bridge.switchToWindow id
|
39
|
+
end
|
27
40
|
end
|
28
41
|
|
29
42
|
#
|
@@ -191,48 +191,92 @@ DelayedCommand.prototype.execute = function(ms) {
|
|
191
191
|
};
|
192
192
|
|
193
193
|
|
194
|
+
/**
|
195
|
+
* @return {boolean} Whether this instance should delay execution of its
|
196
|
+
* command for a pending request in the current window's nsILoadGroup.
|
197
|
+
*/
|
198
|
+
DelayedCommand.prototype.shouldDelayExecutionForPendingRequest_ = function() {
|
199
|
+
try {
|
200
|
+
if (this.loadGroup_.isPending()) {
|
201
|
+
var hasOnLoadBlocker = false;
|
202
|
+
var numPending = 0;
|
203
|
+
var requests = this.loadGroup_.requests;
|
204
|
+
while (requests.hasMoreElements()) {
|
205
|
+
var request =
|
206
|
+
requests.getNext().QueryInterface(Components.interfaces.nsIRequest);
|
207
|
+
if (request.isPending()) {
|
208
|
+
numPending += 1;
|
209
|
+
hasOnLoadBlocker = hasOnLoadBlocker ||
|
210
|
+
(request.name == 'about:document-onload-blocker');
|
211
|
+
|
212
|
+
if (numPending > 1) {
|
213
|
+
// More than one pending request, need to wait.
|
214
|
+
return true;
|
215
|
+
}
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
if (numPending && !hasOnLoadBlocker) {
|
220
|
+
Utils.dumpn('Ignoring pending about:document-onload-blocker request');
|
221
|
+
// If we only have one pending request and it is not a
|
222
|
+
// document-onload-blocker, we need to wait. We do not wait for
|
223
|
+
// document-onload-blocker requests since these are created when
|
224
|
+
// one of document.[open|write|writeln] is called. If document.close is
|
225
|
+
// never called, the document-onload-blocker request will not be
|
226
|
+
// completed.
|
227
|
+
return true;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
} catch(e) {
|
231
|
+
Utils.dumpn('Problem while checking if we should delay execution: ' + e);
|
232
|
+
}
|
233
|
+
|
234
|
+
return false;
|
235
|
+
};
|
236
|
+
|
237
|
+
|
194
238
|
/**
|
195
239
|
* Attempts to execute the command. If the window is not ready for the command
|
196
240
|
* to execute, will set a timeout to try again.
|
197
241
|
* @private
|
198
242
|
*/
|
199
243
|
DelayedCommand.prototype.executeInternal_ = function() {
|
200
|
-
if (this.
|
244
|
+
if (this.shouldDelayExecutionForPendingRequest_()) {
|
245
|
+
return this.execute(this.sleepDelay_);
|
246
|
+
}
|
247
|
+
|
248
|
+
// Ugh! New windows open on "about:blank" before going to their
|
249
|
+
// destination URL. This check attempts to tell the difference between a
|
250
|
+
// newly opened window and someone actually wanting to do something on
|
251
|
+
// about:blank.
|
252
|
+
if (this.driver_.window.location == 'about:blank' && !this.onBlank_) {
|
253
|
+
this.onBlank_ = true;
|
201
254
|
return this.execute(this.sleepDelay_);
|
202
255
|
} else {
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
this.response_.isError = true;
|
228
|
-
this.response_.response = 'element is obsolete';
|
229
|
-
this.response_.send();
|
230
|
-
} else {
|
231
|
-
Utils.dumpn(
|
232
|
-
'Exception caught by driver: ' + this.command_.commandName +
|
233
|
-
'(' + this.command_.parameters + ')\n' + e);
|
234
|
-
this.response_.reportError(e);
|
235
|
-
}
|
256
|
+
try {
|
257
|
+
this.response_.commandName = this.command_.commandName;
|
258
|
+
// TODO(simon): This is rampantly ugly, but allows an alert to kill the command
|
259
|
+
// TODO(simon): This is never cleared, but _should_ be okay, because send wipes itself
|
260
|
+
this.driver_.response_ = this.response_;
|
261
|
+
|
262
|
+
this.driver_[this.command_.commandName](
|
263
|
+
this.response_, this.command_.parameters);
|
264
|
+
} catch (e) {
|
265
|
+
// if (e instanceof StaleElementError) won't work here since
|
266
|
+
// StaleElementError is defined in the utils.js subscript which is
|
267
|
+
// loaded independently in this component and in the main driver
|
268
|
+
// component.
|
269
|
+
// TODO(jmleyba): Continue cleaning up the extension and replacing the
|
270
|
+
// subscripts with proper components.
|
271
|
+
if (e.isStaleElementError) {
|
272
|
+
this.response_.isError = true;
|
273
|
+
this.response_.response = 'element is obsolete';
|
274
|
+
this.response_.send();
|
275
|
+
} else {
|
276
|
+
Utils.dumpn(
|
277
|
+
'Exception caught by driver: ' + this.command_.commandName +
|
278
|
+
'(' + this.command_.parameters + ')\n' + e);
|
279
|
+
this.response_.reportError(e);
|
236
280
|
}
|
237
281
|
}
|
238
282
|
}
|
@@ -865,7 +865,7 @@ Utils.fireHtmlEvent = function(context, element, eventName) {
|
|
865
865
|
var doc = element.ownerDocument;
|
866
866
|
var e = doc.createEvent("HTMLEvents");
|
867
867
|
e.initEvent(eventName, true, true);
|
868
|
-
element.dispatchEvent(e);
|
868
|
+
return element.dispatchEvent(e);
|
869
869
|
};
|
870
870
|
|
871
871
|
|
@@ -268,7 +268,7 @@ FirefoxDriver.prototype.getAttribute = function(respond, value) {
|
|
268
268
|
attributeName = attributeName.toLowerCase();
|
269
269
|
|
270
270
|
if (attributeName == "disabled") {
|
271
|
-
respond.response = element.disabled;
|
271
|
+
respond.response = (element.disabled === undefined ? false : element.disabled);
|
272
272
|
respond.send();
|
273
273
|
return;
|
274
274
|
} else if ((attributeName == "checked" || attributeName == "selected") &&
|
@@ -315,25 +315,37 @@ FirefoxDriver.prototype.hover = function(respond) {
|
|
315
315
|
respond.send();
|
316
316
|
};
|
317
317
|
|
318
|
-
|
319
318
|
FirefoxDriver.prototype.submit = function(respond) {
|
320
319
|
var element = Utils.getElementAt(respond.elementId, respond.context);
|
321
320
|
|
322
|
-
|
323
|
-
|
324
|
-
|
321
|
+
if (element) {
|
322
|
+
while (element.parentNode != null && element.tagName.toLowerCase() != "form") {
|
323
|
+
element = element.parentNode;
|
324
|
+
}
|
325
|
+
if (element.tagName.toLowerCase() == "form") {
|
326
|
+
if (Utils.fireHtmlEvent(respond.context, element, "submit")) {
|
327
|
+
new WebLoadingListener(Utils.getBrowser(respond.context), function() {
|
328
|
+
respond.send();
|
329
|
+
});
|
330
|
+
element.submit();
|
331
|
+
return;
|
332
|
+
} else {
|
333
|
+
//Event was blocked, so don't submit
|
334
|
+
respond.send();
|
335
|
+
return;
|
336
|
+
}
|
337
|
+
} else {
|
338
|
+
respond.isError = true;
|
339
|
+
respond.response = "Element was not in a form so couldn't submit";
|
325
340
|
respond.send();
|
326
|
-
|
327
|
-
|
328
|
-
submitElement.submit();
|
329
|
-
else
|
330
|
-
submitElement.click();
|
341
|
+
return;
|
342
|
+
}
|
331
343
|
} else {
|
332
344
|
respond.send();
|
345
|
+
return;
|
333
346
|
}
|
334
347
|
};
|
335
348
|
|
336
|
-
|
337
349
|
FirefoxDriver.prototype.isSelected = function(respond) {
|
338
350
|
var element = Utils.getElementAt(respond.elementId, respond.context);
|
339
351
|
|
@@ -388,10 +400,21 @@ FirefoxDriver.prototype.setSelected = function(respond) {
|
|
388
400
|
try {
|
389
401
|
var option =
|
390
402
|
element.QueryInterface(Components.interfaces.nsIDOMHTMLOptionElement);
|
403
|
+
var select = element;
|
404
|
+
while (select.parentNode != null && select.tagName.toLowerCase() != "select") {
|
405
|
+
select = select.parentNode;
|
406
|
+
}
|
407
|
+
if (select.tagName.toLowerCase() == "select") {
|
408
|
+
select = select.QueryInterface(Components.interfaces.nsIDOMHTMLSelectElement);
|
409
|
+
} else {
|
410
|
+
//If we're not within a select element, fire the event from the option, and hope that it bubbles up
|
411
|
+
Utils.dumpn("Falling back to event firing from option, not select element");
|
412
|
+
select = option;
|
413
|
+
}
|
391
414
|
respond.isError = false;
|
392
415
|
if (!option.selected) {
|
393
416
|
option.selected = true;
|
394
|
-
Utils.fireHtmlEvent(respond.context,
|
417
|
+
Utils.fireHtmlEvent(respond.context, select, "change");
|
395
418
|
}
|
396
419
|
wasSet = "";
|
397
420
|
} catch(e) {
|
@@ -16,8 +16,9 @@ module Selenium
|
|
16
16
|
_, status = wait
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
|
20
|
+
if status && status.to_i != 0
|
21
|
+
raise Error::WebDriverError, "could not create base profile: (exit status: #{status})"
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
Binary file
|
Binary file
|
@@ -81,13 +81,13 @@ module Selenium
|
|
81
81
|
#
|
82
82
|
|
83
83
|
def session_id
|
84
|
-
@session_id || raise(
|
84
|
+
@session_id || raise(Error::WebDriverError, "no current session exists")
|
85
85
|
end
|
86
86
|
|
87
87
|
|
88
88
|
def create_session(desired_capabilities)
|
89
|
-
resp
|
90
|
-
@session_id = resp['sessionId'] || raise('no sessionId in returned payload')
|
89
|
+
resp = raw_execute :newSession, {}, desired_capabilities
|
90
|
+
@session_id = resp['sessionId'] || raise(Error::WebDriverError, 'no sessionId in returned payload')
|
91
91
|
Capabilities.json_create resp['value']
|
92
92
|
end
|
93
93
|
|
@@ -160,7 +160,9 @@ module Selenium
|
|
160
160
|
end
|
161
161
|
|
162
162
|
def executeScript(script, *args)
|
163
|
-
|
163
|
+
unless capabilities.javascript?
|
164
|
+
raise Error::UnsupportedOperationError, "underlying webdriver instance does not support javascript"
|
165
|
+
end
|
164
166
|
|
165
167
|
typed_args = args.map { |arg| wrap_script_argument(arg) }
|
166
168
|
response = raw_execute :executeScript, {}, script, typed_args
|
@@ -370,14 +372,14 @@ module Selenium
|
|
370
372
|
#
|
371
373
|
|
372
374
|
def raw_execute(command, opts = {}, *args)
|
373
|
-
verb, path = COMMANDS[command] || raise("
|
375
|
+
verb, path = COMMANDS[command] || raise("unknown command #{command.inspect}")
|
374
376
|
path = path.dup
|
375
377
|
|
376
378
|
path[':session_id'] = @session_id if path.include?(":session_id")
|
377
379
|
path[':context'] = @context if path.include?(":context")
|
378
380
|
|
379
381
|
begin
|
380
|
-
opts.each { |key, value| path[key.inspect] = value }
|
382
|
+
opts.each { |key, value| path[key.inspect] = URI.escape(value.to_s) }
|
381
383
|
rescue IndexError
|
382
384
|
raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
|
383
385
|
end
|
@@ -71,10 +71,10 @@ module Selenium
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
# @option browser_name [String] required browser name
|
75
|
-
# @option version [String] required browser version number
|
76
|
-
# @option platform [Symbol] one of :any, :win, :mac, or :x
|
77
|
-
# @option javascript_enabled [Boolean] should the test run with javascript enabled?
|
74
|
+
# @option :browser_name [String] required browser name
|
75
|
+
# @option :version [String] required browser version number
|
76
|
+
# @option :platform [Symbol] one of :any, :win, :mac, or :x
|
77
|
+
# @option :javascript_enabled [Boolean] should the test run with javascript enabled?
|
78
78
|
#
|
79
79
|
# @api public
|
80
80
|
#
|
@@ -4,7 +4,8 @@ module Selenium
|
|
4
4
|
module WebDriver
|
5
5
|
module Remote
|
6
6
|
class DefaultHttpClient
|
7
|
-
CONTENT_TYPE
|
7
|
+
CONTENT_TYPE = "application/json"
|
8
|
+
DEFAULT_HEADERS = { "Accept" => CONTENT_TYPE }
|
8
9
|
|
9
10
|
class RetryException < StandardError; end
|
10
11
|
|
@@ -15,7 +16,7 @@ module Selenium
|
|
15
16
|
def call(verb, url, *args)
|
16
17
|
response = nil
|
17
18
|
url = @server_url.merge(url) unless url.kind_of?(URI)
|
18
|
-
headers =
|
19
|
+
headers = DEFAULT_HEADERS.dup
|
19
20
|
|
20
21
|
if args.any?
|
21
22
|
headers.merge!("Content-Type" => "#{CONTENT_TYPE}; charset=utf-8")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selenium-webdriver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jari Bakken
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-05 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|