sauce 1.0.2 → 2.0.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.
Files changed (56) hide show
  1. data/.document +5 -0
  2. data/.gitignore +30 -0
  3. data/Gemfile +16 -0
  4. data/README.markdown +39 -145
  5. data/Rakefile +46 -20
  6. data/bin/sauce +72 -61
  7. data/gemfiles/rails2.gemfile +10 -0
  8. data/gemfiles/rails2.gemfile.lock +77 -0
  9. data/gemfiles/rails3.gemfile +9 -0
  10. data/gemfiles/rails3.gemfile.lock +137 -0
  11. data/lib/generators/sauce/install/install_generator.rb +1 -2
  12. data/lib/sauce.rb +0 -22
  13. data/lib/sauce/capybara.rb +70 -32
  14. data/lib/sauce/capybara/cucumber.rb +121 -0
  15. data/lib/sauce/config.rb +57 -13
  16. data/lib/sauce/connect.rb +22 -11
  17. data/lib/sauce/integrations.rb +27 -69
  18. data/lib/sauce/jasmine.rb +35 -0
  19. data/lib/sauce/jasmine/rake.rb +47 -0
  20. data/lib/sauce/jasmine/runner.rb +4 -0
  21. data/lib/sauce/job.rb +10 -6
  22. data/lib/sauce/raketasks.rb +0 -21
  23. data/lib/sauce/selenium.rb +9 -18
  24. data/lib/sauce/utilities.rb +0 -17
  25. data/sauce.gemspec +8 -60
  26. data/spec/integration/connect_integration_spec.rb +84 -0
  27. data/spec/sauce/capybara/cucumber_spec.rb +156 -0
  28. data/spec/sauce/capybara/spec_helper.rb +42 -0
  29. data/spec/sauce/capybara_spec.rb +121 -0
  30. data/spec/sauce/config_spec.rb +239 -0
  31. data/spec/sauce/jasmine_spec.rb +49 -0
  32. data/spec/sauce/selenium_spec.rb +57 -0
  33. data/spec/spec_helper.rb +4 -0
  34. data/support/Sauce-Connect.jar +0 -0
  35. data/test/test_integrations.rb +202 -0
  36. data/test/test_testcase.rb +13 -0
  37. metadata +170 -171
  38. data/examples/helper.rb +0 -16
  39. data/examples/other_spec.rb +0 -7
  40. data/examples/saucelabs_spec.rb +0 -12
  41. data/examples/test_saucelabs.rb +0 -13
  42. data/examples/test_saucelabs2.rb +0 -9
  43. data/support/sauce_connect +0 -938
  44. data/support/selenium-server.jar +0 -0
  45. data/support/simplejson/LICENSE.txt +0 -19
  46. data/support/simplejson/__init__.py +0 -437
  47. data/support/simplejson/decoder.py +0 -421
  48. data/support/simplejson/encoder.py +0 -501
  49. data/support/simplejson/ordered_dict.py +0 -119
  50. data/support/simplejson/scanner.py +0 -77
  51. data/support/simplejson/tool.py +0 -39
  52. data/test/test_config.rb +0 -112
  53. data/test/test_connect.rb +0 -45
  54. data/test/test_job.rb +0 -13
  55. data/test/test_selenium.rb +0 -50
  56. data/test/test_selenium2.rb +0 -9
data/lib/sauce/config.rb CHANGED
@@ -4,12 +4,15 @@ require 'uri'
4
4
 
5
5
  module Sauce
6
6
  def self.config
7
- @cfg = Sauce::Config.new(false)
8
- yield @cfg
7
+ yield get_config
9
8
  end
10
9
 
11
10
  def self.get_config
12
- @cfg
11
+ @cfg ||= Sauce::Config.new(false)
12
+ end
13
+
14
+ def self.clear_config
15
+ @cfg = nil
13
16
  end
14
17
 
15
18
  class Config
@@ -24,7 +27,8 @@ module Sauce
24
27
  :browser_version => "3.6.",
25
28
  :job_name => "Unnamed Ruby job",
26
29
  :local_application_port => "3001",
