selenium-connect 3.1.2 → 3.2.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.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ #3.2.0 (2013-07-15)
2
+ added better error handling to the sauce api calls and improved naming convention for job assets
3
+
4
+ - Bumped version to 3.2.0 to prepare for release.
5
+ - cleaned up the way the job log was saving
6
+ - added one more error check around the sauce data
7
+ - refactored the log saving and sauce api access, now can configure the api time out and the requests will poll when trying to fetch the data from the server
8
+
1
9
  #3.1.2 (2013-07-14)
2
10
  removed references to curb
3
11
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- #selenium-connect 3.1.2 (2013-07-14)
1
+ #selenium-connect 3.2.0 (2013-07-15)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/selenium-connect.png)](http://badge.fury.io/rb/selenium-connect) [![Build Status](https://travis-ci.org/arrgyle/selenium-connect.png?branch=develop)](https://travis-ci.org/arrgyle/selenium-connect) [![Code Climate](https://codeclimate.com/github/arrgyle/selenium-connect.png)](https://codeclimate.com/github/arrgyle/selenium-connect) [![Coverage Status](https://coveralls.io/repos/arrgyle/selenium-connect/badge.png?branch=develop)](https://coveralls.io/r/arrgyle/selenium-connect?branch=develop)
4
4
 
@@ -90,6 +90,7 @@ sauce_username: 'test_user_name'
90
90
  sauce_api_key:
91
91
  browser_version:
92
92
  description: #sauce job/test description
93
+ api_timeout: #how many seconds we should try to get the assets (default 10)
93
94
  ```
94
95
 
95
96
  You can pass parameters into the new config object like:
@@ -124,7 +125,7 @@ report = job.finish failed: true, failshot: true
124
125
  The `report` is simply a container for arbitrary data. Right now we are passing back the sauce details. Here is an example of `report.data` for a failed job:
125
126
 
126
127
  ```
127
- {:failshot=>"failed_e8ebfe9fc5004df7865b6a6f9f1f5491.png", :server_log=>"sauce_job_e8ebfe9fc5004df7865b6a6f9f1f5491.log", :sauce_data=>{:id=>"e8ebfe9fc5004df7865b6a6f9f1f5491", :"custom-data"=>nil, :owner=>"testing_arrgyle", :status=>"complete", :error=>nil, :name=>"failshot", :browser=>"iexplore", :browser_version=>"7.0.5730.13", :os=>"Windows 2003", :creation_time=>1373831090, :start_time=>1373831090, :end_time=>1373831105, :video_url=>"http://saucelabs.com/jobs/e8ebfe9fc5004df7865b6a6f9f1f5491/video.flv", :log_url=>"http://saucelabs.com/jobs/e8ebfe9fc5004df7865b6a6f9f1f5491/selenium-server.log", :public=>nil, :tags=>[], :passed=>false}}
128
+ {:assets=>{:server_log=>"failed_serverlog_failing_sauce_job_3ee1fddf4032476fa3f9de94298766ae.log", :job_data_log=>"failed_saucejob_failing_sauce_job_3ee1fddf4032476fa3f9de94298766ae.log"}, :sauce_data=>{:id=>"3ee1fddf4032476fa3f9de94298766ae", :"custom-data"=>nil, :owner=>"testing_arrgyle", :status=>"in progress", :error=>nil, :name=>"failing_sauce_job", :browser=>"iexplore", :browser_version=>"7.0.5730.13", :os=>"Windows 2003", :creation_time=>1373916900, :start_time=>1373916901, :end_time=>0, :video_url=>"http://saucelabs.com/jobs/3ee1fddf4032476fa3f9de94298766ae/video.flv", :log_url=>"http://saucelabs.com/jobs/3ee1fddf4032476fa3f9de94298766ae/selenium-server.log", :public=>nil, :tags=>[], :passed=>false}}
128
129
  ```
129
130
 
130
131
  ## Contribution Guidelines
@@ -0,0 +1,73 @@
1
+ # Encoding: utf-8
2
+
3
+ require 'json'
4
+ require 'sauce_whisk'
5
+ require 'rest_client'
6
+ require 'sauce/client'
7
+
8
+ module Sauce
9
+ # wrapps the sauce whick api accessor with sane error handling
10
+ class SauceFacade
11
+
12
+ attr_accessor :job_id, :timeout
13
+
14
+ def initialize(timeout = 10)
15
+ @timeout = timeout ||= 10
16
+ end
17
+
18
+ def fail_job
19
+ requires_job_id
20
+ SauceWhisk::Jobs.fail_job @job_id
21
+ end
22
+
23
+ def pass_job
24
+ requires_job_id
25
+ SauceWhisk::Jobs.pass_job @job_id
26
+ end
27
+
28
+ def fetch_last_screenshot
29
+ requires_job_id
30
+ polling_api_request @timeout do
31
+ SauceWhisk::Jobs.fetch_asset @job_id, 'final_screenshot.png'
32
+ end
33
+ end
34
+
35
+ def fetch_server_log
36
+ requires_job_id
37
+ polling_api_request @timeout do
38
+ SauceWhisk::Jobs.fetch_asset @job_id, 'selenium-server.log'
39
+ end
40
+ end
41
+
42
+ def fetch_job_data
43
+ requires_job_id
44
+ # TODO let's switch this over to use whisk as well
45
+ # This is used because it's easy to get all the data out of the job
46
+ begin
47
+ job = Sauce::Job.find @job_id
48
+ JSON.parse job.to_json
49
+ rescue StandardError => e
50
+ puts "An error occured while fetching the job data: #{e.message}"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def requires_job_id
57
+ raise ArgumentError, "#{caller[0][/`.*'/][1..-2]} requires that the job_id be set in this object." unless @job_id
58
+ end
59
+
60
+ def polling_api_request(timeout)
61
+ begin
62
+ sleep 1
63
+ yield
64
+ rescue RestClient::Exception => e
65
+ if timeout > 0
66
+ polling_api_request(timeout - 1) { yield }
67
+ else
68
+ puts "Request timed out after #{timeout} with: #{e.message}"
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -17,7 +17,7 @@ class SeleniumConnect
17
17
 
