lapis_lazuli 0.6.3 → 0.7.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b5527d2ff09b634197137d35cbe04a7b67c4a60
4
- data.tar.gz: 262d2f7c7b6201f250dcce19971027c0eda5267a
3
+ metadata.gz: c231db7a9cb304150e2ca2eccf956be02c367482
4
+ data.tar.gz: 3eb2d929510b34cfd70f2e944eb2590b6059f6e6
5
5
  SHA512:
6
- metadata.gz: 77883ebc45311366dd20819a803013d703c1116d2c8e98ea81de43868a1ee43b77a6d7c7262657abb28f152c5ad3e029e87e631146d8f963a1caf981fb60f8d0
7
- data.tar.gz: 4563b9880ed1c985e0d88155b752a1d55c2480f0d7768e3d82b820e40abbcaba3464602617de29d29ffd6b5226a8e3aa45c93c78b9368eda67bbc63d0182ae7d
6
+ metadata.gz: cede96cad3beb40c9a3f331c47cc930906ccd707283b393aaddccd4d713120233b3e01c4b3d7f15dbf1f117fde4b1c9d3593f1ae43d8189b2d6316345d2e4b16
7
+ data.tar.gz: d941873ef4a11ef98524f994c931b0acea5348dc261b6392dddabdff7212b1d678b91a311b18fbe1d21cd58c986100e386e88de270299093e6b0c580b28a9915
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lapis_lazuli (0.6.2)
4
+ lapis_lazuli (0.7.0)
5
5
  facets (~> 2.9)
6
6
  faraday (~> 0.9.0)
7
7
  faraday_middleware (~> 0.9.1)
@@ -31,7 +31,7 @@ GEM
31
31
  multi_json (~> 1.0)
32
32
  simplecov-html (~> 0.9.0)
33
33
  simplecov-html (0.9.0)
34
- teelogger (0.4.0)
34
+ teelogger (0.4.1)
35
35
  thor (0.19.1)
36
36
 
37
37
  PLATFORMS
@@ -18,6 +18,7 @@ require 'lapis_lazuli/browser/find'
18
18
  require "lapis_lazuli/browser/wait"
19
19
  require "lapis_lazuli/browser/screenshots"
20
20
  require "lapis_lazuli/browser/interaction"
21
+ require "lapis_lazuli/browser/remote"
21
22
  require 'lapis_lazuli/generic/xpath'
22
23
  require 'lapis_lazuli/generic/assertions'
23
24
 
@@ -26,7 +27,7 @@ module LapisLazuli
26
27
  # Extension to the Watir browser
27
28
  #
28
29
  # This class handles initialization, for the most part. BrowserModules
29
- # included here can rely on @world being set to the current cucumber world
30
+ # included here can rely on world being set to the current cucumber world
30
31
  # object, and for some WorldModules to exist in it (see assertions in
31
32
  # constructor).
32
33
  class Browser
@@ -37,29 +38,54 @@ module LapisLazuli
37
38
  include LapisLazuli::BrowserModule::Wait
38
39
  include LapisLazuli::BrowserModule::Screenshots
39
40
  include LapisLazuli::BrowserModule::Interaction
41
+ include LapisLazuli::BrowserModule::Remote
40
42
  include LapisLazuli::GenericModule::XPath
41
- include LapisLazuli::GenericModule::Assertions
42
43
 
43
- @world
44
- @browser
45
- @cached_browser_wanted
46
- @cached_optional_data
44
+ @@world=nil
45
+ @@cached_browser_options={}
46
+ @@browsers=[]
47
+ class << self
48
+ include LapisLazuli::GenericModule::Assertions
49
+
50
+ def browsers
51
+ return @@browsers
52
+ end
53
+
54
+ def add_browser(b)
55
+ # Add destructor for all browsers
56
+ Runtime.instance.set_if(self, :browsers, LapisLazuli::Browser.method(:close_all))
57
+ @@browsers.push(b)
58
+ end
59
+
60
+ def remove_browser(b)
61
+ @@browsers.delete(b)
62
+ end
63
+
64
+ def set_world(w)
65
+ @@world = w
66
+ end
47
67
 
68
+ def check_world?
69
+ assert @@world.respond_to?(:config), "Need to include LapisLazuli::WorldModule::Config in your cucumber world."
70
+ assert @@world.respond_to?(:log), "Need to include LapisLazuli::WorldModule::Logging in your cucumber world."
71
+ assert @@world.respond_to?(:error), "Need to include LapisLazuli::WorldModule::Error in your cucumber world."
72
+ assert @@world.respond_to?(:has_proxy?), "Need to include LapisLazuli::WorldModule::Proxy in your cucumber world."
73
+ end
74
+ end
75
+
76
+ @browser
48
77
  @browser_name
49
- attr_reader :browser_name
78
+ @browser_wanted
79
+ @optional_data
80
+
81
+ attr_reader :browser_name, :browser_wanted, :optional_data
50
82
 
51
- def initialize(world, *args)
83
+ def initialize(*args)
52
84
  # The class only works with some modules loaded; they're loaded by the
53
85
  # Browser module, but we can't be sure that's been used.
54
- assert world.respond_to?(:config), "Need to include LapisLazuli::WorldModule::Config in your cucumber world."
55
- assert world.respond_to?(:log), "Need to include LapisLazuli::WorldModule::Logging in your cucumber world."
56
- assert world.respond_to?(:error), "Need to include LapisLazuli::WorldModule::Error in your cucumber world."
57
- assert world.respond_to?(:has_proxy?), "Need to include LapisLazuli::WorldModule::Proxy in your cucumber world."
86
+ LapisLazuli::Browser.check_world?
58
87
 
