seleniumrc 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +2 -0
- data/README +0 -0
- data/Rakefile +68 -0
- data/lib/seleniumrc/app_server_checker.rb +43 -0
- data/lib/seleniumrc/dsl/selenium_dsl.rb +186 -0
- data/lib/seleniumrc/dsl/test_unit_dsl.rb +95 -0
- data/lib/seleniumrc/extensions/selenium_driver.rb +33 -0
- data/lib/seleniumrc/extensions/testrunnermediator.rb +19 -0
- data/lib/seleniumrc/mongrel_selenium_server_runner.rb +35 -0
- data/lib/seleniumrc/selenium_configuration.rb +78 -0
- data/lib/seleniumrc/selenium_context.rb +226 -0
- data/lib/seleniumrc/selenium_element.rb +195 -0
- data/lib/seleniumrc/selenium_helper.rb +5 -0
- data/lib/seleniumrc/selenium_page.rb +76 -0
- data/lib/seleniumrc/selenium_server_runner.rb +33 -0
- data/lib/seleniumrc/selenium_test_case.rb +95 -0
- data/lib/seleniumrc/tasks/selenium_test_task.rb +21 -0
- data/lib/seleniumrc/wait_for.rb +47 -0
- data/lib/seleniumrc/webrick_selenium_server_runner.rb +33 -0
- data/lib/seleniumrc.rb +28 -0
- data/spec/seleniumrc/app_server_checker_spec.rb +56 -0
- data/spec/seleniumrc/mongrel_selenium_server_runner_spec.rb +42 -0
- data/spec/seleniumrc/selenese_interpreter_spec.rb +25 -0
- data/spec/seleniumrc/selenium_configuration_spec.rb +21 -0
- data/spec/seleniumrc/selenium_context_spec.rb +362 -0
- data/spec/seleniumrc/selenium_element_spec.rb +530 -0
- data/spec/seleniumrc/selenium_page_spec.rb +226 -0
- data/spec/seleniumrc/selenium_server_runner_spec.rb +42 -0
- data/spec/seleniumrc/selenium_test_case_class_method_spec.rb +41 -0
- data/spec/seleniumrc/selenium_test_case_spec.rb +908 -0
- data/spec/seleniumrc/selenium_test_case_spec_helper.rb +23 -0
- data/spec/seleniumrc/webrick_selenium_server_runner_spec.rb +116 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/spec_suite.rb +4 -0
- metadata +83 -0
@@ -0,0 +1,226 @@
|
|
1
|
+
module Seleniumrc
|
2
|
+
# The application state and Dependency Injection container of the Seleniumrc objects.
|
3
|
+
# All objects are created through the SeleniumContext. All global state is encapsulated here.
|
4
|
+
class SeleniumContext
|
5
|
+
attr_accessor :configuration,
|
6
|
+
:env,
|
7
|
+
:rails_env,
|
8
|
+
:rails_root,
|
9
|
+
:browsers,
|
10
|
+
:current_browser,
|
11
|
+
:interpreter,
|
12
|
+
:browser_mode,
|
13
|
+
:selenium_server_host,
|
14
|
+
:selenium_server_port,
|
15
|
+
:app_server_engine,
|
16
|
+
:internal_app_server_host,
|
17
|
+
:internal_app_server_port,
|
18
|
+
:external_app_server_host,
|
19
|
+
:external_app_server_port,
|
20
|
+
:server_engine,
|
21
|
+
:keep_browser_open_on_failure,
|
22
|
+
:verify_remote_app_server_is_running
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
self.verify_remote_app_server_is_running = true
|
26
|
+
@before_suite_listeners = []
|
27
|
+
@after_selenese_interpreter_started_listeners = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# A callback hook that gets run before the suite is run.
|
31
|
+
def before_suite(&block)
|
32
|
+
@before_suite_listeners << block
|
33
|
+
end
|
34
|
+
|
35
|
+
# Notify all before_suite callbacks.
|
36
|
+
def notify_before_suite
|
37
|
+
for listener in @before_suite_listeners
|
38
|
+
listener.call
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# A callback hook that gets run after the Selenese Interpreter is started.
|
43
|
+
def after_selenese_interpreter_started(&block)
|
44
|
+
@after_selenese_interpreter_started_listeners << block
|
45
|
+
end
|
46
|
+
|
47
|
+
# Notify all after_selenese_interpreter_started callbacks.
|
48
|
+
def notify_after_selenese_interpreter_started(interpreter)
|
49
|
+
for listener in @after_selenese_interpreter_started_listeners
|
50
|
+
listener.call(interpreter)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# The browser formatted for the Selenese interpreter.
|
55
|
+
def formatted_browser
|
56
|
+
return "*#{@current_browser}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Has a failure occurred in the tests?
|
60
|
+
def failure_has_occurred?
|
61
|
+
@failure_has_occurred = true
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sets the failure state to true
|
65
|
+
def failure_has_occurred!
|
66
|
+
@failure_has_occurred = true
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the failure state to false
|
70
|
+
def failure_has_not_occurred!
|
71
|
+
@failure_has_occurred = false
|
72
|
+
end
|
73
|
+
|
74
|
+
# The http host name and port to be entered into the browser address bar
|
75
|
+
def browser_url
|
76
|
+
"http://#{external_app_server_host}:#{external_app_server_port}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# The root directory (public) of the Rails application
|
80
|
+
def server_root
|
81
|
+
File.expand_path("#{rails_root}/public/")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Sets the Test Suite to open a new browser instance for each TestCase
|
85
|
+
def test_browser_mode!
|
86
|
+
@browser_mode = SeleniumConfiguration::BrowserMode::Test
|
87
|
+
end
|
88
|
+
|
89
|
+
# Are we going to open a new browser instance for each TestCase?
|
90
|
+
def test_browser_mode?
|
91
|
+
@browser_mode == SeleniumConfiguration::BrowserMode::Test
|
92
|
+
end
|
93
|
+
|
94
|
+
# Sets the Test Suite to use one browser instance
|
95
|
+
def suite_browser_mode!
|
96
|
+
@browser_mode = SeleniumConfiguration::BrowserMode::Suite
|
97
|
+
end
|
98
|
+
|
99
|
+
# Does the Test Suite to use one browser instance?
|
100
|
+
def suite_browser_mode?
|
101
|
+
@browser_mode == SeleniumConfiguration::BrowserMode::Suite
|
102
|
+
end
|
103
|
+
|
104
|
+
def run_each_browser # nodoc
|
105
|
+
browsers.each do |browser|
|
106
|
+
self.current_browser = browser
|
107
|
+
yield
|
108
|
+
break if @failure_has_occurred
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# The Selenese Interpreter object. This is the Interpreter provided by the Selenium RC (http://openqa.org/selenium-rc/) project.
|
113
|
+
def selenese_interpreter
|
114
|
+
return nil unless suite_browser_mode?
|
115
|
+
unless @interpreter
|
116
|
+
@interpreter = create_and_initialize_interpreter
|
117
|
+
end
|
118
|
+
@interpreter
|
119
|
+
end
|
120
|
+
|
121
|
+
def stop_interpreter_if_necessary(suite_passed) # nodoc
|
122
|
+
failure_has_occurred! unless suite_passed
|
123
|
+
if @interpreter && stop_selenese_interpreter?(suite_passed)
|
124
|
+
@interpreter.stop
|
125
|
+
@interpreter = nil
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def stop_selenese_interpreter?(passed) # nodoc
|
130
|
+
return true if passed
|
131
|
+
return !keep_browser_open_on_failure
|
132
|
+
end
|
133
|
+
|
134
|
+
def create_app_server_checker # nodoc
|
135
|
+
app_server_checker = AppServerChecker.new()
|
136
|
+
app_server_checker.context = self
|
137
|
+
app_server_checker.tcp_socket_class = TCPSocket
|
138
|
+
return app_server_checker
|
139
|
+
end
|
140
|
+
|
141
|
+
def create_and_initialize_interpreter # nodoc
|
142
|
+
interpreter = create_interpreter
|
143
|
+
interpreter.start
|
144
|
+
notify_after_selenese_interpreter_started(interpreter)
|
145
|
+
interpreter
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_interpreter # nodoc
|
149
|
+
return Selenium::SeleneseInterpreter.new(
|
150
|
+
selenium_server_host,
|
151
|
+
selenium_server_port,
|
152
|
+
formatted_browser,
|
153
|
+
browser_url,
|
154
|
+
15000
|
155
|
+
)
|
156
|
+
end
|
157
|
+
|
158
|
+
def create_server_runner # nodoc
|
159
|
+
case @app_server_engine.to_sym
|
160
|
+
when :mongrel
|
161
|
+
create_mongrel_runner
|
162
|
+
when :webrick
|
163
|
+
create_webrick_runner
|
164
|
+
else
|
165
|
+
raise "Invalid server type: #{selenium_context.app_server_type}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def create_webrick_runner # nodoc
|
170
|
+
require 'webrick_server'
|
171
|
+
runner = WebrickSeleniumServerRunner.new
|
172
|
+
runner.context = self
|
173
|
+
runner.thread_class = Thread
|
174
|
+
runner.socket = Socket
|
175
|
+
runner.dispatch_servlet = DispatchServlet
|
176
|
+
runner.environment_path = File.expand_path("#{@rails_root}/config/environment")
|
177
|
+
runner
|
178
|
+
end
|
179
|
+
|
180
|
+
def create_webrick_server # nodoc
|
181
|
+
WEBrick::HTTPServer.new({
|
182
|
+
:Port => @internal_app_server_port,
|
183
|
+
:BindAddress => @internal_app_server_host,
|
184
|
+
:ServerType => WEBrick::SimpleServer,
|
185
|
+
:MimeTypes => WEBrick::HTTPUtils::DefaultMimeTypes,
|
186
|
+
:Logger => new_logger,
|
187
|
+
:AccessLog => []
|
188
|
+
})
|
189
|
+
end
|
190
|
+
|
191
|
+
def new_logger
|
192
|
+
Logger.new(StringIO.new)
|
193
|
+
end
|
194
|
+
|
195
|
+
def create_mongrel_runner # nodoc
|
196
|
+
runner = MongrelSeleniumServerRunner.new
|
197
|
+
runner.context = self
|
198
|
+
runner.thread_class = Thread
|
199
|
+
runner
|
200
|
+
end
|
201
|
+
|
202
|
+
def create_mongrel_configurator # nodoc
|
203
|
+
dir = File.dirname(__FILE__)
|
204
|
+
require 'mongrel/rails'
|
205
|
+
settings = {
|
206
|
+
:host => internal_app_server_host,
|
207
|
+
:port => internal_app_server_port,
|
208
|
+
:cwd => @rails_root,
|
209
|
+
:log_file => "#{@rails_root}/log/mongrel.log",
|
210
|
+
:pid_file => "#{@rails_root}/log/mongrel.pid",
|
211
|
+
:environment => @rails_env,
|
212
|
+
:docroot => "public",
|
213
|
+
:mime_map => nil,
|
214
|
+
:daemon => false,
|
215
|
+
:debug => false,
|
216
|
+
:includes => ["mongrel"],
|
217
|
+
:config_script => nil
|
218
|
+
}
|
219
|
+
|
220
|
+
configurator = Mongrel::Rails::RailsConfigurator.new(settings) do
|
221
|
+
log "Starting Mongrel in #{defaults[:environment]} mode at #{defaults[:host]}:#{defaults[:port]}"
|
222
|
+
end
|
223
|
+
configurator
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module Seleniumrc
|
2
|
+
class SeleniumElement
|
3
|
+
include WaitFor
|
4
|
+
attr_reader :selenium, :locator
|
5
|
+
|
6
|
+
def initialize(selenium, locator)
|
7
|
+
@selenium = selenium
|
8
|
+
@locator = locator
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_present(params={})
|
12
|
+
params = {:message => "Expected element '#{locator}' to be present, but it was not"}.merge(params)
|
13
|
+
wait_for(params) do
|
14
|
+
is_present?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def is_present?
|
18
|
+
selenium.is_element_present(locator)
|
19
|
+
end
|
20
|
+
|
21
|
+
def is_not_present(params={})
|
22
|
+
params = {:message => "Expected element '#{locator}' to be absent, but it was not"}.merge(params)
|
23
|
+
wait_for(:message => params[:message]) do
|
24
|
+
is_not_present?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
def is_not_present?
|
28
|
+
!selenium.is_element_present(locator)
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_value(expected_value)
|
32
|
+
is_present
|
33
|
+
wait_for do |context|
|
34
|
+
actual_value = selenium.get_value(locator)
|
35
|
+
context.message = "Expected '#{locator}' to be '#{expected_value}' but was '#{actual_value}'"
|
36
|
+
has_value? expected_value, actual_value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
def has_value?(expected_value, actual_value=selenium.get_value(locator))
|
40
|
+
expected_value == actual_value
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_attribute(expected_value)
|
44
|
+
is_present
|
45
|
+
wait_for do |context|
|
46
|
+
actual = selenium.get_attribute(locator) #todo: actual value
|
47
|
+
context.message = "Expected attribute '#{locator}' to be '#{expected_value}' but was '#{actual}'"
|
48
|
+
expected_value == actual
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_selected(expected_value)
|
53
|
+
is_present
|
54
|
+
wait_for do |context|
|
55
|
+
actual = selenium.get_selected_label(locator)
|
56
|
+
context.message = "Expected '#{locator}' to be selected with '#{expected_value}' but was '#{actual}"
|
57
|
+
expected_value == actual
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def is_visible(options={})
|
62
|
+
is_present
|
63
|
+
options = {
|
64
|
+
:message => "Expected '#{locator}' to be visible, but it wasn't"
|
65
|
+
}.merge(options)
|
66
|
+
wait_for(options) do
|
67
|
+
selenium.is_visible(locator)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def is_not_visible(options={})
|
72
|
+
is_present
|
73
|
+
options = {
|
74
|
+
:message => "Expected '#{locator}' to be hidden, but it wasn't"
|
75
|
+
}.merge(options)
|
76
|
+
wait_for(options) do
|
77
|
+
!selenium.is_visible(locator)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def is_checked
|
82
|
+
is_present
|
83
|
+
wait_for(:message => "Expected '#{locator}' to be checked") do
|
84
|
+
selenium.is_checked(locator)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def is_not_checked
|
89
|
+
is_present
|
90
|
+
wait_for(:message => "Expected '#{locator}' to be checked") do
|
91
|
+
!selenium.is_checked(locator)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def has_text(expected_text, options={})
|
96
|
+
is_present
|
97
|
+
wait_for(options) do |context|
|
98
|
+
actual = selenium.get_text(locator)
|
99
|
+
context.message = "Expected text '#{expected_text}' to be full contents of #{locator} but was '#{actual}')"
|
100
|
+
expected_text == actual
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def contains_text(expected_text, options={})
|
105
|
+
is_present
|
106
|
+
options = {
|
107
|
+
:message => "#{locator} should contain #{expected_text}"
|
108
|
+
}.merge(options)
|
109
|
+
wait_for(options) do
|
110
|
+
inner_html.include?(expected_text)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def does_not_contain_text(expected_text, options={})
|
115
|
+
is_present
|
116
|
+
wait_for(options) do
|
117
|
+
!inner_html.include?(expected_text)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def has_next_sibling(expected_sibling_id, options = {})
|
122
|
+
is_present
|
123
|
+
eval_js = "this.page().findElement('#{locator}').nextSibling.id"
|
124
|
+
wait_for(:message => "id '#{locator}' should be next to '#{expected_sibling_id}'") do
|
125
|
+
actual_sibling_id = selenium.get_eval(eval_js)
|
126
|
+
expected_sibling_id == actual_sibling_id
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def has_text_in_order(*text_fragments)
|
131
|
+
is_present
|
132
|
+
wait_for do |context|
|
133
|
+
success = false
|
134
|
+
|
135
|
+
html = selenium.get_text(locator)
|
136
|
+
results = find_text_order_error_fragments(html, text_fragments)
|
137
|
+
fragments_not_found = results[:fragments_not_found]
|
138
|
+
fragments_out_of_order = results[:fragments_out_of_order]
|
139
|
+
|
140
|
+
if !fragments_not_found.empty?
|
141
|
+
context.message = "Certain fragments weren't found:\n" <<
|
142
|
+
"#{fragments_not_found.join("\n")}\n" <<
|
143
|
+
"\nhtml follows:\n #{html}\n"
|
144
|
+
elsif !fragments_out_of_order.empty?
|
145
|
+
context.message = "Certain fragments were out of order:\n" <<
|
146
|
+
"#{fragments_out_of_order.join("\n")}\n" <<
|
147
|
+
"\nhtml follows:\n #{html}\n"
|
148
|
+
else
|
149
|
+
success = true
|
150
|
+
end
|
151
|
+
|
152
|
+
success
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def inner_html
|
157
|
+
selenium.get_eval("this.page().findElement(\"#{locator}\").innerHTML")
|
158
|
+
end
|
159
|
+
|
160
|
+
def ==(other)
|
161
|
+
return false unless other.is_a?(SeleniumElement)
|
162
|
+
return false unless self.selenium == other.selenium
|
163
|
+
return false unless self.locator == other.locator
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
protected
|
168
|
+
def find_text_order_error_fragments(html, text_fragments)
|
169
|
+
fragments_not_found = []
|
170
|
+
fragments_out_of_order = []
|
171
|
+
|
172
|
+
previous_index = -1
|
173
|
+
previous_fragment = ''
|
174
|
+
text_fragments.each do |current_fragment|
|
175
|
+
current_index = html.index(current_fragment)
|
176
|
+
if current_index
|
177
|
+
if current_index < previous_index
|
178
|
+
message = "Fragment #{current_fragment} out of order:\n" <<
|
179
|
+
"\texpected '#{previous_fragment}'\n" <<
|
180
|
+
"\tto come before '#{current_fragment}'\n"
|
181
|
+
fragments_out_of_order << message
|
182
|
+
end
|
183
|
+
else
|
184
|
+
fragments_not_found << "Fragment #{current_fragment} was not found\n"
|
185
|
+
end
|
186
|
+
previous_index = current_index
|
187
|
+
previous_fragment = current_fragment
|
188
|
+
end
|
189
|
+
{
|
190
|
+
:fragments_not_found => fragments_not_found,
|
191
|
+
:fragments_out_of_order => fragments_out_of_order
|
192
|
+
}
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
# Expand the path to environment so that Ruby does not load it multiple times
|
2
|
+
# File.expand_path can be removed if Ruby 1.9 is in use.
|
3
|
+
if (Object.const_defined?(:ActiveRecord) && !ActiveRecord::Base.allow_concurrency)
|
4
|
+
raise "Since Selenium spawns an internal app server, we need ActiveRecord to be multi-threaded. Please set 'ActiveRecord::Base.allow_concurrency = true' in your environment file (e.g. test.rb)."
|
5
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Seleniumrc
|
2
|
+
class SeleniumPage
|
3
|
+
include WaitFor
|
4
|
+
attr_reader :selenium
|
5
|
+
PAGE_LOADED_COMMAND = "this.browserbot.getDocument().body ? true : false"
|
6
|
+
|
7
|
+
def initialize(selenium)
|
8
|
+
@selenium = selenium
|
9
|
+
end
|
10
|
+
|
11
|
+
def open_and_wait(url)
|
12
|
+
selenium.open(url)
|
13
|
+
wait_for_page_to_load
|
14
|
+
end
|
15
|
+
|
16
|
+
def has_title(expected_title, params = {})
|
17
|
+
wait_for(params) do |context|
|
18
|
+
actual_title = selenium.get_title
|
19
|
+
context.message = "Expected title '#{expected_title}' but was '#{actual_title}'"
|
20
|
+
has_title? expected_title, actual_title
|
21
|
+
end
|
22
|
+
end
|
23
|
+
def has_title?(expected_title, actual_title=selenium.get_title)
|
24
|
+
expected_title == actual_title
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_text_present(expected_text, options = {})
|
28
|
+
options = {
|
29
|
+
:message => "Expected '#{expected_text}' to be present, but it wasn't"
|
30
|
+
}.merge(options)
|
31
|
+
wait_for(options) do
|
32
|
+
is_text_present? expected_text
|
33
|
+
end
|
34
|
+
end
|
35
|
+
def is_text_present?(expected_text)
|
36
|
+
page_loaded? && selenium.is_text_present(expected_text)
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_text_not_present(unexpected_text, options = {})
|
40
|
+
options = {
|
41
|
+
:message => "Expected '#{unexpected_text}' to be absent, but it wasn't"
|
42
|
+
}.merge(options)
|
43
|
+
wait_for(options) do
|
44
|
+
is_text_not_present? unexpected_text
|
45
|
+
end
|
46
|
+
end
|
47
|
+
def is_text_not_present?(unexpected_text)
|
48
|
+
page_loaded? && !selenium.is_text_present(unexpected_text)
|
49
|
+
end
|
50
|
+
|
51
|
+
def page_loaded?
|
52
|
+
selenium.get_eval(PAGE_LOADED_COMMAND) == true.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
def url_ends_with(ends_with, options={})
|
56
|
+
options = {
|
57
|
+
:message => "Expected '#{selenium.get_location}' to end with '#{ends_with}'"
|
58
|
+
}.merge(options)
|
59
|
+
wait_for(options) do
|
60
|
+
url_ends_with? ends_with
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def url_ends_with?(ends_with)
|
64
|
+
if selenium.get_location =~ Regexp.new("#{Regexp.escape(ends_with)}$")
|
65
|
+
true
|
66
|
+
else
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other)
|
72
|
+
return false unless other.is_a?(SeleniumPage)
|
73
|
+
self.selenium == other.selenium
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Seleniumrc
|
2
|
+
class SeleniumServerRunner
|
3
|
+
attr_accessor :context, :thread_class
|
4
|
+
def initialize
|
5
|
+
@started = false
|
6
|
+
end
|
7
|
+
|
8
|
+
def start
|
9
|
+
@thread_class.start do
|
10
|
+
start_server
|
11
|
+
end
|
12
|
+
@started = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop
|
16
|
+
stop_server
|
17
|
+
@started = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def started?
|
21
|
+
@started
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
def start_server
|
26
|
+
raise NotImplementedError.new("this is abstract!")
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop_server
|
30
|
+
raise NotImplementedError.new("this is abstract!")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Seleniumrc
|
2
|
+
# The Test Case class that runs your Selenium tests.
|
3
|
+
# You are able to use all methods provided by Selenium::SeleneseInterpreter with some additions.
|
4
|
+
class SeleniumTestCase < Test::Unit::TestCase
|
5
|
+
module ClassMethods
|
6
|
+
def subclasses
|
7
|
+
@subclasses ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def inherited(subclass)
|
11
|
+
# keep a list of all subclasses on the fly, so we can run them all later from the Runner
|
12
|
+
subclasses << subclass unless subclasses.include?(subclass)
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def all_subclasses_as_suite(configuration)
|
17
|
+
suite = Test::Unit::TestSuite.new
|
18
|
+
all_descendant_classes.each do |test_case_class|
|
19
|
+
test_case_class.suite.tests.each do |test_case|
|
20
|
+
test_case.configuration = configuration
|
21
|
+
suite << test_case
|
22
|
+
end
|
23
|
+
end
|
24
|
+
suite
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_descendant_classes
|
28
|
+
extract_subclasses(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_subclasses(parent_class)
|
32
|
+
classes = []
|
33
|
+
parent_class.subclasses.each do |subclass|
|
34
|
+
classes << subclass
|
35
|
+
classes.push(*extract_subclasses(subclass))
|
36
|
+
end
|
37
|
+
classes
|
38
|
+
end
|
39
|
+
|
40
|
+
unless Object.const_defined?(:RAILS_ROOT)
|
41
|
+
attr_accessor :use_transactional_fixtures, :use_instantiated_fixtures
|
42
|
+
end
|
43
|
+
end
|
44
|
+
extend ClassMethods
|
45
|
+
|
46
|
+
self.use_transactional_fixtures = false
|
47
|
+
self.use_instantiated_fixtures = true
|
48
|
+
|
49
|
+
include SeleniumDsl
|
50
|
+
def setup
|
51
|
+
# set "setup_once" to true
|
52
|
+
# to prevent fixtures from being re-loaded and data deleted from the DB.
|
53
|
+
# this is handy if you want to generate a DB full of sample data
|
54
|
+
# from the tests. Make sure none of your selenium tests manually
|
55
|
+
# reset data!
|
56
|
+
#TODO: make this configurable
|
57
|
+
setup_once = false
|
58
|
+
|
59
|
+
raise "Cannot use transactional fixtures if ActiveRecord concurrency is turned on (which is required for Selenium tests to work)." if self.class.use_transactional_fixtures
|
60
|
+
# @beginning = time_class.now
|
61
|
+
unless setup_once
|
62
|
+
ActiveRecord::Base.connection.update('SET FOREIGN_KEY_CHECKS = 0')
|
63
|
+
super
|
64
|
+
ActiveRecord::Base.connection.update('SET FOREIGN_KEY_CHECKS = 1')
|
65
|
+
else
|
66
|
+
unless InstanceMethods.const_defined?("ALREADY_SETUP_ONCE")
|
67
|
+
super
|
68
|
+
InstanceMethods.const_set("ALREADY_SETUP_ONCE", true)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
# puts self.class.to_s + "#" + @method_name
|
72
|
+
@selenium = configuration.selenese_interpreter
|
73
|
+
end
|
74
|
+
|
75
|
+
def teardown
|
76
|
+
@selenium.stop if should_stop_selenese_interpreter?
|
77
|
+
super
|
78
|
+
if @beginning
|
79
|
+
duration = (time_class.now - @beginning).to_f
|
80
|
+
puts "#{duration} seconds"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def selenium_test_case
|
85
|
+
@selenium_test_case ||= SeleniumTestCase
|
86
|
+
end
|
87
|
+
|
88
|
+
def run(result, &block)
|
89
|
+
return if @method_name.nil? || @method_name.to_sym == :default_test
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
SeleniumTestCase = Seleniumrc::SeleniumTestCase
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Seleniumrc
|
3
|
+
module Tasks
|
4
|
+
class SeleniumTestTask
|
5
|
+
attr_reader :rails_env, :rails_root
|
6
|
+
|
7
|
+
def initialize(rails_env = RAILS_ENV, rails_root = RAILS_ROOT)
|
8
|
+
@rails_env = rails_env
|
9
|
+
@rails_root = rails_root
|
10
|
+
end
|
11
|
+
|
12
|
+
def invoke(suite_relative_path = "test/selenium/selenium_suite")
|
13
|
+
rails_env.replace "test"
|
14
|
+
require "#{rails_root}/" + suite_relative_path
|
15
|
+
|
16
|
+
passed = Test::Unit::AutoRunner.run
|
17
|
+
raise "Test failures" unless passed
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Seleniumrc
|
2
|
+
module WaitFor
|
3
|
+
Context = Struct.new(:message)
|
4
|
+
# Poll continuously for the return value of the block to be true. You can use this to assert that a client side
|
5
|
+
# or server side condition was met.
|
6
|
+
# wait_for do
|
7
|
+
# User.count == 5
|
8
|
+
# end
|
9
|
+
def wait_for(params={})
|
10
|
+
timeout = params[:timeout] || default_wait_for_time
|
11
|
+
message = params[:message] || "Timeout exceeded"
|
12
|
+
context = Context.new(message)
|
13
|
+
begin_time = time_class.now
|
14
|
+
while (time_class.now - begin_time) < timeout
|
15
|
+
return if yield(context)
|
16
|
+
sleep 0.25
|
17
|
+
end
|
18
|
+
flunk(context.message + " (after #{timeout} sec)")
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_wait_for_time
|
23
|
+
5
|
24
|
+
end
|
25
|
+
|
26
|
+
def time_class
|
27
|
+
Time
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait_for_page_to_load(timeout=default_timeout)
|
31
|
+
selenium.wait_for_page_to_load timeout
|
32
|
+
if selenium.get_title.include?("Exception caught")
|
33
|
+
flunk "We got a new page, but it was an application exception page.\n\n" + get_html_source
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# The default Selenium Core client side timeout.
|
38
|
+
def default_timeout
|
39
|
+
@default_timeout ||= 20000
|
40
|
+
end
|
41
|
+
attr_writer :default_timeout
|
42
|
+
|
43
|
+
def flunk(message)
|
44
|
+
raise Test::Unit::AssertionFailedError, message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|