jasmine-selenium-sauce 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Guardfile +6 -0
- data/LICENSE +22 -0
- data/README.md +63 -0
- data/Rakefile +8 -0
- data/jasmine-selenium-sauce.gemspec +28 -0
- data/lib/jasmine-selenium-sauce.rb +24 -0
- data/lib/jasmine-selenium-sauce/jasmine_results.rb +19 -0
- data/lib/jasmine-selenium-sauce/rspec_reporter.rb +87 -0
- data/lib/jasmine-selenium-sauce/sauce_config.rb +56 -0
- data/lib/jasmine-selenium-sauce/selenium_runner.rb +91 -0
- data/lib/jasmine-selenium-sauce/selenium_saucelabs_driver.rb +52 -0
- data/lib/jasmine-selenium-sauce/tasks/railtie.rb +17 -0
- data/lib/jasmine-selenium-sauce/tasks/rake_runner.rb +17 -0
- data/lib/jasmine-selenium-sauce/tasks/sauce.rake +16 -0
- data/lib/jasmine-selenium-sauce/version.rb +7 -0
- data/spec/fixtures/vcr_cassettes/jasmine_failures.yml +319 -0
- data/spec/fixtures/vcr_cassettes/jasmine_success.yml +308 -0
- data/spec/jasmine-selenium-sauce/jasmine_results_spec.rb +40 -0
- data/spec/jasmine-selenium-sauce/sauce_config_spec.rb +120 -0
- data/spec/jasmine-selenium-sauce/selenium_runner_spec.rb +93 -0
- data/spec/jasmine-selenium-sauce/selenium_saucelabs_driver_spec.rb +106 -0
- data/spec/jasmine-selenium-sauce_spec.rb +54 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/reporter_fake.rb +31 -0
- data/spec/support/sample_results.rb +79 -0
- data/spec/vcr_helper.rb +11 -0
- metadata +236 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Daren
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Jasmine::Sauce::Ci
|
2
|
+
|
3
|
+
Uses SauceLabs to connect to your server and run your Jasmine suites, producing an RSpec report
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'jasmine-selenium-sauce'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install jasmine-selenium-sauce
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
rake jasmine:sauce
|
22
|
+
|
23
|
+
### Required Environment Variables
|
24
|
+
|
25
|
+
Requires the following environment variables to be set:
|
26
|
+
|
27
|
+
#### SAUCELABS_URL
|
28
|
+
|
29
|
+
URL for Saucelabs with your credentials included:
|
30
|
+
|
31
|
+
SAUCELABS_URL=http://username:password@ondemand.saucelabs.com:80/wd/hub
|
32
|
+
|
33
|
+
#### JASMINE_URL
|
34
|
+
|
35
|
+
Where your Jasmine tests are hosted:
|
36
|
+
|
37
|
+
JASMINE_URL=http://my.server.com/jasmine
|
38
|
+
|
39
|
+
#### SAUCE_BROWSER
|
40
|
+
|
41
|
+
Which browser SauceLabs should use to run your tests:
|
42
|
+
|
43
|
+
SAUCE_BROWSER=chrome
|
44
|
+
|
45
|
+
### Optional Configuration
|
46
|
+
|
47
|
+
#### RSpec
|
48
|
+
|
49
|
+
You can specify the format of the RSpec report with:
|
50
|
+
|
51
|
+
JASMINE_SPEC_FORMAT=documentation
|
52
|
+
|
53
|
+
#### Sauce Labs Configuration
|
54
|
+
|
55
|
+
See [sauce_config.rb](https://github.com/darend/jasmine-selenium-sauce/tree/master/lib/jasmine-selenium-sauce/sauce_config.rb)
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
1. Fork it
|
60
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
61
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
62
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
63
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/jasmine-selenium-sauce/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Daren"]
|
6
|
+
gem.email = ["darend@gmail.com"]
|
7
|
+
gem.description = %q{Runs Jasmine suites on any server through Saucelabs}
|
8
|
+
gem.summary = %q{Uses SauceLabs to connect to your server and run your Jasmine suites, producing an RSpec report}
|
9
|
+
gem.homepage = "https://github.com/darend/jasmine-selenium-sauce"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "jasmine-selenium-sauce"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Jasmine::Sauce::Ci::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'fakeweb'
|
19
|
+
gem.add_development_dependency 'guard'
|
20
|
+
gem.add_development_dependency 'guard-rspec'
|
21
|
+
gem.add_development_dependency 'json_pure'
|
22
|
+
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'rb-fsevent'
|
24
|
+
gem.add_development_dependency 'vcr'
|
25
|
+
|
26
|
+
gem.add_dependency 'rspec', ">= 2.0"
|
27
|
+
gem.add_dependency 'selenium-webdriver'
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'jasmine-selenium-sauce/version'
|
2
|
+
require 'jasmine-selenium-sauce/jasmine_results'
|
3
|
+
require 'jasmine-selenium-sauce/rspec_reporter'
|
4
|
+
require 'jasmine-selenium-sauce/sauce_config'
|
5
|
+
require 'jasmine-selenium-sauce/selenium_runner'
|
6
|
+
require 'jasmine-selenium-sauce/selenium_saucelabs_driver'
|
7
|
+
|
8
|
+
module Jasmine
|
9
|
+
module Sauce
|
10
|
+
module CI
|
11
|
+
|
12
|
+
class Main
|
13
|
+
def self.run(config, reporter = RspecReporter.new)
|
14
|
+
driver = SeleniumSauceLabsDriver.new(config)
|
15
|
+
selenium_runner = SeleniumRunner.new(driver)
|
16
|
+
results = selenium_runner.run(config.jasmine_server_url)
|
17
|
+
reporter.report(results)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'jasmine-selenium-sauce/tasks/railtie' if defined?(Rails)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Jasmine
|
2
|
+
module Sauce
|
3
|
+
module CI
|
4
|
+
|
5
|
+
class JasmineResults
|
6
|
+
attr_reader :suites
|
7
|
+
|
8
|
+
def initialize(suites, suite_results)
|
9
|
+
@suites = suites
|
10
|
+
@suite_results = suite_results
|
11
|
+
end
|
12
|
+
|
13
|
+
def for_spec_id(id)
|
14
|
+
@suite_results[id]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Reused from https://github.com/pivotal/jasmine-gem
|
6
|
+
#
|
7
|
+
|
8
|
+
module Jasmine
|
9
|
+
module Sauce
|
10
|
+
module CI
|
11
|
+
class RspecReporter
|
12
|
+
|
13
|
+
def report(results)
|
14
|
+
@results = results
|
15
|
+
declare_suites(@results.suites)
|
16
|
+
end
|
17
|
+
|
18
|
+
def declare_suites(suites)
|
19
|
+
suites.each do |suite|
|
20
|
+
group = example_group(suite["name"]) {}
|
21
|
+
process_children(group, suite["children"])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_children(parent, children)
|
26
|
+
children.each do |suite_or_spec|
|
27
|
+
type = suite_or_spec["type"]
|
28
|
+
if type == "suite"
|
29
|
+
process_children(parent.describe(suite_or_spec["name"]), suite_or_spec["children"])
|
30
|
+
elsif type == "spec"
|
31
|
+
declare_spec(parent, suite_or_spec)
|
32
|
+
else
|
33
|
+
raise "unknown type #{type} for #{suite_or_spec.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def declare_spec(parent, spec)
|
39
|
+
me = self
|
40
|
+
example_name = spec["name"]
|
41
|
+
parent.it example_name, {} do
|
42
|
+
me.report_spec(spec["id"])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def report_spec(spec_id)
|
47
|
+
spec_results = results_for(spec_id)
|
48
|
+
out = ""
|
49
|
+
messages = spec_results['messages'].each do |message|
|
50
|
+
case
|
51
|
+
when message["type"] == "log"
|
52
|
+
puts message["text"]
|
53
|
+
puts "\n"
|
54
|
+
else
|
55
|
+
unless message["message"] =~ /^Passed.$/
|
56
|
+
STDERR << message["message"]
|
57
|
+
STDERR << "\n"
|
58
|
+
|
59
|
+
out << message["message"]
|
60
|
+
out << "\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
if !message["passed"] && message["trace"]["stack"]
|
64
|
+
stack_trace = message["trace"]["stack"].gsub(/<br \/>/, "\n").gsub(/<\/?b>/, " ")
|
65
|
+
STDERR << stack_trace.gsub(/\(.*\)@http:\/\/localhost:[0-9]+\/specs\//, "/spec/")
|
66
|
+
STDERR << "\n"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
fail out unless spec_results['result'] == 'passed'
|
72
|
+
puts out unless out.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def example_group(*args, &block)
|
78
|
+
RSpec::Core::ExampleGroup.describe(*args, &block).register
|
79
|
+
end
|
80
|
+
|
81
|
+
def results_for(spec_id)
|
82
|
+
@results.for_spec_id(spec_id.to_s)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Jasmine
|
2
|
+
module Sauce
|
3
|
+
module CI
|
4
|
+
|
5
|
+
class SauceConfig
|
6
|
+
|
7
|
+
def validate
|
8
|
+
raise ArgumentError.new("SAUCELABS_URL was not set") unless saucelabs_server_url
|
9
|
+
raise ArgumentError.new("JASMINE_URL was not set") unless jasmine_server_url
|
10
|
+
raise ArgumentError.new("SAUCE_BROWSER was not set") unless browser
|
11
|
+
end
|
12
|
+
|
13
|
+
def saucelabs_server_url
|
14
|
+
ENV['SAUCELABS_URL']
|
15
|
+
end
|
16
|
+
|
17
|
+
def jasmine_server_url
|
18
|
+
ENV['JASMINE_URL']
|
19
|
+
end
|
20
|
+
|
21
|
+
def browser
|
22
|
+
ENV['SAUCE_BROWSER']
|
23
|
+
end
|
24
|
+
|
25
|
+
def platform
|
26
|
+
ENV['SAUCE_PLATFORM'] ? ENV['SAUCE_PLATFORM'].to_s.upcase.to_sym : :VISTA
|
27
|
+
end
|
28
|
+
|
29
|
+
def browser_version
|
30
|
+
ENV['SAUCE_BROWSER_VERSION']
|
31
|
+
end
|
32
|
+
|
33
|
+
def record_screenshots
|
34
|
+
ENV['SAUCE_SCREENSHOTS'] ? ENV['SAUCE_SCREENSHOTS'] : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def record_video
|
38
|
+
ENV['SAUCE_VIDEO'] ? ENV['SAUCE_VIDEO'] : false
|
39
|
+
end
|
40
|
+
|
41
|
+
def idle_timeout
|
42
|
+
ENV['SAUCE_IDLE_TIMEOUT'] ? ENV['SAUCE_IDLE_TIMEOUT'] : 90
|
43
|
+
end
|
44
|
+
|
45
|
+
def max_duration
|
46
|
+
ENV['SAUCE_MAX_DURATION'] ? ENV['SAUCE_MAX_DURATION'] : 180
|
47
|
+
end
|
48
|
+
|
49
|
+
def selenium_client_timeout
|
50
|
+
ENV['SELENIUM_CLIENT_TIMEOUT'] ? ENV['SELENIUM_CLIENT_TIMEOUT'] : 120
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Jasmine
|
4
|
+
module Sauce
|
5
|
+
module CI
|
6
|
+
class SeleniumRunner
|
7
|
+
|
8
|
+
def initialize(driver, result_batch_size = 500)
|
9
|
+
@driver = driver
|
10
|
+
@result_batch_size = result_batch_size
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(jasmine_url)
|
14
|
+
@driver.connect jasmine_url
|
15
|
+
wait_for_jasmine_to_load(load_jasmine_timeout)
|
16
|
+
suites = fetch_suites
|
17
|
+
wait_for_jasmine_to_finish(jasmine_execution_timeout)
|
18
|
+
suite_results = fetch_suite_results(suites)
|
19
|
+
@driver.disconnect
|
20
|
+
Jasmine::Sauce::CI::JasmineResults.new(suites, suite_results)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def load_jasmine_timeout
|
26
|
+
60
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait_for_jasmine_to_load(timeout)
|
30
|
+
started = Time.now
|
31
|
+
while !evaluate_js('return jsApiReporter && jsApiReporter.started') do
|
32
|
+
raise "Timed out after #{timeout}s waiting for Jasmine to load" if (started + timeout < Time.now)
|
33
|
+
sleep 0.1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_suites
|
38
|
+
evaluate_js(
|
39
|
+
"var result = jsApiReporter.suites(); " \
|
40
|
+
"if (window.Prototype && Object.toJSON) { return Object.toJSON(result) } " \
|
41
|
+
"else { return JSON.stringify(result) }")
|
42
|
+
end
|
43
|
+
|
44
|
+
def jasmine_execution_timeout
|
45
|
+
300
|
46
|
+
end
|
47
|
+
|
48
|
+
def wait_for_jasmine_to_finish(timeout)
|
49
|
+
started = Time.now
|
50
|
+
while !evaluate_js('return jsApiReporter.finished') do
|
51
|
+
raise "Timed out after #{timeout}s waiting for Jasmine to finish" if (started + timeout < Time.now)
|
52
|
+
sleep 0.1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_suite_results(suites)
|
57
|
+
spec_results = {}
|
58
|
+
extract_spec_ids(suites).each_slice(@result_batch_size) do |slice|
|
59
|
+
slice_results = evaluate_js(
|
60
|
+
"var result = jsApiReporter.resultsForSpecs(#{json_generate(slice)}); " \
|
61
|
+
"if (window.Prototype && Object.toJSON) { return Object.toJSON(result) } " \
|
62
|
+
"else { return JSON.stringify(result) }")
|
63
|
+
spec_results.merge!(slice_results)
|
64
|
+
end
|
65
|
+
spec_results
|
66
|
+
end
|
67
|
+
|
68
|
+
def extract_spec_ids(suites)
|
69
|
+
map_spec_ids = lambda do |suites|
|
70
|
+
suites.map do |suite_or_spec|
|
71
|
+
if suite_or_spec['type'] == 'spec'
|
72
|
+
suite_or_spec['id']
|
73
|
+
else
|
74
|
+
map_spec_ids.call(suite_or_spec['children'])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
map_spec_ids.call(suites).compact.flatten
|
79
|
+
end
|
80
|
+
|
81
|
+
def evaluate_js(script)
|
82
|
+
@driver.evaluate_js(script)
|
83
|
+
end
|
84
|
+
|
85
|
+
def json_generate(obj)
|
86
|
+
JSON.generate(obj)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|