59
- @world = world
60
-
61
- # Create a new browser with optional arguments
62
- @browser = self.init(*args)
88
+ self.start(*args)
63
89
 
64
90
  # Add registered world modules.
65
91
  if not LapisLazuli::WorldModule::Browser.browser_modules.nil?
@@ -69,92 +95,23 @@ module LapisLazuli
69
95
  end
70
96
  end
71
97
 
72
- ##
73
- # The main browser window for testing
74
- def init(browser_wanted=(no_browser_wanted=true;nil), optional_data=(no_optional_data=true;nil))
75
- # Store the optional data so on restart of the browser it still has the
76
- # correct configuration
77
- if no_optional_data and optional_data.nil? and @cached_optional_data
78
- optional_data = @cached_optional_data
79
- elsif optional_data.nil?
80
- optional_data = {}
81
- else
82
- # Duplicate the data as Webdriver modifies it
83
- @cached_optional_data = optional_data.dup
84
- end
85
-
86
- # Do the same caching stuff for the browser
87
- if no_browser_wanted and browser_wanted.nil? and @cached_browser_wanted
88
- browser_wanted = @cached_browser_wanted
89
- else
90
- @cached_browser_wanted = browser_wanted
91
- end
92
-
93
- # Create the browser
94
- self.create_internal(browser_wanted, optional_data)
98
+ # Support browser.dup to create a duplicate
99
+ def initialize_copy(source)
100
+ super
101
+ @optional_data = @optional_data.dup
102
+ @browser = create_driver(@browser_wanted, @optional_data)
103
+ # Add this browser to the list of all browsers
104
+ LapisLazuli::Browser.add_browser(self)
95
105
  end
96
106
 
97
107
  ##
98
108
  # Creates a new browser instance.
99
109
  def create(*args)
100
- return Browser.new(@world, *args)
110
+ return Browser.new(*args)
101
111
  end
102
112
 
103
- ##
104
- # Create a new browser depending on settings
105
- # Always cached the supplied arguments
106
- def create_internal(browser_wanted=nil, optional_data=nil)
107
- # No browser? Does the config have a browser? Default to firefox
108
- if browser_wanted.nil?
109
- browser_wanted = @world.env_or_config('browser', 'firefox')
110
- end
111
-
112
- # Select the correct browser
113
- case browser_wanted.to_s.downcase
114
- when 'chrome'
115
- # Check Platform running script
116
- browser = :chrome
117
- when 'safari'
118
- browser = :safari
119
- when 'ie'
120
- require 'rbconfig'
121
- if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
122
- browser = :ie
123
- else
124
- @world.error("You can't run IE tests on non-Windows machine")
125
- end
126
- when 'ios'
127
- if RUBY_PLATFORM.downcase.include?("darwin")
128
- browser = :iphone
129
- else
130
- @world.error("You can't run IOS tests on non-mac machine")
131
- end
132
- else
133
- browser = :firefox
134
- end
135
-
136
- args = [browser]
137
- @browser_name = browser.to_s
138
- if not optional_data.nil? and not optional_data.empty?
139
- @world.log.debug("Got optional data: #{optional_data}")
140
- args.push(optional_data)
141
- elsif @world.has_proxy?
142
- # Create a session if needed
143
- if !@world.proxy.has_session?
144
- @world.proxy.create()
145
- end
146
-
147
- proxy_url = "#{@world.proxy.ip}:#{@world.proxy.port}"
148
- if browser == :firefox
149
- @world.log.debug("Configuring Firefox proxy: #{proxy_url}")
150
- profile = Selenium::WebDriver::Firefox::Profile.new
151
- profile.proxy = Selenium::WebDriver::Proxy.new :http => proxy_url, :ssl => proxy_url
152
- args.push({:profile => profile})
153
- end
154
- end
155
-
156
- browser_instance = Watir::Browser.new(*args)
157
- return browser_instance
113
+ def world
114
+ @@world
158
115
  end
159
116
 
160
117
  ##
@@ -165,23 +122,25 @@ module LapisLazuli
165
122
 
166
123
  ##
167
124
  # Start the browser if it's not yet open.
168
- def start
125
+ def start(*args)
169
126
  if @browser.nil?
170
- @browser = self.init
127
+ @browser = init(*args)
128
+ # Add this browser to the list of all browsers
129
+ LapisLazuli::Browser.add_browser(self)
171
130
  end
172
131
  end
173
132
 
174
133
  ##
175
134
  # Close and create a new browser
176
135
  def restart
177
- @world.log.debug "Restarting browser"
136
+ world.log.debug "Restarting browser"
178
137
  @browser.close
179
138
  self.start
180
139
  end
181
140
 
182
141
  ##
183
142
  # Closes the browser and updates LL so that it will open a new one if needed
184
- def close(reason = nil)
143
+ def close(reason = nil, remove_from_list=true)
185
144
  if not @browser.nil?
186
145
  if not reason.nil?
187
146
  reason = " after #{reason}"
@@ -189,8 +148,11 @@ module LapisLazuli
189
148
  reason = ""
190
149
  end
191
150
 
192
- @world.log.debug "Closing browser#{reason}: #{@browser}"
151
+ world.log.debug "Closing browser#{reason}: #{@browser}"
193
152
  @browser.close
