shoot 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +8 -0
- data/README.md +32 -0
- data/lib/shoot/browser.rb +119 -0
- data/lib/shoot/cli.rb +40 -120
- data/lib/shoot/ngrok.rb +23 -0
- data/lib/shoot/ngrok_pow.rb +13 -0
- data/lib/shoot/scenario.rb +29 -20
- data/lib/shoot/scenario_runner.rb +40 -0
- data/lib/shoot/ui.rb +24 -0
- data/lib/shoot/version.rb +1 -1
- data/lib/shoot.rb +8 -3
- data/shoot.gemspec +1 -0
- data/spec/cli_spec.rb +22 -24
- data/spec/scenario_spec.rb +2 -3
- metadata +21 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 152d35943f02692f5c763b5928e6dcfcf0c51e29
|
4
|
+
data.tar.gz: 51152e373355366c011881fbb8b6aecca33444a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ae30e0c7ba313cab8298b866d9ffa0750c0e16a26242d8eebb5bf77efff96db56b27ab5c1c9d81d110f1ea92c8de0ca9df132a0221573c5c3c50106273e1a7d
|
7
|
+
data.tar.gz: 9edf0ae57d9cea0a13df09202a63f6fe9c4e08aa4b0e5ebbe079d143a2578a2115834b0259d4e69c4ed9d1d93fdbb7761c3f95eb6df14f09cfa103baa235f54b
|
data/Changelog.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# 2.1.0
|
2
|
+
|
3
|
+
- Adds Ngrok and NgrokPow class to make forwarding much easier;
|
4
|
+
- Adds a list of _ad hoc_ emulators BrowserStack provides for iPhones/iPads that is not on the API; 😢
|
5
|
+
- Writes a backtrace files in the appropriate folder if the thing explodes (both external and internal issues);
|
6
|
+
- Updates the Changelog to contain this.
|
7
|
+
|
8
|
+
|
1
9
|
# 2.0.0
|
2
10
|
|
3
11
|
- Adds interactive mode (`shoot -i`);
|
data/README.md
CHANGED
@@ -145,6 +145,38 @@ You got the idea.
|
|
145
145
|
shoot update # Update browser list (WARNING: will override active browsers)
|
146
146
|
shoot version, --version, -v # Shoot version
|
147
147
|
|
148
|
+
### Using ngrok
|
149
|
+
|
150
|
+
In order to access your local development environment on BrowserStack you need to forward it somehow to the external work (a.k.a. the internet). BrowserStack has it's own forwarded, but ngrok is better. If you wanna use it:
|
151
|
+
|
152
|
+
1. Install it from [https://ngrok.com/download](https://ngrok.com/download)
|
153
|
+
|
154
|
+
2. Enable subdomains by registering.
|
155
|
+
|
156
|
+
3. Use the `Shoot::Ngrok` class in your test, like this:
|
157
|
+
|
158
|
+
``` ruby
|
159
|
+
def my_test
|
160
|
+
my_server = Shoot::Ngrok(12345)
|
161
|
+
visit my_server.url
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
Where `12345` is the port of your local server. The default is `3000`, since I believe you're probably using Rails.
|
166
|
+
|
167
|
+
#### What if I'm using pow?
|
168
|
+
|
169
|
+
If you're using pow, skip step 3 above and do it like this instead:
|
170
|
+
|
171
|
+
``` ruby
|
172
|
+
def my_test
|
173
|
+
my_server = Shoot::NgrokPow(:my_server_folder)
|
174
|
+
visit my_server.url
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
NgrokPow will create another symlink of your server folder with a unique name and forward it correctly to ngrok. This symlink will be properly removed at the end of the execution of shoot.
|
179
|
+
|
148
180
|
## Contributing
|
149
181
|
|
150
182
|
1. Fork it ( https://github.com/joaomilho/shoot/fork )
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def symbolize_keys
|
5
|
+
self.keys.each do |key|
|
6
|
+
self[key.to_sym] = self.delete(key)
|
7
|
+
end
|
8
|
+
self
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
module Shoot
|
14
|
+
class Browser
|
15
|
+
BROWSERS_PATH = '.screenshots/.browsers.json'
|
16
|
+
EMULATORS_UNAVAILABLE_ON_THE_API = ["iPad 3rd", "iPad 3rd (6.0)", "iPad Mini", "iPad 4th", "iPhone 4S", "iPhone 4S (6.0)", "iPhone 5", "iPhone 5S"].map do |device|
|
17
|
+
{
|
18
|
+
browser: "iPhone",
|
19
|
+
os: "MAC",
|
20
|
+
device: device,
|
21
|
+
emulator: true
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.all
|
26
|
+
@@all ||= fetch_json.map { |browser| Browser.new(browser.symbolize_keys) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.activate(ids)
|
30
|
+
update(select_by_ids(ids), :activate)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.deactivate(ids)
|
34
|
+
update(select_by_ids(ids), :deactivate)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.deactivate_all
|
38
|
+
update(active, :deactivate)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.active
|
42
|
+
all.select(&:active)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.update_json
|
46
|
+
File.write(BROWSERS_PATH, JSON.dump(fetch_and_prepare))
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.filter(filter)
|
50
|
+
all.select { |browser| browser.inspect =~ /#{filter}/i }
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.select_by_ids(ids)
|
54
|
+
ids = ids.map(&:to_i)
|
55
|
+
all.select { |browser| ids.include?(browser.id) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.save
|
59
|
+
File.write(BROWSERS_PATH, JSON.pretty_generate(all.map(&:to_h)))
|
60
|
+
end
|
61
|
+
|
62
|
+
attr_reader :id, :os, :os_version, :browser, :device, :browser_version, :active, :emulator
|
63
|
+
def initialize(id:, os:, browser:, device:, os_version: nil, browser_version: nil, active: false, emulator: false)
|
64
|
+
@id = id
|
65
|
+
@os = os
|
66
|
+
@os_version = os_version
|
67
|
+
@browser = browser
|
68
|
+
@device = device
|
69
|
+
@browser_version = browser_version
|
70
|
+
@active = active
|
71
|
+
@emulator = emulator
|
72
|
+
end
|
73
|
+
|
74
|
+
def activate
|
75
|
+
@active = true
|
76
|
+
end
|
77
|
+
|
78
|
+
def deactivate
|
79
|
+
@active = false
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_h
|
83
|
+
{
|
84
|
+
id: id,
|
85
|
+
os: os,
|
86
|
+
os_version: os_version,
|
87
|
+
browser: browser,
|
88
|
+
device: device,
|
89
|
+
browser_version: browser_version,
|
90
|
+
active: active,
|
91
|
+
emulator: emulator
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def self.update(browsers, action)
|
98
|
+
browsers.map(&action)
|
99
|
+
save
|
100
|
+
browsers
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.fetch_json
|
104
|
+
update_json unless File.exist?(BROWSERS_PATH)
|
105
|
+
JSON.parse(File.read(BROWSERS_PATH))
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.fetch_and_prepare
|
109
|
+
require 'rest_client'
|
110
|
+
json = JSON.parse(RestClient.get("https://#{ENV['BROWSERSTACK_USER']}:#{ENV['BROWSERSTACK_KEY']}@www.browserstack.com/automate/browsers.json"))
|
111
|
+
json += EMULATORS_UNAVAILABLE_ON_THE_API
|
112
|
+
json.each_with_index do |browser, index|
|
113
|
+
browser['id'] = index
|
114
|
+
end
|
115
|
+
json
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
data/lib/shoot/cli.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'thor'
|
2
|
-
require '
|
2
|
+
require 'benchmark'
|
3
3
|
require 'highline/import'
|
4
4
|
|
5
5
|
module Shoot
|
6
6
|
class CLI < Thor
|
7
|
+
include UI
|
8
|
+
|
7
9
|
require 'fileutils'
|
8
10
|
FileUtils::mkdir_p '.screenshots'
|
9
|
-
BROWSERS_PATH = '.screenshots/.browsers.json'
|
10
11
|
map %w[--version -v] => :version
|
11
12
|
map %w[--interactive -i] => :interactive
|
12
13
|
|
@@ -15,29 +16,28 @@ module Shoot
|
|
15
16
|
puts Shoot::VERSION
|
16
17
|
end
|
17
18
|
|
19
|
+
INTERACTIVE_COMMANDS = {
|
20
|
+
active: ->(_) { active },
|
21
|
+
activate: ->(params){ activate(*params.split(" ")) },
|
22
|
+
deactivate: ->(params){ deactivate(*params.split(" ")) },
|
23
|
+
deactivate_all: ->(_) { deactivate_all },
|
24
|
+
list: ->(params){ list(params) },
|
25
|
+
open: ->(_) { open },
|
26
|
+
test: ->(params){ test(params) },
|
27
|
+
scenario: ->(params){ scenario(params) },
|
28
|
+
update: ->(_) { update },
|
29
|
+
exit: ->(_) { @exit = true }
|
30
|
+
}
|
31
|
+
|
18
32
|
desc 'interactive, --interactive, -i', 'Interactive mode'
|
19
33
|
def interactive
|
20
34
|
@exit = false
|
21
|
-
|
22
|
-
available_commands = {
|
23
|
-
active: ->(_) { active },
|
24
|
-
activate: ->(params){ activate(*params.split(" ")) },
|
25
|
-
deactivate: ->(params){ deactivate(*params.split(" ")) },
|
26
|
-
deactivate_all: ->(_) { deactivate_all },
|
27
|
-
list: ->(params){ list(params) },
|
28
|
-
open: ->(_) { open },
|
29
|
-
test: ->(params){ test(params) },
|
30
|
-
scenario: ->(params){ scenario(params) },
|
31
|
-
update: ->(_) { update },
|
32
|
-
exit: ->(_) { @exit = true }
|
33
|
-
}
|
34
|
-
|
35
35
|
while ! @exit
|
36
36
|
choose do |menu|
|
37
37
|
menu.layout = :menu_only
|
38
38
|
menu.shell = true
|
39
39
|
|
40
|
-
|
40
|
+
INTERACTIVE_COMMANDS.each do |command_name, command_action|
|
41
41
|
menu.choice(command_name, desc(command_name)){|_, details| command_action.call(details) }
|
42
42
|
end
|
43
43
|
end
|
@@ -46,27 +46,27 @@ module Shoot
|
|
46
46
|
|
47
47
|
desc 'open', 'Opens all screenshots taken'
|
48
48
|
def open
|
49
|
-
|
49
|
+
`open #{Dir.glob(".screenshots/**/*.png").join(" ")}`
|
50
50
|
end
|
51
51
|
|
52
52
|
desc 'list [FILTER]', 'List all platforms. Optionally passing a filter'
|
53
53
|
def list(filter = nil)
|
54
|
-
table
|
54
|
+
table filter ? Browser.filter(filter) : Browser.all
|
55
55
|
end
|
56
56
|
|
57
57
|
desc 'active', 'List active platforms.'
|
58
58
|
def active
|
59
|
-
table
|
59
|
+
table Browser.active
|
60
60
|
end
|
61
61
|
|
62
62
|
desc 'scenario PATH', 'Runs the given scenario or all files in a directory on all active platforms'
|
63
63
|
def scenario(path)
|
64
|
-
|
64
|
+
scenarios = File.directory?(path) ? Dir.glob("#{path}/*.rb") : [path]
|
65
65
|
|
66
66
|
elapsed_time do
|
67
|
-
|
68
|
-
|
69
|
-
run
|
67
|
+
Browser.active.each do |browser|
|
68
|
+
scenarios.each do |scenario|
|
69
|
+
run scenario, browser
|
70
70
|
end
|
71
71
|
end
|
72
72
|
print set_color("\nAll tests finished", :blue)
|
@@ -75,44 +75,40 @@ module Shoot
|
|
75
75
|
|
76
76
|
desc 'test PATH', 'Runs the given scenario or all files in a directory on a local phantomjs'
|
77
77
|
def test(path)
|
78
|
-
|
78
|
+
scenarios = File.directory?(path) ? Dir.glob("#{path}/*.rb") : [path]
|
79
79
|
elapsed_time do
|
80
|
-
|
80
|
+
scenarios.each{|scenario| run scenario }
|
81
81
|
print set_color("\nAll tests finished", :blue)
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
85
|
desc 'activate IDs', 'Activate platforms, based on IDs'
|
86
86
|
def activate(*ids)
|
87
|
-
|
87
|
+
return puts "No ids provided, e.g. 'activate 123'" if ids.empty?
|
88
|
+
table Browser.activate(ids)
|
88
89
|
end
|
89
90
|
|
90
91
|
desc 'deactivate IDs', 'Deactivate platforms, based on IDs'
|
91
92
|
def deactivate(*ids)
|
92
|
-
|
93
|
+
return puts "No ids provided, e.g. 'deactivate 123'" if ids.empty?
|
94
|
+
table Browser.deactivate(ids)
|
93
95
|
end
|
94
96
|
|
95
97
|
desc 'deactivate_all', 'Deactivate all the platforms'
|
96
98
|
def deactivate_all
|
97
|
-
|
98
|
-
child['active'] = false
|
99
|
-
end
|
100
|
-
save_json
|
99
|
+
Browser.deactivate_all
|
101
100
|
end
|
102
101
|
|
103
102
|
desc 'update', 'Update browser list (WARNING: will override active browsers)'
|
104
103
|
def update
|
105
|
-
update_json
|
104
|
+
Browser.update_json
|
106
105
|
list
|
107
106
|
end
|
108
107
|
|
109
108
|
no_commands do
|
110
|
-
def elapsed_time
|
111
|
-
require 'benchmark'
|
112
109
|
|
113
|
-
|
114
|
-
|
115
|
-
end
|
110
|
+
def elapsed_time
|
111
|
+
elapsed_time = Benchmark.measure { yield }
|
116
112
|
print set_color " (#{elapsed_time.real.to_i}s)\n", :blue
|
117
113
|
end
|
118
114
|
|
@@ -120,99 +116,23 @@ module Shoot
|
|
120
116
|
CLI.commands[command.to_s].description rescue nil
|
121
117
|
end
|
122
118
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
table json.select{|item| ids.include?(item['id']) }
|
129
|
-
end
|
130
|
-
|
131
|
-
def _deactivate(ids)
|
132
|
-
return puts "No ids provided, e.g. 'deactivate 123'" if ids.empty?
|
133
|
-
ids.map!(&:to_i)
|
134
|
-
ids.each { |id| json[id]['active'] = false }
|
135
|
-
save_json
|
136
|
-
table json.select{|item| ids.include?(item['id']) }
|
137
|
-
end
|
138
|
-
|
139
|
-
def open_all_screenshots
|
140
|
-
`open #{Dir.glob(".screenshots/**/*.png").join(" ")}`
|
141
|
-
end
|
142
|
-
|
143
|
-
def run(file, config = nil)
|
144
|
-
klass = get_const_from_file(file)
|
145
|
-
instance = klass.new(config)
|
146
|
-
puts set_color instance.platform_name, :white, :bold
|
147
|
-
klass.instance_methods(false).each do |method|
|
148
|
-
print set_color " ➥ #{klass}##{method} ... ", :white, :bold
|
119
|
+
def run(scenario, browser = nil)
|
120
|
+
runner = ScenarioRunner.new(scenario, browser)
|
121
|
+
puts set_color runner.platform_name, :white, :bold
|
122
|
+
runner.each_method do |method|
|
123
|
+
print set_color " ➥ #{runner.klass}##{method} ... ", :white, :bold
|
149
124
|
error = nil
|
150
125
|
|
151
126
|
elapsed_time do
|
152
|
-
ok, error =
|
127
|
+
ok, error = runner.run(method)
|
153
128
|
|
154
129
|
print ok ? set_color("OK", :green) : set_color("FAILED", :red)
|
155
130
|
end
|
156
131
|
puts set_color " ⚠ #{error}", :red if error
|
157
132
|
end
|
158
|
-
instance.ok
|
159
|
-
end
|
160
|
-
|
161
|
-
def require_file(file)
|
162
|
-
require Dir.pwd + '/' + file
|
163
|
-
end
|
164
133
|
|
165
|
-
def constantize_file_name(file)
|
166
|
-
klass_name = File.basename(file, '.rb').split('_').map(&:capitalize).join
|
167
|
-
Kernel.const_get(klass_name)
|
168
134
|
end
|
169
135
|
|
170
|
-
def get_const_from_file(file)
|
171
|
-
require_file(file)
|
172
|
-
constantize_file_name(file)
|
173
|
-
end
|
174
|
-
|
175
|
-
def table(browsers)
|
176
|
-
table = browsers.map do |p|
|
177
|
-
to_row(p)
|
178
|
-
end.unshift(['ID', 'OS #', 'Browser #', 'Device'])
|
179
|
-
print_table table, truncate: true
|
180
|
-
end
|
181
|
-
|
182
|
-
def to_row(p)
|
183
|
-
[
|
184
|
-
set_color(p['id'].to_s, p['active'] ? :green : :red),
|
185
|
-
"#{p['os']} #{p['os_version']}",
|
186
|
-
"#{p['browser']} #{p['browser_version']}",
|
187
|
-
p['device']
|
188
|
-
]
|
189
|
-
end
|
190
|
-
|
191
|
-
def update_json
|
192
|
-
File.write(BROWSERS_PATH, JSON.dump(fetch_json_and_prepare))
|
193
|
-
end
|
194
|
-
|
195
|
-
def json
|
196
|
-
update_json unless File.exist?(BROWSERS_PATH)
|
197
|
-
@json ||= JSON.parse(File.read(BROWSERS_PATH))
|
198
|
-
end
|
199
|
-
|
200
|
-
def _active
|
201
|
-
@active ||= json.select { |p| p['active'] }
|
202
|
-
end
|
203
|
-
|
204
|
-
def save_json
|
205
|
-
File.write(BROWSERS_PATH, JSON.pretty_generate(json))
|
206
|
-
end
|
207
|
-
|
208
|
-
def fetch_json_and_prepare
|
209
|
-
require 'rest_client'
|
210
|
-
JSON.parse(RestClient.get("https://#{ENV['BROWSERSTACK_USER']}:#{ENV['BROWSERSTACK_KEY']}@www.browserstack.com/automate/browsers.json")).tap do |json|
|
211
|
-
json.each_with_index do |browser, index|
|
212
|
-
browser['id'] = index
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
136
|
end
|
217
137
|
end
|
218
138
|
end
|
data/lib/shoot/ngrok.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Shoot
|
6
|
+
class Ngrok
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@process, :start, :stop, :exited?
|
9
|
+
|
10
|
+
def initialize(port = 3000)
|
11
|
+
@process = ChildProcess.build("ngrok", "-log=stdout", "-subdomain=#{subdomain}", port.to_s)
|
12
|
+
start
|
13
|
+
end
|
14
|
+
|
15
|
+
def subdomain
|
16
|
+
@subdomain ||= "shoot-#{Time.now.to_i}-#{SecureRandom.random_number(10**8)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def url
|
20
|
+
@url ||= "http://#{subdomain}.ngrok.com"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Shoot
|
2
|
+
class NgrokPow < Ngrok
|
3
|
+
def initialize(server)
|
4
|
+
`ln -s ~/.pow/#{server} ~/.pow/#{subdomain}`
|
5
|
+
@process = ChildProcess.build("ngrok", "-log=stdout", "-subdomain=#{subdomain}", "#{subdomain}.dev:80")
|
6
|
+
start
|
7
|
+
|
8
|
+
at_exit do
|
9
|
+
`rm ~/.pow/#{subdomain}`
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/shoot/scenario.rb
CHANGED
@@ -43,51 +43,60 @@ class Shoot::Scenario
|
|
43
43
|
def run(method)
|
44
44
|
@current_method = method
|
45
45
|
send(method)
|
46
|
-
#Kernel.sleep(1) # Just in case
|
47
46
|
shoot(:finish)
|
48
47
|
[true, nil]
|
49
48
|
rescue => e
|
50
|
-
|
49
|
+
File.write("#{directory}/backtrace.txt", e.backtrace.join("\n"))
|
51
50
|
shoot(:failed)
|
52
51
|
[false, e]
|
53
52
|
end
|
54
53
|
|
55
|
-
def
|
54
|
+
def quit
|
56
55
|
page.driver.quit
|
57
56
|
end
|
58
57
|
|
59
58
|
def platform_name
|
60
|
-
@platform_name ||= if @platform
|
61
|
-
@platform
|
59
|
+
@platform_name ||= if @platform.device
|
60
|
+
@platform.device
|
62
61
|
else
|
63
|
-
|
64
|
-
|
62
|
+
[
|
63
|
+
@platform.browser,
|
64
|
+
@platform.browser_version,
|
65
|
+
@platform.os,
|
66
|
+
@platform.os_version
|
67
|
+
].join(' ')
|
65
68
|
end
|
66
69
|
end
|
67
70
|
|
68
71
|
private
|
69
72
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
def directory
|
74
|
+
".screenshots/#{platform_name.to_s.gsub(" ", "_")}/#{self.class.name}/#{@current_method}".tap do |dir|
|
75
|
+
unless Dir.exist?(dir)
|
76
|
+
require 'fileutils'
|
77
|
+
FileUtils::mkdir_p dir
|
78
|
+
end
|
75
79
|
end
|
80
|
+
end
|
76
81
|
|
82
|
+
def shoot(label)
|
77
83
|
save_screenshot("#{directory}/#{label}.png")
|
84
|
+
rescue => e
|
85
|
+
File.write("#{directory}/#{label}.error.txt", %(#{e.inspect}\n\n#{e.backtrace.join("\n")}))
|
78
86
|
end
|
79
87
|
|
80
88
|
def config_capabilities # rubocop:disable AbcSize
|
81
89
|
@capabilities = Selenium::WebDriver::Remote::Capabilities.new
|
82
|
-
@capabilities[:browser] = @platform
|
83
|
-
@capabilities[:browser_version] = @platform
|
84
|
-
@capabilities[:os] = @platform
|
85
|
-
@capabilities[:os_version] = @platform
|
90
|
+
@capabilities[:browser] = @platform.browser
|
91
|
+
@capabilities[:browser_version] = @platform.browser_version
|
92
|
+
@capabilities[:os] = @platform.os
|
93
|
+
@capabilities[:os_version] = @platform.os_version
|
86
94
|
@capabilities['browserstack.debug'] = 'true'
|
87
|
-
@capabilities[:name] = "Digital Goods - #{@platform}"
|
88
|
-
@capabilities[:browserName] = @platform
|
89
|
-
@capabilities[:platform] = @platform
|
90
|
-
@capabilities[:device] = @platform
|
95
|
+
@capabilities[:name] = "Digital Goods - #{@platform.to_h}"
|
96
|
+
@capabilities[:browserName] = @platform.browser
|
97
|
+
@capabilities[:platform] = @platform.os
|
98
|
+
@capabilities[:device] = @platform.device if @platform.device
|
99
|
+
@capabilities[:emulator] = @platform.emulator
|
91
100
|
end
|
92
101
|
|
93
102
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Shoot
|
2
|
+
class ScenarioRunner
|
3
|
+
attr_reader :klass
|
4
|
+
def initialize(scenario, browser = nil)
|
5
|
+
@klass = get_const_from_file(scenario)
|
6
|
+
@instance = @klass.new(browser)
|
7
|
+
end
|
8
|
+
|
9
|
+
def platform_name
|
10
|
+
@instance.platform_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def each_method
|
14
|
+
@klass.instance_methods(false).each do |method|
|
15
|
+
yield(method)
|
16
|
+
end
|
17
|
+
@instance.quit
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(method)
|
21
|
+
@instance.run(method)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def require_file(file)
|
27
|
+
require Dir.pwd + '/' + file
|
28
|
+
end
|
29
|
+
|
30
|
+
def constantize_file_name(file)
|
31
|
+
klass_name = File.basename(file, '.rb').split('_').map(&:capitalize).join
|
32
|
+
Kernel.const_get(klass_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_const_from_file(file)
|
36
|
+
require_file(file)
|
37
|
+
constantize_file_name(file)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/shoot/ui.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shoot
|
2
|
+
module UI
|
3
|
+
TABLE_HEADER = ['ID', 'OS #', 'Browser #', 'Device', 'Emulator']
|
4
|
+
|
5
|
+
def table(browsers)
|
6
|
+
table = browsers.map do |browser|
|
7
|
+
to_row(browser)
|
8
|
+
end.unshift(TABLE_HEADER)
|
9
|
+
print_table table, truncate: true
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def to_row(browser)
|
15
|
+
[
|
16
|
+
set_color(browser.id.to_s, browser.active ? :green : :red),
|
17
|
+
"#{browser.os} #{browser.os_version}",
|
18
|
+
"#{browser.browser} #{browser.browser_version}",
|
19
|
+
browser.device,
|
20
|
+
browser.emulator ? 'Yes' : set_color('No', :black)
|
21
|
+
]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/shoot/version.rb
CHANGED
data/lib/shoot.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
module Shoot
|
2
|
-
autoload :
|
3
|
-
autoload :
|
4
|
-
autoload :
|
2
|
+
autoload :Browser, "shoot/browser"
|
3
|
+
autoload :CLI, "shoot/cli"
|
4
|
+
autoload :Ngrok, "shoot/ngrok"
|
5
|
+
autoload :NgrokPow, "shoot/ngrok_pow"
|
6
|
+
autoload :Scenario, "shoot/scenario"
|
7
|
+
autoload :ScenarioRunner, "shoot/scenario_runner"
|
8
|
+
autoload :UI, "shoot/ui"
|
9
|
+
autoload :VERSION, "shoot/version"
|
5
10
|
end
|
data/shoot.gemspec
CHANGED
data/spec/cli_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative '../lib/shoot
|
1
|
+
require_relative '../lib/shoot'
|
2
2
|
|
3
3
|
describe 'Shoot::CLI' do
|
4
4
|
subject(:cli) do
|
@@ -7,18 +7,18 @@ describe 'Shoot::CLI' do
|
|
7
7
|
|
8
8
|
let(:mock_json) do
|
9
9
|
[
|
10
|
-
{os: "Foo", os_version: "Foo 1", browser: "Foo browser", device: nil, browser_version: "6.0",
|
11
|
-
{os: "Bar", os_version: "Bar 2", browser: "Bar browser", device: nil, browser_version: "7.0",
|
10
|
+
{id: 0, os: "Foo", os_version: "Foo 1", browser: "Foo browser", device: nil, browser_version: "6.0", active: false, emulator: false},
|
11
|
+
{id: 1, os: "Bar", os_version: "Bar 2", browser: "Bar browser", device: nil, browser_version: "7.0", active: true, emulator: false}
|
12
12
|
]
|
13
13
|
end
|
14
14
|
|
15
15
|
before do
|
16
|
-
allow(
|
16
|
+
allow(Shoot::Browser).to receive(:fetch_json).and_return(mock_json)
|
17
|
+
allow(cli).to receive(:table) {|arg| arg.map(&:to_h) }
|
17
18
|
end
|
18
19
|
|
19
20
|
describe 'list' do
|
20
21
|
before do
|
21
|
-
allow(cli).to receive(:table) {|arg| arg }
|
22
22
|
end
|
23
23
|
|
24
24
|
context 'list all' do
|
@@ -36,10 +36,6 @@ describe 'Shoot::CLI' do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
describe 'active' do
|
39
|
-
before do
|
40
|
-
allow(cli).to receive(:table) {|arg| arg }
|
41
|
-
end
|
42
|
-
|
43
39
|
it 'displays the active browsers' do
|
44
40
|
expect(cli.active).to eq([mock_json[1]])
|
45
41
|
end
|
@@ -48,42 +44,44 @@ describe 'Shoot::CLI' do
|
|
48
44
|
describe 'activate' do
|
49
45
|
before do
|
50
46
|
allow(cli).to receive(:table) {|arg| arg }
|
51
|
-
allow(
|
47
|
+
allow(Shoot::Browser).to receive(:save)
|
52
48
|
end
|
53
49
|
|
54
50
|
it 'activates the browser' do
|
55
|
-
expect{ cli.activate(0) }.to change{
|
56
|
-
expect(
|
51
|
+
expect{ cli.activate(0) }.to change{ Shoot::Browser.all[0].active }.from(false).to(true)
|
52
|
+
expect(Shoot::Browser).to have_received(:save)
|
57
53
|
end
|
58
54
|
end
|
59
55
|
|
60
56
|
describe 'deactivate' do
|
61
57
|
before do
|
62
58
|
allow(cli).to receive(:table) {|arg| arg }
|
63
|
-
allow(
|
59
|
+
allow(Shoot::Browser).to receive(:save)
|
64
60
|
end
|
65
61
|
|
66
62
|
it 'deactivates the browser' do
|
67
|
-
|
68
|
-
expect(
|
63
|
+
Shoot::Browser.all[1].activate
|
64
|
+
expect{ cli.deactivate(1) }.to change{ Shoot::Browser.all[1].active }.from(true).to(false)
|
65
|
+
expect(Shoot::Browser).to have_received(:save)
|
69
66
|
end
|
70
67
|
end
|
71
68
|
|
72
69
|
describe 'deactivate_all' do
|
73
70
|
before do
|
74
71
|
allow(cli).to receive(:table) {|arg| arg }
|
75
|
-
allow(
|
72
|
+
allow(Shoot::Browser).to receive(:save)
|
76
73
|
end
|
77
74
|
|
78
75
|
it 'deactivates the browser' do
|
79
|
-
|
80
|
-
expect
|
76
|
+
Shoot::Browser.all[1].activate
|
77
|
+
expect{ cli.deactivate_all }.to change{ Shoot::Browser.all[1].active }.from(true).to(false)
|
78
|
+
expect(Shoot::Browser).to have_received(:save)
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
84
82
|
describe 'scenario' do
|
85
83
|
before do
|
86
|
-
allow(
|
84
|
+
allow(Shoot::Browser).to receive(:active).and_return(["foo"])
|
87
85
|
allow(cli).to receive(:run)
|
88
86
|
end
|
89
87
|
|
@@ -102,7 +100,7 @@ describe 'Shoot::CLI' do
|
|
102
100
|
|
103
101
|
it 'runs scenario' do
|
104
102
|
cli.test('foo.rb')
|
105
|
-
expect(cli).to have_received(:run).with("foo.rb"
|
103
|
+
expect(cli).to have_received(:run).with("foo.rb")
|
106
104
|
end
|
107
105
|
end
|
108
106
|
|
@@ -115,8 +113,8 @@ describe 'Shoot::CLI' do
|
|
115
113
|
|
116
114
|
it 'runs scenario' do
|
117
115
|
cli.test('foo')
|
118
|
-
expect(cli).to have_received(:run).with("foo/bar.rb"
|
119
|
-
expect(cli).to have_received(:run).with("foo/baz.rb"
|
116
|
+
expect(cli).to have_received(:run).with("foo/bar.rb")
|
117
|
+
expect(cli).to have_received(:run).with("foo/baz.rb")
|
120
118
|
end
|
121
119
|
end
|
122
120
|
end
|
@@ -129,9 +127,9 @@ describe 'Shoot::CLI' do
|
|
129
127
|
end
|
130
128
|
allow_any_instance_of(Foo).to receive(:run).and_return([true, ""])
|
131
129
|
allow_any_instance_of(Foo).to receive(:platform_name)
|
132
|
-
expect_any_instance_of(Foo).to receive(:
|
130
|
+
expect_any_instance_of(Foo).to receive(:quit)
|
133
131
|
|
134
|
-
|
132
|
+
allow_any_instance_of(Shoot::ScenarioRunner).to receive(:get_const_from_file).with("foo.rb").and_return(Foo)
|
135
133
|
end
|
136
134
|
|
137
135
|
it 'runs scenario' do
|
data/spec/scenario_spec.rb
CHANGED
@@ -8,14 +8,13 @@ describe 'Shoot::Scenario' do
|
|
8
8
|
allow(Capybara).to receive(:register_driver).with("browser 5.0 os 22.0")
|
9
9
|
allow(Capybara).to receive(:current_driver=).with("browser 5.0 os 22.0")
|
10
10
|
allow(FileUtils).to receive(:mkdir_p)
|
11
|
-
allow(Kernel).to receive(:sleep)
|
12
11
|
|
13
|
-
@scenario = Shoot::Scenario.new({
|
12
|
+
@scenario = Shoot::Scenario.new(OpenStruct.new({
|
14
13
|
'browser' => 'browser',
|
15
14
|
'browser_version' => '5.0',
|
16
15
|
'os' => 'os',
|
17
16
|
'os_version' => '22.0'
|
18
|
-
})
|
17
|
+
}))
|
19
18
|
|
20
19
|
allow(@scenario).to receive(:foo)
|
21
20
|
allow(@scenario).to receive(:save_screenshot)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shoot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Juan Lulkin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -136,6 +136,20 @@ dependencies:
|
|
136
136
|
- - "~>"
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '1.7'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: childprocess
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0.5'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0.5'
|
139
153
|
description: A helper to take shots on BrowserStack. Run the shoot binary for more
|
140
154
|
info.
|
141
155
|
email:
|
@@ -154,8 +168,13 @@ files:
|
|
154
168
|
- Rakefile
|
155
169
|
- bin/shoot
|
156
170
|
- lib/shoot.rb
|
171
|
+
- lib/shoot/browser.rb
|
157
172
|
- lib/shoot/cli.rb
|
173
|
+
- lib/shoot/ngrok.rb
|
174
|
+
- lib/shoot/ngrok_pow.rb
|
158
175
|
- lib/shoot/scenario.rb
|
176
|
+
- lib/shoot/scenario_runner.rb
|
177
|
+
- lib/shoot/ui.rb
|
159
178
|
- lib/shoot/version.rb
|
160
179
|
- shoot.gemspec
|
161
180
|
- spec/cli_spec.rb
|