selenium_shots 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +46 -9
- data/bin/selenium_shots +1 -1
- data/examples/google.rb +14 -0
- data/lib/selenium_shots/cli/client.rb +1 -18
- data/lib/selenium_shots/cli/command.rb +44 -42
- data/lib/selenium_shots/cli/commands/app.rb +11 -15
- data/lib/selenium_shots/cli/commands/auth.rb +117 -117
- data/lib/selenium_shots/cli/commands/base.rb +50 -36
- data/lib/selenium_shots/cli/commands/help.rb +11 -10
- data/lib/selenium_shots/cli/init.rb +2 -2
- data/lib/selenium_shots/test_selenium_shots.rb +125 -49
- data/spec/base.rb +14 -0
- data/spec/commands/app_spec.rb +26 -0
- data/spec/commands/auth_spec.rb +83 -0
- data/spec/commands/base_spec.rb +38 -0
- data/spec/commands/server_spec.rb +22 -0
- metadata +25 -18
data/README.rdoc
CHANGED
@@ -3,15 +3,52 @@
|
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
== Install
|
7
|
+
|
8
|
+
=== Rails 3
|
9
|
+
|
10
|
+
Add this to your Gemfile:
|
11
|
+
|
12
|
+
group :development do
|
13
|
+
gem 'selenium_shots'
|
14
|
+
end
|
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`
|
15
52
|
|
16
53
|
== Copyright
|
17
54
|
|
data/bin/selenium_shots
CHANGED
@@ -11,6 +11,6 @@ command = args.shift.strip rescue 'help'
|
|
11
11
|
if File.exists?('config/environment.rb')
|
12
12
|
SeleniumShots::Command.run(command, args)
|
13
13
|
else
|
14
|
-
puts "
|
14
|
+
puts "app rails not found!, you need stay inside on the root of one rails app"
|
15
15
|
end
|
16
16
|
|
data/examples/google.rb
ADDED
@@ -0,0 +1,14 @@
|
|
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
|
+
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'rexml/document'
|
3
2
|
require 'rest_client'
|
4
3
|
require 'uri'
|
5
4
|
require 'time'
|
@@ -8,16 +7,11 @@ class SeleniumShots::Client
|
|
8
7
|
|
9
8
|
attr_reader :host, :api_key
|
10
9
|
|
11
|
-
def initialize(api_key, host='seleniumshots.
|
10
|
+
def initialize(api_key, host='www.seleniumshots.com')
|
12
11
|
@api_key = api_key
|
13
12
|
@host = host
|
14
13
|
end
|
15
14
|
|
16
|
-
def list
|
17
|
-
#get list app from selenium_shots
|
18
|
-
[]
|
19
|
-
end
|
20
|
-
|
21
15
|
############
|
22
16
|
def resource(uri)
|
23
17
|
RestClient::Resource.new("http://#{host}", api_key)[uri]
|
@@ -38,16 +32,5 @@ class SeleniumShots::Client
|
|
38
32
|
def delete(uri)
|
39
33
|
resource(uri).delete
|
40
34
|
end
|
41
|
-
|
42
|
-
def xml(raw) # :nodoc:
|
43
|
-
REXML::Document.new(raw)
|
44
|
-
end
|
45
|
-
|
46
|
-
def escape(value) # :nodoc:
|
47
|
-
escaped = URI.escape(value.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
|
48
|
-
escaped.gsub('.', '%2E') # not covered by the previous URI.escape
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
35
|
end
|
53
36
|
|
@@ -1,50 +1,52 @@
|
|
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
|
-
|
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
|
+
rescue RestClient::Unauthorized
|
12
|
+
display "Authentication failure. For more information you can go to http://www.seleniumshots.com"
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
def run_internal(command, args)
|
16
|
+
namespace, command = parse(command)
|
17
|
+
require "#{namespace}"
|
18
|
+
klass = SeleniumShots::Command.const_get(namespace.capitalize).new(args)
|
19
|
+
raise InvalidCommand unless klass.respond_to?(command)
|
20
|
+
klass.send(command)
|
21
|
+
end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def display(msg)
|
24
|
+
puts(msg)
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
def parse(command)
|
28
|
+
parts = command.split(':')
|
29
|
+
case parts.size
|
30
|
+
when 1
|
31
|
+
if namespaces.include? command
|
32
|
+
return command, 'index'
|
33
|
+
else
|
34
|
+
return 'app', command
|
35
|
+
end
|
36
|
+
when 2
|
37
|
+
raise InvalidCommand unless namespaces.include? parts[0]
|
38
|
+
return parts
|
39
|
+
else
|
40
|
+
raise InvalidCommand
|
41
|
+
end
|
42
|
+
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
def namespaces
|
45
|
+
@@namespaces ||= Dir["#{File.dirname(__FILE__)}/commands/*"].map do |namespace|
|
46
|
+
namespace.gsub(/.*\//, '').gsub(/\.rb/, '')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
49
51
|
end
|
50
52
|
|
@@ -1,20 +1,16 @@
|
|
1
1
|
module SeleniumShots::Command
|
2
|
-
|
2
|
+
class App < Base
|
3
3
|
def create
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
name = args.shift.downcase.strip rescue nil
|
5
|
+
if name
|
6
|
+
selenium_shots_api_key
|
7
|
+
if make_config_file(name, @api_key) == "y"
|
8
|
+
display "Created #{name}\nYou can configurate selenium shots on config/selenium_shots.yml"
|
9
|
+
end
|
10
|
+
else
|
11
|
+
display "You need specify a name for your app. Run 'selenium_shots help' for usage information"
|
12
|
+
end
|
8
13
|
end
|
9
|
-
|
10
|
-
def list
|
11
|
-
list = selenium_shots.list
|
12
|
-
if list.size > 0
|
13
|
-
display list.join("\n")
|
14
|
-
else
|
15
|
-
display "You have no apps."
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
14
|
+
end
|
19
15
|
end
|
20
16
|
|
@@ -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://seleniumshots.
|
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://www.seleniumshots.com/selenium_tests/get_api_key', :user_session => { :email => @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 "Email: "
|
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,61 +1,75 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
3
|
module SeleniumShots::Command
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
class Base
|
5
|
+
attr_accessor :args
|
6
|
+
def initialize(args)
|
7
|
+
@args = args
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def selenium_shots
|
11
|
+
@selenium_shots ||= SeleniumShots::Command.run_internal('auth:client', args)
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def selenium_shots_api_key
|
15
|
+
@api_key ||= SeleniumShots::Command.run_internal('auth:api_key', args)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def display(msg, newline=true)
|
19
|
+
newline ? puts(msg) : print(msg)
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def ask
|
23
|
+
gets.strip
|
24
|
+
end
|
25
|
+
|
26
|
+
def shell(cmd)
|
27
|
+
`#{cmd}`
|
28
|
+
end
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
def home_directory
|
31
|
+
running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
def running_on_windows?
|
35
|
+
RUBY_PLATFORM =~ /mswin32/
|
36
|
+
end
|
33
37
|
|
34
38
|
def config_file
|
35
39
|
'config/selenium_shots.yml'
|
36
40
|
end
|
37
41
|
|
42
|
+
|
43
|
+
def ask_for_config_file
|
44
|
+
if File.exists?(config_file)
|
45
|
+
print "The file config/selenium_shots.yml exists, do you want overwrite this? (y/n): "
|
46
|
+
ask
|
47
|
+
else
|
48
|
+
"y"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
38
52
|
def make_config_file(name, api_key)
|
39
|
-
|
40
|
-
|
53
|
+
overwrite_or_create_file = ask_for_config_file
|
54
|
+
if overwrite_or_create_file == "y"
|
55
|
+
config_file_hash = <<EOFILE
|
41
56
|
api_key: "#{api_key}"
|
42
|
-
|
43
|
-
|
44
|
-
default_browser_url: 'default url'
|
57
|
+
mode: "remote" # "local" for run test locally
|
58
|
+
default_browser_url: "http://www.myapp.com"
|
45
59
|
application_name: "#{name}"
|
60
|
+
local_browser: "firefox"
|
46
61
|
browsers:
|
47
62
|
- IE8 on XP
|
63
|
+
- Firefox3.6 on XP
|
48
64
|
EOFILE
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
def inside_rails_app?
|
55
|
-
File.exists?('config/environment.rb')
|
65
|
+
File.open(config_file, 'w') do |f|
|
66
|
+
f.puts config_file_hash
|
67
|
+
end
|
56
68
|
end
|
69
|
+
overwrite_or_create_file
|
70
|
+
end
|
57
71
|
|
58
|
-
|
72
|
+
end
|
59
73
|
|
60
74
|
end
|
61
75
|
|
@@ -1,15 +1,16 @@
|
|
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]
|
11
|
+
help # show this usage
|
12
|
+
create [name] # create file config for your app
|
13
|
+
|
13
14
|
=== Example story:
|
14
15
|
|
15
16
|
rails myapp
|
@@ -17,7 +18,7 @@ module SeleniumShots::Command
|
|
17
18
|
(...make edits...)
|
18
19
|
selenium_shots create example_one
|
19
20
|
EOTXT
|
20
|
-
|
21
|
-
|
21
|
+
end
|
22
|
+
end
|
22
23
|
end
|
23
24
|
|
@@ -1,38 +1,72 @@
|
|
1
1
|
require "test/unit"
|
2
2
|
require "rubygems"
|
3
3
|
require "selenium/client"
|
4
|
+
require "selenium-webdriver"
|
4
5
|
require 'active_support'
|
5
6
|
require 'active_support/test_case'
|
6
7
|
require 'ostruct'
|
7
8
|
|
8
|
-
|
9
9
|
#load config
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
PICS_LINUX_PATH = ''
|
14
|
-
PICS_MACOS_PATH = ''
|
10
|
+
ASSET_ROOT = File.expand_path((defined?(Rails) && Rails.root.to_s.length > 0) ? Rails.root : ".") unless defined?(ASSET_ROOT)
|
11
|
+
|
12
|
+
SeleniumConfig = OpenStruct.new(YAML.load_file(ASSET_ROOT + '/config/selenium_shots.yml'))
|
15
13
|
#
|
14
|
+
|
16
15
|
#activeresource models
|
17
16
|
class SeleniumTest < ActiveResource::Base
|
18
|
-
self.site = "http://seleniumshots.
|
17
|
+
self.site = "http://seleniumshots.com"
|
19
18
|
self.user = SeleniumConfig.api_key
|
20
19
|
end
|
21
20
|
|
21
|
+
class SeleniumShots < ActionController::IntegrationTest
|
22
22
|
|
23
|
-
|
23
|
+
attr_reader :driver, :agent, :take_screenshot
|
24
|
+
cattr_accessor :expected_test_count
|
24
25
|
|
25
|
-
|
26
|
+
if SeleniumConfig.mode == "remote"
|
27
|
+
PICS_WINDOWS_PATH = "Z:"
|
28
|
+
PICS_LINUX_PATH = ''
|
29
|
+
PICS_MACOS_PATH = ''
|
30
|
+
HOST = "staging.advisorshq.com"
|
31
|
+
PORT = "8888"
|
32
|
+
else
|
33
|
+
HOST = "127.0.0.1"
|
34
|
+
PORT = "4444"
|
35
|
+
end
|
26
36
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
37
|
+
def pid_file
|
38
|
+
"/tmp/selenium_shots.pid"
|
39
|
+
end
|
40
|
+
|
41
|
+
def local_browsers
|
42
|
+
["firefox", "ie", "chrome"]
|
43
|
+
end
|
30
44
|
|
45
|
+
def selected_browsers
|
46
|
+
if SeleniumConfig.mode == "remote"
|
47
|
+
SeleniumConfig.browsers
|
48
|
+
else
|
49
|
+
if defined?(SeleniumConfig.local_browser)
|
50
|
+
[SeleniumConfig.local_browser]
|
51
|
+
else
|
52
|
+
[local_browsers.first]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.core_test(description, take_screenshot = true, &block)
|
58
|
+
@@group = (@group || "Default")
|
31
59
|
test_name = "test_#{description.gsub(/\s+/,'_')}".to_sym
|
32
60
|
defined = instance_method(test_name) rescue false
|
33
61
|
raise "#{test_name} is already defined in #{self}" if defined
|
34
62
|
if block_given?
|
35
|
-
define_method(test_name
|
63
|
+
define_method(test_name) do
|
64
|
+
@description = description
|
65
|
+
@take_screenshot = take_screenshot
|
66
|
+
run_in_all_browsers do
|
67
|
+
instance_eval &block
|
68
|
+
end
|
69
|
+
end
|
36
70
|
else
|
37
71
|
define_method(test_name) do
|
38
72
|
flunk "No implementation provided for #{name}"
|
@@ -40,65 +74,107 @@ class ActiveSupport::TestCase
|
|
40
74
|
end
|
41
75
|
end
|
42
76
|
|
43
|
-
def
|
44
|
-
|
45
|
-
:host => SeleniumConfig.hub_url,
|
46
|
-
:port => SeleniumConfig.hub_port,
|
47
|
-
:browser => browser,
|
48
|
-
:url => url || SeleniumConfig.default_browser_url,
|
49
|
-
:timeout_in_second => 60,
|
50
|
-
:highlight_located_element => true
|
51
|
-
@browser.start_new_browser_session
|
77
|
+
def self.selenium_test(description, &block)
|
78
|
+
core_test(description, nil, &block)
|
52
79
|
end
|
53
80
|
|
54
|
-
def
|
55
|
-
|
56
|
-
browser.click locator
|
57
|
-
browser.focus locator
|
81
|
+
def self.selenium_shot(description, &block)
|
82
|
+
core_test(description, true, &block)
|
58
83
|
end
|
59
84
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
85
|
+
def run_in_all_browsers(&block)
|
86
|
+
@error = nil
|
87
|
+
browsers = (@selected_browser || selected_browsers)
|
88
|
+
browsers.each do |browser_spec|
|
89
|
+
begin
|
90
|
+
run_webdriver(browser_spec, block)
|
91
|
+
rescue => error
|
92
|
+
@driver.quit if @driver
|
93
|
+
@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
|
+
end
|
104
|
+
end
|
105
|
+
assert @error.nil?, "Expected zero failures or errors, but got #{@error}\n"
|
63
106
|
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
|
64
137
|
|
65
|
-
|
66
|
-
begin
|
67
|
-
proc.call
|
68
|
-
@error = nil
|
69
|
-
rescue => e
|
70
|
-
@error = e.message
|
138
|
+
@driver = Selenium::WebDriver.for(:remote, :desired_capabilities => caps, :http_client => client) if caps
|
71
139
|
end
|
72
|
-
|
140
|
+
|
141
|
+
@driver.manage.timeouts.implicit_wait = 2 #seconds
|
142
|
+
@driver.navigate.to SeleniumConfig.default_browser_url
|
73
143
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
144
|
+
begin
|
145
|
+
block.call
|
146
|
+
rescue => error
|
147
|
+
@error = error.message
|
148
|
+
puts error.message
|
149
|
+
puts error.backtrace
|
150
|
+
ensure
|
151
|
+
save_test({:selenium_test_group_name => @@group, :selenium_test_name => @name,
|
152
|
+
:description => @description}) if SeleniumConfig.mode == "remote"
|
153
|
+
@driver.quit
|
154
|
+
end
|
78
155
|
end
|
79
156
|
|
80
157
|
def capture_screenshot_on(src)
|
81
158
|
browser.window_focus
|
82
159
|
browser.window_maximize
|
83
160
|
sleep(2)
|
84
|
-
if browser.
|
85
|
-
|
86
|
-
elsif browser.
|
87
|
-
|
88
|
-
elsif browser.
|
89
|
-
|
161
|
+
if @driver.browser.to_s.match(/XP/)
|
162
|
+
@driver.capture_entire_page_screenshot("#{PICS_WINDOWS_PATH}\\#{src}", "background=#FFFFFF")
|
163
|
+
elsif @driver.browser.to_s.match(/SnowLeopard/)
|
164
|
+
@driver.capture_entire_page_screenshot("#{PICS_MACOS_PATH}/#{src}", "background=#FFFFFF")
|
165
|
+
elsif @driver.browser.to_s.match(/Linux/)
|
166
|
+
@driver.capture_entire_page_screenshot("#{PICS_LINUX_PATH}/#{src}", "background=#FFFFFF")
|
90
167
|
end
|
91
168
|
end
|
92
169
|
|
93
170
|
def save_test(params)
|
94
171
|
src = "#{SeleniumConfig.application_name}_#{params[:selenium_test_group_name]}_#{params[:selenium_test_name]}_" +
|
95
|
-
|
172
|
+
"#{@driver.browser.to_s.gsub(/\s+/,"_").downcase}.png"
|
96
173
|
|
97
174
|
capture_screenshot_on(src)
|
98
175
|
|
99
176
|
SeleniumTest.create(:selenium_test_name => params[:selenium_test_name], :description => params[:description],
|
100
|
-
:url =>
|
177
|
+
:url => @driver.current_url, :error_message => @error, :is_error => !@error.nil?, :environment => @driver.browser.to_s,
|
101
178
|
:selenium_test_group_name => params[:selenium_test_group_name], :application_name => SeleniumConfig.application_name)
|
102
179
|
end
|
103
180
|
end
|
104
|
-
|
data/spec/base.rb
ADDED
@@ -0,0 +1,14 @@
|
|
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
|
+
|
@@ -0,0 +1,26 @@
|
|
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
|
+
|
@@ -0,0 +1,83 @@
|
|
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
|
+
|
@@ -0,0 +1,38 @@
|
|
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
|
+
|
@@ -0,0 +1,22 @@
|
|
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
|
+
|
metadata
CHANGED
@@ -5,17 +5,18 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Kyle J. Ginavan
|
13
13
|
- Mauro Torres
|
14
|
+
- Mike Hemesath
|
14
15
|
autorequire:
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
17
18
|
|
18
|
-
date: 2010-
|
19
|
+
date: 2010-12-21 00:00:00 -06:00
|
19
20
|
default_executable: selenium_shots
|
20
21
|
dependencies:
|
21
22
|
- !ruby/object:Gem::Dependency
|
@@ -31,21 +32,21 @@ dependencies:
|
|
31
32
|
type: :development
|
32
33
|
version_requirements: *id001
|
33
34
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
35
|
+
name: rspec
|
35
36
|
prerelease: false
|
36
37
|
requirement: &id002 !ruby/object:Gem::Requirement
|
37
38
|
requirements:
|
38
|
-
- - "
|
39
|
+
- - "="
|
39
40
|
- !ruby/object:Gem::Version
|
40
41
|
segments:
|
41
|
-
-
|
42
|
-
-
|
43
|
-
-
|
44
|
-
version:
|
45
|
-
type: :
|
42
|
+
- 1
|
43
|
+
- 1
|
44
|
+
- 12
|
45
|
+
version: 1.1.12
|
46
|
+
type: :development
|
46
47
|
version_requirements: *id002
|
47
48
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
49
|
+
name: selenium-webdriver
|
49
50
|
prerelease: false
|
50
51
|
requirement: &id003 !ruby/object:Gem::Requirement
|
51
52
|
requirements:
|
@@ -53,23 +54,23 @@ dependencies:
|
|
53
54
|
- !ruby/object:Gem::Version
|
54
55
|
segments:
|
55
56
|
- 0
|
56
|
-
-
|
57
|
-
-
|
58
|
-
version: 0.
|
57
|
+
- 1
|
58
|
+
- 0
|
59
|
+
version: 0.1.0
|
59
60
|
type: :runtime
|
60
61
|
version_requirements: *id003
|
61
62
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
63
|
+
name: rest-client
|
63
64
|
prerelease: false
|
64
65
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
66
|
requirements:
|
66
67
|
- - ">="
|
67
68
|
- !ruby/object:Gem::Version
|
68
69
|
segments:
|
69
|
-
-
|
70
|
+
- 0
|
71
|
+
- 8
|
70
72
|
- 2
|
71
|
-
|
72
|
-
version: 1.2.18
|
73
|
+
version: 0.8.2
|
73
74
|
type: :runtime
|
74
75
|
version_requirements: *id004
|
75
76
|
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
|
@@ -126,5 +127,11 @@ signing_key:
|
|
126
127
|
specification_version: 3
|
127
128
|
summary: Integration Tests made easy
|
128
129
|
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
|
129
135
|
- test/helper.rb
|
130
136
|
- test/test_selenium_shots.rb
|
137
|
+
- examples/google.rb
|