simulacrum-browserstack 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.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +4 -0
- data/Rakefile +2 -0
- data/lib/simulacrum/browserstack.rb +7 -0
- data/lib/simulacrum/browserstack/api.rb +51 -0
- data/lib/simulacrum/browserstack/capybara_patch.rb +22 -0
- data/lib/simulacrum/browserstack/driver.rb +93 -0
- data/lib/simulacrum/browserstack/local_wrapper.rb +42 -0
- data/lib/simulacrum/browserstack/runner.rb +193 -0
- data/lib/simulacrum/browserstack/summary.rb +89 -0
- data/lib/simulacrum/browserstack/tunnel.rb +105 -0
- data/lib/simulacrum/browserstack/version.rb +5 -0
- data/simulacrum-browserstack.gemspec +26 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6d33c5ed98259b24be10580a2e31b397eb97551d
|
4
|
+
data.tar.gz: c80c8ea8b9ec5656acaaa6d10cfac461007ce05f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90d4e359d4504ae1a354a2f9cbc9694b37c89895c780364d99b38f4ac36210be7455ce179f5b69182f8544926402adf016e6e9d006d558e65a18c102ed59da09
|
7
|
+
data.tar.gz: cd99c323cee573b9a63e5a4cc4e740fdd4e69d4180374f4a80101b17e1015ae63523b283b84aa9a4fcc278c3652318c6b1b006906b0ad34ebf1f73092f00dc6d
|
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
|
12
|
+
## Specific to RubyMotion:
|
13
|
+
.dat*
|
14
|
+
.repl_history
|
15
|
+
build/
|
16
|
+
|
17
|
+
## Documentation cache and generated files:
|
18
|
+
/.yardoc/
|
19
|
+
/_yardoc/
|
20
|
+
/doc/
|
21
|
+
/rdoc/
|
22
|
+
|
23
|
+
## Environment normalisation:
|
24
|
+
/.bundle/
|
25
|
+
/lib/bundler/man/
|
26
|
+
|
27
|
+
# for a library or gem, you might want to ignore these files since the code is
|
28
|
+
# intended to run in multiple environments; otherwise, check them in:
|
29
|
+
# Gemfile.lock
|
30
|
+
# .ruby-version
|
31
|
+
# .ruby-gemset
|
32
|
+
|
33
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
34
|
+
.rvmrc
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Justin Morris
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'uri'
|
3
|
+
require 'net/https'
|
4
|
+
|
5
|
+
module Simulacrum
|
6
|
+
module Browserstack
|
7
|
+
# The Browserstack API class wraps up functionality for talking to the
|
8
|
+
# Browserstack REST API.
|
9
|
+
class API
|
10
|
+
def initialize(username, apikey)
|
11
|
+
@username = username
|
12
|
+
@apikey = apikey
|
13
|
+
end
|
14
|
+
|
15
|
+
def account_details
|
16
|
+
request = request('https://www.browserstack.com/automate/plan.json')
|
17
|
+
account_details = OpenStruct.new
|
18
|
+
account_details.sessions_running = request['parallel_sessions_running'].to_i
|
19
|
+
account_details.sessions_allowed = request['parallel_sessions_max_allowed'].to_i
|
20
|
+
account_details
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def request(url)
|
26
|
+
response = make_request(url)
|
27
|
+
JSON.parse(response.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_url(url)
|
31
|
+
URI.parse(url)
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare_http(uri)
|
35
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
36
|
+
http.read_timeout = 30
|
37
|
+
http.use_ssl = true
|
38
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
39
|
+
http
|
40
|
+
end
|
41
|
+
|
42
|
+
def make_request(url)
|
43
|
+
uri = parse_url(url)
|
44
|
+
http = prepare_http(uri)
|
45
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
46
|
+
request.basic_auth(@username, @apikey)
|
47
|
+
http.request(request)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Selenium
|
5
|
+
# Monkey patch the Capybara Driver class to ensure that we can call `quit`
|
6
|
+
# at the end of a parallel process run and not generate errors if capybara
|
7
|
+
# then attempts to quit.
|
8
|
+
#
|
9
|
+
# We need to do this so that sessions are terminated as early as possible
|
10
|
+
# at the remote end.
|
11
|
+
class Driver < Capybara::Driver::Base
|
12
|
+
def quit
|
13
|
+
@browser.quit if @browser
|
14
|
+
rescue Errno::ECONNREFUSED,
|
15
|
+
::Selenium::WebDriver::Error::UnknownError
|
16
|
+
# Browser must have already gone
|
17
|
+
ensure
|
18
|
+
@browser = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'simulacrum/driver'
|
3
|
+
require 'capybara'
|
4
|
+
require 'selenium-webdriver'
|
5
|
+
require 'selenium/webdriver/remote/http/persistent'
|
6
|
+
|
7
|
+
module Simulacrum
|
8
|
+
module Browserstack
|
9
|
+
# Responsible for registering a custom Capybara driver for use with
|
10
|
+
# Selenium grid endpoints (Browserstack, SauceLabs, etc.). Configures
|
11
|
+
# selenium options via ENV vars so that they can be passed into
|
12
|
+
class Driver < Simulacrum::Driver
|
13
|
+
private
|
14
|
+
|
15
|
+
def configuration
|
16
|
+
{
|
17
|
+
browser: :remote,
|
18
|
+
url: selenium_remote_url,
|
19
|
+
desired_capabilities: capabilities,
|
20
|
+
http_client: persistent_http_client
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def persistent_http_client
|
25
|
+
Selenium::WebDriver::Remote::Http::Persistent.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def capabilities
|
29
|
+
caps = Selenium::WebDriver::Remote::Capabilities.new
|
30
|
+
caps['project'] = Simulacrum.configuration.project_name
|
31
|
+
caps['build'] = Simulacrum.configuration.build_name
|
32
|
+
caps['browserstack.local'] = true
|
33
|
+
caps['browserstack.debug'] = false
|
34
|
+
caps['browser'] = browser
|
35
|
+
caps['browserName'] = browser_name
|
36
|
+
caps['browser_version'] = browser_version
|
37
|
+
caps['os'] = os
|
38
|
+
caps['os_version'] = os_version
|
39
|
+
caps['device'] = device
|
40
|
+
caps['deviceOrientation'] = device_orientation
|
41
|
+
caps['platform'] = platform
|
42
|
+
caps['resolution'] = resolution
|
43
|
+
caps['requireWindowFocus'] = require_window_focus
|
44
|
+
caps['realMobile'] = real_mobile
|
45
|
+
caps
|
46
|
+
end
|
47
|
+
|
48
|
+
def browser
|
49
|
+
ENV['SELENIUM_BROWSER']
|
50
|
+
end
|
51
|
+
|
52
|
+
def browser_name
|
53
|
+
ENV['BS_BROWSERNAME'] || ''
|
54
|
+
end
|
55
|
+
|
56
|
+
def real_mobile
|
57
|
+
ENV['BS_REALMOBILE'] || false
|
58
|
+
end
|
59
|
+
|
60
|
+
def browser_version
|
61
|
+
ENV['SELENIUM_VERSION'] || ''
|
62
|
+
end
|
63
|
+
|
64
|
+
def os
|
65
|
+
ENV['BS_AUTOMATE_OS'] || ''
|
66
|
+
end
|
67
|
+
|
68
|
+
def os_version
|
69
|
+
ENV['BS_AUTOMATE_OS_VERSION'] || ''
|
70
|
+
end
|
71
|
+
|
72
|
+
def device
|
73
|
+
ENV['BS_AUTOMATE_DEVICE'] || ''
|
74
|
+
end
|
75
|
+
|
76
|
+
def device_orientation
|
77
|
+
ENV['BS_AUTOMATE_DEVICEORIENTATION'] || ''
|
78
|
+
end
|
79
|
+
|
80
|
+
def platform
|
81
|
+
ENV['BS_AUTOMATE_PLATFORM'] || ''
|
82
|
+
end
|
83
|
+
|
84
|
+
def resolution
|
85
|
+
ENV['BS_AUTOMATE_RESOLUTION'] || ''
|
86
|
+
end
|
87
|
+
|
88
|
+
def require_window_focus
|
89
|
+
ENV['BS_AUTOMATE_REQUIREWINDOWFOCUS'] || ''
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# # encoding: UTF-8
|
2
|
+
|
3
|
+
# module Simulacrum
|
4
|
+
# module Browserstack
|
5
|
+
# # Class for ensuring BrowserStackLocal is available
|
6
|
+
# class LocalWrapper
|
7
|
+
# Simulacrum.logger.debug('BrowserStack') { 'Downloading BrowserStackLocal binary for ' }
|
8
|
+
|
9
|
+
# def download_binary
|
10
|
+
|
11
|
+
# end
|
12
|
+
|
13
|
+
# "https://www.browserstack.com/browserstack-local/BrowserStackLocal-"
|
14
|
+
|
15
|
+
# private
|
16
|
+
|
17
|
+
# def platform
|
18
|
+
|
19
|
+
# end
|
20
|
+
|
21
|
+
# def windows?
|
22
|
+
# (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
23
|
+
# end
|
24
|
+
|
25
|
+
# def mac?
|
26
|
+
# (/darwin/ =~ RUBY_PLATFORM) != nil
|
27
|
+
# end
|
28
|
+
|
29
|
+
# def linux?
|
30
|
+
# (/linux/ =~ RUBY_PLATFORM) != nil
|
31
|
+
# end
|
32
|
+
|
33
|
+
# def x64?
|
34
|
+
# (/x86_64/ =~ RUBY_PLATFORM) != nil
|
35
|
+
# end
|
36
|
+
|
37
|
+
# def i386?
|
38
|
+
# (/i386/ =~ RUBY_PLATFORM) != nil
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
# end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require_relative './capybara_patch'
|
3
|
+
require_relative './tunnel'
|
4
|
+
require_relative './summary'
|
5
|
+
require_relative './api'
|
6
|
+
require_relative './driver'
|
7
|
+
require 'simulacrum/runner'
|
8
|
+
require 'parallel'
|
9
|
+
require 'yaml'
|
10
|
+
require 'retries'
|
11
|
+
require 'pry'
|
12
|
+
|
13
|
+
module Simulacrum
|
14
|
+
module Browserstack
|
15
|
+
# A Runner Class for Browserstack that handles creating a Browserstack
|
16
|
+
# tunnel, closing it when done. Also handles running the suite in parallel.
|
17
|
+
class Runner < Simulacrum::Runner
|
18
|
+
# Exception to indicate that Browserstack has no available sessions
|
19
|
+
# to start a new test run, this is used inside a retries loop but will
|
20
|
+
# be raised if maximum retries is exceeded
|
21
|
+
class NoRemoteSessionsAvailable < RuntimeError; end
|
22
|
+
|
23
|
+
attr_reader :app_ports
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
start_timer
|
27
|
+
|
28
|
+
@username = Simulacrum.runner_options.username
|
29
|
+
@apikey = Simulacrum.runner_options.apikey
|
30
|
+
@app_ports = app_ports
|
31
|
+
@api = Simulacrum::Browserstack::API.new(@username, @apikey)
|
32
|
+
@tunnel = Simulacrum::Browserstack::Tunnel.new(@username, @apikey, @app_ports)
|
33
|
+
|
34
|
+
set_global_env
|
35
|
+
execute
|
36
|
+
summarize_results
|
37
|
+
summarize_exit_codes
|
38
|
+
ensure
|
39
|
+
@tunnel.close if @tunnel
|
40
|
+
end
|
41
|
+
|
42
|
+
def execute
|
43
|
+
Simulacrum.logger.info('BrowserStack') { "Using runner with #{processes} remote workers" }
|
44
|
+
@process_exit_codes, @process_results = Parallel.map_with_index(browsers, in_processes: processes) do |(name, caps), index|
|
45
|
+
begin
|
46
|
+
ensure_available_remote_runner
|
47
|
+
configure_app_port(index)
|
48
|
+
configure_environment(name, caps)
|
49
|
+
configure_browser_setting(name)
|
50
|
+
exit_code = run
|
51
|
+
[exit_code, { results: dump_results }]
|
52
|
+
rescue SystemExit
|
53
|
+
exit 1
|
54
|
+
ensure
|
55
|
+
quit_browser
|
56
|
+
end
|
57
|
+
end.transpose
|
58
|
+
ensure
|
59
|
+
stop_timer
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def quit_browser
|
65
|
+
Capybara.current_session.driver.browser.quit
|
66
|
+
end
|
67
|
+
|
68
|
+
def configure_browser_setting(name)
|
69
|
+
RSpec.configuration.around do |example|
|
70
|
+
example.metadata[:browser] = name
|
71
|
+
begin
|
72
|
+
example.run
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def configure_rspec
|
78
|
+
super
|
79
|
+
RSpec.configuration.instance_variable_set(:@reporter, reporter)
|
80
|
+
end
|
81
|
+
|
82
|
+
def configure_driver
|
83
|
+
Simulacrum::Browserstack::Driver.use
|
84
|
+
end
|
85
|
+
|
86
|
+
def reporter
|
87
|
+
@reporter ||= RSpec::Core::Reporter.new(formatter)
|
88
|
+
end
|
89
|
+
|
90
|
+
def formatter
|
91
|
+
@formatter ||= Simulacrum::Formatters::SimulacrumFormatter.new($stdout)
|
92
|
+
end
|
93
|
+
|
94
|
+
def dump_results
|
95
|
+
Marshal.dump(formatter.output_hash)
|
96
|
+
end
|
97
|
+
|
98
|
+
def ensure_available_remote_runner
|
99
|
+
with_retries(max_tries: 20, base_sleep_seconds: 0.5, max_sleep_seconds: 15) do
|
100
|
+
remote_worker_available?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def remote_worker_available?
|
105
|
+
account_details = @api.account_details
|
106
|
+
unless account_details.sessions_running < account_details.sessions_allowed
|
107
|
+
fail NoRemoteSessionsAvailable
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def start_timer
|
112
|
+
@start_time = Time.now
|
113
|
+
end
|
114
|
+
|
115
|
+
def stop_timer
|
116
|
+
@end_time = Time.now
|
117
|
+
end
|
118
|
+
|
119
|
+
def summarize_results
|
120
|
+
summary = Simulacrum::Browserstack::Summary.new(@process_results, @start_time, @end_time)
|
121
|
+
summary.dump_summary
|
122
|
+
summary.dump_failures
|
123
|
+
summary.dump_pending
|
124
|
+
end
|
125
|
+
|
126
|
+
def summarize_exit_codes
|
127
|
+
@exit_code = (@process_exit_codes.inject(&:+) == 0) ? 0 : 1
|
128
|
+
end
|
129
|
+
|
130
|
+
def configure_app_port(index)
|
131
|
+
ENV['APP_SERVER_PORT'] = app_ports[index].to_s
|
132
|
+
end
|
133
|
+
|
134
|
+
# rubocop:disable MethodLength
|
135
|
+
def configure_environment(name, caps)
|
136
|
+
ENV['SELENIUM_REMOTE_URL'] = @tunnel.selenium_remote_url
|
137
|
+
ENV['BS_DRIVER_NAME'] = name
|
138
|
+
ENV['SELENIUM_BROWSER'] = caps['browser']
|
139
|
+
ENV['SELENIUM_VERSION'] = caps['browser_version'].to_s
|
140
|
+
ENV['BS_AUTOMATE_OS'] = caps['os']
|
141
|
+
ENV['BS_AUTOMATE_OS_VERSION'] = caps['os_version'].to_s
|
142
|
+
ENV['BS_AUTOMATE_RESOLUTION'] = caps['resolution']
|
143
|
+
ENV['BS_AUTOMATE_REQUIREWINDOWFOCUS'] = caps['requireWindowFocus'].to_s
|
144
|
+
ENV['BS_AUTOMATE_PLATFORM'] = caps['platform']
|
145
|
+
ENV['BS_AUTOMATE_DEVICE'] = caps['device']
|
146
|
+
ENV['BS_AUTOMATE_DEVICEORIENTATION'] = caps['deviceOrientation']
|
147
|
+
ENV['BS_BROWSERNAME'] = caps['browserName']
|
148
|
+
ENV['BS_REALMOBILE'] = caps['realMobile'].to_s
|
149
|
+
end
|
150
|
+
# rubocop:enable MethodLength
|
151
|
+
|
152
|
+
def processes
|
153
|
+
Simulacrum.runner_options.max_processes || @api.account_details.sessions_allowed
|
154
|
+
end
|
155
|
+
|
156
|
+
def set_global_env
|
157
|
+
ENV['SELENIUM_REMOTE_URL'] = @tunnel.selenium_remote_url
|
158
|
+
end
|
159
|
+
|
160
|
+
def app_ports
|
161
|
+
@app_ports ||= begin
|
162
|
+
if browsers.any?
|
163
|
+
browsers.length.times.map { find_available_port }
|
164
|
+
else
|
165
|
+
[find_available_port]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def find_available_port
|
171
|
+
server = TCPServer.new('127.0.0.1', 0)
|
172
|
+
server.addr[1]
|
173
|
+
ensure
|
174
|
+
server.close if server
|
175
|
+
end
|
176
|
+
|
177
|
+
def browsers
|
178
|
+
@browsers ||= begin
|
179
|
+
if Simulacrum.config_file?
|
180
|
+
browsers = Simulacrum.config_file['browsers']
|
181
|
+
browsers = browsers.select do |name, value|
|
182
|
+
name == Simulacrum.runner_options.browser
|
183
|
+
end if Simulacrum.runner_options.browser
|
184
|
+
browsers
|
185
|
+
else
|
186
|
+
# TODO: Raise a better error...
|
187
|
+
fail 'DERP!'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rspec/core/formatters/helpers'
|
3
|
+
require 'rspec/core/formatters/base_text_formatter'
|
4
|
+
|
5
|
+
module Simulacrum
|
6
|
+
module Browserstack
|
7
|
+
# The Summary Class is responsible for combining dumped examlpes from one
|
8
|
+
# or more other RSpec runs.
|
9
|
+
class Summary < RSpec::Core::Formatters::BaseTextFormatter
|
10
|
+
# TODO: Rename me, I'm really just a formatter anyway
|
11
|
+
# TODO: Also, should this just be an external gem...? Prolly not?
|
12
|
+
def initialize(results_set, start_time, end_time)
|
13
|
+
super($stdout)
|
14
|
+
@results_set = results_set
|
15
|
+
@start_time = start_time
|
16
|
+
@end_time = end_time
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump_commands_to_rerun_failed_examples
|
20
|
+
return if failed_examples.empty?
|
21
|
+
output.puts
|
22
|
+
output.puts('Failed examples:')
|
23
|
+
output.puts
|
24
|
+
|
25
|
+
failed_examples.each do |example|
|
26
|
+
output.puts(failure_color("simulacrum --browser=#{example.metadata[:browser]} #{RSpec::Core::Metadata::relative_path(example.location)}") + ' ' + detail_color("# #{example.full_description}"))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def dump_summary
|
31
|
+
super(duration, example_count, failure_count, pending_count)
|
32
|
+
end
|
33
|
+
|
34
|
+
def failed_examples
|
35
|
+
examples.select do |example|
|
36
|
+
example.execution_result[:status] == 'failed'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def pending_examples
|
41
|
+
examples.select do |example|
|
42
|
+
example.execution_result[:status] == 'pending'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def duration
|
47
|
+
@end_time - @start_time
|
48
|
+
end
|
49
|
+
|
50
|
+
def example_count
|
51
|
+
summaries.map { |x| x[:example_count] }.reduce(:+)
|
52
|
+
end
|
53
|
+
|
54
|
+
def failure_count
|
55
|
+
summaries.map { |x| x[:failure_count] }.reduce(:+)
|
56
|
+
end
|
57
|
+
|
58
|
+
def pending_count
|
59
|
+
summaries.map { |x| x[:pending_count] }.reduce(:+)
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_shared_group(example)
|
63
|
+
end
|
64
|
+
|
65
|
+
def group_and_parent_groups(example)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def color_enabled?
|
71
|
+
Simulacrum.runner_options.color
|
72
|
+
end
|
73
|
+
|
74
|
+
def examples
|
75
|
+
dumped_results.map { |result| result[:examples] }.flatten
|
76
|
+
end
|
77
|
+
|
78
|
+
def summaries
|
79
|
+
dumped_results.map { |result| result[:summary] }.flatten
|
80
|
+
end
|
81
|
+
|
82
|
+
def dumped_results
|
83
|
+
@dumped_results ||= @results_set.map do |result|
|
84
|
+
Marshal.load(result[:results])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'timeout'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'English'
|
5
|
+
|
6
|
+
module Simulacrum
|
7
|
+
module Browserstack
|
8
|
+
# Class for handling Browserstack tunnel opening/closing/management
|
9
|
+
class Tunnel
|
10
|
+
attr_reader :selenium_remote_url, :pid, :ports, :open
|
11
|
+
alias_method :open?, :open
|
12
|
+
|
13
|
+
DEFAULT_OPTIONS = {
|
14
|
+
skip_check: true,
|
15
|
+
only_automate: false,
|
16
|
+
verbose: false,
|
17
|
+
force: true
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize(username, apikey, ports, options = {})
|
21
|
+
@pid = nil
|
22
|
+
@open = false
|
23
|
+
@username = username
|
24
|
+
@apikey = apikey
|
25
|
+
@ports = ports
|
26
|
+
@options = OpenStruct.new(DEFAULT_OPTIONS.clone.merge!(options))
|
27
|
+
|
28
|
+
create_tunnel
|
29
|
+
ensure_open
|
30
|
+
end
|
31
|
+
|
32
|
+
def selenium_remote_url
|
33
|
+
"http://#{@username}:#{@apikey}@hub.browserstack.com/wd/hub"
|
34
|
+
end
|
35
|
+
|
36
|
+
def close
|
37
|
+
kill
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def binary_path
|
43
|
+
binary_path = `which BrowserStackLocal`.strip
|
44
|
+
if $CHILD_STATUS.success? && File.executable?(binary_path)
|
45
|
+
binary_path
|
46
|
+
else
|
47
|
+
Simulacrum.logger.fail('BrowserStack') { 'BrowserStackLocal binary not found or not executable' }
|
48
|
+
fail
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_tunnel
|
53
|
+
@process = IO.popen(command)
|
54
|
+
@pid = @process.pid
|
55
|
+
Simulacrum.logger.debug('BrowserStack') { "Openning tunnel (pid #{@pid})" }
|
56
|
+
end
|
57
|
+
|
58
|
+
def command
|
59
|
+
cmd = [binary_path]
|
60
|
+
cmd << '-skipCheck' if @options.skip_check == true
|
61
|
+
cmd << '-onlyAutomate' if @options.only_automate == true
|
62
|
+
cmd << '-force' if @options.force == true
|
63
|
+
cmd << '-v' if @options.verbose == true
|
64
|
+
cmd << @apikey
|
65
|
+
cmd << formatted_ports
|
66
|
+
cmd.join(' ')
|
67
|
+
end
|
68
|
+
|
69
|
+
def formatted_ports
|
70
|
+
ports.map { |port| "localhost,#{port},0" }.join(',')
|
71
|
+
end
|
72
|
+
|
73
|
+
def kill
|
74
|
+
Process.kill('TERM', @pid) if running?
|
75
|
+
end
|
76
|
+
|
77
|
+
def ensure_open
|
78
|
+
Simulacrum.logger.debug('BrowserStack') { 'Waiting for tunnel to open' }
|
79
|
+
Timeout.timeout(240) do
|
80
|
+
sleep 1 until tunnel_open?
|
81
|
+
end
|
82
|
+
@open = true
|
83
|
+
Simulacrum.logger.debug('BrowserStack') { 'Tunnel open' }
|
84
|
+
rescue Timeout::Error
|
85
|
+
Simulacrum.logger.debug('BrowserStack') { 'Tunnel failed to open, exiting.' }
|
86
|
+
exit(1)
|
87
|
+
end
|
88
|
+
|
89
|
+
def tunnel_open?
|
90
|
+
`curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:45691`.to_i == 200
|
91
|
+
end
|
92
|
+
|
93
|
+
def running?
|
94
|
+
if @pid.nil?
|
95
|
+
false
|
96
|
+
else
|
97
|
+
Process.getpgid(@pid)
|
98
|
+
true
|
99
|
+
end
|
100
|
+
rescue Errno::ESRCH
|
101
|
+
false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'simulacrum/browserstack/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'simulacrum-browserstack'
|
8
|
+
gem.version = Simulacrum::Browserstack::VERSION
|
9
|
+
gem.authors = ['Justin Morris']
|
10
|
+
gem.email = ['desk@pixelbloom.com']
|
11
|
+
gem.summary = %q{BrowserStack runner for Simulacrum}
|
12
|
+
gem.description = %q{BrowserStack runner for Simulacrum}
|
13
|
+
gem.homepage = ''
|
14
|
+
gem.license = 'MIT'
|
15
|
+
|
16
|
+
gem.files = `git ls-files -z`.split("\x0")
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ['lib']
|
20
|
+
|
21
|
+
gem.add_dependency 'parallel', ['~> 1.2.0']
|
22
|
+
gem.add_dependency 'net-http-persistent'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'bundler', '~> 1.6'
|
25
|
+
gem.add_development_dependency 'rake'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simulacrum-browserstack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Morris
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parallel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-http-persistent
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: BrowserStack runner for Simulacrum
|
70
|
+
email:
|
71
|
+
- desk@pixelbloom.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- Gemfile
|
78
|
+
- LICENSE
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/simulacrum/browserstack.rb
|
82
|
+
- lib/simulacrum/browserstack/api.rb
|
83
|
+
- lib/simulacrum/browserstack/capybara_patch.rb
|
84
|
+
- lib/simulacrum/browserstack/driver.rb
|
85
|
+
- lib/simulacrum/browserstack/local_wrapper.rb
|
86
|
+
- lib/simulacrum/browserstack/runner.rb
|
87
|
+
- lib/simulacrum/browserstack/summary.rb
|
88
|
+
- lib/simulacrum/browserstack/tunnel.rb
|
89
|
+
- lib/simulacrum/browserstack/version.rb
|
90
|
+
- simulacrum-browserstack.gemspec
|
91
|
+
homepage: ''
|
92
|
+
licenses:
|
93
|
+
- MIT
|
94
|
+
metadata: {}
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
require_paths:
|
98
|
+
- lib
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 2.0.14
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: BrowserStack runner for Simulacrum
|
115
|
+
test_files: []
|
116
|
+
has_rdoc:
|