selenium_shots 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -46
- data/bin/selenium_shots_local_server +54 -0
- data/lib/selenium_shots/cli/client.rb +7 -2
- data/lib/selenium_shots/cli/command.rb +42 -44
- data/lib/selenium_shots/cli/commands/app.rb +14 -7
- data/lib/selenium_shots/cli/commands/auth.rb +117 -117
- data/lib/selenium_shots/cli/commands/base.rb +37 -36
- data/lib/selenium_shots/cli/commands/help.rb +10 -11
- data/lib/selenium_shots/cli/init.rb +2 -2
- data/lib/selenium_shots/test_selenium_shots.rb +52 -102
- data/vendor/selenium-server-1.0.2-SNAPSHOT-standalone.jar +0 -0
- metadata +10 -43
- data/examples/google.rb +0 -14
- data/spec/base.rb +0 -14
- data/spec/commands/app_spec.rb +0 -26
- data/spec/commands/auth_spec.rb +0 -83
- data/spec/commands/base_spec.rb +0 -38
- data/spec/commands/server_spec.rb +0 -22
data/README.rdoc
CHANGED
@@ -3,52 +3,15 @@
|
|
3
3
|
http://www.seleniumshots.com
|
4
4
|
Selenium Shots is an Integration Testing Service that transparently distributes your integration tests across multiple operating systems with different versions of all major browsers AND captures a screen shot. This eliminates the need to have multiple vm's on your computer or the need for multiple machines on your test to test your web application. Running your tests remotely will dramatically speed up in-browser web testing and leave more time to and create a slide show available to confirm visuals making it easy for you to improve your web application.
|
5
5
|
|
6
|
-
==
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Add
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
=== Rails 2
|
17
|
-
|
18
|
-
To install add teh following to config/environment.rb:
|
19
|
-
|
20
|
-
config.gem 'selenium_shots'
|
21
|
-
|
22
|
-
== Configure
|
23
|
-
|
24
|
-
In configure/selenium_shots.yml you will need to define the application
|
25
|
-
* api_key
|
26
|
-
* mode
|
27
|
-
* default_browser_url
|
28
|
-
* application_name
|
29
|
-
* local_browser
|
30
|
-
* browsers
|
31
|
-
|
32
|
-
== Creating Tests
|
33
|
-
|
34
|
-
|
35
|
-
class MyTest < SeleniumShots
|
36
|
-
|
37
|
-
@group = "MyTestGroup"
|
38
|
-
|
39
|
-
selenium_shot "Should run my test and pass." do
|
40
|
-
@name = "My Test Name"
|
41
|
-
browser.open "/my_site"
|
42
|
-
browser.type "search[query]", "Cats"
|
43
|
-
browser.click "find"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
== Selenium Setup
|
48
|
-
Download the latest version of selenium grid from http://seleniumhq.org/download/
|
49
|
-
|
50
|
-
===Using a Custom Selenium Server
|
51
|
-
`ant launch-remote-control -DcustomRemoteControl=/path/to/your/customer/selenium-server.jar`
|
6
|
+
== Note on Patches/Pull Requests
|
7
|
+
|
8
|
+
* Fork the project.
|
9
|
+
* Make your feature addition or bug fix.
|
10
|
+
* Add tests for it. This is important so I don't break it in a
|
11
|
+
future version unintentionally.
|
12
|
+
* Commit, do not mess with rakefile, version, or history.
|
13
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
14
|
+
* Send me a pull request. Bonus points for topic branches.
|
52
15
|
|
53
16
|
== Copyright
|
54
17
|
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
GEM_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
7
|
+
SELENIUM_SERVER = File.join(GEM_ROOT, 'vendor', 'selenium-server-1.0.2-SNAPSHOT-standalone.jar')
|
8
|
+
|
9
|
+
|
10
|
+
module SeleniumShots
|
11
|
+
module Server
|
12
|
+
class << self
|
13
|
+
def pid_file
|
14
|
+
'/tmp/selenium_shots.pid'
|
15
|
+
end
|
16
|
+
|
17
|
+
def start_process
|
18
|
+
if File.exists?(pid_file)
|
19
|
+
puts "the selenium shots server is running...."
|
20
|
+
else
|
21
|
+
pipe = IO.popen("java -jar #{SELENIUM_SERVER}")
|
22
|
+
File.open(pid_file, 'w') {|f| f.write(pipe.pid) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop_process
|
27
|
+
if File.exists?(pid_file)
|
28
|
+
process_id = File.open(pid_file,'r').readline
|
29
|
+
Process.kill 9, process_id.to_i
|
30
|
+
FileUtils.rm(pid_file)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
help = <<EOF
|
38
|
+
selenium_shots_local_server {start|stop}
|
39
|
+
EOF
|
40
|
+
|
41
|
+
args = ARGV.dup
|
42
|
+
ARGV.clear
|
43
|
+
|
44
|
+
command = args.shift.strip rescue help
|
45
|
+
|
46
|
+
case command
|
47
|
+
when "start"
|
48
|
+
SeleniumShots::Server.start_process
|
49
|
+
when "stop":
|
50
|
+
SeleniumShots::Server.stop_process
|
51
|
+
else
|
52
|
+
puts help
|
53
|
+
end
|
54
|
+
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'rest_client'
|
2
|
+
#require 'rest_client'
|
3
3
|
require 'uri'
|
4
4
|
require 'time'
|
5
5
|
|
@@ -7,11 +7,16 @@ class SeleniumShots::Client
|
|
7
7
|
|
8
8
|
attr_reader :host, :api_key
|
9
9
|
|
10
|
-
def initialize(api_key, host='
|
10
|
+
def initialize(api_key, host='seleniumshots.heroku.com')
|
11
11
|
@api_key = api_key
|
12
12
|
@host = host
|
13
13
|
end
|
14
14
|
|
15
|
+
def list
|
16
|
+
#get list app from selenium_shots
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
|
15
20
|
############
|
16
21
|
def resource(uri)
|
17
22
|
RestClient::Resource.new("http://#{host}", api_key)[uri]
|
@@ -1,52 +1,50 @@
|
|
1
1
|
module SeleniumShots
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
module Command
|
3
|
+
class InvalidCommand < RuntimeError; end
|
4
|
+
class CommandFailed < RuntimeError; end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
display "Authentication failure. For more information you can go to http://www.seleniumshots.com"
|
13
|
-
end
|
6
|
+
class << self
|
7
|
+
def run(command, args)
|
8
|
+
run_internal(command, args)
|
9
|
+
rescue InvalidCommand
|
10
|
+
display "Unknown command. Run 'selenium_shots help' for usage information."
|
11
|
+
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
13
|
+
def run_internal(command, args)
|
14
|
+
namespace, command = parse(command)
|
15
|
+
require "#{namespace}"
|
16
|
+
klass = SeleniumShots::Command.const_get(namespace.capitalize).new(args)
|
17
|
+
raise InvalidCommand unless klass.respond_to?(command)
|
18
|
+
klass.send(command)
|
19
|
+
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
def display(msg)
|
22
|
+
puts(msg)
|
23
|
+
end
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
25
|
+
def parse(command)
|
26
|
+
parts = command.split(':')
|
27
|
+
case parts.size
|
28
|
+
when 1
|
29
|
+
if namespaces.include? command
|
30
|
+
return command, 'index'
|
31
|
+
else
|
32
|
+
return 'app', command
|
33
|
+
end
|
34
|
+
when 2
|
35
|
+
raise InvalidCommand unless namespaces.include? parts[0]
|
36
|
+
return parts
|
37
|
+
else
|
38
|
+
raise InvalidCommand
|
39
|
+
end
|
40
|
+
end
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
42
|
+
def namespaces
|
43
|
+
@@namespaces ||= Dir["#{File.dirname(__FILE__)}/commands/*"].map do |namespace|
|
44
|
+
namespace.gsub(/.*\//, '').gsub(/\.rb/, '')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
51
49
|
end
|
52
50
|
|
@@ -1,16 +1,23 @@
|
|
1
1
|
module SeleniumShots::Command
|
2
|
-
|
2
|
+
class App < Base
|
3
3
|
def create
|
4
|
-
|
4
|
+
name = args.shift.downcase.strip rescue nil
|
5
5
|
if name
|
6
|
-
|
7
|
-
|
8
|
-
display "Created #{name}\nYou can configurate selenium shots on config/selenium_shots.yml"
|
9
|
-
end
|
6
|
+
api_key ||= SeleniumShots::Command.run_internal('auth:api_key', args)
|
7
|
+
display "Created #{name}" if make_config_file(name, api_key) == "y"
|
10
8
|
else
|
11
9
|
display "You need specify a name for your app. Run 'selenium_shots help' for usage information"
|
12
10
|
end
|
13
11
|
end
|
14
|
-
|
12
|
+
|
13
|
+
def list
|
14
|
+
list = selenium_shots.list
|
15
|
+
if list.size > 0
|
16
|
+
display list.join("\n")
|
17
|
+
else
|
18
|
+
display "You have no apps."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
@@ -1,127 +1,127 @@
|
|
1
1
|
module SeleniumShots::Command
|
2
|
-
|
3
|
-
|
2
|
+
class Auth < Base
|
3
|
+
attr_accessor :api_key_hash
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
def client
|
6
|
+
@client ||= init_selenium_shots
|
7
|
+
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
def init_selenium_shots
|
10
|
+
SeleniumShots::Client.new(api_key)
|
11
|
+
end
|
12
12
|
|
13
|
-
|
13
|
+
def api_key
|
14
14
|
get_api_key
|
15
|
-
|
15
|
+
end
|
16
16
|
|
17
17
|
def get_api_key_from_host
|
18
|
-
RestClient.post 'http://
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
18
|
+
RestClient.post 'http://seleniumshots.heroku.com/selenium_tests/get_api_key', :user_session => { :login => @api_key_hash[0],
|
19
|
+
:password => @api_key_hash[1]}
|
20
|
+
end
|
21
|
+
|
22
|
+
def api_key_file
|
23
|
+
"#{home_directory}/.selenium_shots/api_key"
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_api_key
|
27
|
+
return if @api_key_hash
|
28
|
+
unless @api_key_hash = read_api_key
|
29
|
+
@api_key_hash = ask_for_api_key
|
30
|
+
save_api_key
|
31
|
+
end
|
32
|
+
@api_key_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def read_api_key
|
36
|
+
if File.exists? api_key_file
|
37
|
+
return File.read(api_key_file).split("\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def echo_off
|
42
|
+
system "stty -echo"
|
43
|
+
end
|
44
|
+
|
45
|
+
def echo_on
|
46
|
+
system "stty echo"
|
47
|
+
end
|
48
|
+
|
49
|
+
def ask_for_api_key
|
50
|
+
puts "Enter your SeleniumShots Account"
|
51
|
+
|
52
|
+
print "Login: "
|
53
|
+
user = ask
|
54
|
+
|
55
|
+
print "Password: "
|
56
|
+
password = running_on_windows? ? ask_for_password_on_windows : ask_for_password
|
57
|
+
|
58
|
+
[ user, password ]
|
59
|
+
end
|
60
|
+
|
61
|
+
def ask_for_password_on_windows
|
62
|
+
require "Win32API"
|
63
|
+
char = nil
|
64
|
+
password = ''
|
65
|
+
|
66
|
+
while char = Win32API.new("crtdll", "_getch", [ ], "L").Call do
|
67
|
+
break if char == 10 || char == 13 # received carriage return or newline
|
68
|
+
if char == 127 || char == 8 # backspace and delete
|
69
|
+
password.slice!(-1, 1)
|
70
|
+
else
|
71
|
+
password << char.chr
|
72
|
+
end
|
73
|
+
end
|
74
|
+
puts
|
75
|
+
return password
|
76
|
+
end
|
77
|
+
|
78
|
+
def ask_for_password
|
79
|
+
echo_off
|
80
|
+
password = ask
|
81
|
+
puts
|
82
|
+
echo_on
|
83
|
+
return password
|
84
|
+
end
|
85
|
+
|
86
|
+
def save_api_key
|
87
|
+
begin
|
88
88
|
@api_key_hash = get_api_key_from_host
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
89
|
+
write_api_key
|
90
|
+
rescue RestClient::Unauthorized => e
|
91
|
+
delete_api_key
|
92
|
+
raise e unless retry_login?
|
93
|
+
display "\nAuthentication failed"
|
94
|
+
@api_key_hash = ask_for_api_key
|
95
|
+
@client = init_selenium_shots
|
96
|
+
retry
|
97
|
+
rescue Exception => e
|
98
|
+
delete_api_key
|
99
|
+
raise e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def retry_login?
|
104
|
+
@login_attempts ||= 0
|
105
|
+
@login_attempts += 1
|
106
|
+
@login_attempts < 3
|
107
|
+
end
|
108
|
+
|
109
|
+
def write_api_key
|
110
|
+
FileUtils.mkdir_p(File.dirname(api_key_file))
|
111
|
+
File.open(api_key_file, 'w') do |f|
|
112
|
+
f.puts self.api_key_hash
|
113
|
+
end
|
114
|
+
set_api_key_permissions
|
115
|
+
end
|
116
|
+
|
117
|
+
def set_api_key_permissions
|
118
|
+
FileUtils.chmod 0700, File.dirname(api_key_file)
|
119
|
+
FileUtils.chmod 0600, api_key_file
|
120
|
+
end
|
121
|
+
|
122
|
+
def delete_api_key
|
123
|
+
FileUtils.rm_f(api_key_file)
|
124
|
+
end
|
125
|
+
end
|
126
126
|
end
|
127
127
|
|
@@ -1,39 +1,35 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
3
|
module SeleniumShots::Command
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def selenium_shots
|
11
|
-
@selenium_shots ||= SeleniumShots::Command.run_internal('auth:client', args)
|
12
|
-
end
|
4
|
+
class Base
|
5
|
+
attr_accessor :args
|
6
|
+
def initialize(args)
|
7
|
+
@args = args
|
8
|
+
end
|
13
9
|
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def selenium_shots
|
11
|
+
@selenium_shots ||= SeleniumShots::Command.run_internal('auth:client', args)
|
12
|
+
end
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
|
14
|
+
def display(msg, newline=true)
|
15
|
+
newline ? puts(msg) : print(msg)
|
16
|
+
end
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
|
18
|
+
def ask
|
19
|
+
gets.strip
|
20
|
+
end
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
def shell(cmd)
|
23
|
+
`#{cmd}`
|
24
|
+
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
26
|
+
def home_directory
|
27
|
+
running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
|
28
|
+
end
|
33
29
|
|
34
|
-
|
35
|
-
|
36
|
-
|
30
|
+
def running_on_windows?
|
31
|
+
RUBY_PLATFORM =~ /mswin32/
|
32
|
+
end
|
37
33
|
|
38
34
|
def config_file
|
39
35
|
'config/selenium_shots.yml'
|
@@ -42,8 +38,8 @@ module SeleniumShots::Command
|
|
42
38
|
|
43
39
|
def ask_for_config_file
|
44
40
|
if File.exists?(config_file)
|
45
|
-
|
46
|
-
|
41
|
+
print "The config file exists, do you want overwrite this? (y/n): "
|
42
|
+
ask
|
47
43
|
else
|
48
44
|
"y"
|
49
45
|
end
|
@@ -53,23 +49,28 @@ module SeleniumShots::Command
|
|
53
49
|
overwrite_or_create_file = ask_for_config_file
|
54
50
|
if overwrite_or_create_file == "y"
|
55
51
|
config_file_hash = <<EOFILE
|
52
|
+
#remote way
|
56
53
|
api_key: "#{api_key}"
|
57
54
|
mode: "remote" # "local" for run test locally
|
58
55
|
default_browser_url: "http://www.myapp.com"
|
59
56
|
application_name: "#{name}"
|
60
|
-
local_browser: "firefox"
|
61
57
|
browsers:
|
62
|
-
- IE8 on XP
|
63
|
-
- Firefox3.6 on XP
|
58
|
+
- IE8 on XP #browser for remote way
|
59
|
+
- Firefox3.6 on XP #browser for remote way
|
60
|
+
# - "*firefox3" #browser for local way
|
64
61
|
EOFILE
|
65
|
-
|
66
|
-
|
67
|
-
|
62
|
+
File.open(config_file, 'w') do |f|
|
63
|
+
f.puts config_file_hash
|
64
|
+
end
|
68
65
|
end
|
69
66
|
overwrite_or_create_file
|
70
67
|
end
|
71
68
|
|
72
|
-
|
69
|
+
def inside_rails_app?
|
70
|
+
File.exists?('config/environment.rb')
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
73
74
|
|
74
75
|
end
|
75
76
|
|
@@ -1,16 +1,15 @@
|
|
1
1
|
module SeleniumShots::Command
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
class Help < Base
|
3
|
+
def index
|
4
|
+
display usage
|
5
|
+
end
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
def usage
|
8
|
+
usage = <<EOTXT
|
9
9
|
=== General Commands
|
10
10
|
|
11
|
-
help
|
12
|
-
create [name]
|
13
|
-
|
11
|
+
help # show this usage
|
12
|
+
create [name] # create file config for your app
|
14
13
|
=== Example story:
|
15
14
|
|
16
15
|
rails myapp
|
@@ -18,7 +17,7 @@ module SeleniumShots::Command
|
|
18
17
|
(...make edits...)
|
19
18
|
selenium_shots create example_one
|
20
19
|
EOTXT
|
21
|
-
|
22
|
-
|
20
|
+
end
|
21
|
+
end
|
23
22
|
end
|
24
23
|
|
@@ -1,26 +1,25 @@
|
|
1
1
|
require "test/unit"
|
2
2
|
require "rubygems"
|
3
3
|
require "selenium/client"
|
4
|
-
require "selenium-webdriver"
|
5
4
|
require 'active_support'
|
6
5
|
require 'active_support/test_case'
|
7
6
|
require 'ostruct'
|
8
7
|
|
9
|
-
#load config
|
10
|
-
ASSET_ROOT = File.expand_path((defined?(Rails) && Rails.root.to_s.length > 0) ? Rails.root : ".") unless defined?(ASSET_ROOT)
|
11
8
|
|
12
|
-
|
9
|
+
#load config
|
10
|
+
SeleniumConfig = OpenStruct.new(YAML.load_file("#{RAILS_ROOT}/config/selenium_shots.yml"))
|
13
11
|
#
|
12
|
+
ENV["RAILS_ENV"] = "test"
|
14
13
|
|
15
14
|
#activeresource models
|
16
15
|
class SeleniumTest < ActiveResource::Base
|
17
|
-
self.site = "http://seleniumshots.com"
|
16
|
+
self.site = "http://seleniumshots.heroku.com"
|
18
17
|
self.user = SeleniumConfig.api_key
|
19
18
|
end
|
20
19
|
|
21
20
|
class SeleniumShots < ActionController::IntegrationTest
|
22
21
|
|
23
|
-
attr_reader :
|
22
|
+
attr_reader :browser, :agent
|
24
23
|
cattr_accessor :expected_test_count
|
25
24
|
|
26
25
|
if SeleniumConfig.mode == "remote"
|
@@ -38,35 +37,36 @@ class SeleniumShots < ActionController::IntegrationTest
|
|
38
37
|
"/tmp/selenium_shots.pid"
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
42
|
-
|
40
|
+
def setup
|
41
|
+
if(not self.class.expected_test_count)
|
42
|
+
self.class.expected_test_count = (self.class.instance_methods.reject{|method| method[0..3] != 'test'}).length
|
43
|
+
if !File.exists?(pid_file) && SeleniumConfig.mode == "local"
|
44
|
+
IO.popen("selenium_shots_local_server start 2>&1")
|
45
|
+
sleep(2)
|
46
|
+
end
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
|
-
def
|
46
|
-
if
|
47
|
-
SeleniumConfig.
|
48
|
-
|
49
|
-
|
50
|
-
[SeleniumConfig.local_browser]
|
51
|
-
else
|
52
|
-
[local_browsers.first]
|
53
|
-
end
|
50
|
+
def teardown
|
51
|
+
if((self.class.expected_test_count-=1) == 0)
|
52
|
+
if File.exists?(pid_file) && SeleniumConfig.mode == "local"
|
53
|
+
IO.popen("selenium_shots_local_server stop 2>&1")
|
54
|
+
end
|
54
55
|
end
|
55
56
|
end
|
56
|
-
|
57
|
-
def self.
|
57
|
+
|
58
|
+
def self.selenium_shot(description, &block)
|
59
|
+
@@description = description
|
58
60
|
@@group = (@group || "Default")
|
59
61
|
test_name = "test_#{description.gsub(/\s+/,'_')}".to_sym
|
60
62
|
defined = instance_method(test_name) rescue false
|
61
63
|
raise "#{test_name} is already defined in #{self}" if defined
|
62
64
|
if block_given?
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
69
|
-
end
|
65
|
+
define_method(test_name) do
|
66
|
+
run_in_all_browsers do |browser|
|
67
|
+
instance_eval &block
|
68
|
+
end
|
69
|
+
end
|
70
70
|
else
|
71
71
|
define_method(test_name) do
|
72
72
|
flunk "No implementation provided for #{name}"
|
@@ -74,107 +74,57 @@ class SeleniumShots < ActionController::IntegrationTest
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
-
def self.selenium_test(description, &block)
|
78
|
-
core_test(description, nil, &block)
|
79
|
-
end
|
80
|
-
|
81
|
-
def self.selenium_shot(description, &block)
|
82
|
-
core_test(description, true, &block)
|
83
|
-
end
|
84
|
-
|
85
77
|
def run_in_all_browsers(&block)
|
86
|
-
|
87
|
-
browsers = (@selected_browser || selected_browsers)
|
88
|
-
browsers.each do |browser_spec|
|
78
|
+
SeleniumConfig.browsers.each do |browser_spec|
|
89
79
|
begin
|
90
|
-
|
91
|
-
|
92
|
-
|
80
|
+
run_browser(browser_spec, block)
|
81
|
+
@error = nil
|
82
|
+
rescue => error
|
93
83
|
@error = error.message
|
94
|
-
if @error.match(/Failed to start new browser session/) && SeleniumConfig.mode == "local"
|
95
|
-
@tmp_browsers ||= local_browsers
|
96
|
-
@tmp_browsers.delete(browser_spec)
|
97
|
-
@selected_browser = [@tmp_browsers.shift]
|
98
|
-
unless @selected_browser.empty?
|
99
|
-
puts "The browser #{browser_spec} is not available, selenium_shots going to try with #{@selected_browser} browser"
|
100
|
-
run_in_all_browsers(&block)
|
101
|
-
end
|
102
|
-
end
|
103
84
|
end
|
85
|
+
assert @error.nil?, "Expected zero failures or errors, but got #{@error}\n"
|
104
86
|
end
|
105
|
-
assert @error.nil?, "Expected zero failures or errors, but got #{@error}\n"
|
106
87
|
end
|
107
|
-
|
108
|
-
def run_webdriver(browser_spec, block)
|
109
|
-
|
110
|
-
client = Selenium::WebDriver::Remote::Http::Default.new
|
111
|
-
client.timeout = 20 # seconds
|
112
|
-
|
113
|
-
if SeleniumConfig.mode == "local"
|
114
|
-
if /(firefox)/i.match(browser_spec)
|
115
|
-
profile = Selenium::WebDriver::Firefox::Profile.new
|
116
|
-
profile.native_events = false
|
117
|
-
@driver = Selenium::WebDriver.for(:firefox, :profile => profile, :http_client => client)
|
118
|
-
elsif /(chrome)/i.match(browser_spec)
|
119
|
-
@driver = Selenium::WebDriver.for(:chrome, :http_client => client)
|
120
|
-
elsif /(ie)/i.match(browser_spec)
|
121
|
-
@driver = Selenium::WebDriver.for(:ie, :http_client => client)
|
122
|
-
end
|
123
|
-
else
|
124
|
-
caps = nil
|
125
|
-
if /(firefox)/i.match(browser_spec)
|
126
|
-
caps = WebDriver::Remote::Capabilities.firefox
|
127
|
-
elsif /(chrome)/i.match(browser_spec)
|
128
|
-
caps = WebDriver::Remote::Capabilities.chrome
|
129
|
-
elsif /(ie)/i.match(browser_spec)
|
130
|
-
caps = WebDriver::Remote::Capabilities.internet_explorer
|
131
|
-
elsif /(safari)/i.match(browser_spec)
|
132
|
-
caps = WebDriver::Remote::Capabilities.safari
|
133
|
-
elsif /(htmlunit)/i.match(browser_spec)
|
134
|
-
caps = WebDriver::Remote::Capabilities.htmlunit
|
135
|
-
caps.javascript_enabled = true
|
136
|
-
end
|
137
|
-
|
138
|
-
@driver = Selenium::WebDriver.for(:remote, :desired_capabilities => caps, :http_client => client) if caps
|
139
|
-
end
|
140
|
-
|
141
|
-
@driver.manage.timeouts.implicit_wait = 2 #seconds
|
142
|
-
@driver.navigate.to SeleniumConfig.default_browser_url
|
143
88
|
|
89
|
+
def run_browser(browser_spec, block)
|
90
|
+
@browser = Selenium::Client::Driver.new(
|
91
|
+
:host => HOST,
|
92
|
+
:port => PORT,
|
93
|
+
:browser => browser_spec,
|
94
|
+
:url => SeleniumConfig.default_browser_url,
|
95
|
+
:timeout_in_second => 120)
|
96
|
+
@browser.start_new_browser_session
|
144
97
|
begin
|
145
|
-
block.call
|
146
|
-
rescue => error
|
147
|
-
@error = error.message
|
148
|
-
puts error.message
|
149
|
-
puts error.backtrace
|
98
|
+
block.call(@browser)
|
150
99
|
ensure
|
151
100
|
save_test({:selenium_test_group_name => @@group, :selenium_test_name => @name,
|
152
|
-
:description =>
|
153
|
-
@
|
154
|
-
end
|
101
|
+
:description => @@description}) if SeleniumConfig.mode == "remote"
|
102
|
+
@browser.close_current_browser_session
|
103
|
+
end
|
155
104
|
end
|
156
105
|
|
157
106
|
def capture_screenshot_on(src)
|
158
107
|
browser.window_focus
|
159
108
|
browser.window_maximize
|
160
109
|
sleep(2)
|
161
|
-
if
|
162
|
-
|
163
|
-
elsif
|
164
|
-
|
165
|
-
elsif
|
166
|
-
|
110
|
+
if browser.browser_string.match(/XP/)
|
111
|
+
browser.capture_entire_page_screenshot("#{PICS_WINDOWS_PATH}\\#{src}", "background=#FFFFFF")
|
112
|
+
elsif browser.browser_string.match(/SnowLeopard/)
|
113
|
+
browser.capture_entire_page_screenshot("#{PICS_MACOS_PATH}/#{src}", "background=#FFFFFF")
|
114
|
+
elsif browser.browser_string.match(/Linux/)
|
115
|
+
browser.capture_entire_page_screenshot("#{PICS_LINUX_PATH}/#{src}", "background=#FFFFFF")
|
167
116
|
end
|
168
117
|
end
|
169
118
|
|
170
119
|
def save_test(params)
|
171
120
|
src = "#{SeleniumConfig.application_name}_#{params[:selenium_test_group_name]}_#{params[:selenium_test_name]}_" +
|
172
|
-
|
121
|
+
"#{browser.browser_string.gsub(/\s+/,"_").downcase}.png"
|
173
122
|
|
174
123
|
capture_screenshot_on(src)
|
175
124
|
|
176
125
|
SeleniumTest.create(:selenium_test_name => params[:selenium_test_name], :description => params[:description],
|
177
|
-
:url =>
|
126
|
+
:url => browser.location, :error_message => @error, :is_error => !@error.nil?, :environment => browser.browser_string,
|
178
127
|
:selenium_test_group_name => params[:selenium_test_group_name], :application_name => SeleniumConfig.application_name)
|
179
128
|
end
|
180
129
|
end
|
130
|
+
|
Binary file
|
metadata
CHANGED
@@ -4,20 +4,19 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
- 0
|
8
7
|
- 1
|
9
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Kyle J. Ginavan
|
13
13
|
- Mauro Torres
|
14
|
-
- Mike Hemesath
|
15
14
|
autorequire:
|
16
15
|
bindir: bin
|
17
16
|
cert_chain: []
|
18
17
|
|
19
|
-
date: 2010-
|
20
|
-
default_executable:
|
18
|
+
date: 2010-04-13 00:00:00 -05:00
|
19
|
+
default_executable:
|
21
20
|
dependencies:
|
22
21
|
- !ruby/object:Gem::Dependency
|
23
22
|
name: thoughtbot-shoulda
|
@@ -32,51 +31,24 @@ dependencies:
|
|
32
31
|
type: :development
|
33
32
|
version_requirements: *id001
|
34
33
|
- !ruby/object:Gem::Dependency
|
35
|
-
name:
|
34
|
+
name: selenium-client
|
36
35
|
prerelease: false
|
37
36
|
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - "="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
segments:
|
42
|
-
- 1
|
43
|
-
- 1
|
44
|
-
- 12
|
45
|
-
version: 1.1.12
|
46
|
-
type: :development
|
47
|
-
version_requirements: *id002
|
48
|
-
- !ruby/object:Gem::Dependency
|
49
|
-
name: selenium-webdriver
|
50
|
-
prerelease: false
|
51
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
52
37
|
requirements:
|
53
38
|
- - ">="
|
54
39
|
- !ruby/object:Gem::Version
|
55
40
|
segments:
|
56
|
-
- 0
|
57
41
|
- 1
|
58
|
-
- 0
|
59
|
-
version: 0.1.0
|
60
|
-
type: :runtime
|
61
|
-
version_requirements: *id003
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: rest-client
|
64
|
-
prerelease: false
|
65
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
segments:
|
70
|
-
- 0
|
71
|
-
- 8
|
72
42
|
- 2
|
73
|
-
|
43
|
+
- 18
|
44
|
+
version: 1.2.18
|
74
45
|
type: :runtime
|
75
|
-
version_requirements: *
|
46
|
+
version_requirements: *id002
|
76
47
|
description: Selenium Shots is an Integration Testing Service that transparently distributes your integration tests across multiple operating systems with different versions of all major browsers AND captures a screen shot
|
77
48
|
email: kyle@4rockets.com
|
78
49
|
executables:
|
79
50
|
- selenium_shots
|
51
|
+
- selenium_shots_local_server
|
80
52
|
extensions: []
|
81
53
|
|
82
54
|
extra_rdoc_files:
|
@@ -93,6 +65,7 @@ files:
|
|
93
65
|
- lib/selenium_shots/cli/commands/help.rb
|
94
66
|
- lib/selenium_shots/cli/init.rb
|
95
67
|
- lib/selenium_shots/test_selenium_shots.rb
|
68
|
+
- vendor/selenium-server-1.0.2-SNAPSHOT-standalone.jar
|
96
69
|
- LICENSE
|
97
70
|
- LICENSE.orig
|
98
71
|
- README.rdoc
|
@@ -127,11 +100,5 @@ signing_key:
|
|
127
100
|
specification_version: 3
|
128
101
|
summary: Integration Tests made easy
|
129
102
|
test_files:
|
130
|
-
- spec/base.rb
|
131
|
-
- spec/commands/app_spec.rb
|
132
|
-
- spec/commands/auth_spec.rb
|
133
|
-
- spec/commands/base_spec.rb
|
134
|
-
- spec/commands/server_spec.rb
|
135
103
|
- test/helper.rb
|
136
104
|
- test/test_selenium_shots.rb
|
137
|
-
- examples/google.rb
|
data/examples/google.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
2
|
-
|
3
|
-
class Google < SeleniumShots
|
4
|
-
|
5
|
-
@group = "Google"
|
6
|
-
|
7
|
-
selenium_shot "should search on google" do
|
8
|
-
@name = "Google search"
|
9
|
-
element = driver.find_element(:name, 'q')
|
10
|
-
element.send_keys "Hello WebDriver!"
|
11
|
-
element.submit
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
data/spec/base.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'spec'
|
3
|
-
require 'fileutils'
|
4
|
-
|
5
|
-
require File.dirname(__FILE__) + '/../lib/selenium_shots/cli/init'
|
6
|
-
|
7
|
-
%w(app auth base server).each { |c| require c }
|
8
|
-
|
9
|
-
def prepare_command(klass)
|
10
|
-
command = klass.new([])
|
11
|
-
command.stub!(:display)
|
12
|
-
command
|
13
|
-
end
|
14
|
-
|
data/spec/commands/app_spec.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../base'
|
2
|
-
|
3
|
-
module SeleniumShots::Command
|
4
|
-
describe App do
|
5
|
-
before do
|
6
|
-
@cli = prepare_command(App)
|
7
|
-
@auth = prepare_command(Auth)
|
8
|
-
end
|
9
|
-
|
10
|
-
it "creates with a name" do
|
11
|
-
@cli.stub!(:args).and_return([ 'myapp' ])
|
12
|
-
@cli.stub!(:selenium_shots_api_key).and_return("api_key")
|
13
|
-
@cli.should_receive(:make_config_file)
|
14
|
-
@cli.create
|
15
|
-
end
|
16
|
-
|
17
|
-
it "cant creates app without a name" do
|
18
|
-
@cli.stub!(:args).and_return([ nil ])
|
19
|
-
@cli.stub!(:selenium_shots_api_key)
|
20
|
-
@cli.should_not_receive(:make_config_file)
|
21
|
-
@cli.create
|
22
|
-
end
|
23
|
-
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
data/spec/commands/auth_spec.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../base'
|
2
|
-
|
3
|
-
module SeleniumShots::Command
|
4
|
-
describe Auth do
|
5
|
-
before do
|
6
|
-
@cli = prepare_command(Auth)
|
7
|
-
end
|
8
|
-
|
9
|
-
it "reads api key from the api keys file" do
|
10
|
-
sandbox = "/tmp/cli_spec_#{Process.pid}"
|
11
|
-
File.open(sandbox, "w") { |f| f.write "api_key" }
|
12
|
-
@cli.stub!(:api_key_file).and_return(sandbox)
|
13
|
-
@cli.read_api_key.should == %w(api_key)
|
14
|
-
end
|
15
|
-
|
16
|
-
it "takes the apikey from the file" do
|
17
|
-
@cli.stub!(:read_api_key).and_return(%w(api_key))
|
18
|
-
@cli.api_key.should == %w(api_key)
|
19
|
-
end
|
20
|
-
|
21
|
-
it "asks for api_key when the file doesn't exist" do
|
22
|
-
sandbox = "/tmp/cli_spec_#{Process.pid}"
|
23
|
-
FileUtils.rm_rf(sandbox)
|
24
|
-
@cli.stub!(:api_key_file).and_return(sandbox)
|
25
|
-
@cli.should_receive(:ask_for_api_key).and_return(['u', 'p'])
|
26
|
-
@cli.should_receive(:save_api_key)
|
27
|
-
@cli.get_api_key.should == [ 'u', 'p' ]
|
28
|
-
end
|
29
|
-
|
30
|
-
it "writes the api_key to a file" do
|
31
|
-
sandbox = "/tmp/cli_spec_#{Process.pid}"
|
32
|
-
FileUtils.rm_rf(sandbox)
|
33
|
-
@cli.stub!(:api_key_file).and_return(sandbox)
|
34
|
-
@cli.stub!(:api_key_hash).and_return(['api_key'])
|
35
|
-
@cli.should_receive(:set_api_key_permissions)
|
36
|
-
@cli.write_api_key
|
37
|
-
File.read(sandbox).should == "api_key\n"
|
38
|
-
end
|
39
|
-
|
40
|
-
it "sets ~/.selenium_shots/api_key to be readable only by the user" do
|
41
|
-
sandbox = "/tmp/cli_spec_#{Process.pid}"
|
42
|
-
FileUtils.rm_rf(sandbox)
|
43
|
-
FileUtils.mkdir_p(sandbox)
|
44
|
-
fname = "#{sandbox}/file"
|
45
|
-
system "touch #{fname}"
|
46
|
-
@cli.stub!(:api_key_file).and_return(fname)
|
47
|
-
@cli.set_api_key_permissions
|
48
|
-
File.stat(sandbox).mode.should == 040700
|
49
|
-
File.stat(fname).mode.should == 0100600
|
50
|
-
end
|
51
|
-
|
52
|
-
it "writes api_key when the account is ok" do
|
53
|
-
@cli.stub!(:api_key)
|
54
|
-
@cli.should_receive(:write_api_key)
|
55
|
-
@cli.should_receive(:get_api_key_from_host).and_return("api_key")
|
56
|
-
@cli.save_api_key
|
57
|
-
end
|
58
|
-
|
59
|
-
it "save_api_key deletes the api_key when the resquest api_key is unauthorized" do
|
60
|
-
@cli.stub!(:write_api_key)
|
61
|
-
@cli.stub!(:retry_login?).and_return(false)
|
62
|
-
@cli.should_receive(:get_api_key_from_host).and_raise(RestClient::Unauthorized)
|
63
|
-
@cli.should_receive(:delete_api_key)
|
64
|
-
lambda { @cli.save_api_key }.should raise_error(RestClient::Unauthorized)
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
it "asks for login again when not authorized, for three times" do
|
69
|
-
@cli.stub!(:read_api_key)
|
70
|
-
@cli.stub!(:write_api_key)
|
71
|
-
@cli.stub!(:delete_api_key)
|
72
|
-
@cli.should_receive(:get_api_key_from_host).exactly(3).times.and_raise(RestClient::Unauthorized)
|
73
|
-
@cli.should_receive(:ask_for_api_key).exactly(4).times
|
74
|
-
lambda { @cli.save_api_key }.should raise_error(RestClient::Unauthorized)
|
75
|
-
end
|
76
|
-
|
77
|
-
it "deletes the api_key file" do
|
78
|
-
FileUtils.should_receive(:rm_f).with(@cli.api_key_file)
|
79
|
-
@cli.delete_api_key
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
data/spec/commands/base_spec.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../base'
|
2
|
-
|
3
|
-
module SeleniumShots::Command
|
4
|
-
describe Base do
|
5
|
-
before do
|
6
|
-
@args = [1, 2]
|
7
|
-
@base = Base.new(@args)
|
8
|
-
@base.stub!(:display)
|
9
|
-
end
|
10
|
-
|
11
|
-
it "initializes the selenium_shots client with the Auth command" do
|
12
|
-
SeleniumShots::Command.should_receive(:run_internal).with('auth:client', @args)
|
13
|
-
@base.selenium_shots
|
14
|
-
end
|
15
|
-
|
16
|
-
it "creates or overwrite the selenium_shots yml file" do
|
17
|
-
sandbox = "/tmp/cli_spec_selenium_shots"
|
18
|
-
@base.stub!(:config_file).and_return(sandbox)
|
19
|
-
@base.should_receive(:ask_for_config_file).and_return("y")
|
20
|
-
@base.make_config_file("myapp", "api_key")
|
21
|
-
File.exists?(sandbox) == true
|
22
|
-
end
|
23
|
-
|
24
|
-
it "not overwrite the selenium_shots yml file" do
|
25
|
-
sandbox = "/tmp/cli_spec_selenium_shots"
|
26
|
-
@base.stub!(:config_file).and_return(sandbox)
|
27
|
-
@base.should_receive(:ask_for_config_file).and_return("n")
|
28
|
-
@base.make_config_file("myapp", "api_key")
|
29
|
-
File.exists?(sandbox) == false
|
30
|
-
end
|
31
|
-
|
32
|
-
it "return the config file name" do
|
33
|
-
@base.config_file.should == 'config/selenium_shots.yml'
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
@@ -1,22 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../base'
|
2
|
-
|
3
|
-
module SeleniumShots::Command
|
4
|
-
describe Server do
|
5
|
-
before do
|
6
|
-
@cli = prepare_command(Server)
|
7
|
-
end
|
8
|
-
|
9
|
-
it "run local instance of selenium server" do
|
10
|
-
@cli.start
|
11
|
-
File.exists?("/tmp/selenium_shots.pid") == true
|
12
|
-
end
|
13
|
-
|
14
|
-
it "stop local instance of selenium server" do
|
15
|
-
@cli.stop
|
16
|
-
File.exists?("/tmp/selenium_shots.pid") == false
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|