selenium-webdriver 0.0.28 → 0.0.29

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 (44) hide show
  1. data/CHANGES +25 -0
  2. data/lib/selenium/webdriver.rb +6 -29
  3. data/lib/selenium/webdriver/chrome.rb +4 -2
  4. data/lib/selenium/webdriver/chrome/extension.zip +0 -0
  5. data/lib/selenium/webdriver/chrome/launcher.rb +15 -16
  6. data/lib/selenium/webdriver/common.rb +18 -0
  7. data/lib/selenium/webdriver/{bridge_helper.rb → common/bridge_helper.rb} +0 -0
  8. data/lib/selenium/webdriver/{core_ext → common/core_ext}/dir.rb +0 -0
  9. data/lib/selenium/webdriver/{core_ext → common/core_ext}/string.rb +0 -0
  10. data/lib/selenium/webdriver/{driver.rb → common/driver.rb} +19 -7
  11. data/lib/selenium/webdriver/{driver_extensions → common/driver_extensions}/takes_screenshot.rb +2 -2
  12. data/lib/selenium/webdriver/{element.rb → common/element.rb} +30 -3
  13. data/lib/selenium/webdriver/{error.rb → common/error.rb} +0 -0
  14. data/lib/selenium/webdriver/{file_reaper.rb → common/file_reaper.rb} +0 -0
  15. data/lib/selenium/webdriver/{find.rb → common/find.rb} +9 -1
  16. data/lib/selenium/webdriver/{keys.rb → common/keys.rb} +0 -0
  17. data/lib/selenium/webdriver/{navigation.rb → common/navigation.rb} +3 -3
  18. data/lib/selenium/webdriver/{options.rb → common/options.rb} +47 -5
  19. data/lib/selenium/webdriver/{platform.rb → common/platform.rb} +10 -0
  20. data/lib/selenium/webdriver/common/socket_poller.rb +47 -0
  21. data/lib/selenium/webdriver/{target_locator.rb → common/target_locator.rb} +11 -8
  22. data/lib/selenium/webdriver/{timeouts.rb → common/timeouts.rb} +0 -0
  23. data/lib/selenium/webdriver/common/wait.rb +60 -0
  24. data/lib/selenium/webdriver/common/zipper.rb +54 -0
  25. data/lib/selenium/webdriver/firefox.rb +6 -3
  26. data/lib/selenium/webdriver/firefox/binary.rb +46 -43
  27. data/lib/selenium/webdriver/firefox/bridge.rb +2 -10
  28. data/lib/selenium/webdriver/firefox/extension.rb +51 -0
  29. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  30. data/lib/selenium/webdriver/firefox/launcher.rb +25 -69
  31. data/lib/selenium/webdriver/firefox/profile.rb +123 -89
  32. data/lib/selenium/webdriver/firefox/profiles_ini.rb +2 -1
  33. data/lib/selenium/webdriver/firefox/socket_lock.rb +77 -0
  34. data/lib/selenium/webdriver/ie/bridge.rb +25 -38
  35. data/lib/selenium/webdriver/ie/lib.rb +11 -1
  36. data/lib/selenium/webdriver/ie/native/win32/InternetExplorerDriver.dll +0 -0
  37. data/lib/selenium/webdriver/ie/native/x64/InternetExplorerDriver.dll +0 -0
  38. data/lib/selenium/webdriver/ie/util.rb +3 -17
  39. data/lib/selenium/webdriver/remote/bridge.rb +9 -1
  40. data/lib/selenium/webdriver/remote/capabilities.rb +53 -20
  41. data/lib/selenium/webdriver/remote/http/default.rb +2 -2
  42. metadata +52 -31
  43. data/lib/selenium/webdriver/child_process.rb +0 -243
  44. data/lib/selenium/webdriver/zip_helper.rb +0 -27
@@ -1,5 +1,3 @@
1
- require "fcntl"
2
-
3
1
  module Selenium
4
2
  module WebDriver
5
3
  module Firefox
