saucer 0.6.5 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8563d0a40f72b5d339a70304d948f103b9655e38
4
- data.tar.gz: 80844b96e29ff34b83ab1e47a6a25fe8206a8e12
3
+ metadata.gz: 0a0d5ca72570b0c717d295613491312bec954800
4
+ data.tar.gz: b12996df6767e459a2746a3eb3dcc7776eb42468
5
5
  SHA512:
6
- metadata.gz: 3001ebb0491bce0b8c1de318d4b757cf26f8ece4c3869a9ec42e11043cf297fbd79fd6b48a6bce5ffb07e76254e84b222a4533443755304ceeb9b4fb02074e96
7
- data.tar.gz: e078e82ec75bee429a90be76deefde3b6992aae3c125845addb5d1cf50a535685c62e262ed2dabf56702fdba0aa7290e3daad668f427f6fd7d61fba7c225fb37
6
+ metadata.gz: 34570556581c60312af0867069b7b3b3080f7e28b5386f44b21bce4124f62c47a37a02c5a00eea0154d95c5b2cd8547a8b37c1cd1bc87bc6bd92c152a8c177f5
7
+ data.tar.gz: fad1c1d36deddf108d981c114ae6e50f83bd54e3b61d9b4219470e5b08c6904cba57fa0c6b07d704f609f4f0bd98b7d9058e854ecfc70babb97b030087a9dd71
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ ### 1.0.0.alpha (2019-05-16)
2
+ * Complete Revamp of the Project
3
+ * Implement Options class to set customized values
4
+ * Implement Session class to directly interface with Sauce
5
+ * Automatically update Test Name, Build Name, Pass/Fail
6
+ * Automatically add custom data: Ruby version, OS, gems
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in saucer.gemspec
4
6
  gemspec
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Titus Fortner
3
+ Copyright (c) 2017-2019 Titus Fortner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,94 +1,105 @@
1
1
  # Saucer
2
2
 
3
- Convenience methods for running your Ruby tests on Sauce Labs
3
+ Make running your tests on Sauce Labs easier with these helpful wrappers and convenience methods
4
4
 
5
5
  ## Disclaimer
6
6
  *This code is provided on an "AS-IS” basis without warranty of any kind, either express or implied, including without limitation any implied warranties of condition, uninterrupted use, merchantability, fitness for a particular purpose, or non-infringement. Your tests and testing environments may require you to modify this framework. Issues regarding this framework should be submitted through GitHub. For questions regarding Sauce Labs integration, please see the Sauce Labs documentation at https://wiki.saucelabs.com/. This framework is not maintained by Sauce Labs Support.*
7
7
 
8
8
  ## Installation
9
9
 
10
- Add this line to your application's Gemfile:
10
+ In your Gemfile:
11
+
12
+ `gem 'saucer'
13
+
14
+ In your project:
11
15
 
12
16
  ```ruby
13
- gem 'saucer'
17
+ require 'saucer'
14
18
  ```
15
19
 
16
20
  ## Usage
17
21
 
18
- #### Configuration
19
-
20
- Can optionally pass in a `Config::Selenium` instance with any of the
21
- [supported Test Configuration Options](https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options)
22
- Note that Ruby syntax is a `Symbol` with snake_case and not `String` with camelCase
22
+ #### Starting the Driver
23
+ Use Saucer to start your sessions
23
24
  ```ruby
24
- config_selenium = Config::Selenium.new(version: '53', browser_name: :firefox)
25
- @driver = Driver.new(config_selenium)
25
+ @session = Saucer::Session.begin
26
+ @driver = @session.driver
26
27
  ```
27
-
28
- #### Cucumber
29
- RSpec doesn't need to be concerned with this, but Cucumber needs an extra step in `env.rb`:
28
+ Optionally you can create options with various parameters to pass into `Session.begin`
30
29
  ```ruby