153
+ if remove_from_list
154
+ LapisLazuli::Browser.remove_browser(self)
155
+ end
194
156
  @browser = nil
195
157
  end
196
158
  end
@@ -209,12 +171,12 @@ module LapisLazuli
209
171
  # Default: feature
210
172
  def close_after_scenario(scenario)
211
173
  # Determine the config
212
- close_browser_after = @world.env_or_config("close_browser_after")
174
+ close_browser_after = world.env_or_config("close_browser_after")
213
175
 
214
176
  case close_browser_after
215
177
  when "scenario"
216
178
  # We always close it
217
- self.close close_browser_after
179
+ LapisLazuli::Browser.close_all close_browser_after
218
180
  when "never"
219
181
  # Do nothing: party time, excellent!
220
182
  when "end"
@@ -222,7 +184,7 @@ module LapisLazuli
222
184
  else
223
185
  if is_last_scenario?(scenario)
224
186
  # Close it
225
- self.close close_browser_after
187
+ LapisLazuli::Browser.close_all close_browser_after
226
188
  end
227
189
  end
228
190
  end
@@ -246,13 +208,143 @@ module LapisLazuli
246
208
  end
247
209
 
248
210
  def destroy(world)
249
- if "end" == world.env_or_config("close_browser_after")
250
- begin
251
- self.close "end"
252
- rescue
253
- world.log.debug("Failed to close the browser, probably chrome")
211
+ # Primary browser should also close other browsers
212
+ LapisLazuli::Browser.close_all("end")
213
+ end
214
+
215
+ def self.close_all(reason=nil)
216
+ # A running browser should exist and we are allowed to close it
217
+ if @@browsers.length != 0 and @@world.env_or_config("close_browser_after") != "never"
218
+ # Notify user
219
+ @@world.log.debug("Closing all browsers")
220
+
221
+ # Close each browser
222
+ @@browsers.each do |b|
223
+ begin
224
+ b.close reason, false
225
+ rescue Exception => err
226
+ # Provide some details
227
+ @@world.log.debug("Failed to close the browser, probably chrome: #{err.to_s}")
228
+ end
254
229
  end
230
+
231
+ # Make sure the array is cleared
232
+ @@browsers = []
255
233
  end
256
234
  end