@@ -10,9 +8,11 @@ module Selenium
10
8
  SOCKET_LOCK_TIMEOUT = 45
11
9
  STABLE_CONNECTION_TIMEOUT = 60
12
10
 
13
- def initialize(binary, port = DEFAULT_PORT, profile = DEFAULT_PROFILE_NAME)
11
+ def initialize(binary, port, profile = nil)
14
12
  @binary = binary
15
- @port = port.to_i
13
+ @port = Integer(port)
14
+
15
+ raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
16
16
 
17
17
  if profile.kind_of? Profile
18
18
  @profile = profile
@@ -21,12 +21,6 @@ module Selenium
21
21
  @profile = nil
22
22
  end
23
23
 
24
- # need to be really specific about what host to use
25
- #
26
- # on os x, "localhost" will resolve to 3 different addresses (see /etc/hosts)
27
- # Ruby will loop over these and happily bind to the same port on each one,
28
- # making it completely unusable for our purposes.
29
- #
30
24
  @host = "127.0.0.1"
31
25
  end
32
26
 
@@ -35,7 +29,7 @@ module Selenium
35
29
  end
36
30
 
37
31
  def launch
38
- with_lock do
32
+ socket_lock.locked do
39
33
  find_free_port
40
34
  create_profile
41
35
  start_silent_and_wait
@@ -46,28 +40,6 @@ module Selenium
46
40
  self
47
41
  end
48
42
 
49
- def with_lock
50
- max_time = Time.now + SOCKET_LOCK_TIMEOUT
51
- locking_port = @port - 1
52
-
53
- until Time.now > max_time
54
- begin
55
- socket_lock = TCPServer.new(@host, locking_port)
56
- # make sure the fd is not inherited by firefox
57
- socket_lock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) if defined? Fcntl::FD_CLOEXEC
58
-
59
- yield
60
- return
61
- rescue SocketError, Errno::EADDRINUSE
62
- sleep 0.1
63
- end
64
- end
65
-
66
- raise Error::WebDriverError, "unable to bind to locking port #{locking_port} within #{SOCKET_LOCK_TIMEOUT} seconds"
67
- ensure
68
- socket_lock.close if socket_lock
69
- end
70
-
71
43
  def find_free_port
72
44
  port = @port
73
45
 
@@ -79,47 +51,29 @@ module Selenium
79
51
  end
80
52
 
81
53
  def create_profile
82
- unless @profile
83
- fetch_profile
84
- if @profile.nil?
85
- raise Error, WebDriverError, "could not find or create profile: #{profile.inspect}"
86
- end
87
- end
54
+ fetch_profile if @profile.nil?
88
55
 
89
- @profile.delete_extensions_cache
56
+ @profile.add_webdriver_extension
90
57
  @profile.port = @port
91
- @profile.add_webdriver_extension(true)
92
- @profile.update_user_prefs
58
+ @profile_dir = @profile.layout_on_disk
93
59
  end
94
60
 
95
61
  def start
96
62
  assert_profile
97
- @binary.start_with @profile
63
+ @binary.start_with @profile, @profile_dir
98
64
  end
99
65
 
100
66
  def start_silent_and_wait
101
67
  assert_profile
102
- @binary.start_with @profile, "--silent"
68
+ @binary.start_with @profile, @profile_dir, "--silent"
103
69
  @binary.wait
104
70
  end
105
71
 
106
72
  def connect_until_stable
107
- max_time = Time.now + STABLE_CONNECTION_TIMEOUT
108
-
109
- until Time.now >= max_time
110
- return if can_connect?
111
- sleep 0.25
73
+ poller = SocketPoller.new(@host, @port, STABLE_CONNECTION_TIMEOUT)
74
+ unless poller.success?
75
+ raise Error::WebDriverError, "unable to obtain stable firefox connection in #{STABLE_CONNECTION_TIMEOUT} seconds"
112
76
  end