31
- Before do |scenario|
32
- Saucer::Config::Sauce.scenario = scenario
33
- @driver = Saucer::Driver.new
34
- end
35
-
36
- After do |scenario|
37
- Saucer::Config::Sauce.scenario = scenario
38
- @driver.quit
39
- end
30
+ options = Saucer::Options.new(browser_name: 'Safari',
31
+ browser_version: '12.0',
32
+ platform_name: 'macOS 10.14')
33
+ @session = Saucer::Session.begin(options)
34
+ @driver = @session.driver
40
35
  ```
41
36
 
42
- #### Parallel
43
- The most basic usage for parallel execution is to define the following Rake task, which
44
- will every spec in the spec directory in 4 processes on the default Sauce platform (Linux with Chrome v48)
45
-
37
+ #### Finishing the session
38
+ You can still quit the driver yourself if you'd like
46
39
  ```ruby
47
- Saucer::Parallel.new.run
40
+ @driver.quit
48
41
  ```
49
-
50
- To Specify basic number of processes, a specific subdirectory (Cucumber or RSpec), and
51
- reporting output file:
52
-
42
+ You get some automatic data population if you end the Session
53
43
  ```ruby
54
- Saucer::Parallel.new(number: 7,
55
- path: 'features/foo',
56
- output: 'foo').run
44
+ @session.end
57
45
  ```
58
46
 
59
-
60
- To specify Sauce configurations, create a Rake Task that takes parameters like this:
61
-
47
+ #### Automatic Data Population
48
+ To automatically pass in the test name, populate pass/fail and provide exception information:
49
+ RSpec doesn't need to do anything, but Cucumber will need to specify the scenario information
50
+ in the `env.rb` or `hooks.rb` file.
62
51
  ```ruby
63
- task :parallel_sauce do
64
- configs = [{os: :mac_10_10, browser: :chrome, browser_version: 38},
65
- {os: :mac_10_11, browser: :firefox, browser_version: 46},
66
- {os: :mac_10_8, browser: :chrome, browser_version: 42}]
67
-
68
- platforms = configs.map { |c| Saucer::PlatformConfiguration.new(c) }
69
-
70
- Saucer::Parallel.new(platforms: platforms).run
52
+ Before do |scenario|
53
+ options = Saucer::Options.new(scenario: scenario)
54
+ session = Saucer::Session.begin(options)
55
+ @driver = session.driver
71
56
  end
72
57
  ```
73
58
 
74
- or you can use the default rake task and define your configurations in
75
- `configs/platform_configs.yml` like this:
76
- ```yaml
77
- - :os: :mac_10_10
78
- :browser: :chrome
79
- :browser_version: 38
80
- - :os: :mac_10_11
81
- :browser: :firefox
82
- :browser_version: 46
83
- - :os: :mac_10_8
84
- :browser: :chrome
85
- :browser_version: 42
59
+ #### Session Commands
60
+ Saucer provides a number of custom methods as part of the session instance:
61
+ ```ruby
62
+ # Add useful information to a test after initializing a session
63
+ @session.comment = "Foo"
64
+ @session.tags = %w[foo bar]
65
+ @session.tags << 'foobar'
66
+ @session.data = {foo: 'bar'}
67
+ @session.data[:bar] = 'foo'
68
+
69
+ # These things should be set automatically, but can be set manually
70
+ @session.name = 'Test Name'
71
+ @session.build = 'Build Name'
72
+ @session.result = 'passed'
73
+ @session.result = 'failed'
74
+
75
+ # Special Features that might be useful
76
+ @session.stop_network
77
+ @session.start_network
78
+ @session.breakpoint
79
+
80
+ # This will cause an error, but is available as an option
81
+ session.stop
82
+
83
+ # These things can be done after the session has ended
84
+ @session.save_screenshots
85
+ @session.save_log(log_type: :selenium)
86
+ @session.save_log(log_type: :sauce)
87
+ @session.save_log(log_type: :automator)
88
+ @session.save_logs
89
+ @session.save_video
90
+ @session.save_assets
91
+ @session.delete_assets
86
92
  ```