27
- :capture_traffic => false
30
+ :capture_traffic => false,
31
+ :start_tunnel => true
28
32
  }
29
33
 
30
34
  ENVIRONMENT_VARIABLES = %w{SAUCE_HOST SAUCE_PORT SAUCE_BROWSER_URL SAUCE_USERNAME
@@ -60,7 +64,22 @@ module Sauce
60
64
  end
61
65
  end
62
66
 
67
+ def [](key)
68
+ @opts[key]
69
+ end
70
+
71
+ def []=(key, value)
72
+ @opts[key] = value
73
+ end
74
+
75
+ def silence_warnings
76
+ false
77
+ end
78
+
63
79
  def method_missing(meth, *args)
80
+ unless self.silence_warnings
81
+ warn "[DEPRECATED] This method (#{meth}) is deprecated, please use the [] and []= accessors instead"
82
+ end
64
83
  if meth.to_s =~ /(.*)=$/
65
84
  @opts[$1.to_sym] = args[0]
66
85
  return args[0]
@@ -144,29 +163,54 @@ module Sauce
144
163
  return ENV['LOCAL_SELENIUM']
145
164
  end
146
165
 
166
+ def username
167
+ @opts[:username]
168
+ end
169
+
170
+ def access_key
171
+ @opts[:access_key]
172
+ end
173
+
174
+ def host
175
+ @opts[:host]
176
+ end
177
+
178
+ def port
179
+ @opts[:port]
180
+ end
181
+
147
182
  private
148
183
 
149
184
  def load_options_from_environment
150
185
  return extract_options_from_hash(ENV)
151
186
  end
152
187
 
188
+ # Heroku supports multiple apps per $PWD. Specify $SAUCE_HEROKU_APP if
189
+ # needed otherwise this can still time out.
153
190
  def load_options_from_heroku
154
- @@herkou_environment ||= begin
155
- buffer = IO.popen("heroku config --shell 2>/dev/null") { |out| out.read }
156
- if $?.exitstatus == 0
157
- env = {}
158
- buffer.split("\n").each do |line|
159
- key, value = line.split("=")
160
- env[key] = value
191
+ @@heroku_environment ||= begin
192
+ if File.exists?(File.expand_path('~/.heroku'))
193
+ heroku_app = ENV['SAUCE_HEROKU_APP']
194
+ cmd = "heroku config #{heroku_app ? "--app #{heroku_app}": ''}"
195
+ cmd += "--shell 2>/dev/null"
196
+ buffer = IO.popen(cmd) { |out| out.read }
197
+ if $?.exitstatus == 0
198
+ env = {}
199
+ buffer.split("\n").each do |line|
200
+ key, value = line.split("=")
201
+ env[key] = value
202
+ end
203
+ extract_options_from_hash(env)
204
+ else
205
+ {}
161
206
  end
162
- extract_options_from_hash(env)
163
207
  else
164
208
  {}
165
209
  end
166
210
  rescue Errno::ENOENT
167
211
  {} # not a Heroku environment
168
212
  end
169
- return @@herkou_environment
213
+ return @@heroku_environment
170
214
  end
171
215
 
172
216
  def load_options_from_yaml
data/lib/sauce/connect.rb CHANGED
@@ -1,6 +1,8 @@
1
+ require 'sauce/config'
2
+
1
3
  module Sauce
2
4
  class Connect
3
- TIMEOUT = 300
5
+ TIMEOUT = 90
4
6
 
5
7
  attr_reader :status, :error
6
8
 
@@ -8,21 +10,25 @@ module Sauce
8
10
  @ready = false
9
11
  @status = "uninitialized"
10
12
  @error = nil
13
+ @quiet = options[:quiet]
11
14
  host = options[:host] || '127.0.0.1'
12
15
  port = options[:port] || '3000'
13
16
  tunnel_port = options[:tunnel_port] || '80'
14
17
  options.delete(:host)
15
18
  options.delete(:port)
16
19
  options.delete(:tunnel_port)
17
- config = Sauce::Config.new(options)
18
- if config.username.nil?
20
+ @config = Sauce::Config.new(options)
21
+ if @config.username.nil?
19
22
  raise ArgumentError, "Username required to launch Sauce Connect. Please set the environment variable $SAUCE_USERNAME"
20
23
  end
21
- if config.access_key.nil?
24
+ if @config.access_key.nil?
22
25
  raise ArgumentError, "Access key required to launch Sauce Connect. Please set the environment variable $SAUCE_ACCESS_KEY"
23
26
  end
24
- args = ['-u', config.username, '-k', config.access_key, '-s', host, '-p', port, '-d', config.domain, '-t', tunnel_port]
25
- @pipe = IO.popen((["exec", "\"#{Sauce::Connect.find_sauce_connect}\""] + args + ["2>&1"]).join(' '))
27
+ end
28
+
29
+ def connect
30
+ puts "[Connecting to Sauce OnDemand...]"
31
+ @pipe = IO.popen("exec #{Sauce::Connect.connect_command} #{@config.username} #{@config.access_key} 2>&1")
26
32
  @process_status = $?
27
33
  at_exit do
28
34
  Process.kill("INT", @pipe.pid)
@@ -32,7 +38,7 @@ module Sauce
32
38
  end
33
39
  Thread.new {
34
40
  while( (line = @pipe.gets) )
35
- if line =~ /Tunnel host is (.*) (\.\.|at)/
41
+ if line =~ /Tunnel remote VM is (.*) (\.\.|at)/
36
42
  @status = $1
37
43
  end
38
44
  if line =~/You may start your tests\./
@@ -43,9 +49,9 @@ module Sauce
43
49
  end
44
50
  if line =~ /== Missing requirements ==/
45
51
  @error = "Missing requirements"
46
- options[:quiet] = false
52
+ @quiet = false
47
53
  end
48
- $stderr.puts line unless options[:quiet]
54
+ $stderr.puts line unless @quiet
49
55
  end
50
56
  @ready = false
51
57
  }
@@ -54,7 +60,7 @@ module Sauce
54
60
  def wait_until_ready
55
61
  start = Time.now
56
62
  while !@ready and (Time.now-start) < TIMEOUT and @error != "Missing requirements"
57
- sleep 0.4
63
+ sleep 0.5
58
64
  end
59
65
 
60
66
  if @error == "Missing requirements"
@@ -76,7 +82,11 @@ module Sauce
76
82
  end
77
83
 
78
84
  def self.find_sauce_connect
79
- File.join(File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__)))), "support", "sauce_connect")
85
+ File.expand_path(File.dirname(__FILE__) + '/../../support/Sauce-Connect.jar')
86
+ end
87
+
88
+ def self.connect_command
89
+ "java -jar #{Sauce::Connect.find_sauce_connect}"
80
90
  end