113
-
114
- raise Error::WebDriverError, "unable to obtain stable firefox connection in #{STABLE_CONNECTION_TIMEOUT} seconds"
115
- end
116
-
117
- def can_connect?
118
- TCPSocket.new(@host, @port).close
119
- true
120
- rescue Errno::ECONNREFUSED, Errno::ENOTCONN, SocketError => e
121
- $stderr.puts "#{e.message} for #{@host}:#{@port}" if $DEBUG
122
- false
123
77
  end
124
78
 
125
79
  def free_port?(port)
@@ -131,20 +85,22 @@ module Selenium
131
85
  end
132
86
 
133
87
  def fetch_profile
134
- existing = Profile.from_name @profile_name
135
-
136
- unless existing
137
- @binary.create_base_profile @profile_name
138
- Profile.ini.refresh
139
- existing = Profile.from_name @profile_name
140
- raise Error::WebDriverError, "unable to find or create new profile" unless existing
88
+ if @profile_name
89
+ @profile = Profile.from_name @profile_name
90
+ if @profile.nil?
91
+ raise Error::WebDriverError, "unable to find profile named: #{@profile_name.inspect}"
92
+ end
93
+ else
94
+ @profile = Profile.new
141
95
  end
142
-
143
- @profile = existing
144
96
  end
145
97
 
146
98
  def assert_profile
147
- raise Error::WebDriverError, "must create_profile first" unless @profile
99
+ raise Error::WebDriverError, "must create_profile first" unless @profile && @profile_dir
100
+ end
101
+
102
+ def socket_lock
103
+ @socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)
148
104
  end
149
105
 
150
106
  end # Launcher
@@ -5,12 +5,17 @@ module Selenium
5
5
 
6
6
  ANONYMOUS_PROFILE_NAME = "WEBDRIVER_ANONYMOUS_PROFILE"
7
7
  EXTENSION_NAME = "fxdriver@googlecode.com"
8
- EM_NAMESPACE_URI = "http://www.mozilla.org/2004/em-rdf#"
9
8
  WEBDRIVER_EXTENSION_PATH = File.expand_path("#{WebDriver.root}/selenium/webdriver/firefox/extension/webdriver.xpi")
10
-
11
- attr_reader :name, :directory
9
+ WEBDRIVER_PREFS = {
10
+ :native_events => 'webdriver_enable_native_events',
11
+ :untrusted_certs => 'webdriver_accept_untrusted_certs',
12
+ :untrusted_issuer => 'webdriver_assume_untrusted_issuer',
13
+ :port => 'webdriver_firefox_port',
14
+ :log_file => 'webdriver.log.file'
15
+ }
16
+
17
+ attr_reader :name, :log_file
12
18
  attr_writer :secure_ssl, :native_events, :load_no_focus_lib
13
- attr_accessor :port
14
19
 
15
20
  class << self
16
21
  def ini
@@ -34,23 +39,56 @@ module Selenium
34
39
  # driver = Selenium::WebDriver.for :firefox, :profile => profile
35
40
  #
36
41
 
37
- def initialize(directory = nil)
38
- @directory = directory ? create_tmp_copy(directory) : Dir.mktmpdir("webdriver-profile")
42
+ def initialize(model = nil)
43
+ @model = verify_model(model)
44
+
45
+ model_prefs = read_model_prefs
39
46
 
40
- unless File.directory? @directory
41
- raise Error::WebDriverError, "Profile directory does not exist: #{@directory.inspect}"
47
+ if model_prefs.empty?
48
+ @native_events = DEFAULT_ENABLE_NATIVE_EVENTS
49
+ @secure_ssl = DEFAULT_SECURE_SSL
50
+ @untrusted_issuer = DEFAULT_ASSUME_UNTRUSTED_ISSUER
51
+ @load_no_focus_lib = DEFAULT_LOAD_NO_FOCUS_LIB
52
+ else
53
+ @native_events = model_prefs[WEBDRIVER_PREFS[:native_events]] == "true"
54
+ @secure_ssl = model_prefs[WEBDRIVER_PREFS[:untrusted_certs]] != "true" # FIXME: 'untrusted_certs' vs 'secure_ssl'
55
+ @untrusted_issuer = model_prefs[WEBDRIVER_PREFS[:untrusted_issuer]] == "true"
56
+ @load_no_focus_lib = model_prefs[WEBDRIVER_PREFS[:load_no_focus_lib]] == "true" # not stored in profile atm, so will always be false.
42
57
  end
