browser_shooter 0.2.3 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +78 -28
- data/Rakefile +1 -1
- data/bin/browser_shooter +4 -8
- data/browser_shoter.gemspec +1 -2
- data/examples/config0.yml +35 -0
- data/examples/config1.yml +24 -21
- data/examples/config2.yml +48 -28
- data/lib/browser_shooter.rb +10 -34
- data/lib/browser_shooter/argv_parser.rb +54 -0
- data/lib/browser_shooter/base.rb +65 -0
- data/lib/browser_shooter/commander.rb +109 -107
- data/lib/browser_shooter/configurator.rb +77 -6
- data/lib/browser_shooter/log_exporter.rb +11 -17
- data/lib/browser_shooter/logger.rb +20 -3
- data/lib/browser_shooter/models/browser.rb +6 -0
- data/lib/browser_shooter/models/suite.rb +6 -0
- data/lib/browser_shooter/models/test.rb +6 -0
- data/lib/browser_shooter/version.rb +2 -2
- data/test/argv_parser_test.rb +15 -0
- data/test/base_test.rb +51 -0
- data/test/commander_test.rb +45 -68
- data/test/configurator_test.rb +69 -3
- data/test/fixtures/config.yml +30 -21
- data/test/fixtures/config_simple.yml +3 -0
- data/test/fixtures/logs/{script1.csv → log.csv} +1 -1
- data/test/log_exporter_test.rb +23 -41
- data/test/logger_test.rb +51 -0
- data/test/models/browser_test.rb +10 -0
- data/test/models/suite_test.rb +10 -0
- data/test/models/test_test.rb +9 -0
- metadata +33 -35
- data/lib/browser_shooter/driver.rb +0 -33
- data/test/browser_shooter_test.rb +0 -23
- data/test/driver_test.rb +0 -34
- data/test/fixtures/logs/logs.json +0 -24
- data/test/fixtures/logs/script2.csv +0 -2
@@ -0,0 +1,65 @@
|
|
1
|
+
module BrowserShooter
|
2
|
+
class Base
|
3
|
+
attr_reader :opts
|
4
|
+
|
5
|
+
def initialize( opts )
|
6
|
+
@opts = opts
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
BrowserShooter::Logger.verbose = opts[:verbose]
|
11
|
+
BrowserShooter::Logger.log( "Starting script running with version #{BrowserShooter::VERSION}..." )
|
12
|
+
|
13
|
+
config = BrowserShooter::Configurator.new( opts )
|
14
|
+
suites = config.suites
|
15
|
+
|
16
|
+
suites.each do |suite|
|
17
|
+
suite.tests.each do |test|
|
18
|
+
suite.browsers.each do |browser|
|
19
|
+
BrowserShooter::Base.run_test(
|
20
|
+
suite,
|
21
|
+
test,
|
22
|
+
browser,
|
23
|
+
config["output_path"]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
BrowserShooter::Logger.log( "... script running ended." )
|
30
|
+
BrowserShooter::Logger.log( "Logs and Shots are in: #{config["output_path"]}", true )
|
31
|
+
BrowserShooter::Logger.log( "BYE!" )
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.run_test( suite, test, browser, output_path )
|
35
|
+
BrowserShooter::Logger.log( "Executing #{suite.name} | #{test.name} | #{browser.name}", true )
|
36
|
+
output_path = "#{output_path}/#{suite.name}/#{test.name}/#{browser.name}"
|
37
|
+
|
38
|
+
driver = nil
|
39
|
+
|
40
|
+
begin
|
41
|
+
driver =
|
42
|
+
Selenium::WebDriver.for(
|
43
|
+
:remote,
|
44
|
+
:url => browser.url,
|
45
|
+
:desired_capabilities => browser.type.to_sym
|
46
|
+
)
|
47
|
+
|
48
|
+
logs =
|
49
|
+
BrowserShooter::Commander.script(
|
50
|
+
test.commands,
|
51
|
+
driver,
|
52
|
+
output_path
|
53
|
+
)
|
54
|
+
|
55
|
+
BrowserShooter::LogExporter.export(
|
56
|
+
logs,
|
57
|
+
"#{output_path}/logs"
|
58
|
+
)
|
59
|
+
|
60
|
+
ensure
|
61
|
+
driver.quit if driver
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -1,141 +1,143 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
sufix
|
13
|
-
)
|
1
|
+
module BrowserShooter::Commander
|
2
|
+
|
3
|
+
def self.script( commands, driver, output_path )
|
4
|
+
test_result =
|
5
|
+
commands.map do |command|
|
6
|
+
command_result =
|
7
|
+
BrowserShooter::Commander.wrapper_execute(
|
8
|
+
command.strip,
|
9
|
+
driver,
|
10
|
+
output_path
|
11
|
+
)
|
14
12
|
|
15
|
-
|
16
|
-
sufix = command.split[1] ? command.split[1].strip : nil
|
13
|
+
BrowserShooter::Logger.command_result( command_result )
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
shots_path,
|
21
|
-
sufix
|
22
|
-
)
|
15
|
+
command_result
|
16
|
+
end
|
23
17
|
|
24
|
-
|
25
|
-
BrowserShooter::Commander.pause( command.split[1].strip.to_i )
|
18
|
+
BrowserShooter::Logger.test_result( test_result )
|
26
19
|
|
27
|
-
|
28
|
-
|
20
|
+
test_result
|
21
|
+
end
|
29
22
|
|
30
|
-
|
31
|
-
|
32
|
-
params[1],
|
33
|
-
params[2].to_i
|
34
|
-
)
|
23
|
+
def self.execute( command, driver, output_path )
|
24
|
+
BrowserShooter::Logger.log "command: #{command}"
|
35
25
|
|
36
|
-
|
37
|
-
|
26
|
+
if( command.split[0].strip == "shot" )
|
27
|
+
sufix = command.split[1] ? command.split[1].strip : nil
|
38
28
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
29
|
+
BrowserShooter::Commander.shot(
|
30
|
+
driver,
|
31
|
+
output_path,
|
32
|
+
sufix
|
33
|
+
)
|
44
34
|
|
45
|
-
|
46
|
-
|
47
|
-
BrowserShooter::Commander.click(
|
48
|
-
client,
|
49
|
-
params[1]
|
50
|
-
)
|
35
|
+
elsif( command.split[0].strip == "pause" )
|
36
|
+
BrowserShooter::Commander.pause( command.split[1].strip.to_i )
|
51
37
|
|
52
|
-
|
53
|
-
|
38
|
+
elsif( command.split[0].strip == "wait_for_element" )
|
39
|
+
params = command.match /wait_for_element "(.*)"\s?,\s?(\d*)/
|
54
40
|
|
55
|
-
|
56
|
-
|
41
|
+
BrowserShooter::Commander.wait_for_element(
|
42
|
+
driver,
|
43
|
+
params[1],
|
44
|
+
params[2].to_i
|
45
|
+
)
|
57
46
|
|
58
|
-
|
59
|
-
|
60
|
-
:time => Time.now.to_i,
|
61
|
-
:command => command
|
62
|
-
}
|
63
|
-
|
64
|
-
begin
|
65
|
-
message =
|
66
|
-
BrowserShooter::Commander.execute(
|
67
|
-
command,
|
68
|
-
client,
|
69
|
-
shots_path
|
70
|
-
)
|
47
|
+
elsif( command.split[0].strip == "type" )
|
48
|
+
params = command.match /type "(.*)"\s?,\s?"(.*)"/
|
71
49
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
50
|
+
BrowserShooter::Commander.type(
|
51
|
+
driver,
|
52
|
+
params[1],
|
53
|
+
params[2]
|
54
|
+
)
|
76
55
|
|
77
|
-
|
78
|
-
|
56
|
+
elsif( command.split[0].strip == "click" )
|
57
|
+
params = command.match /click "(.*)"/
|
58
|
+
BrowserShooter::Commander.click(
|
59
|
+
driver,
|
60
|
+
params[1]
|
61
|
+
)
|
62
|
+
|
63
|
+
else
|
64
|
+
eval "driver.#{command}"
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
79
68
|
|
80
|
-
|
81
|
-
|
82
|
-
|
69
|
+
def self.wrapper_execute( command, driver, output_path )
|
70
|
+
result = {
|
71
|
+
:time => Time.now.to_i,
|
72
|
+
:command => command
|
73
|
+
}
|
74
|
+
|
75
|
+
begin
|
76
|
+
message =
|
77
|
+
BrowserShooter::Commander.execute(
|
78
|
+
command,
|
79
|
+
driver,
|
80
|
+
output_path
|
83
81
|
)
|
84
82
|
|
85
|
-
end
|
86
83
|
|
87
|
-
|
88
|
-
|
84
|
+
result.merge!(
|
85
|
+
:success => true,
|
86
|
+
:message => message
|
87
|
+
)
|
89
88
|
|
90
|
-
|
91
|
-
|
92
|
-
path = "#{path}_#{sufix}.png"
|
89
|
+
rescue Exception => e
|
90
|
+
BrowserShooter::Logger.log "ERROR: #{e.message}"
|
93
91
|
|
94
|
-
|
95
|
-
|
92
|
+
# puts "XXX: Exception"
|
93
|
+
# puts e.backtrace.join( "\n" )
|
94
|
+
|
95
|
+
result.merge!(
|
96
|
+
:success => false,
|
97
|
+
:message => e.message
|
98
|
+
)
|
96
99
|
|
97
|
-
return path
|
98
100
|
end
|
99
101
|
|
100
|
-
|
101
|
-
|
102
|
-
# sufix = timestamp unless sufix
|
103
|
-
# path = "#{path}_#{sufix}.system.png"
|
102
|
+
return result
|
103
|
+
end
|
104
104
|
|
105
|
-
|
105
|
+
def self.shot( driver, output_path, sufix = nil )
|
106
|
+
sufix = timestamp unless sufix
|
107
|
+
shot_path = "#{output_path}/shots/#{sufix}.png"
|
106
108
|
|
107
|
-
|
108
|
-
# f.write( Base64.decode64( client.capture_screenshot_to_string ) )
|
109
|
-
# end
|
109
|
+
BrowserShooter::Logger.log "shooting in '#{shot_path}'"
|
110
110
|
|
111
|
-
|
112
|
-
|
111
|
+
FileUtils.mkdir_p( File.dirname( shot_path ) )
|
112
|
+
driver.save_screenshot( shot_path )
|
113
113
|
|
114
|
-
|
115
|
-
|
114
|
+
return shot_path
|
115
|
+
end
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
end
|
120
|
-
end
|
117
|
+
def self.wait_for_element( driver, css_selector, timeout )
|
118
|
+
wait = Selenium::WebDriver::Wait.new( :timeout => timeout )
|
121
119
|
|
122
|
-
|
123
|
-
|
120
|
+
wait.until do
|
121
|
+
driver.find_element( "css", css_selector )
|
124
122
|
end
|
123
|
+
end
|
125
124
|
|
126
|
-
|
127
|
-
|
128
|
-
|
125
|
+
def self.click( driver, css_selector )
|
126
|
+
driver.find_element( "css", css_selector ).click
|
127
|
+
end
|
129
128
|
|
130
|
-
|
131
|
-
|
132
|
-
|
129
|
+
def self.type( driver, css_selector, text )
|
130
|
+
driver.find_element( "css", css_selector ).send_keys( text )
|
131
|
+
end
|
133
132
|
|
134
|
-
|
135
|
-
|
133
|
+
def self.pause( seconds )
|
134
|
+
BrowserShooter::Logger.log "pausing #{seconds} seconds"
|
135
|
+
Kernel.sleep seconds
|
136
136
|
|
137
|
-
|
138
|
-
|
139
|
-
|
137
|
+
return "#{seconds} seconds later..."
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.timestamp
|
141
|
+
Time.now.to_i
|
140
142
|
end
|
141
|
-
end
|
143
|
+
end
|
@@ -1,5 +1,80 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module BrowserShooter
|
2
|
+
class Configurator
|
3
|
+
attr_reader :suites
|
4
|
+
|
5
|
+
def initialize( opts )
|
6
|
+
@config = BrowserShooter::Configurator.load_config( opts[:config_file] )
|
7
|
+
models = BrowserShooter::Configurator.build_models( @config )
|
8
|
+
@suites = BrowserShooter::Configurator.filter_suites( models, opts )
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](value)
|
12
|
+
@config[value]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.filter_suites( models, opts )
|
16
|
+
suites = []
|
17
|
+
|
18
|
+
if( opts[:suite] )
|
19
|
+
suite = models[:suites].select{ |e| e.name == opts[:suite] }.first
|
20
|
+
raise ArgumentError, "Not suite found '#{opts[:suite]}'" if suite.nil?
|
21
|
+
|
22
|
+
suites = [suite]
|
23
|
+
|
24
|
+
elsif( opts[:test] && opts[:browsers] )
|
25
|
+
test = models[:tests].select{ |e| e.name == opts[:test] }.first
|
26
|
+
raise ArgumentError, "Not test found '#{opts[:test]}'" if test.nil?
|
27
|
+
|
28
|
+
browsers = models[:browsers].select{ |e| opts[:browsers].include? e.name }
|
29
|
+
raise ArgumentError, "Not browsers found '#{opts[:browsers].join( "," )}'" if browsers.empty?
|
30
|
+
|
31
|
+
suite = BrowserShooter::Models::Suite.new( "anonymous", [test], browsers )
|
32
|
+
suites = [suite]
|
33
|
+
|
34
|
+
elsif( opts[:test] )
|
35
|
+
test = models[:tests].select{ |e| e.name == opts[:test] }.first
|
36
|
+
raise ArgumentError, "Not test found '#{opts[:test]}'" if test.nil?
|
37
|
+
|
38
|
+
browsers = models[:browsers]
|
39
|
+
suite = BrowserShooter::Models::Suite.new( "anonymous", [test], browsers )
|
40
|
+
suites = [suite]
|
41
|
+
|
42
|
+
else
|
43
|
+
suites = models[:suites]
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
suites
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.build_models( config )
|
51
|
+
tests =
|
52
|
+
config["tests"].map do |name, commands|
|
53
|
+
test_commands = commands.split( "\n" )
|
54
|
+
|
55
|
+
BrowserShooter::Models::Test.new( name, test_commands )
|
56
|
+
end
|
57
|
+
|
58
|
+
browsers =
|
59
|
+
config["browsers"].map do |name, opts|
|
60
|
+
BrowserShooter::Models::Browser.new( name, opts["url"], opts["type"] )
|
61
|
+
end
|
62
|
+
|
63
|
+
suites =
|
64
|
+
config["suites"].map do |name, opts|
|
65
|
+
suite_tests = tests.select{ |e| opts["tests"].include? e.name }
|
66
|
+
suite_browsers = browsers.select{ |e| opts["browsers"].include? e.name }
|
67
|
+
|
68
|
+
BrowserShooter::Models::Suite.new( name, suite_tests, suite_browsers )
|
69
|
+
end
|
70
|
+
|
71
|
+
{
|
72
|
+
:tests => tests,
|
73
|
+
:browsers => browsers,
|
74
|
+
:suites => suites
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
3
78
|
def self.load_config( config_file_path )
|
4
79
|
config = {
|
5
80
|
"output_path" => "~/browser_shooter",
|
@@ -17,10 +92,6 @@ class BrowserShooter
|
|
17
92
|
output_path = File.expand_path( "#{output_path}/#{timestamp}" )
|
18
93
|
BrowserShooter::Logger.log( "output_path: #{output_path}" )
|
19
94
|
|
20
|
-
FileUtils.mkdir_p( output_path )
|
21
|
-
FileUtils.mkdir( "#{output_path}/shots" )
|
22
|
-
FileUtils.mkdir( "#{output_path}/logs" )
|
23
|
-
|
24
95
|
output_path
|
25
96
|
end
|
26
97
|
|
@@ -1,26 +1,20 @@
|
|
1
|
-
|
1
|
+
module BrowserShooter
|
2
2
|
module LogExporter
|
3
|
-
def self.export( logs,
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
def self.export( logs, logs_path, format = "csv" )
|
4
|
+
logs_path = File.expand_path( "#{logs_path}/log.#{format}" )
|
5
|
+
BrowserShooter::Logger.log "Exporting '#{format}' logs to #{logs_path}"
|
6
|
+
FileUtils.mkdir_p( File.dirname( logs_path ) )
|
7
7
|
|
8
|
-
|
9
|
-
File.open( "#{path}/logs.json", "w" ) do |f|
|
10
|
-
f.write JSON.pretty_generate( logs )
|
11
|
-
end
|
8
|
+
send(:"export_to_#{format}", logs, logs_path )
|
12
9
|
end
|
13
10
|
|
14
11
|
def self.export_to_csv( logs, path )
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
File.open( _path, "w" ) do |f|
|
19
|
-
f.puts results.first.keys.join( " | " )
|
12
|
+
File.open( path, "w" ) do |f|
|
13
|
+
f.puts "time | success | command | message"
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
logs.each do |result|
|
16
|
+
line = "#{result[:time]} | #{result[:success]} | #{result[:command]} | #{result[:message]}".gsub( "\n", " - " )
|
17
|
+
f.puts line
|
24
18
|
end
|
25
19
|
end
|
26
20
|
end
|
@@ -1,7 +1,24 @@
|
|
1
|
-
|
1
|
+
module BrowserShooter
|
2
2
|
module Logger
|
3
|
-
|
4
|
-
|
3
|
+
extend self
|
4
|
+
|
5
|
+
attr_accessor :verbose
|
6
|
+
|
7
|
+
def log( message, force = verbose )
|
8
|
+
if force
|
9
|
+
Kernel.puts "[BrowserShooter #{Time.now.strftime( "%F %T" )}] #{message}"
|
10
|
+
end
|
5
11
|
end
|
12
|
+
|
13
|
+
def command_result( command_result )
|
14
|
+
Kernel.print "." if command_result[:success]
|
15
|
+
Kernel.print "F" if !command_result[:success]
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_result( test_result )
|
19
|
+
Kernel.puts " (success)" if test_result.all? { |e| e[:success] }
|
20
|
+
Kernel.puts " (fail)" if !test_result.all? { |e| e[:success] }
|
21
|
+
end
|
22
|
+
|
6
23
|
end
|
7
24
|
end
|