87
93
 
94
+ #### Additional API Interactions
95
+ A more fully featured wrapping of the Sauce API is planned for upcoming releases.
96
+ For now, make use of [SauceWhisk](https://github.com/saucelabs/sauce_whisk).
97
+
88
98
  ## Contributing
89
99
 
90
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/saucer.
100
+ Bug reports and pull requests are welcome on GitHub at https://github.com/titusfortner/saucer.
91
101
 
92
- ## License
102
+ ## License & Copyright
93
103
 
94
104
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
105
+ see LICENSE.txt for full details and copyright.
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/lib/saucer.rb CHANGED
@@ -1,6 +1,19 @@
1
- require "require_all"
1
+ # frozen_string_literal: true
2
+
2
3
  require 'selenium-webdriver'
3
- require "saucer/version"
4
- require "sauce_whisk"
5
- require_rel "saucer"
4
+ require 'saucer/options'
5
+ require 'saucer/data_collection'
6
+ require 'saucer/asset_management'
7
+ require 'saucer/custom_commands'
8
+ require 'saucer/job_update'
9
+ require 'saucer/runners'
10
+ require 'saucer/session'
11
+ require 'saucer/version'
12
+
13
+ module Saucer
14
+ class AuthenticationError < StandardError
15
+ end
6
16
 
17
+ class APIError < StandardError
18
+ end
19
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Saucer
4
+ module AssetManagement
5
+ def screenshots
6
+ start_time = Time.now
7
+ begin
8
+ urls = SauceWhisk::Jobs.job_assets(job_id)['screenshot_urls']
9
+ raise APIError, 'No screenshots found' if urls.nil?
10
+ rescue APIError
11
+ retry if Time.now - start_time < 5
12
+ end
13
+ job.screenshots
14
+ end
15
+
16
+ def save_screenshots(path = nil)
17
+ screenshots&.each do |screenshot|
18
+ save_file(path: path, file_name: screenshot.name, data: screenshot.data.body, type: 'screenshots')
19
+ end
20
+ end
21
+
22
+ # TODO: refactor leveraging `details.end_time`
23
+ def log(log_type = nil)
24
+ log_type ||= :sauce
25
+ valid = {sauce: 'log.json',
26
+ selenium: 'selenium-server.log',
27
+ automator: 'automator.log'}
28
+ unless valid.key?(log_type)
29
+ raise ArgumentError,
30
+ "#{log_type} is not a valid log type; use one of #{valid.keys}"
31
+ end
32
+
33
+ start_time = Time.now
34
+ begin
35
+ response = asset(valid[log_type]).body
36
+ raise APIError, "Can not retrieve log: #{response['message']}" if JSON.parse(response).key?('message')
37
+ rescue NoMethodError
38
+ # Sauce Log is Special
39
+ JSON.parse(response).map do |hash|
40
+ hash.map do |key, value|
41
+ "#{key}: #{value}"
42
+ end.join("\n")
43
+ end.join("\n\n")
44
+ rescue JSON::ParserError, NoMethodError
45
+ response
46
+ rescue APIError
47
+ retry if Time.now - start_time < 5
48
+ raise
49
+ end
50
+ end
51
+
52
+ def save_log(path: nil, log_type: nil)
53
+ log_type ||= :sauce
54
+ save_file(path: path, file_name: "#{log_type}.log", data: log(log_type), type: 'logs')
55
+ end
56
+
57
+ def save_logs(path: nil)
58
+ %i[sauce selenium automator].each do |type|
59
+ save_log(path: path, log_type: type)
60
+ end
61
+ end
62
+
63
+ def video_stream
64
+ start_time = Time.now
65
+ begin
66
+ response = asset('video.mp4').body
67
+ raise APIError, "Can not retrieve video: #{response['message']}" if JSON.parse(response).key?('message')
68
+ rescue JSON::ParserError
69
+ response
70
+ rescue APIError
71
+ retry if Time.now - start_time < 5
72
+ raise
73
+ end
74
+ end
75
+
76
+ def save_video(path = nil)
77
+ save_file(path: path, file_name: 'video.mp4', data: video_stream, type: 'videos')
78
+ end
79
+
80
+ def save_assets
81
+ save_logs
82
+ save_video
83
+ save_screenshots
84
+ end
85
+
86
+ def delete_assets
87
+ start_time = Time.now
88
+ begin
89
+ response = JSON.parse(SauceWhisk::Assets.delete(job_id).data.body)
90
+ raise APIError, "Can not delete assets: #{response}" if response.is_a?(String)
91
+ rescue APIError
92
+ retry if Time.now - start_time < 5
93
+ raise
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def save_file(path:, data:, file_name:, type:)
100
+ path ||= File.expand_path("../../../assets/#{job_id}/#{type}/#{file_name}", __FILE__)
101
+
102
+ file_name = path[%r{[^/]*$}]
103
+ base_path = path.gsub(file_name, '')
104
+
105
+ FileUtils.mkdir_p(base_path) unless File.exist?(base_path)
106
+ File.write(path, data)
107
+ end
108
+
109
+ def asset(asset)
110
+ SauceWhisk::Jobs.fetch_asset(@job_id, asset)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Saucer
4
+ module CustomCommands
5
+ def comment=(comment)
6
+ @driver.execute_script("sauce: context=#{comment}")
7
+ end
8
+
9
+ def stop_network
10
+ @driver.execute_script('sauce: stop network')
11
+ end
12
+
13
+ def start_network
14
+ @driver.execute_script('sauce: start network')
15
+ end
16
+
17
+ def breakpoint
18
+ @driver.execute_script('sauce: break')
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Saucer
4
+ class DataCollection
5
+ PAGE_OBJECTS = %w[site_prism page-object watirsome watir_drops].freeze
6
+
7
+ def gems
8
+ Bundler.definition.specs.map(&:name).each_with_object({}) do |gem_name, hash|
9
+ name = Bundler.environment.specs.to_hash[gem_name]
10
+ next if name.empty?
11
+
12
+ hash[gem_name] = name.first.version
13
+ end
14
+ end
15
+
16
+ def page_objects
17
+ page_objects_gems = PAGE_OBJECTS & gems.keys
18
+ if page_objects_gems.size > 1
19
+ 'multiple'
20
+ elsif page_objects_gems.empty?
21
+ 'unknown'
22
+ else
23
+ page_objects_gems.first
24
+ end
25
+ end
26
+
27
+ def selenium_version
28
+ gems['selenium-webdriver']
29
+ end
30
+
31
+ def test_library
32
+ if gems['watir'] && gems['capybara']
33
+ 'multiple'
34
+ elsif gems['capybara']
35
+ 'capybara'
36
+ elsif gems['watir']
37
+ 'watir'
38
+ else
39
+ 'unknown'
40
+ end
41
+ end
42
+
43
+ def test_runner(runner)
44
+ gems[runner.name.to_s] ? "#{runner.name} v#{gems[runner.name.to_s]}" : 'Unknown'
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Saucer
4
+ module JobUpdate
5
+ def name=(name)
6
+ job.name = name
7
+ end
8
+
9
+ def build=(name)
10
+ job.build = name
11
+ end
12
+
13
+ def visibility=(value)
14
+ valid = %i[public public_restricted share team private]
15
+ raise ArgumentError, "#{value} is not a valid visibility value; use one of #{valid}" unless valid.include?(value)
16
+
17
+ job.visibility = value.to_s
18
+ end
19
+
20
+ def save
21
+ job.tags = tags unless tags.empty?
22
+ job.custom_data = data unless data.empty?
23
+ SauceWhisk::Jobs.save(job)
24
+ end
25
+ end
26
+ end