43
58
 
44
- FileReaper << @directory
59
+ @additional_prefs = {}
60
+ @extensions = {}
61
+ end
45
62
 
46
- # TODO: replace constants with options hash
47
- @port = DEFAULT_PORT
48
- @native_events = DEFAULT_ENABLE_NATIVE_EVENTS
49
- @secure_ssl = DEFAULT_SECURE_SSL
50
- @untrusted_issuer = DEFAULT_ASSUME_UNTRUSTED_ISSUER
51
- @load_no_focus_lib = DEFAULT_LOAD_NO_FOCUS_LIB
63
+ def layout_on_disk
64
+ profile_dir = @model ? create_tmp_copy(@model) : Dir.mktmpdir("webdriver-profile")
65
+ FileReaper << profile_dir
52
66
 
53
- @additional_prefs = {}
67
+ install_extensions(profile_dir)
68
+ delete_lock_files(profile_dir)
69
+ delete_extensions_cache(profile_dir)
70
+ update_user_prefs_in(profile_dir)
71
+
72
+ profile_dir
73
+ end
74
+
75
+ def as_json(opts = nil)
76
+ {'zip' => Zipper.zip(layout_on_disk)}
77
+ end
78
+
79
+ def to_json(*args)
80
+ as_json.to_json(*args)
81
+ end
82
+
83
+ def self.from_json(json)
84
+ zip_file = Tempfile.new("webdriver-profile-duplicate-#{json.hash}")
85
+
86
+ zip_file << JSON.parse(json)['zip'].unpack("m")[0]
87
+ zip_file.close
88
+
89
+ new(Zipper.unzip(zip_file.path))
90
+ ensure
91
+ zip_file.delete if zip_file
54
92
  end
55
93
 
56
94
  #
@@ -75,80 +113,27 @@ module Selenium
75
113
  @additional_prefs[key.to_s] = value
76
114
  end
77
115
 
78
- def absolute_path
79
- if Platform.win?
80
- directory.gsub("/", "\\")
81
- else
82
- directory
83
- end
116
+ def port=(port)
117
+ self[WEBDRIVER_PREFS[:port]] = port
84
118
  end
85
119
 
86
- def update_user_prefs
87
- prefs = current_user_prefs
88
-
89
- prefs.merge! OVERRIDABLE_PREFERENCES
90
- prefs.merge! @additional_prefs
91
- prefs.merge! DEFAULT_PREFERENCES
92
-
93
- prefs['webdriver_firefox_port'] = @port
94
- prefs['webdriver_accept_untrusted_certs'] = !secure_ssl?
95
- prefs['webdriver_enable_native_events'] = native_events?
96
- prefs['webdriver_assume_untrusted_issuer'] = assume_untrusted_certificate_issuer?
97
-
98
- # If the user sets the home page, we should also start up there
99
- prefs["startup.homepage_welcome_url"] = prefs["browser.startup.homepage"]
100
-
101
- write_prefs prefs
120
+ def log_file=(file)
121
+ @log_file = file
122
+ self[WEBDRIVER_PREFS[:log_file]] = file
102
123
  end
103
124
 
104
- def add_webdriver_extension(force_creation = false)
105
- ext_path = File.join(extensions_dir, EXTENSION_NAME)
106
-
107
- if File.exists? ext_path
108
- return unless force_creation
125
+ def add_webdriver_extension
126
+ unless @extensions.has_key?(:webdriver)
127
+ add_extension(WEBDRIVER_EXTENSION_PATH, :webdriver)
109
128
  end
110
-
111
- add_extension WEBDRIVER_EXTENSION_PATH
112
- delete_extensions_cache
113
129
  end
