selenium-connect 3.1.2 → 3.2.0

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