81
91
 
82
92
  # Global Sauce Connect-ness
@@ -84,6 +94,7 @@ module Sauce
84
94
 
85
95
  def self.connect!(*args)
86
96
  @connection = self.new(*args)
97
+ @connection.connect
87
98
  @connection.wait_until_ready
88
99
  at_exit do
89
100
  @connection.disconnect
@@ -1,3 +1,5 @@
1
+ require 'sauce/utilities'
2
+
1
3
  begin
2
4
  require 'spec'
3
5
  module Sauce
@@ -15,8 +17,9 @@ begin
15
17
  before :suite do
16
18
  config = Sauce::Config.new
17
19
  if @@need_tunnel
18
- if config.application_host && !config.local?
20
+ if config.application_host
19
21
  @@tunnel = Sauce::Connect.new(:host => config.application_host, :port => config.application_port || 80)
22
+ @@tunnel.connect
20
23
  @@tunnel.wait_until_ready
21
24
  end
22
25
  if Sauce::Utilities::RailsServer.is_rails_app?
@@ -35,29 +38,11 @@ begin
35
38
  config = Sauce::Config.new
36
39
  description = [self.class.description, self.description].join(" ")
37
40
  config.browsers.each do |os, browser, version|
38
- if config.single_session?
39
- if config.local?
40
- @selenium = Sauce.cached_session(:host => "127.0.0.1", :port => 4444, :browser => "*" +
41
- browser, :url => "http://127.0.0.1:#{config.local_application_port}/")
42
- else
43
- @selenium = Sauce.cached_session({:os => os, :browser => browser, :browser_version => version,
44
- :job_name => self.class.description})
45
- end
46
- super(*args)
47
- else
48
- if config.local?
49
- @selenium = ::Selenium::Client::Driver.new(:host => "127.0.0.1",
50
- :port => 4444,
51
- :browser => "*" + browser,
52
- :url => "http://127.0.0.1:#{config.local_application_port}/")
53
- else
54
- @selenium = Sauce::Selenium.new({:os => os, :browser => browser, :browser_version => version,
55
- :job_name => "#{description}"})
56
- end
57
- @selenium.start_new_browser_session(:captureNetworkTraffic => config.capture_traffic?)
58
- super(*args)
59
- @selenium.stop
60
- end
41
+ @selenium = Sauce::Selenium2.new({:os => os, :browser => browser,
42
+ :browser_version => version,
43
+ :job_name => description})
44
+ super(*args)
45
+ @selenium.stop
61
46
  end