114
130
 
115
131
  #
116
- # Aadd the extension (directory, .zip or .xpi) at the given path to the profile.
132
+ # Add the extension (directory, .zip or .xpi) at the given path to the profile.
117
133
  #
118
134
 
119
- def add_extension(path)
120
- unless File.exist?(path)
121
- raise Error::WebDriverError, "could not find extension at #{path.inspect}"
122
- end
123
-
124
- if File.directory? path
125
- root = path
126
- else
127
- unless %w[.zip .xpi].include? File.extname(path)
128
- raise Error::WebDriverError, "expected .zip or .xpi extension, got #{path.inspect}"
129
- end
130
-
131
- root = ZipHelper.unzip(path)
132
- end
133
-
134
- ext_path = File.join extensions_dir, read_id_from_install_rdf(root)
135
-
136
- FileUtils.rm_rf ext_path
137
- FileUtils.mkdir_p File.dirname(ext_path), :mode => 0700
138
- FileUtils.cp_r root, ext_path
139
- end
140
-
141
- def extensions_dir
142
- @extensions_dir ||= File.join(directory, "extensions")
143
- end
144
-
145
- def user_prefs_path
146
- @user_prefs_path ||= File.join(directory, "user.js")
147
- end
148
-
149
- def delete_extensions_cache
150
- cache = File.join(directory, "extensions.cache")
151
- FileUtils.rm_f cache if File.exist?(cache)
135
+ def add_extension(path, name = extension_name_for(path))
136
+ @extensions[name] = Extension.new(path)
152
137
  end
153
138
 
154
139
  def native_events?
@@ -173,11 +158,42 @@ module Selenium
173
158
 
174
159
  private
175
160
 
176
- def read_id_from_install_rdf(directory)
177
- rdf_path = File.join(directory, "install.rdf")
178
- doc = REXML::Document.new(File.read(rdf_path))
161
+ def install_extensions(directory)
162
+ destination = File.join(directory, "extensions")
179
163
 
180
- REXML::XPath.first(doc, "//em:id").text
164
+ @extensions.each do |name, extension|
165
+ p :extension => name if $DEBUG
166
+ extension.write_to(destination)
167
+ end
168
+ end
169
+
170
+ def verify_model(model)
171
+ return unless model
172
+
173
+ raise Errno::ENOENT, model unless File.exist?(model)
174
+ raise Errno::ENOTDIR, model unless File.directory?(model)
175
+
176
+ model
177
+ end
178
+
179
+ def read_model_prefs
180
+ return {} unless @model
181
+
182
+ read_user_prefs(File.join(@model, 'user.js'))
183
+ end
184
+
185
+ def delete_extensions_cache(directory)
186
+ FileUtils.rm_f File.join(directory, "extensions.cache")
187
+ end
188
+
189
+ def delete_lock_files(directory)
190
+ %w[.parentlock parent.lock].each do |name|
191
+ FileUtils.rm_f File.join(directory, name)
192
+ end
193
+ end
194
+
195
+ def extension_name_for(path)
196
+ File.basename(path, File.extname(path))
181
197
  end
182
198
 
183
199
  def create_tmp_copy(directory)
@@ -191,13 +207,30 @@ module Selenium
191
207
  tmp_directory
192
208
  end
193
209
 
210
+ def update_user_prefs_in(directory)
211
+ path = File.join(directory, 'user.js')
212
+ prefs = read_user_prefs(path)
213
+
214
+ prefs.merge! OVERRIDABLE_PREFERENCES
215
+ prefs.merge! @additional_prefs
216
+ prefs.merge! DEFAULT_PREFERENCES
217
+
218
+ prefs[WEBDRIVER_PREFS[:untrusted_certs]] = !secure_ssl?
219
+ prefs[WEBDRIVER_PREFS[:native_events]] = native_events?
220
+ prefs[WEBDRIVER_PREFS[:untrusted_issuer]] = assume_untrusted_certificate_issuer?
221
+
222
+ # If the user sets the home page, we should also start up there
223
+ prefs["startup.homepage_welcome_url"] = prefs["browser.startup.homepage"]
224
+
225
+ write_prefs prefs, path
226
+ end
194
227
 