235
+
236
+ private
237
+ ##
238
+ # The main browser window for testing
239
+ def init(browser_wanted=(no_browser_wanted=true;nil), optional_data=(no_optional_data=true;nil))
240
+ # Store the optional data so on restart of the browser it still has the
241
+ # correct configuration
242
+ if no_optional_data and optional_data.nil? and @@cached_browser_options.has_key?(:optional_data) and (browser_wanted.nil? or browser_wanted == @@cached_browser_options[:browser])
243
+ optional_data = @@cached_browser_options[:optional_data]
244
+ elsif optional_data.nil?
245
+ optional_data = {}
246
+ end
247
+
248
+ # Do the same caching stuff for the browser
249
+ if no_browser_wanted and browser_wanted.nil? and @@cached_browser_options.has_key?(:browser)
250
+ browser_wanted = @@cached_browser_options[:browser]
251
+ end
252
+
253
+
254
+ if !@@cached_browser_options.has_key? :browser
255
+ @@cached_browser_options[:browser] = browser_wanted
256
+ # Duplicate the data as Webdriver modifies it
257
+ @@cached_browser_options[:optional_data] = optional_data.dup
258
+ end
259
+
260
+ @browser_wanted = browser_wanted
261
+ @optional_data = optional_data
262
+ # Create the browser
263
+ create_driver(@browser_wanted, @optional_data)
264
+ end
265
+
266
+ ##
267
+ # Create a new browser depending on settings
268
+ # Always cached the supplied arguments
269
+ def create_driver(browser_wanted=nil, optional_data=nil)
270
+ # No browser? Does the config have a browser? Default to firefox
271
+ if browser_wanted.nil?
272
+ browser_wanted = world.env_or_config('browser', 'firefox')
273
+ end
274
+
275
+ # Select the correct browser
276
+ case browser_wanted.to_s.downcase
277
+ when 'chrome'
278
+ # Check Platform running script
279
+ b = :chrome
280
+ when 'safari'
281
+ b = :safari
282
+ when 'ie'
283
+ require 'rbconfig'
284
+ if (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
285
+ b = :ie
286
+ else
287
+ world.error("You can't run IE tests on non-Windows machine")
288
+ end
289
+ when 'ios'
290
+ if RUBY_PLATFORM.downcase.include?("darwin")
291
+ b = :iphone
292
+ else
293
+ world.error("You can't run IOS tests on non-mac machine")
294
+ end
295
+ when 'remote'
296
+ b = :remote
297
+ else
298
+ b = :firefox
299
+ end
300
+
301
+ args = [b]
302
+ @browser_name = b.to_s
303
+ if b == :remote
304
+ # Get the config
305
+ remote_config = world.env_or_config("remote", {})
306
+
307
+ # The settings we are going to use to create the browser
308
+ remote_settings = {}
309
+
310
+ # Add the config to the settings using downcase string keys
311
+ remote_config.each{|k,v| remote_settings[k.to_s.downcase] = v}
312
+
313
+ if optional_data.is_a? Hash
314
+ # Convert the optional data to downcase string keys
315
+ string_hash = Hash.new
316
+ optional_data.each{|k,v| string_hash[k.to_s.downcase] = v}
317
+
318
+ # Merge them with the settings
319
+ remote_settings.merge! string_hash
320
+ end
321
+
322
+ args.push(remote_browser_config(remote_settings))
323
+ elsif not optional_data.nil? and not optional_data.empty?
324
+ world.log.debug("Got optional data: #{optional_data}")
325
+ args.push(optional_data)
326
+ elsif world.has_proxy?
327
+ # Create a session if needed
328
+ if !world.proxy.has_session?
329
+ world.proxy.create()
330
+ end
331
+
332
+ proxy_url = "#{world.proxy.ip}:#{world.proxy.port}"
333
+ if b == :firefox
334
+ world.log.debug("Configuring Firefox proxy: #{proxy_url}")
335
+ profile = Selenium::WebDriver::Firefox::Profile.new
336
+ profile.proxy = Selenium::WebDriver::Proxy.new :http => proxy_url, :ssl => proxy_url
337
+ args.push({:profile => profile})
338
+ end
339
+ end
340
+
341
+ begin
342
+ browser_instance = Watir::Browser.new(*args)
343
+ rescue Selenium::WebDriver::Error::UnknownError => err
344
+ raise err
345
+ end
346
+ return browser_instance
347
+ end
257
348
  end
349
+
258
350
  end
@@ -25,9 +25,9 @@ module BrowserModule
25
25
  if errors.length > 0 or self.get_http_status.to_i > 299
26
26
  errors.each do |error|
27
27
  if error.is_a? Hash
28
- @world.log.debug("#{error["message"]} #{error["url"]} #{error["line"]} #{error["column"]}\n#{error["stack"]}")
28
+ world.log.debug("#{error["message"]} #{error["url"]} #{error["line"]} #{error["column"]}\n#{error["stack"]}")
29
29
  else
30
- @world.log.debug("#{error}")
30
+ world.log.debug("#{error}")
31
31
  end
32
32
  end
33
33
  return true
@@ -42,12 +42,12 @@ module BrowserModule
42
42
  def get_html_errors
43
43
  result = []
44
44
  # Need some error strings
45
- if @world.has_env_or_config?("error_strings")
45
+ if world.has_env_or_config?("error_strings")
46
46
  begin
47
47
  # Get the HTML of the page
48
48
  page_text = @browser.html
49
49
  # Try to find all errors
50
- @world.env_or_config("error_strings").each {|error|
50
+ world.env_or_config("error_strings").each {|error|
51
51
  if page_text.include? error
52
52
  # Add to the result list
53
53
  result.push error
@@ -55,7 +55,7 @@ module BrowserModule
55
55
  }
56
56
  rescue RuntimeError => err
57
57
  # An error?
58
- @world.log.debug "Cannot read the html for page #{@browser.url}: #{err}"
58
+ world.log.debug "Cannot read the html for page #{@browser.url}: #{err}"
59
59
  end
60
60
  end
61
61
  # By default we don't have errors
@@ -157,7 +157,7 @@ module BrowserModule
157
157
  else
158
158
  options[:message] = optional_message("Invalid :pick value #{pick}.", options)
159
159
  options[:groups] = ['find', 'pick']
160
- @world.error(options)
160
+ world.error(options)
161
161
  end
162
162
  end
163
163
  end
@@ -243,7 +243,7 @@ module BrowserModule
243
243
  if not like_opts.has_key? :element
244
244
  selector[:message] = optional_message("Like selector are missing the :element key.", selector)
245
245
  selector[:groups] = ['find', 'selector']
246
- @world.error(selector)
246
+ world.error(selector)
247
247
  end
248
248
  end
249
249
 
@@ -443,7 +443,7 @@ module BrowserModule
443
443
  res = []
444
444
  lambdas.each do |func|
445
445
  res = func.call
446
- # @world.log.debug("Got: #{res}")
446
+ # world.log.debug("Got: #{res}")
447
447
  if res.length > 0
448
448
  break
449
449
  end
@@ -453,7 +453,7 @@ module BrowserModule
453
453
  else
454
454
  options[:message] = optional_message("Invalid mode '#{options[:mode]}' provided to multi_find_all.", options)
455
455
  options[:groups] = ['find', 'multi', 'mode']
456
- @world.error(options)
456
+ world.error(options)
457
457
  end
458
458
  end
459
459
 
@@ -492,7 +492,7 @@ module BrowserModule
492
492
  rescue RuntimeError => err
493
493
  opts[:message] = optional_message(message, selectors)
494
494
  opts[:exception] = err
495
- @world.error(opts)
495
+ world.error(opts)
496
496
  end
497
497
  end
498
498
  end # module Find
@@ -69,7 +69,7 @@ module BrowserModule
69
69
  end
70
70
 
71
71
  if errors.length > 0
72
- raise "Could not click #{elem} given any of these click types: #{types}: #{errors}"
72
+ world.error("Could not click #{elem} given any of these click types: #{types}: #{errors}")
73
73
  end
74
74
  end
75
75
 
@@ -0,0 +1,140 @@
1
+ #
2
+ # LapisLazuli
3
+ # https://github.com/spriteCloud/lapis-lazuli
4
+ #
5
+ # Copyright (c) 2013-2014 spriteCloud B.V. and other LapisLazuli contributors.
6
+ # All rights reserved.
7
+ #
8
+
9
+ module LapisLazuli
10
+ module BrowserModule
11
+ module Remote
12
+ # Convert settings to a valid remote driver argument
13
+ #
14
+ # Features:
15
+ # - settings hash can be case insensitive "URL","Url", "url"
16
+ # - caps.firefox_profile will be converted to a Selenium::WebDriver::Firefox::Profile
17
+ # - caps.proxy / caps.firefox_profile.proxy will be converted to a Selenium::WebDriver::Proxy
18
+ # - Hashes can have a String or a Symbol as key
19
+ #
20
+ # Example:
21
+ # args = remote_browser_config(
22
+ # {
23
+ # "url"=>"http://test.com",
24
+ # "user"=>"user21",
25
+ # "password"=>"jehwiufhewuf",
26
+ # "caps"=> {
27
+ # "browser_name"=>"firefox",
28
+ # "version"=>"37",
29
+ # "firefox_profile"=>{
30
+ # "plugin.state.flash"=>0,
31
+ # "secure_ssl"=>true,
32
+ # "proxy"=>{"http"=>"test.com:9000"}
33
+ # },
34
+ # "proxy"=>{:http=>"test.com:7000"},
35
+ # :css_selectors_enabled => true
36
+ # }
37
+ # })
38
+ # Watir::Browser.new :remote, args
39
+ def remote_browser_config(settings)
40
+ require "uri"
41
+ require "selenium-webdriver"
42
+
43
+ if !settings.is_a? Hash
44
+ world.error("Missing Remote Browser Settings")
45
+ end
46
+
47
+ # Fetch the URl
48
+ url = hash_get_case_insensitive(settings,"url")
49
+
50
+ # Test if its a valid URL
51
+ if not (url.to_s =~ /\A#{URI::regexp(["http", "https"])}\z/)
52
+ raise "Incorrect Remote URL: '#{url.to_s}'"
53
+ end
54
+
55
+ # Create URI object
56
+ uri = URI.parse(url)
57
+
58
+ # Add user if needed
59
+ user = hash_get_case_insensitive(settings,"user")
60
+ if !user.nil?
61
+ uri.user = user
62
+ end
63
+
64
+ # Add password if needed
65
+ password = hash_get_case_insensitive(settings,"password")
66
+ if !password.nil?
67
+ uri.password = password
68
+ end
69
+
70
+ # Create capabil
71
+ # Check ities
72
+ caps = Selenium::WebDriver::Remote::Capabilities.new
73
+ # Fetch the settings
74
+ caps_settings = hash_get_case_insensitive(settings,"caps")
75
+
76
+ # If we have settings
77
+ if !caps_settings.nil? and caps_settings.is_a? Hash
78
+ caps_settings.each do |key, val|
79
+ # Convert to proxy
80
+ if key.to_s == "proxy"
81
+ set_proxy(caps, val)
82
+ # Convert to FF profile
83
+ elsif key.to_s == "firefox_profile"
84
+ profile = Selenium::WebDriver::Firefox::Profile.new
85
+ # Set all the options
86
+ val.each do |fkey, fval|
87
+ # Convert to proxy
88
+ if fkey.to_s == "proxy"
89
+ set_proxy(profile,fval)
90
+ else
91
+ set_key(profile, fkey, fval)
92
+ end
93
+ end
94
+ # Set the profile
95
+ caps[:firefox_profile] = profile
96
+ else
97
+ # Use set_key to assign the key
98
+ set_key(caps, key, val)
99
+ end
100
+ end
101
+ end
102
+
103
+ world.log.debug("Using remote browser: #{url} (#{uri.user}) #{caps.to_json}")
104
+
105
+ return {
106
+ :url => uri.to_s,
107
+ :desired_capabilities => caps
108
+ }
109
+ end
110
+
111
+ private
112
+ def hash_get_case_insensitive(hash, key)
113
+ new_key = hash.keys.find {|e| e.to_s.casecmp(key.to_s) == 0}
114
+ if new_key.nil?
115
+ return nil
116
+ end
117
+ return hash[new_key]
118
+ end
119
+
120
+ # Sets a selenium proxy to the object
121
+ def set_proxy(object, hash)
122
+ proxy = Selenium::WebDriver::Proxy.new
123
+ hash.each do |key, val|
124
+ set_key(proxy, key, val)
125
+ end
126
+ object.proxy = proxy
127
+ end
128
+
129
+ # Uses function based on key or key itself to store the value in the object
130
+ def set_key(object, key, val)
131
+ if object.respond_to? "#{key}="
132
+ object.send("#{key}=", val)
133
+ else
134
+ object[key] = val
135
+ end
136
+ end
137
+
138
+ end # module Remote
139
+ end # module BrowserModule
140
+ end # module LapisLazuli
@@ -15,24 +15,23 @@ module BrowserModule
15
15
  ##
16
16
  # Returns the name of the screenshot, if take_screenshot is called now.
17
17
  def screenshot_name(suffix="")
18
- dir = @world.env_or_config("screenshot_dir")
18
+ dir = world.env_or_config("screenshot_dir")
19
19
 
20
20
  # Generate the file name according to the new or old scheme.
21
- name = 'screenshot'
22
- case @world.env_or_config("screenshot_scheme")
21
+ case world.env_or_config("screenshot_scheme")
23
22
  when "new"
24
- # For non-cucumber cases: we don't have @world.scenario.data
25
- if not @world.scenario.data.nil?
26
- name = @world.scenario.id
23
+ # For non-cucumber cases: we don't have world.scenario.data
24
+ if not world.scenario.data.nil?
25
+ name = world.scenario.id
27
26
  end
28
27
  # FIXME random makes this non-repeatable, sadly
29
- name = "#{@world.scenario.time[:iso_short]}-#{name}-#{Random.rand(10000).to_s}.png"
28
+ name = "#{world.scenario.time[:iso_short]}-#{@browser.object_id}-#{name}-#{Random.rand(10000).to_s}.png"
30
29
  else # 'old' and default
31
- # For non-cucumber cases: we don't have @world.scenario.data
32
- if not @world.scenario.data.nil?
33
- name = @world.scenario.data.name.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/, '_').squeeze('_')
30
+ # For non-cucumber cases: we don't have world.scenario.data
31
+ if not world.scenario.data.nil?
32
+ name = world.scenario.data.name.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/, '_').squeeze('_')
34
33
  end
35
- name = @world.time[:timestamp] + "_" + name + '.png'
34
+ name = world.time[:timestamp] + "_" + name + '.png'
36
35
  end
37
36
 
38
37
  # Full file location
@@ -46,7 +45,7 @@ module BrowserModule
46
45
  # Using the name as defined at the start of every scenario
47
46
  def take_screenshot(suffix="")
48
47
  # If the target directory does not exist, create it.
49
- dir = @world.env_or_config("screenshot_dir")
48
+ dir = world.env_or_config("screenshot_dir")
50
49
  begin
51
50
  Dir.mkdir dir
52
51
  rescue SystemCallError => ex
@@ -61,14 +60,14 @@ module BrowserModule
61
60
  begin
62
61
  # Save the screenshot
63
62
  @browser.screenshot.save fileloc
64
- @world.log.debug "Screenshot saved: #{fileloc}"
63
+ world.log.debug "Screenshot saved: #{fileloc}"
65
64
 
66
65
  # Try to store the screenshot name
67
- if @world.respond_to? :annotate
68
- @world.annotate :screenshot => fileloc
66
+ if world.respond_to? :annotate
67
+ world.annotate :screenshot => fileloc
69
68
  end
70
69
  rescue RuntimeError => e
71
- @world.log.debug "Failed to save screenshot to '#{fileloc}'. Error message #{e.message}"
70
+ world.log.debug "Failed to save screenshot to '#{fileloc}'. Error message #{e.message}"
72
71
  end
73
72
  return fileloc
74
73
  end
@@ -103,7 +103,7 @@ module BrowserModule
103
103
  begin
104
104
  res = Watir::Wait.send(condition, timeout, &find_proc)
105
105
  rescue Watir::Wait::TimeoutError => e
106
- @world.log.debug("Caught timeout: #{e}")
106
+ world.log.debug("Caught timeout: #{e}")
107
107
  err = e
108
108
  end
109
109
 
@@ -112,7 +112,7 @@ module BrowserModule
112
112
  # Error handling
113
113
  if not err.nil? and filter_results.empty?
114
114
  options[:exception] = err
115
- @world.error(options)
115
+ world.error(options)
116
116
  end
117
117
 
118
118
  # Set if the underlying find function returns single results
@@ -39,7 +39,10 @@ module LapisLazuli
39
39
  return @objects[name]
40
40
  end
41
41
 
42
- obj = block.call
42
+ obj = nil
43
+ if !block.nil?
44
+ obj = block.call
45
+ end
43
46
 
44
47
  set(world, name, obj, destructor)
45
48
 
@@ -73,6 +76,17 @@ module LapisLazuli
73
76
  return world.send(name).destroy(world)
74
77
  end
75
78
 
79
+ # Try to run destroy on the object itself
80
+ obj = Runtime.get(name)
81
+ if obj.respond_to? :destroy
82
+ return obj.send(:destroy)
83
+ end
84
+
85
+ # If the object has stream/socket functions close ends the connection
86
+ if obj.respond_to? :close
87
+ return obj.send(:close)
88
+ end
89
+
76
90
  # If all else fails, we have to log an error. We can't rely
77
91
  # on log existing in world, though...
78
92
  message = "No destructor available for #{name}."
@@ -6,5 +6,5 @@
6
6
  # All rights reserved.
7
7
  #
8
8
  module LapisLazuli
9
- VERSION = "0.6.3"
9
+ VERSION = "0.7.0"
10
10
  end
@@ -50,9 +50,10 @@ module WorldModule
50
50
  def browser(*args)
51
51
  b = Runtime.instance.set_if(self, :browser) do
52
52
  # Add LL to the arguments for the browser
53
- browser_args = args.unshift(self)
53
+ LapisLazuli::Browser.set_world(self)
54
+
54
55
  # Create & return a new browser object
55
- LapisLazuli::Browser.new(*browser_args)
56
+ LapisLazuli::Browser.new(*args)
56
57
  end
57
58
 
58
59
  if not b.is_open?
@@ -238,7 +238,7 @@ module WorldModule
238
238
  # Returns current environment
239
239
  def current_env
240
240
  init
241
-
241
+
242
242
  return @env
243
243
  end
244
244
 
@@ -255,11 +255,9 @@ module WorldModule
255
255
 
256
256
  # Environment variables for known options override environment specific
257
257
  # options, too
258
- if CONFIG_OPTIONS.has_key? variable
259
- var = variable.upcase
260
- if ENV.has_key? var
261
- return ENV[var]
262
- end
258
+ env_var = var_from_env(variable, default)
259
+ if env_var != default
260
+ return env_var
263
261
  end
264
262
 
265
263
  return self.config("#{@env}.#{variable}",default)
@@ -281,17 +279,104 @@ module WorldModule
281
279
  # Make sure the configured configuration is loaded, if possible
282
280
  init
283
281
 
282
+ # Environment variables for known options override environment specific
283
+ # options, too
284
+ env_var = var_from_env(variable, default)
285
+ if env_var != default
286
+ return env_var
287
+ end
288
+
284
289
  if self.has_env?(variable)
285
290
  return self.env(variable, default)
286
291
  elsif self.has_config?(variable)
287
292
  return self.config(variable, default)
288
293
  else
289
- return nil
294
+ return default
290
295
  end
291
296
  end
292
297
 
298
+ def var_from_env(var, default=nil)
299
+ # Simple solution for single depth variables like "browser"
300
+ if ENV.has_key? var
301
+ return ENV[var]
302
+ end
293
303
 
304
+ value = default
294
305
 
306
+ # Variables like:
307
+ # var_from_env("remote.url","http://test.test")
308
+ if var.is_a? String and
309
+ var.include? "." and
310
+ not default.is_a? Hash
311
+
312
+ # Env variables cannot contain a . replace them by _
313
+ key_wanted = var.gsub(".","__")
314
+
315
+ # Do a case insensitive compare
316
+ ENV.keys.each do |key|
317
+ if key.casecmp(key_wanted) == 0
318
+ value = ENV[key]
319
+ break
320
+ end
321
+ end
322
+
323
+ # Environment:
324
+ # REMOTE__USER=test
325
+ # REMOTE__PASS=test
326
+ # REMOTE__PROXY__HTTP=http://test.com
327
+ #
328
+ # Call:
329
+ # var_from_env("remote",{})
330
+ #
331
+ # Result:
332
+ # {"USER" => "test",
333
+ # "PASS" => "test",
334
+ # "proxy" => {"HTTP" => "http://test.con"}}
335
+ elsif default.is_a? Hash
336
+ # Env variables cannot contain a . replace them by _
337
+ key_wanted = var.gsub(".","__")
338
+ # Use a regular expression starting with the wanted key
339
+ rgx = Regexp.new("^#{key_wanted}","i")
340
+
341
+ result = {}
342
+ # For each key check if it matched the regexp
343
+ ENV.keys.each do |key|
344
+ if (key =~ rgx) == 0
345
+ tmp = result
346
+ # Remove start and split into parts
347
+ parts = key.sub(rgx, "").split("__")
348
+ # Remove empty start if needed
349
+ if parts[0].to_s.empty?
350
+ parts.shift
351
+ end
352
+
353
+ # For each part
354
+ parts.each_with_index do |part, index|
355
+ # Final part should store the value in the hash
356
+ if index == parts.length - 1
357
+ tmp[part] = ENV[key]
358
+ else
359
+ # Otherwise, downcase the partname
360
+ part.downcase!
361
+ # Set it to an object if needed
362
+ if !tmp.has_key? part
363
+ tmp[part] = {}
364
+ end
365
+ # Assign tmp to the new hash
366
+ tmp = tmp[part]
367
+ end
368
+ end
369
+ end
370
+ end
371
+
372
+ # If we have set keys in the result return it
373
+ if result.keys.length > 0
374
+ return result
375
+ end
376
+ end
377
+
378
+ return value
379
+ end
295
380
 
296
381
  end # module Config
297
382
  end # module WorldModule
@@ -77,18 +77,27 @@ module WorldModule
77
77
  end
78
78
 
79
79
  # Did we fail?
80
- if respond_to? :scenario and respond_to? :browser and respond_to? :config
81
- if (cuke_scenario.failed? or (scenario.check_browser_errors and browser.has_error?))
80
+ if respond_to? :scenario and respond_to? :has_browser? and respond_to? :browser and respond_to? :config
81
+ if has_browser? and (cuke_scenario.failed? or (scenario.check_browser_errors and browser.has_error?))
82
82
  # Take a screenshot if needed
83
83
  if has_env_or_config?('screenshot_on_failure')
84
- fileloc = browser.take_screenshot()
84
+ if env_or_config("screenshot_scheme") == "new"
85
+ # Take screenshots on all active browsers
86
+ LapisLazuli::Browser.browsers.each do |b|
87
+ fileloc = b.take_screenshot()
88
+ end
89
+ else
90
+ browser.take_screenshot()
91
+ end
85
92
  end
86
93
  end
87
94
  end
88
95
 
89
96
  # Close browser if needed
90
- if respond_to? :browser
91
- browser.close_after_scenario(cuke_scenario)
97
+ if respond_to? :has_browser? and respond_to? :browser
98
+ if has_browser?
99
+ browser.close_after_scenario(cuke_scenario)
100
+ end
92
101
  end
93
102
  end
94
103
 
@@ -23,8 +23,8 @@ module WorldModule
23
23
  ##
24
24
  # Checks if there is a proxy started
25
25
  def has_proxy?
26
- p = Runtime.instance.get :proxy
27
- return !p.nil?
26
+ proxy = Runtime.instance.get :proxy
27
+ return !proxy.nil?
28
28
  end
29
29
 
30
30
  ##
@@ -46,7 +46,7 @@ module WorldModule
46
46
  end
47
47
 
48
48
  # Try to start the proxy
49
- p = LapisLazuli::Proxy.new(proxy_ip, proxy_port, proxy_master)
49
+ proxy = LapisLazuli::Proxy.new(proxy_ip, proxy_port, proxy_master)
50
50
 
51
51
  log.debug("Found proxy: #{proxy_ip}:#{proxy_port}, spritecloud: #{proxy_master}")
52
52
  rescue StandardError => err
data/test/Gemfile CHANGED
@@ -36,8 +36,7 @@ gem 'cucumber', '>1.3.19'
36
36
  # gem 'cucumber', '=1.3.19'
37
37
 
38
38
  # Testing related
39
- gem 'simplecov', group: :test, require: nil
40
- gem "codeclimate-test-reporter", group: :test, require: nil
39
+ gem 'simplecov'
41
40
 
42
41
  # LapisLazul itself
43
42
  gem 'lapis_lazuli', :github => 'spriteCloud/lapis-lazuli', :branch => 'master'
@@ -0,0 +1,14 @@
1
+ @config @p
2
+ Feature: config
3
+ When I want to test the Lapis Lazuli library
4
+ I want to run a webserver with some test files
5
+ And execute the each library function that handles configurations.
6
+
7
+ @config_01
8
+ Scenario: config_01 - Environment Hash
9
+ Given I set environment variable "configtest__user" to "username"
10
+ And I set environment variable "configtest__pass" to "password"
11
+ Then the environment variable "configtest" has "user" set to "username"
12
+ And the environment variable "configtest.user" is set to "username"
13
+ And the environment variable "configtest" has "pass" set to "password"
14
+ And the environment variable "close_browser_after" is set to "feature"
@@ -49,13 +49,13 @@ Given(/^I create a firefox browser named "(.*?)"( with proxy to "(.*?)")$/) do |
49
49
  else
50
50
  b = browser.create :firefox
51
51
  end
52
- scenario.storage.set(name, browser)
52
+ scenario.storage.set(name, b)
53
53
  end
54
54
 
55
55
  Given(/^I close the browser named "(.*?)"$/) do |name|
56
56
  if scenario.storage.has? name
57
- browser = scenario.storage.get name
58
- browser.close
57
+ b = scenario.storage.get name
58
+ b.close
59
59
  else
60
60
  error("No item in the storage named #{name}")
61
61
  end
@@ -139,6 +139,9 @@ Then(/^I should be able to click the first button by force click$/) do
139
139
  )