18
18
  # SauceLabs
19
19
  attr_accessor :sauce_username, :sauce_api_key,
20
- :os, :browser_version, :description
20
+ :os, :browser_version, :description, :api_timeout
21
21
 
22
22
  def initialize(opts = {})
23
23
  @host = 'localhost'
@@ -1,26 +1,27 @@
1
1
  # Encoding: utf-8
2
+
2
3
  require 'selenium_connect/runner'
3
4
  require 'sauce/client'
4
5
  require 'rest_client'
5
6
  require 'selenium-webdriver'
6
7
  require 'json'
7
8
  require 'sauce_whisk'
8
-
9
9
  # selenium connect
10
10
  class SeleniumConnect
11
11
  # encapsulates the creation of a driver and a run
12
12
  class Job
13
13
 
14
- def initialize(config, report_factory)
14
+ def initialize(config, report_factory, sauce_facade)
15
15
  @config = config
16
16
  @report_factory = report_factory
17
+ @sauce_facade = sauce_facade
17
18
  end
18
19
 
19
20
  # Creates and returns the driver, using options passed in
20
21
  def start(opts = {})
21
-
22
+ @job_name = slugify_name opts[:name] if opts.has_key? :name
22
23
  # TODO this could be refactored out into an options parser of sorts
23
- @config.description = opts[:name] if opts.has_key? :name
24
+ @config.description = @job_name ||= 'unnamed_job'
24
25
  @driver = Runner.new(@config).driver
25
26
  end
26
27
 
@@ -30,63 +31,54 @@ class SeleniumConnect
30
31
  # extracted from the earlier main finish
31
32
  begin
32
33
  @driver.quit
33
- data = {}
34
- if @config.host == 'saucelabs'
35
- job_id = @driver.session_id
36
- if opts.has_key?(:failed) && opts[:failed]
37
- fail_job job_id
38
- if opts.has_key?(:failshot) && opts[:failshot]
39
- data[:failshot] = save_last_screenshot job_id
40
- end
41
- end
42
- if opts.has_key?(:passed) && opts[:passed]
43
- pass_job job_id
44
- end
45
- data.merge! fetch_logs(job_id)
46
- end
34
+ @data = { assets: {} }
35
+ process_sauce_logs(opts) if @config.host == 'saucelabs'
47
36
  # rubocop:disable HandleExceptions