62
47
  end
63
48
 
@@ -86,16 +71,10 @@ begin
86
71
  config = Sauce::Config.new
87
72
  description = the_test.metadata[:full_description]
88
73
  config.browsers.each do |os, browser, version|
89
- if config.local?
90
- @selenium = ::Selenium::Client::Driver.new(:host => "127.0.0.1",
91
- :port => 4444,
92
- :browser => "*" + browser,
93
- :url => "http://127.0.0.1:#{config.local_application_port}/")
94
- else
95
- @selenium = Sauce::Selenium.new({:os => os, :browser => browser, :browser_version => version,
96
- :job_name => "#{description}"})
97
- end
98
- @selenium.start_new_browser_session(:captureNetworkTraffic => config.capture_traffic?)
74
+ @selenium = Sauce::Selenium2.new({:os => os,
75
+ :browser => browser,
76
+ :browser_version => version,
77
+ :job_name => description})
99
78
  begin
100
79
  the_test.run
101
80
  ensure
@@ -111,15 +90,18 @@ begin
111
90
  ::RSpec.configuration.before :suite do
112
91
  need_tunnel = false
113
92
  config = Sauce::Config.new
114
- if config.application_host && !config.local?
115
- need_tunnel = ::RSpec.configuration.settings[:files_to_run].any? {|file| file =~ /spec\/selenium\//}
93
+ files_to_run = ::RSpec.configuration.respond_to?(:files_to_run) ? ::RSpec.configuration.files_to_run :
94
+ ::RSpec.configuration.settings[:files_to_run]
95
+ if config.application_host
96
+ need_tunnel = files_to_run.any? {|file| file =~ /spec\/selenium\//}
116
97
  end
117
98
  if need_tunnel
118
99
  @@tunnel = Sauce::Connect.new(:host => config.application_host, :port => config.application_port || 80)
100
+ @@tunnel.connect
119
101
  @@tunnel.wait_until_ready
120
102
  end
121
103
 
122
- if ::RSpec.configuration.settings[:files_to_run].any? {|file| file =~ /spec\/selenium\//} &&
104
+ if files_to_run.any? {|file| file =~ /spec\/selenium\//} &&
123
105
  Sauce::Utilities::RailsServer.is_rails_app?
124
106
  @@server = Sauce::Utilities::RailsServer.new
125
107
  @@server.start
@@ -152,7 +134,7 @@ module Sauce
152
134
  end
153
135
  unless my_name =~ /^default_test/
154
136
  config = Sauce::Config.new
155
- if config.application_host && !config.local?
137
+ if config.application_host
156
138
  unless ENV['TEST_ENV_NUMBER'].to_i > 1
157
139
  Sauce::Connect.ensure_connected(:host => config.application_host, :port => config.application_port || 80)
158
140
  end
@@ -171,37 +153,13 @@ module Sauce
171
153
  end
172
154
 
173
155
  config.browsers.each do |os, browser, version|
174
- if config.single_session?
175
- if config.local?
176
- @browser = Sauce.cached_session(:host => "127.0.0.1", :port => 4444, :browser => "*" +
177
- browser, :url => "http://127.0.0.1:#{config.local_application_port}/")
178
- else
179
- options = self.class.sauce_config
180
- options.merge!({:os => os, :browser => browser, :browser_version => version,
181
- :job_name => "#{Rails.root.split[1].to_s} test suite"})
182
- @browser = Sauce.cached_session(options)
183
- end
184
- super(*args, &blk)
185
- else
186
- if config.local?
187
- @browser = ::Selenium::Client::Driver.new(:host => "127.0.0.1",
188
- :port => 4444,
189
- :browser => "*" + browser,
190
- :url => "http://127.0.0.1:#{config.local_application_port}/")
191
- else
192
- options = self.class.sauce_config
193
- options.merge!({:os => os, :browser => browser, :browser_version => version,
194
- :job_name => "#{my_name}"})
195
- @browser = Sauce::Selenium.new(options)
196
- end
197
- if self.class.selenium_flags
198
- @browser.start_new_browser_session(self.class.selenium_flags.merge(:captureNetworkTraffic => config.capture_traffic?))
199
- else
200
- @browser.start_new_browser_session(:captureNetworkTraffic => config.capture_traffic?)
201
- end
202
- super(*args, &blk)
203
- @browser.stop
204
- end
156
+ options = self.class.sauce_config
157
+ options.merge!({:os => os, :browser => browser,
158
+ :browser_version => version,
159
+ :job_name => my_name.to_s})
160
+ @browser = Sauce::Selenium2.new(options)
161
+ super(*args, &blk)
162
+ @browser.stop
205
163
  end
206
164
  end
207
165
  end
@@ -0,0 +1,35 @@
1
+ require 'jasmine'
2
+
3
+ module Sauce
4
+ module Jasmine
5
+ class Driver < ::Jasmine::SeleniumDriver
6
+ attr_reader :http_address, :driver, :browser
7
+
8
+ def initialize(browser, http_address)
9
+ @browser = browser
10
+ @http_address = http_address
11
+ @driver = Sauce::Selenium2.new(:browser => browser, :job_name => job_name)
12
+ puts "Starting job named: #{job_name}"
13
+ end
14
+
15
+ def job_name
16
+ "Jasmine Test Run #{Time.now.utc.to_i}"
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ module Jasmine
23
+ class Config
24
+ def jasmine_port
25
+ '3001'
26
+ end
27
+
28
+ def start
29
+ @client = Sauce::Jasmine::Driver.new(browser, "#{jasmine_host}:#{jasmine_port}/")
30
+ @client.connect
31
+ end
32
+ end
33
+ end
34
+
35
+
@@ -0,0 +1,47 @@
1
+ require 'jasmine'
2
+ require 'rspec/core/rake_task'
3
+
4
+ namespace :jasmine do
5
+ def run_jasmine_server
6
+ ENV['JASMINE_PORT'] = '3001'
7
+ Jasmine::Config.new.start_jasmine_server
8
+ end
9
+
10
+ desc "Execute Jasmine tests in a Chrome browser on Sauce Labs"
11
+ task :sauce do
12
+ run_jasmine_server
13
+ Rake::Task['jasmine:sauce:chrome'].execute
14
+ end
15
+
16
+ namespace :sauce do
17
+ desc "Execute Jasmine tests in Chrome, Firefox and Internet Explorer on Sauce Labs"
18
+ task :all do
19
+ run_jasmine_server
20
+ threads = []
21
+ [:firefox, :chrome, :iexplore].each do |browser|
22
+ t = Thread.new do
23
+ Rake::Task["jasmine:sauce:#{browser}"].invoke
24
+ end
25
+ t.abort_on_exception = true
26
+ threads << t
27
+ end
28
+
29
+ threads.each do |t|
30
+ t.join
31
+ end
32
+ end
33
+
34
+ [[:firefox, 8], [:chrome, nil], [:iexplore, 8]].each do |browser, version|
35
+ desc "Execute Jasmine tests in #{browser}"
36
+ RSpec::Core::RakeTask.new(browser) do |t|
37
+ ENV['SAUCE_BROWSER'] = browser.to_s
38
+ unless version.nil?
39
+ ENV['SAUCE_BROWSER_VERSION'] = version.to_s
40
+ end
41
+ t.rspec_opts = '--color'
42
+ t.pattern = [File.expand_path(File.dirname(__FILE__) + '/runner.rb')]
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,4 @@
1
+ require 'sauce'
2
+ require 'sauce/jasmine'
3
+ ENV['JASMINE_PORT'] = '3001'
4
+ require 'jasmine/runner'
data/lib/sauce/job.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'sauce/client'
2
+
1
3
  module Sauce
2
4
  # Interact with a Sauce Labs selenium jobs as if it were a ruby object
3
5
  class Job
@@ -8,14 +10,14 @@ module Sauce
8
10
  attr_accessor :name, :browser, :browser_version, :os
9
11
  attr_accessor :creation_time, :start_time, :end_time
10
12
  attr_accessor :public, :video_url, :log_url, :tags
11
- attr_accessor :passed
13
+ attr_accessor :passed, :custom_data
12
14
 
13
15
  # Get the class @@client.
14
16
  # TODO: Consider metaprogramming this away
15
17
  def self.client
16
18
  @@client
17
19
  end
18
-
20
+
19
21
  # Set the class @@client.
20
22
  # TODO: Consider metaprogramming this away
21
23
  def self.client=(client)
@@ -64,7 +66,7 @@ module Sauce
64
66
  end
65
67
 
66
68
  @@client ||= Sauce::Client.new
67
-
69
+
68
70
  #puts "GET-URL: #{@@client.url}jobs/#{id}"
69
71
  response = @@client["jobs/#{id}"].get
70
72
 
@@ -75,6 +77,7 @@ module Sauce
75
77
  # Creates an instance representing a job.
76
78
  def initialize(options)
77
79
  build!(options)
80
+ @client = Sauce::Client.new
78
81
  end
79
82
 
80
83
  # Retrieves the latest information on this job from the Sauce Labs' server
@@ -88,7 +91,7 @@ module Sauce
88
91
  # Save/update the current information for the job
89
92
  def save
90
93
  #puts "Saving job:\n -X PUT #{@@client['jobs']}/#{@id} -H 'Content-Type: application/json' -d '#{self.to_json}'"
91
- response = @@client["jobs/#{@id}"].put(self.to_json,
94
+ response = @client["jobs/#{@id}"].put(self.to_json,
92
95
  {:content_type => :json,
93
96
  :accept => :json}).body
94
97
  JSON.parse(response)
@@ -97,6 +100,7 @@ module Sauce
97
100
  def to_json(options={})
98
101
  json = {
99
102
  :id => @id,
103
+ :'custom-data' => @custom_data,
100
104
  :owner => @owner,
101
105
  :status => @status,
102
106
  :error => @error,
@@ -116,7 +120,7 @@ module Sauce
116
120
 
117
121
  options[:except].each { |key| json.delete(key) } if options[:except]
118
122
  json = json.select { |key,value| options[:only].include? key } if options[:only]
119
-
123
+
120
124
  return json.to_json
121
125
  end
122
126
 
@@ -128,7 +132,6 @@ module Sauce
128
132
 
129
133
  # Sets all internal variables from a hash
130
134
  def build!(options)
131
- #puts "\tBuild with: #{options.inspect}"
132
135
  # Massage JSON
133
136
  options.each { |key,value| options[key] = false if options[key] == "false" }
134
137
 
@@ -148,6 +151,7 @@ module Sauce
148
151
  @public = options["public"]
149
152
  @tags = options["tags"]
150
153
  @passed = options["passed"]
154
+ @custom_data = options['custom-data']
151
155
 
152
156
  raise NoIDError if @id.nil? or @id.empty?
153
157
  end