140
140
  end
141
141
 
142
+ Given(/^I set environment variable "(.*?)" to "(.*?)"$/) do |var, val|
143
+ ENV[var]=val
144
+ end
142
145
 
143
146
  Given(/^I annotate a step with (.*?)$/) do |data|
144
147
  annotate data
@@ -230,8 +230,14 @@ end
230
230
  Then(/^the firefox browser named "(.*?)" has a profile$/) do |name|
231
231
  if scenario.storage.has? name
232
232
  browser = scenario.storage.get name
233
- if browser.driver.capabilities.firefox_profile.nil?
234
- raise "Profile is not set"
233
+ if browser.browser_name == "remote"
234
+ if browser.driver.capabilities.firefox_profile.nil?
235
+ raise "Remote Firefox Profile is not set"
236
+ end
237
+ else
238
+ if browser.optional_data.has_key? "profile" or browser.optional_data.has_key? :profile
239
+ raise "No profile found in the optional data"
240
+ end
235
241
  end
236
242
  else
237
243
  error("No item in the storage named #{name}")
@@ -348,3 +354,14 @@ Then(/^I expect to use tagname to hash options to (.*?) find element (.*?)$/) do
348
354
  end
349
355
  end
350
356
 
357
+ Then(/^the environment variable "(.*?)" is set to "(.*?)"$/) do |var,val|
358
+ value = env(var)
359
+ assert_equal val, value, "Incorrect value"
360
+ end
361
+
362
+ Then(/^the environment variable "(.*?)" has "(.*?)" set to "(.*?)"$/) do |var, key, val|
363
+ hash = env(var,{})
364
+ assert hash.is_a?(Hash), "Config element is not a Hash: #{hash}"
365
+ assert hash.has_key?(key), "Config is missing #{key}"
366
+ assert_equal hash[key], val, "Config has incorrect value"
367
+ end
@@ -2,9 +2,6 @@
2
2
  # Copyright 2014 spriteCloud B.V. All rights reserved.
