jsunit-sauce 0.0.1

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.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Sauce Labs Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ JSUnit glue for running your JSUnit tests in Sauce OnDemand
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib'
7
+ test.pattern = 'test/test_*.rb'
8
+ test.verbose = true
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,18 @@
1
+ module Sauce
2
+ module JSUnit
3
+ class RunUtils
4
+ def self.run(command, options = {})
5
+ default_options = {
6
+ :raise_on_fail => true
7
+ }
8
+ options = default_options.merge(options)
9
+ puts "Executing: #{command}"
10
+ success = system(command)
11
+ if !success && options[:raise_on_fail]
12
+ raise "Command failed: #{command}"
13
+ end
14
+ success
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,220 @@
1
+ module Sauce
2
+ module JSUnit
3
+ class SeleniumConfig
4
+
5
+ include Utilities
6
+
7
+ attr_reader :configuration
8
+
9
+ def initialize(configuration_name = nil, selenium_yml_path = nil)
10
+ selenium_yml_path = selenium_yml_path || File.join(ENV['RAILS_ROOT'] || RAILS_ROOT, 'config', 'selenium.yml')
11
+ SeleniumConfig.parse_yaml(selenium_yml_path)
12
+ build_configuration(configuration_name)
13
+ end
14
+
15
+ def []=(attribute, value)
16
+ @configuration[attribute.to_s] = value
17
+ end
18
+
19
+ [ :test_framework, :start_server,
20
+ :selenium_server_address, :selenium_server_port,
21
+ :application_address, :application_port,
22
+ :saucelabs_username, :saucelabs_access_key,
23
+ :saucelabs_browser_os, :saucelabs_browser, :saucelabs_browser_version,
24
+ :saucelabs_max_duration_seconds,
25
+ :tunnel_method, :tunnel_to_localhost_port, :tunnel_startup_timeout,
26
+ :tunnel_username, :tunnel_password, :tunnel_keyfile,
27
+ :jsunit_polling_interval_seconds, :kill_mongrel_after_suite ].each do |attr|
28
+ define_method(attr) do
29
+ @configuration[attr.to_s]
30
+ end
31
+ end
32
+
33
+ def selenium_browser_key
34
+ if selenium_server_address == 'saucelabs.com'
35
+ # Create the JSON string that Saucelabs needs:
36
+ { 'username' => saucelabs_username,
37
+ 'access-key' => saucelabs_access_key,
38
+ 'os' => saucelabs_browser_os,
39
+ 'browser' => saucelabs_browser,
40
+ 'browser-version' => saucelabs_browser_version,
41
+ 'max-duration' => saucelabs_max_duration_seconds.to_i,
42
+ 'job-name' => ENV['SAUCELABS_JOB_NAME'] || Socket.gethostname
43
+ }.to_json
44
+ else
45
+ @configuration['selenium_browser_key']
46
+ end
47
+ end
48
+
49
+ def application_address
50
+ if start_tunnel? &&
51
+ [:sauceconnecttunnel, :saucetunnel].include?(@configuration['tunnel_method'].to_sym)
52
+ # We are using Sauce Labs and Sauce Connect Tunnel or Sauce Tunnel.
53
+ # We need to use a masquerade hostname on the EC2/Sauce end of the tunnel that will be unique within the scope of
54
+ # this account (e.g. pivotallabs). Therefore we mint a fairly unique hostname here.
55
+ hostname = Socket.gethostname.split(".").first
56
+ "#{hostname}-#{Process.pid}.com"
57
+ else
58
+ @configuration['application_address']
59
+ end
60
+
61
+ end
62
+
63
+ # Takes a Webrat::Configuration object and configures it by calling methods on it
64
+ def configure_webrat(webrat_configuration_object)
65
+ # NOTE: application_port_for_selenium requires version > 0.7.3 of webrat
66
+ # Prior versions only have application_address, and don't have a concept of
67
+ # starting a rails server at one port, and hitting it at selenium via another
68
+ {
69
+ 'selenium_server_address' => :selenium_server_address,
70
+ 'selenium_server_port' => :selenium_server_port,
71
+ 'selenium_browser_key' => :selenium_browser_key,
72
+ 'application_address' => :application_address,
73
+ 'application_port_for_selenium' => start_tunnel? ? :tunnel_to_localhost_port : :application_port,
74
+ 'application_port' => :application_port
75
+ }.each do |webrat_configuration_method, our_accessor|
76
+ webrat_configuration_object.send("#{webrat_configuration_method}=", self.send(our_accessor).to_s)
77
+ end
78
+ end
79
+
80
+ # Takes a Polonium::Configuration object and configures it by calling methods on it
81
+ def configure_polonium(polonium_configuration_object)
82
+ {
83
+ 'selenium_server_host' => :selenium_server_address,
84
+ 'selenium_server_port' => :selenium_server_port,
85
+ 'browser' => :selenium_browser_key,
86
+ 'external_app_server_host' => :application_address,
87
+ 'external_app_server_port' => :application_port
88
+ }.each do |polonium_configuration_method, our_accessor|
89
+ polonium_configuration_object.send("#{polonium_configuration_method}=", self.send(our_accessor).to_s)
90
+ end
91
+ end
92
+
93
+ def create_driver(selenium_args = {})
94
+ args = selenium_client_driver_args.merge(selenium_args)
95
+ say "Connecting to Selenium RC server at #{args[:host]}:#{args[:port]} (testing app at #{args[:url]})" if ENV['SAUCELABS_ADAPTER_DEBUG']
96
+ say "args = #{display_safely(args)}" if ENV['SAUCELABS_ADAPTER_DEBUG']
97
+ driver = ::Selenium::Client::Driver.new(args)
98
+ debug "done"
99
+ driver
100
+ end
101
+
102
+ def start_tunnel?
103
+ !tunnel_method.nil? && tunnel_method.to_sym != :othertunnel
104
+ end
105
+
106
+ def kill_mongrel_after_suite?
107
+ return true if kill_mongrel_after_suite.nil?
108
+ kill_mongrel_after_suite.to_s == 'true'
109
+ end
110
+
111
+ def self.parse_yaml(selenium_yml_path)
112
+ raise "[saucelabs-adapter] could not open #{selenium_yml_path}" unless File.exist?(selenium_yml_path)
113
+ file_contents = File.open(selenium_yml_path).read
114
+ erb_parsed_file_contents = ERB.new(%{#{file_contents}}).result
115
+ configs = YAML.load(erb_parsed_file_contents)
116
+ @@selenium_configs ||= configs
117
+ end
118
+
119
+ private
120
+
121
+ def display_safely(selenium_args)
122
+ safe = selenium_args.dup
123
+ begin
124
+ safe[:browser] = JSON.parse( safe[:browser])
125
+ safe[:browser]['access-key'] = safe[:browser]['access-key'][0..4] + '...'
126
+ safe[:browser] = safe[:browser].to_json
127
+ rescue
128
+ # args are not always json, e.g. when running locally
129
+ # for now, just ignore any exceptions when trying to parse args with json
130
+ end
131
+ safe.inspect
132
+ end
133
+
134
+ def build_configuration(configuration_name)
135
+ @configuration = @@selenium_configs[configuration_name]
136
+ raise "[saucelabs-adapter] stanza '#{configuration_name}' not found in #{@selenium_yml}" unless @configuration
137
+ # If the Saucelabs-Adapter picked a port out of a range during this session, use it.
138
+ if ENV['SAUCELABS_ADAPTER_APPLICATION_PORT']
139
+ @configuration['application_port'] = ENV['SAUCELABS_ADAPTER_APPLICATION_PORT'].to_i
140
+ debug("Using application port #{application_port} from environment variable SAUCELABS_ADAPTER_APPLICATION_PORT", 2)
141
+ end
142
+ check_configuration(configuration_name)
143
+ end
144
+
145
+ def check_configuration(configuration_name)
146
+ errors = []
147
+ errors << require_attributes([:selenium_server_address, :selenium_server_port, :application_port])
148
+ if selenium_server_address == 'saucelabs.com'
149
+ errors << require_attributes([ :saucelabs_username, :saucelabs_access_key,
150
+ :saucelabs_browser_os, :saucelabs_browser, :saucelabs_browser_version,
151
+ :saucelabs_max_duration_seconds ],
152
+ :when => "when selenium_server_address is saucelabs.com")
153
+ if tunnel_method
154
+ case tunnel_method.to_sym
155
+ when nil, ""
156
+ when :saucetunnel
157
+ errors << require_attributes([:tunnel_to_localhost_port ], :when => "if tunnel_method is :saucetunnel")
158
+ when :sauceconnecttunnel
159
+ errors << require_attributes([:tunnel_to_localhost_port ], :when => "if tunnel_method is :sauceconnecttunnel")
160
+ when :othertunnel
161
+ errors << require_attributes([:application_address], :when => "when tunnel_method is :othertunnel")
162
+ errors << require_attributes([:tunnel_to_localhost_port ], :when => "if tunnel_method is :othertunnel")
163
+ when :sshtunnel
164
+ errors << require_attributes([:application_address], :when => "when tunnel_method is :sshtunnel")
165
+ errors << require_attributes([:tunnel_password, :tunnel_keyfile],
166
+ :when => "when tunnel_method is :sshtunnel",
167
+ :any_or_all => :any)
168
+ if application_address && application_port.is_a?(String) && application_port =~ /(\d+)-(\d+)/
169
+ # We have been given a port range. Find an unused one.
170
+ port = find_unused_port(application_address, ($1.to_i)..($2.to_i))
171
+ @configuration['application_port'] = port
172
+ @configuration['tunnel_to_localhost_port'] = port if test_framework.to_sym == :webrat
173
+ # Pass this calculated value on to any other instances of SeleniumConfig created
174
+ ENV['SAUCELABS_ADAPTER_APPLICATION_PORT'] = port.to_s
175
+ end
176
+ if tunnel_keyfile && !File.exist?(File.expand_path(tunnel_keyfile))
177
+ errors << "tunnel_keyfile '#{tunnel_keyfile}' does not exist"
178
+ end
179
+ else
180
+ errors << "Unknown tunnel_method: #{tunnel_method}"
181
+ end
182
+ end
183
+ else
184
+ errors << require_attributes([:selenium_browser_key, :application_address ],
185
+ :when => "unless server is saucelabs.com")
186
+ end
187
+
188
+ errors.flatten!.compact!
189
+ if !errors.empty?
190
+ raise "[saucelabs-adapter] Aborting; stanza #{configuration_name} has the following errors:\n\t" + errors.join("\n\t")
191
+ end
192
+ end
193
+
194
+ def require_attributes(names, options = {})
195
+ default_options = {
196
+ :when => "",
197
+ :any_or_all => :all
198
+ }
199
+ options = default_options.merge(options)
200
+
201
+ errors = []
202
+ names.each do |attribute|
203
+ errors << "#{attribute} is required #{options[:when]}" if send(attribute).nil?
204
+ end
205
+ errors = [] if options[:any_or_all] == :any && errors.size < names.size
206
+ errors
207
+ end
208
+
209
+ def selenium_client_driver_args
210
+ {
211
+ :host => selenium_server_address,
212
+ :port => selenium_server_port.to_s,
213
+ :browser => selenium_browser_key,
214
+ :url => "http://#{application_address}:#{application_port}",
215
+ :timeout_in_seconds => 600
216
+ }
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,69 @@
1
+ module Sauce
2
+ module JSUnit
3
+ module Utilities
4
+
5
+ def diagnostics_prefix
6
+ @diagnostics_prefix ||= '[saucelabs-adapter]'
7
+ end
8
+
9
+ def say(what)
10
+ STDOUT.puts "#{diagnostics_prefix} #{what}"
11
+ end
12
+
13
+ def debug(what, print_if_level_ge = 0)
14
+ if ENV['SAUCELABS_ADAPTER_DEBUG']
15
+ actual_level = ENV['SAUCELABS_ADAPTER_DEBUG'].to_i
16
+ STDOUT.puts "#{diagnostics_prefix} #{what}" if print_if_level_ge >= actual_level
17
+ end
18
+ end
19
+
20
+ def raise_with_message(message)
21
+ raise "#{diagnostics_prefix} #{message}"
22
+ end
23
+
24
+ def find_unused_port(hostname, range = (3000..5000))
25
+ debug 'searching for unused port', 2
26
+ range.each do |port|
27
+ debug "trying #{hostname}:#{port}", 2
28
+ begin
29
+ socket = TCPSocket.new(hostname, port)
30
+ rescue Errno::ECONNREFUSED
31
+ debug "it's good, returning #{port}", 2
32
+ return port
33
+ ensure
34
+ socket.close if socket
35
+ end
36
+ end
37
+ end
38
+
39
+ # parameters required when invoked by test_unit
40
+ def start_mongrel(suite_name = {})
41
+ pid_file = File.join(RAILS_ROOT, "tmp", "pids", "mongrel_selenium.pid")
42
+ port = suite_name[:port] rescue @selenium_config.application_port
43
+ say "Starting mongrel at #{pid_file}, port #{port}"
44
+ system "mongrel_rails start -d --chdir='#{RAILS_ROOT}' --port=#{port} --environment=test --pid #{pid_file} %"
45
+ end
46
+
47
+ def kill_mongrel_if_needed(suite_name = {})
48
+ mongrel_pid_file = File.join(RAILS_ROOT, "tmp", "pids", "mongrel_selenium.pid")
49
+ if File.exists?(mongrel_pid_file)
50
+ pid = File.read(mongrel_pid_file).to_i
51
+ say "Killing mongrel at #{pid}"
52
+ Process.kill("KILL", pid)
53
+ end
54
+ if File.exists?(mongrel_pid_file)
55
+ FileUtils.rm(mongrel_pid_file)
56
+ end
57
+ end
58
+
59
+ def setup_tunnel(suite_name = {})
60
+ @tunnel = SaucelabsAdapter::Tunnel.factory(@selenium_config)
61
+ @tunnel.start_tunnel
62
+ end
63
+
64
+ def teardown_tunnel(suite_name = {})
65
+ @tunnel.shutdown
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,134 @@
1
+ require 'sauce/jsunit/utilities'
2
+ module Sauce
3
+ module JSUnit
4
+ include Utilities
5
+
6
+ def requires
7
+ require 'sauce'
8
+ require 'sauce/jsunit/run_utils'
9
+ require 'sauce/jsunit/selenium_config'
10
+ require "selenium/client"
11
+ require 'lsof'
12
+ end
13
+
14
+ def setup_jsunit_selenium(options = {})
15
+ @diagnostics_prefix = '[SauceJSUnit]'
16
+ requires
17
+ @selenium_config = SeleniumConfig.new(ENV['SELENIUM_ENV'])
18
+ start_app_server(options)
19
+ @tunnel = Sauce::Connect.new(:port => 8080, :domain => "jsunit.test")
20
+ @tunnel.wait_until_ready
21
+ @selenium_driver = Sauce::Selenium.new(:job_name => "JSUnit", :browser_url=>"http://jsunit.test/")
22
+ debug "calling @selenium_driver.start"
23
+ @selenium_driver.start_new_browser_session :trustAllSSLCertificates => false
24
+ debug "@selenium_driver.start done"
25
+ end
26
+
27
+ def teardown_jsunit_selenium
28
+ @selenium_driver.stop
29
+ @tunnel.disconnect
30
+ stop_app_server
31
+ end
32
+
33
+ def run_jsunit_test(jsunit_params, options = {})
34
+ if $:.detect{ |x| x =~ /Selenium/}
35
+ raise_with_message 'Selenium gem should not be in path! (deprecated in favor of selenium-client, which we require)'
36
+ end
37
+
38
+ default_jsunit_params = {
39
+ :testPage => "/jsunit/javascripts/test-pages/suite.html",
40
+ :autorun => "true",
41
+ :setupPageTimeout => "60",
42
+ :pageLoadTimeout => "60",
43
+ :suppressCacheBuster => (@selenium_config.selenium_server_address == 'saucelabs.com').to_s
44
+ }
45
+ jsunit_params = default_jsunit_params.merge(jsunit_params)
46
+
47
+ test_url = "/jsunit/javascripts/jsunit/jsunit/testRunner.html?" + jsunit_params.map { |k,v| "#{k}=#{v}" }.join("&")
48
+ if @selenium_config.jsunit_polling_interval_seconds
49
+ options = {:polling_interval => @selenium_config.jsunit_polling_interval_seconds}.merge(options)
50
+ end
51
+ run_suite(@selenium_driver, test_url, options)
52
+ end
53
+
54
+ private
55
+
56
+ def pid_file
57
+ prepare_pid_file("#{RAILS_ROOT}/tmp/pids", "mongrel_selenium.pid")
58
+ end
59
+
60
+ def prepare_pid_file(file_path, pid_file_name)
61
+ FileUtils.mkdir_p File.expand_path(file_path)
62
+ File.expand_path("#{file_path}/#{pid_file_name}")
63
+ end
64
+
65
+ def local_app_server_port
66
+ @selenium_config.tunnel_to_localhost_port || @selenium_config.application_port
67
+ end
68
+
69
+ def start_app_server(options = {})
70
+ stop_app_server
71
+ say "starting application server:"
72
+ app_server_logfile_path = options[:app_server_logfile_path] || "#{RAILS_ROOT}/log/jsunit_jetty_app_server.log"
73
+ RunUtils.run "ant -f #{RAILS_ROOT}/public/javascripts/jsunit/jsunit/build.xml start_server " +
74
+ "-Dport=#{local_app_server_port} " +
75
+ "-DcustomJsUnitJarPath=#{RAILS_ROOT}/public/javascripts/jsunit/jsunit_jar/jsunit.jar " +
76
+ "-DresourceBase=#{RAILS_ROOT}/public >> #{app_server_logfile_path} 2>&1 &"
77
+ end
78
+
79
+ def stop_app_server
80
+ raise_with_message "oops don't know port app server is running on" unless local_app_server_port
81
+ while Lsof.running?(local_app_server_port)
82
+ say "Killing app server at #{local_app_server_port}..."
83
+ Lsof.kill(local_app_server_port)
84
+ sleep 1
85
+ end
86
+ end
87
+
88
+ def run_suite(selenium_driver, suite_path, options = {})
89
+ default_options = {
90
+ :timeout_in_seconds => 1200,
91
+ :polling_interval => 5
92
+ }
93
+ options = default_options.merge(options)
94
+
95
+ selenium_driver.open(suite_path)
96
+
97
+ # It would be nice if this worked, but it doesn't (it returns nil even though 'Done' is not in the element).
98
+ # selenium.wait_for_condition(
99
+ # "new RegExp('Done').test(window.mainFrame.mainStatus.document.getElementById('content').innerHTML)")
100
+
101
+ tests_completed = false
102
+ begin_time = Time.now
103
+ status = ""
104
+ say "Starting to poll JsUnit (every #{options[:polling_interval]}s)..." if options[:verbose]
105
+ while (Time.now - begin_time) < options[:jsunit_suite_timeout_seconds] && !tests_completed
106
+ sleep options[:polling_interval]
107
+ debug "polling now...", 2
108
+ status = selenium_driver.js_eval("window.mainFrame.mainStatus.document.getElementById('content').innerHTML")
109
+ status.gsub!(/^<[bB]>Status:<\/[bB]> /, '')
110
+ # Long form: window.frames['mainFrame'].frames['mainCounts'].frames['mainCountsRuns'].document.getElementById('content').innerHTML
111
+ runs = selenium_driver.js_eval("window.mainFrame.mainCounts.mainCountsRuns.document.getElementById('content').innerHTML").strip
112
+ fails = selenium_driver.js_eval("window.mainFrame.mainCounts.mainCountsFailures.document.getElementById('content').innerHTML").strip
113
+ errors = selenium_driver.js_eval("window.mainFrame.mainCounts.mainCountsErrors.document.getElementById('content').innerHTML").strip
114
+ run_count = runs.match(/\d+$/)[0].to_i
115
+ fail_count = fails.match(/\d+$/)[0].to_i
116
+ error_count = errors.match(/\d+$/)[0].to_i
117
+ say "runs/fails/errors: #{run_count}/#{fail_count}/#{error_count} status: #{status}" if options[:verbose]
118
+ if status =~ /^Done /
119
+ tests_completed = true
120
+ end
121
+ end
122
+ raise_with_message "Tests failed to complete after #{options[:jsunit_suite_timeout_seconds]}, status was '#{status}'" unless tests_completed
123
+
124
+ say "********** JSUnit tests complete, Runs: #{run_count}, Fails: #{fail_count}, Errors: #{error_count} **********"
125
+
126
+ if (fail_count + error_count > 0)
127
+ error_messages = selenium_driver.js_eval("window.mainFrame.mainErrors.document.getElementsByName('problemsList')[0].innerHTML")
128
+ say "Error messages: #{error_messages}"
129
+ end
130
+
131
+ (fail_count + error_count) == 0
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsunit-sauce
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Eric Allen
14
+ - Chad Wooley
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-02-04 00:00:00 -08:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: sauce
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 95
31
+ segments:
32
+ - 0
33
+ - 16
34
+ - 0
35
+ version: 0.16.0
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: childprocess
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ hash: 23
47
+ segments:
48
+ - 0
49
+ - 1
50
+ - 6
51
+ version: 0.1.6
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ description: Adapter to run JsUnit test suites using browsers in Sauce OnDemand
55
+ email: eric@hackerengineer.net
56
+ executables: []
57
+
58
+ extensions: []
59
+
60
+ extra_rdoc_files:
61
+ - LICENSE
62
+ - README.md
63
+ files:
64
+ - LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - /Users/epall/Dropbox/code/jsunit-sauce/lib/sauce/jsunit/run_utils.rb
68
+ - /Users/epall/Dropbox/code/jsunit-sauce/lib/sauce/jsunit/selenium_config.rb
69
+ - /Users/epall/Dropbox/code/jsunit-sauce/lib/sauce/jsunit/utilities.rb
70
+ - /Users/epall/Dropbox/code/jsunit-sauce/lib/sauce/jsunit.rb
71
+ - /Users/epall/Dropbox/code/jsunit-sauce/test/helper.rb
72
+ has_rdoc: true
73
+ homepage: http://github.com/saucelabs/jsunit-sauce
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.4.2
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: JsUnit + Sauce OnDemand
106
+ test_files: []
107
+