48
37
  rescue Selenium::WebDriver::Error::WebDriverError
49
38
  # rubocop:enable HandleExceptions
50
39
  end
51
- report_data = symbolize_keys data
52
- @report_factory.build :job, report_data
40
+ @report_factory.build :job, @data
53
41
  end
54
42
 
55
43
  private
56
44
 
57
- # TODO all this sauce stuff needs to be pulled out of the job class
58
- # TODO need to put error handling around the sauce api requests
59
- def save_last_screenshot(job_id)
60
- begin
61
- # Seemingly need to wait slightly for the images to be processed
62
- sleep(2)
63
- filename = "failed_#{job_id}.png"
64
- image = SauceWhisk::Jobs.fetch_asset job_id, 'final_screenshot.png'
65
- image_file = File.join(Dir.getwd, @config.log, filename) if @config.log
66
- File.open(image_file, 'w') { |f| f.write image }
67
- filename
68
- rescue RestClient::ResourceNotFound
69
- puts 'Unable to download image!'
45
+ def process_sauce_logs(opts = {})
46
+ job_id = @driver.session_id
47
+ @sauce_facade.job_id = job_id
48
+ if opts.has_key?(:failed) && opts[:failed]
49
+ status = 'failed'
50
+ @sauce_facade.fail_job
51
+ if opts.has_key?(:failshot) && opts[:failshot]
52
+ screenshot = @sauce_facade.fetch_last_screenshot
53
+ @data[:assets][:failshot] = save_asset("#{status}_failshot_#{@job_name}_#{job_id}.png", screenshot) if screenshot
54
+ end
70
55
  end
71
- end
56
+ if opts.has_key?(:passed) && opts[:passed]
57
+ status = 'passed'
58
+ @sauce_facade.pass_job
59
+ end
60
+ server_log = @sauce_facade.fetch_server_log
61
+ @data[:assets][:server_log] = save_asset("#{status}_serverlog_#{@job_name}_#{job_id}.log", server_log) if server_log
72
62
 
73
- def fail_job(job_id)
74
- SauceWhisk::Jobs.fail_job job_id
63
+ job_data = @sauce_facade.fetch_job_data
64
+ @data[:sauce_data] = job_data if job_data
65
+
66
+ job_data_log_file = "#{status}_saucejob_#{@job_name}_#{job_id}.log"
67
+ @data[:assets][:job_data_log] = job_data_log_file
68
+ @data = symbolize_keys @data
69
+ save_asset(job_data_log_file, @data)
75
70
  end
76
71
 
77
- def pass_job(job_id)
78
- SauceWhisk::Jobs.pass_job job_id
72
+ def save_asset(filename, asset)
73
+ if @config.log
74
+ asset_file = File.join(Dir.getwd, @config.log, filename)
75
+ File.open(asset_file, 'w') { |f| f.write asset }
76
+ filename
77
+ end
79
78
  end
80
79
 
81
- def fetch_logs(job_id)
82
- sauce_job = Sauce::Job.find(job_id)
83
- # Seemingly need to wait slightly for the images to be processed
84
- sleep(2)
85
- filename = "sauce_job_#{job_id}.log"
86
- server_log = SauceWhisk::Jobs.fetch_asset job_id, 'selenium-server.log'
87
- log_file = File.join(Dir.getwd, @config.log, filename) if @config.log
88
- File.open(log_file, 'w') { |f| f.write server_log }
89
- { server_log: filename, sauce_data: JSON.parse(sauce_job.to_json) }
80
+ def slugify_name(name)
81
+ name.downcase.strip.gsub(' ', '_').gsub(/[^\w-]/, '')
90
82
  end
91
83
 
92
84
  # TODO this should be pulled out into a generic report... or something
@@ -3,6 +3,7 @@
3
3
  require 'selenium_connect/job'
