sauce 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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