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.
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