4
4
  require 'selenium_connect/server'
5
5
  require 'selenium_connect/configuration'
6
+ require 'sauce/sauce_facade'
6
7
  require 'selenium_connect/report/report_factory'
7
8
 
8
9
  # Selenium Connect main module
@@ -24,7 +25,8 @@ class SeleniumConnect
24
25
  end
25
26
 
26
27
  def create_job(opts = {})
27
- SeleniumConnect::Job.new @config, @report_factory
28
+ sauce_facade = Sauce::SauceFacade.new @config.api_timeout
29
+ SeleniumConnect::Job.new @config, @report_factory, sauce_facade
28
30
  end
29
31
 
30
32
  def finish
@@ -1,12 +1,12 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'selenium-connect'
3
- s.version = '3.1.2'
3
+ s.version = '3.2.0'
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.authors = ['Dave Haeffner', 'Jason Fox']
6
6
  s.email = ['dave@arrgyle.com', 'jason@arrgyle.com']
7
7
  s.homepage = 'https://github.com/arrgyle/selenium-connect'
8
8
  s.summary = 'A stupid simple way to run your Selenium tests on localhost, against a Selenium Grid, or in the cloud (e.g. SauceLabs).'
9
- s.description = 'removed references to curb'
9
+ s.description = 'added better error handling to the sauce api calls and improved naming convention for job assets'
10
10
  s.license = 'MIT'
11
11
 
12
12
  s.files = `git ls-files`.split($/)
@@ -26,10 +26,10 @@ describe 'Sauce Labs', selenium: true do
26
26
  execute_simple_test driver
27
27
  report = job.finish passed: true
28
28
  sauce_id = report.data[:sauce_data][:id]
29
- report.data[:sauce_data][:name].should be == name
29
+ report.data[:sauce_data][:name].should be == 'successful_sauce_job'
30
30
  report.data[:sauce_data][:passed].should be_true
31
- report.data[:server_log].should be == "sauce_job_#{sauce_id}.log"
32
- File.exist?(File.join(Dir.pwd, 'build', 'tmp', "sauce_job_#{sauce_id}.log")).should be_true
31
+ report.data[:assets][:server_log].should be == "passed_serverlog_successful_sauce_job_#{sauce_id}.log"
32
+ File.exist?(File.join(Dir.pwd, 'build', 'tmp', "passed_serverlog_successful_sauce_job_#{sauce_id}.log")).should be_true
33
33
  end
34
34
 
35
35
  it 'should mark a sauce job as failed' do
@@ -56,8 +56,8 @@ describe 'Sauce Labs', selenium: true do
56
56
  end
57
57
  sauce_id = report.data[:sauce_data][:id]
58
58
  report.data[:sauce_data][:passed].should be false
59
- report.data[:failshot].should be == "failed_#{sauce_id}.png"
60
- File.exist?(File.join(Dir.pwd, 'build', 'tmp', "failed_#{sauce_id}.png")).should be_true
59
+ report.data[:assets][:failshot].should be == "failed_failshot_failshot_#{sauce_id}.png"
60
+ File.exist?(File.join(Dir.pwd, 'build', 'tmp', "failed_failshot_failshot_#{sauce_id}.png")).should be_true
61
61
  end
62
62
 
63
63
  after(:each) do
@@ -21,3 +21,4 @@ sauce_username: 'test_user_name'
21
21
  sauce_api_key:
22
22
  browser_version:
23
23
  description:
24
+ api_timeout: 10
@@ -0,0 +1,55 @@
1
+ # Encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'sauce/sauce_facade'
5
+
6
+ describe Sauce::SauceFacade do
7
+
8
+ before(:each) do
9
+ @facade = Sauce::SauceFacade.new
10
+ end
11
+
12
+ it 'should be initialized' do
13
+ @facade.should be_an_instance_of Sauce::SauceFacade
14
+ @facade.timeout.should be == 10
15
+ end
16
+
17
+ it 'can be initialized with an optional time out value' do
18
+ facade = Sauce::SauceFacade.new 30
19
+ facade.timeout.should be == 30
20
+ end
21
+
22
+ it 'should respond to fail_job' do
23
+ @facade.should respond_to :fail_job
24
+ end
25
+
26
+ it 'should respond to pass_job' do
27
+ @facade.should respond_to :pass_job
28
+ end
29
+
30
+ it 'should respond to fetch_last_screenshot' do
31
+ @facade.should respond_to :fetch_last_screenshot
32
+ end
33
+
34
+ it 'should respond to fetch_server_log' do
35
+ @facade.should respond_to :fetch_server_log
36
+ end
37
+
38
+ it 'should respoind to fetch_job_data' do
39
+ @facade.should respond_to :fetch_job_data
40
+ end
41
+
42
+ it 'should respond to job_id' do
43
+ @facade.should respond_to :job_id
44
+ @facade.should respond_to :job_id=
45
+ end
46
+
47
+ it 'should raise and exception if a function is called that requires an id but it is not set' do
48
+ [:fail_job, :pass_job, :fetch_last_screenshot, :fetch_server_log, :fetch_job_data].each do |method|
49
+ expect do
50
+ @facade.send method
51
+ end.to raise_error ArgumentError, "#{method.to_s} requires that the job_id be set in this object."
52
+ end
53
+ end
54
+ end
55
+
@@ -8,9 +8,10 @@ describe SeleniumConnect::Job do
8
8
  before(:each) do
9
9
  config = double 'SeleniumConnect::Configuration'
10
10
  report_factory = double 'SeleniumConnect::Report::ReportFactory'
11
+ sauce_facade = double 'Sauce::SauceFacade'
11
12
  @report = double 'SeleniumConnect::Report::JobReport'
12
13
  allow(report_factory).to receive(:build).and_return(@report)
13
- @job = SeleniumConnect::Job.new config, report_factory
14
+ @job = SeleniumConnect::Job.new config, report_factory, sauce_facade
14
15
  end
15
16
 
16
17
  it 'should be initialized' do
@@ -9,6 +9,7 @@ describe SeleniumConnect do
9
9
  @config = double 'SeleniumConnect::Configuration'
10
10
  allow(@config).to receive(:is_a?).and_return(true)
11
11
  allow(@config).to receive(:host).and_return(false)
12
+ allow(@config).to receive(:api_timeout).and_return(10)
12
13
 
13
14
  report_factory = double 'SeleniumConnect::Report::ReportFactory'
14
15
  @report = double 'SeleniumConnect::Report::MainReport'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selenium-connect
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.2
4
+ version: 3.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-07-14 00:00:00.000000000 Z
13
+ date: 2013-07-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: selenium-webdriver
@@ -140,7 +140,8 @@ dependencies:
140
140
  - - ~>
141
141
  - !ruby/object:Gem::Version
142
142
  version: 0.6.7
143
- description: removed references to curb
143
+ description: added better error handling to the sauce api calls and improved naming
144
+ convention for job assets
144
145
  email:
145
146
  - dave@arrgyle.com
146
147
  - jason@arrgyle.com
@@ -163,6 +164,7 @@ files:
163
164
  - bin/chromedriver
164
165
  - bin/phantomjs
165
166
  - bin/selenium-server-standalone-2.33.0.jar
167
+ - lib/sauce/sauce_facade.rb
166
168
  - lib/selenium-connect.rb
167
169
  - lib/selenium_connect.rb
168
170
  - lib/selenium_connect/configuration.rb
@@ -190,6 +192,7 @@ files:
190
192
  - spec/support/example.yaml
191
193
  - spec/support/example_page_object.rb
192
194
  - spec/support/integration_helper.rb
195
+ - spec/unit/lib/sauce/sauce_facade_spec.rb
193
196
  - spec/unit/lib/selenium-connect_spec.rb
194
197
  - spec/unit/lib/selenium_connect/configuration_spec.rb
195
198
  - spec/unit/lib/selenium_connect/job_spec.rb
@@ -219,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
222
  version: '0'
220
223
  segments:
221
224
  - 0
222
- hash: -2083194116376404584
225
+ hash: -3833046545710782296
223
226
  requirements: []
224
227
  rubyforge_project:
225
228
  rubygems_version: 1.8.25