3
3
  # Generated by LapisLazuli, version 0.0.1
4
4
  # Author: "Onno Steenbergen" <onno@steenbe.nl>
5
- require "codeclimate-test-reporter"
6
- CodeClimate::TestReporter.start
7
-
8
5
  require 'lapis_lazuli'
9
6
  require 'lapis_lazuli/cucumber'
10
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lapis_lazuli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Onno Steenbergen
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-04-22 00:00:00.000000000 Z
14
+ date: 2015-05-06 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -196,6 +196,7 @@ files:
196
196
  - lib/lapis_lazuli/browser/error.rb
197
197
  - lib/lapis_lazuli/browser/find.rb
198
198
  - lib/lapis_lazuli/browser/interaction.rb
199
+ - lib/lapis_lazuli/browser/remote.rb
199
200
  - lib/lapis_lazuli/browser/screenshots.rb
200
201
  - lib/lapis_lazuli/browser/wait.rb
201
202
  - lib/lapis_lazuli/cli.rb
@@ -239,6 +240,7 @@ files:
239
240
  - test/features/browser.feature
240
241
  - test/features/button.feature
241
242
  - test/features/click.feature
243
+ - test/features/config.feature
242
244
  - test/features/error.feature
243
245
  - test/features/find.feature
244
246
  - test/features/har.feature
@@ -293,6 +295,7 @@ test_files:
293
295
  - test/features/browser.feature
294
296
  - test/features/button.feature
295
297
  - test/features/click.feature
298
+ - test/features/config.feature
296
299
  - test/features/error.feature
297
300
  - test/features/find.feature
298
301
  - test/features/har.feature