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.
Files changed (25) hide show
  1. data/chrome/src/extension/background.js +31 -12
  2. data/chrome/src/extension/content_script.js +29 -28
  3. data/chrome/src/extension/manifest-nonwin.json +2 -1
  4. data/chrome/src/extension/manifest-win.json +2 -1
  5. data/chrome/src/extension/utils.js +16 -5
  6. data/chrome/src/rb/lib/selenium/webdriver/chrome/bridge.rb +1 -2
  7. data/chrome/src/rb/lib/selenium/webdriver/chrome/command_executor.rb +60 -17
  8. data/chrome/src/rb/lib/selenium/webdriver/chrome/launcher.rb +21 -5
  9. data/common/src/rb/lib/selenium/webdriver.rb +28 -15
  10. data/common/src/rb/lib/selenium/webdriver/bridge_helper.rb +5 -2
  11. data/common/src/rb/lib/selenium/webdriver/child_process.rb +1 -0
  12. data/common/src/rb/lib/selenium/webdriver/driver.rb +1 -1
  13. data/common/src/rb/lib/selenium/webdriver/element.rb +12 -2
  14. data/common/src/rb/lib/selenium/webdriver/keys.rb +6 -2
  15. data/common/src/rb/lib/selenium/webdriver/target_locator.rb +14 -1
  16. data/firefox/src/extension/components/nsCommandProcessor.js +78 -34
  17. data/firefox/src/extension/components/utils.js +1 -1
  18. data/firefox/src/extension/components/wrappedElement.js +35 -12
  19. data/firefox/src/rb/lib/selenium/webdriver/firefox/binary.rb +3 -2
  20. data/jobbie/prebuilt/Win32/Release/InternetExplorerDriver.dll +0 -0
  21. data/jobbie/prebuilt/x64/Release/InternetExplorerDriver.dll +0 -0
  22. data/remote/client/src/rb/lib/selenium/webdriver/remote/bridge.rb +8 -6
  23. data/remote/client/src/rb/lib/selenium/webdriver/remote/capabilities.rb +4 -4
  24. data/remote/client/src/rb/lib/selenium/webdriver/remote/default_http_client.rb +3 -2
  25. 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 = "http://127.0.0.1:9700/chromeCommandExecutor";
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
- // Falling through, as if we do have a page, we want to treat this like a
502
- // normal request
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
- // Falling through, as if we do have a page, we want to treat this like a
511
- // normal request
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: getElementId_(element)});
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.fireHtmlEvent(oldFocusedElement, "blur");
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.parentElement;
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.parentElement;
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
  "permissions": ["http://*/*", "tabs"]
@@ -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
- Utils.fireHtmlEvent = function(element, eventName) {
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: getElementId_(element)},
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, function(){});
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: getElementId_(element)},
211
+ {type: "ELEMENT", value: addElementToInternalArray(element)},
201
212
  {type: "STRING", value: eventType},
202
213
  {type: "NUMBER", value: clientX},
203
214
  {type: "NUMBER", value: clientY}
@@ -8,8 +8,7 @@ module Selenium
8
8
  @executor = CommandExecutor.new
9
9
 
10
10
  @launcher = Launcher.launcher
11
- @launcher.launch
12
- # TODO: @launcher.kill
11
+ @launcher.launch(@executor.uri)
13
12
  end
14
13
 
15
14
  def browser
@@ -2,13 +2,16 @@ module Selenium
2
2
  module WebDriver
3
3
  module Chrome
4
4
  class CommandExecutor
5
- TEMPLATE = "HTTP/1.1 200 OK\r\nContent-Length: %d\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n%s"
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", 9700)
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 = TEMPLATE % [json.length, json]
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
- @server.close
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
- loop do
53
+ while(@listening) do
40
54
  socket = @server.accept
41
- @queue << socket
42
55
 
43
- unless accepted_any?
44
- read_response socket
45
- @queue.pop
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 => e
51
- raise e unless @server.closed?
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
- while !socket.closed? && ((line = socket.gets.chomp) != "EOResponse")
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
- "about:blank"
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 ||= "#{Platform.home}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe"
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)
@@ -45,8 +45,11 @@ module Selenium
45
45
  end
46
46
  end
47
47
 
48
- def element_id_from(arr)
49
- arr.to_s.split("/").last
48
+ def element_id_from(id)
49
+ str = id.kind_of?(Array) ? id.first : id
50
+ after = str.split("/").last
51
+
52
+ after
50
53
  end
51
54
 
52
55
  def parse_cookie_string(str)
@@ -94,6 +94,7 @@ module Selenium
94
94
 
95
95
  def wait
96
96
  @process.waitFor
97
+ [nil, @process.exitValue] # no robust way to get pid here
97
98
  end
98
99
 
99
100
  def exit_value
@@ -39,7 +39,7 @@ module Selenium
39
39
  when :firefox, :ff
40
40
  WebDriver::Firefox::Bridge.new(*args)
41
41
  else
42
- raise ArgumentError, "unknown driver: #{driver.inspect}"
42
+ raise ArgumentError, "unknown driver: #{browser.inspect}"
43
43
  end
44
44
 
45
45
  driver = new(bridge)
@@ -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
- if arg.kind_of?(Symbol)
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
- :back_space => "\xEE\x80\x83",
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 #{e.inspect}")
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
- @bridge.switchToWindow id
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.loadGroup_.isPending()) {
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
- // Ugh! New windows open on "about:blank" before going to their
204
- // destination URL. This check attempts to tell the difference between a
205
- // newly opened window and someone actually wanting to do something on
206
- // about:blank.
207
- if (this.driver_.window.location == 'about:blank' && !this.onBlank_) {
208
- this.onBlank_ = true;
209
- return this.execute(this.sleepDelay_);
210
- } else {
211
- try {
212
- this.response_.commandName = this.command_.commandName;
213
- // TODO(simon): This is rampantly ugly, but allows an alert to kill the command
214
- // TODO(simon): This is never cleared, but _should_ be okay, because send wipes itself
215
- this.driver_.response_ = this.response_;
216
-
217
- this.driver_[this.command_.commandName](
218
- this.response_, this.command_.parameters);
219
- } catch (e) {
220
- // if (e instanceof StaleElementError) won't work here since
221
- // StaleElementError is defined in the utils.js subscript which is
222
- // loaded independently in this component and in the main driver
223
- // component.
224
- // TODO(jmleyba): Continue cleaning up the extension and replacing the
225
- // subscripts with proper components.
226
- if (e.isStaleElementError) {
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
- var submitElement = Utils.findForm(element);
323
- if (submitElement) {
324
- new WebLoadingListener(Utils.getBrowser(respond.context), function() {
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
- if (submitElement["submit"])
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, option, "change");
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
- if status && !status.success?
20
- raise Error::WebDriverError, "could not create base profile: (exit status: #{status.exitstatus})"
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
 
@@ -81,13 +81,13 @@ module Selenium
81
81
  #
82
82
 
83
83
  def session_id
84
- @session_id || raise(StandardError, "no current session exists")
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 = raw_execute :newSession, {}, desired_capabilities
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
- raise UnsupportedOperationError, "underlying webdriver instace does not support javascript" unless capabilities.javascript?
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("Unknown command #{command.inspect}")
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 = "application/json"
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 = { "Accept" => CONTENT_TYPE }
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.13
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: 2009-12-17 00:00:00 +01:00
12
+ date: 2010-01-05 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency