selenium-webdriver 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|