195
- def current_user_prefs
196
- return {} unless File.exist?(user_prefs_path)
228
+ def read_user_prefs(path)
229
+ return {} unless File.exist?(path)
197
230
 
198
231
  prefs = {}
199
232
 
200
- File.read(user_prefs_path).split("\n").each do |line|
233
+ File.read(path).split("\n").each do |line|
201
234
  if line =~ /user_pref\("([^"]+)"\s*,\s*(.+?)\);/
202
235
  prefs[$1.strip] = $2.strip
203
236
  end
@@ -206,8 +239,8 @@ module Selenium
206
239
  prefs
207
240
  end
208
241
 
209
- def write_prefs(prefs)
210
- File.open(user_prefs_path, "w") { |file|
242
+ def write_prefs(prefs, path)
243
+ File.open(path, "w") { |file|
211
244
  prefs.each do |key, value|
212
245
  p key => value if $DEBUG
213
246
  file.puts %{user_pref("#{key}", #{value});}
@@ -238,6 +271,7 @@ module Selenium
238
271
  "extensions.update.enabled" => 'false',
239
272
  "extensions.update.notifyUser" => 'false',
240
273
  "network.manage-offline-status" => 'false',
274
+ "network.http.max-connections-per-server" => '10',
241
275
  "security.warn_entering_secure" => 'false',
242
276
  "security.warn_submit_insecure" => 'false',
243
277
  "security.warn_entering_secure.show_once" => 'false',
@@ -13,7 +13,8 @@ module Selenium
13
13
  end
14
14
 
15
15
  def [](name)
16
- Profile.new @profile_paths[name]
16
+ path = @profile_paths[name]
17
+ path && Profile.new(path)
17
18
  end
18
19
 
19
20
  def refresh
@@ -0,0 +1,77 @@
1
+ require "fcntl"
2
+
3
+ module Selenium
4
+ module WebDriver
5
+ module Firefox
6
+
7
+ #
8
+ # @private
9
+ #
10
+
11
+ class SocketLock
12
+
13
+ #
14
+ # Need to be really specific about what host to use
15
+ #
16
+ # On os x, "localhost" will resolve to 3 different addresses (see /etc/hosts).
17
+ # Ruby will loop over these and happily bind to the same port on each one,
18
+ # making it completely unusable for our purposes.
19
+ #
20
+
21
+ HOST = "127.0.0.1"
22
+
23
+ def initialize(port, timeout)
24
+ @port = port
25
+ @timeout = timeout
26
+ end
27
+
28
+ def locked(&blk)
29
+ lock
30
+
31
+ begin
32
+ yield
33
+ ensure
34
+ release
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def lock
41
+ max_time = Time.now + @timeout
42
+
43
+ until can_lock? || Time.now >= max_time
44
+ sleep 0.1
45
+ end
46
+
47
+ unless did_lock?
48
+ raise Error::WebDriverError, "unable to bind to locking port #{@port} within #{@timeout} seconds"
49
+ end
50
+ end
51
+
52
+ def release
53
+ @server && @server.close
54
+ end
55
+
56
+ def can_lock?
57
+ @server = TCPServer.new(HOST, @port)
58
+
59
+ # make sure the fd is not inherited by exec()'d processes
60
+ if defined? Fcntl::FD_CLOEXEC
61
+ @server.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
62
+ end
63
+
64
+ true
65
+ rescue SocketError, Errno::EADDRINUSE => ex
66
+ $stderr.puts "#{self}: #{ex.message}" if $DEBUG
67
+ false
68
+ end
69
+
70
+ def did_lock?
71
+ !!@server
72
+ end
73
+
74
+ end # SocketLock
75
+ end # Firefox
76
+ end # WebDriver
77
+ end # Selenium