rsel 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.
Files changed (59) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +7 -0
  3. data/Gemfile +2 -0
  4. data/MIT-LICENSE +22 -0
  5. data/README.md +35 -0
  6. data/Rakefile +75 -0
  7. data/docs/custom.md +87 -0
  8. data/docs/development.md +48 -0
  9. data/docs/index.md +13 -0
  10. data/docs/install.md +33 -0
  11. data/docs/todo.md +15 -0
  12. data/docs/usage.md +50 -0
  13. data/lib/rsel.rb +4 -0
  14. data/lib/rsel/exceptions.rb +4 -0
  15. data/lib/rsel/selenium_test.rb +524 -0
  16. data/rsel.gemspec +29 -0
  17. data/spec/selenium_test_spec.rb +296 -0
  18. data/spec/spec_helper.rb +41 -0
  19. data/test/app.rb +34 -0
  20. data/test/config.ru +4 -0
  21. data/test/views/about.erb +9 -0
  22. data/test/views/form.erb +102 -0
  23. data/test/views/index.erb +15 -0
  24. data/vendor/rubyslim-0.1.1/.gitignore +7 -0
  25. data/vendor/rubyslim-0.1.1/README.md +159 -0
  26. data/vendor/rubyslim-0.1.1/Rakefile +21 -0
  27. data/vendor/rubyslim-0.1.1/bin/rubyslim +10 -0
  28. data/vendor/rubyslim-0.1.1/lib/rubyslim/list_deserializer.rb +69 -0
  29. data/vendor/rubyslim-0.1.1/lib/rubyslim/list_executor.rb +12 -0
  30. data/vendor/rubyslim-0.1.1/lib/rubyslim/list_serializer.rb +45 -0
  31. data/vendor/rubyslim-0.1.1/lib/rubyslim/ruby_slim.rb +61 -0
  32. data/vendor/rubyslim-0.1.1/lib/rubyslim/slim_error.rb +3 -0
  33. data/vendor/rubyslim-0.1.1/lib/rubyslim/slim_helper_library.rb +23 -0
  34. data/vendor/rubyslim-0.1.1/lib/rubyslim/socket_service.rb +53 -0
  35. data/vendor/rubyslim-0.1.1/lib/rubyslim/statement.rb +79 -0
  36. data/vendor/rubyslim-0.1.1/lib/rubyslim/statement_executor.rb +174 -0
  37. data/vendor/rubyslim-0.1.1/lib/rubyslim/table_to_hash_converter.rb +32 -0
  38. data/vendor/rubyslim-0.1.1/lib/test_module/library_new.rb +13 -0
  39. data/vendor/rubyslim-0.1.1/lib/test_module/library_old.rb +12 -0
  40. data/vendor/rubyslim-0.1.1/lib/test_module/should_not_find_test_slim_in_here/test_slim.rb +7 -0
  41. data/vendor/rubyslim-0.1.1/lib/test_module/simple_script.rb +9 -0
  42. data/vendor/rubyslim-0.1.1/lib/test_module/test_chain.rb +5 -0
  43. data/vendor/rubyslim-0.1.1/lib/test_module/test_slim.rb +86 -0
  44. data/vendor/rubyslim-0.1.1/lib/test_module/test_slim_with_arguments.rb +23 -0
  45. data/vendor/rubyslim-0.1.1/lib/test_module/test_slim_with_no_sut.rb +5 -0
  46. data/vendor/rubyslim-0.1.1/rubyslim.gemspec +22 -0
  47. data/vendor/rubyslim-0.1.1/spec/instance_creation_spec.rb +40 -0
  48. data/vendor/rubyslim-0.1.1/spec/it8f_spec.rb +8 -0
  49. data/vendor/rubyslim-0.1.1/spec/list_deserializer_spec.rb +66 -0
  50. data/vendor/rubyslim-0.1.1/spec/list_executor_spec.rb +187 -0
  51. data/vendor/rubyslim-0.1.1/spec/list_serialzer_spec.rb +38 -0
  52. data/vendor/rubyslim-0.1.1/spec/method_invocation_spec.rb +127 -0
  53. data/vendor/rubyslim-0.1.1/spec/slim_helper_library_spec.rb +80 -0
  54. data/vendor/rubyslim-0.1.1/spec/socket_service_spec.rb +98 -0
  55. data/vendor/rubyslim-0.1.1/spec/spec_helper.rb +6 -0
  56. data/vendor/rubyslim-0.1.1/spec/statement_executor_spec.rb +50 -0
  57. data/vendor/rubyslim-0.1.1/spec/statement_spec.rb +17 -0
  58. data/vendor/rubyslim-0.1.1/spec/table_to_hash_converter_spec.rb +31 -0
  59. metadata +241 -0
@@ -0,0 +1,4 @@
1
+ Gemfile.lock
2
+ .yardoc/*
3
+ .rvmrc
4
+ selenium-rc.log
@@ -0,0 +1,7 @@
1
+ --readme docs/index.md
2
+ --markup markdown
3
+ --no-highlight
4
+ --exclude lib/rsel/selenium.rb
5
+ lib/rsel/*.rb
6
+ -
7
+ docs/*.md
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Automation Excellence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ Rsel
2
+ ====
3
+
4
+ Rsel connects [FitNesse](http://fitnesse.org) to [Selenium](http://seleniumhq.org)
5
+ via [Ruby](http://ruby-lang.org).
6
+
7
+ [Full documentation is on rdoc.info](http://rdoc.info/github/a-e/rsel/master/frames).
8
+
9
+
10
+ Copyright
11
+ ---------
12
+
13
+ The MIT License
14
+
15
+ Copyright (c) 2011 Automation Excellence
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining
18
+ a copy of this software and associated documentation files (the
19
+ "Software"), to deal in the Software without restriction, including
20
+ without limitation the rights to use, copy, modify, merge, publish,
21
+ distribute, sublicense, and/or sell copies of the Software, and to
22
+ permit persons to whom the Software is furnished to do so, subject to
23
+ the following conditions:
24
+
25
+ The above copyright notice and this permission notice shall be
26
+ included in all copies or substantial portions of the Software.
27
+
28
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
31
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
32
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
33
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35
+
@@ -0,0 +1,75 @@
1
+ require 'rake'
2
+ require 'net/http'
3
+ require 'selenium/rake/tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ ROOT_DIR = File.expand_path(File.dirname(__FILE__))
7
+ TEST_APP = File.join(ROOT_DIR, 'test', 'app.rb')
8
+ SELENIUM_RC_JAR = File.join(ROOT_DIR, 'test', 'server', 'selenium-server-1.0.3-SNAPSHOT-standalone.jar')
9
+ SELENIUM_RC_LOG = File.join(ROOT_DIR, 'selenium-rc.log')
10
+
11
+ namespace 'testapp' do
12
+ desc "Start the test webapp in the background"
13
+ task :start do
14
+ puts "Starting the test webapp: #{TEST_APP}"
15
+ system("ruby #{TEST_APP} &")
16
+ end
17
+
18
+ desc "Stop the test webapp"
19
+ task :stop do
20
+ puts "Stopping the test webapp"
21
+ begin
22
+ Net::HTTP.get('localhost', '/shutdown', 8070)
23
+ rescue EOFError
24
+ # This is expected
25
+ puts "Test webapp stopped."
26
+ end
27
+ end
28
+ end
29
+
30
+ Selenium::Rake::RemoteControlStartTask.new do |rc|
31
+ rc.jar_file = SELENIUM_RC_JAR
32
+ rc.port = 4444
33
+ rc.background = true
34
+ rc.timeout_in_seconds = 60
35
+ rc.wait_until_up_and_running = true
36
+ rc.log_to = SELENIUM_RC_LOG
37
+ end
38
+
39
+ Selenium::Rake::RemoteControlStopTask.new do |rc|
40
+ rc.host = 'localhost'
41
+ rc.port = 4444
42
+ rc.timeout_in_seconds = 60
43
+ rc.wait_until_stopped = true
44
+ end
45
+
46
+ desc "Run spec tests"
47
+ RSpec::Core::RakeTask.new(:spec) do |t|
48
+ t.pattern = 'spec/**/*_spec.rb'
49
+ t.rspec_opts = ['--color', '--format doc']
50
+ end
51
+
52
+ namespace 'servers' do
53
+ desc "Start the Selenium and testapp servers"
54
+ task :start do
55
+ Rake::Task['testapp:start'].invoke
56
+ Rake::Task['selenium:rc:start'].invoke
57
+ end
58
+
59
+ desc "Stop the Selenium and testapp servers"
60
+ task :stop do
61
+ Rake::Task['selenium:rc:stop'].invoke
62
+ Rake::Task['testapp:stop'].invoke
63
+ end
64
+ end
65
+
66
+ desc "Start Selenium and testapp servers, run tests, then stop servers"
67
+ task :test do
68
+ begin
69
+ Rake::Task['servers:start'].invoke
70
+ Rake::Task['spec'].invoke
71
+ ensure
72
+ Rake::Task['servers:stop'].invoke
73
+ end
74
+ end
75
+
@@ -0,0 +1,87 @@
1
+ Customization
2
+ -------------
3
+
4
+ Rsel provides only the most basic imperative navigational steps and
5
+ verification. For real-world testing, you will most likely want to define your
6
+ own steps, built up from the low-level ones provided by Rsel.
7
+
8
+ It's pretty easy to do this by subclassing `SeleniumTest` and adding your own
9
+ methods to it. Create a sibling directory to your `FitNesseRoot`, named
10
+ something like `custom_rsel`, then create a Ruby file in there. The Ruby file
11
+ can be called whatever you want--it's most logical to name it after your
12
+ application:
13
+
14
+ - `FitNesseRoot`
15
+ - `custom_rsel`
16
+ - `my_app_test.rb`
17
+
18
+ Inside `my_app_test.rb`, you must define a module named after the folder, and a
19
+ class named after the file. You'll also need to include some `require` statements
20
+ to make sure `rsel/selenium` gets loaded. In this case, it should be:
21
+
22
+ require `rubygems` # If you installed Rsel from a gem
23
+ require `rsel/selenium_test` # Makes the SeleniumTest class available
24
+
25
+ module CustomRsel
26
+ class MyAppTest < Rsel::SeleniumTest
27
+ # Custom methods go here
28
+ end
29
+ end
30
+
31
+ Inside your custom class, you can define any methods you want, and you can call
32
+ any of the methods defined in the `Rsel::SeleniumTest` class. For example,
33
+ let's say the login process for your application consists of three steps:
34
+
35
+ | Fill in | Username | with | admin |
36
+ | Fill in | Password | with | letmein |
37
+ | Press | Login |
38
+
39
+ If you're logging in a lot, you may want a single-step login method. Here's how
40
+ you might define one:
41
+
42
+ module CustomRsel
43
+ class MyAppTest < Rsel::SeleniumTest
44
+
45
+ # Login with the given username and password
46
+ #
47
+ # Example:
48
+ # | Login as | admin | with password | letmein |
49
+ #
50
+ def login_as_with_password(username, password)
51
+ fill_in_with("Username", username)
52
+ fill_in_with("Password", password)
53
+ press("Login")
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ In your FitNesse `SetUp` page, rather than (or in addition to) importing the `Rsel` module,
60
+ import your custom module:
61
+
62
+ !| import |
63
+ | CustomRsel |
64
+
65
+ Note that this name must match the `module` line in your Ruby file, and the
66
+ folder where your Ruby file resides must be the lowercase_and_underscore
67
+ version of that same module name. Finally, in your actual test table, instead of:
68
+
69
+ !| script | selenium test | ... |
70
+
71
+ You'll use:
72
+
73
+ !| script | my app test | ... |
74
+
75
+ This will ensure that the `MyAppTest` class will be used for evaluating the
76
+ steps contained in that table.
77
+
78
+ It's worth noting that the `SeleniumTest` class (and hence any derived classes)
79
+ have a `@browser` attribute referring to the underlying `Selenium::Client::Driver`
80
+ instance, which includes
81
+ [a wealth of methods](http://rdoc.info/github/ph7/selenium-client/master/Selenium/Client/Driver)
82
+ for interacting with webpages and performing verifications. If `SeleniumTest`
83
+ itself does not provide the methods you need, you can use the `@browser`
84
+ attribute directly.
85
+
86
+ Next: [Development](development.md)
87
+
@@ -0,0 +1,48 @@
1
+ Development
2
+ -----------
3
+
4
+ If you would like to contribute to development of Rsel, create a fork of the
5
+ [Github repository](https://github.com/a-e/rsel), clone your fork, and submit
6
+ pull requests for any improvements you would like to share.
7
+
8
+ While developing, you should run the RSpec tests frequently to ensure nothing
9
+ gets broken. You can do this with a single `rake` command:
10
+
11
+ $ rake test
12
+
13
+ This will do the following:
14
+
15
+ - Start the Sinatra test application (code in `test/app.rb` and `test/views/*.erb`)
16
+ - Start the Selenium server (the `.jar` file in `test/server`)
17
+ - Run RSpec
18
+ - Stop the Selenium server
19
+ - Stop the Sinatra test application
20
+
21
+ Since there's a large startup cost associated with all of this (in particular
22
+ the Selenium server), when running frequent tests you may want to start those
23
+ up and keep them running. There are rake tasks for that also:
24
+
25
+ $ rake servers:start
26
+
27
+ Now do:
28
+
29
+ $ rake spec
30
+
31
+ as many times as you want. When you're done, manually stop the servers:
32
+
33
+ $ rake servers:stop
34
+
35
+ You can also start the Selenium and Sinatra testapp servers individually:
36
+
37
+ $ rake selenium:rc:start
38
+ $ rake testapp:start
39
+
40
+ and stop them:
41
+
42
+ $ rake selenium:rc:stop
43
+ $ rake testapp:stop
44
+
45
+ If you are developing new features, please add spec tests in the `spec`
46
+ directory!
47
+
48
+ Next: [To-do list](todo.md)
@@ -0,0 +1,13 @@
1
+ Rsel
2
+ ====
3
+
4
+ Rsel connects [FitNesse](http://fitnesse.org) to
5
+ [Selenium](http://seleniumhq.org) via [Ruby](http://ruby-lang.org). It allows
6
+ you to use a natural English syntax to write web application tests.
7
+
8
+ - [Installation & Configuration](install.md)
9
+ - [Usage](usage.md)
10
+ - [Customization](custom.md)
11
+ - [Development](development.md)
12
+ - [To-do list](todo.md)
13
+
@@ -0,0 +1,33 @@
1
+ Installation & Configuration
2
+ ----------------------------
3
+
4
+ To install Rsel from a gem:
5
+
6
+ $ gem install rsel
7
+
8
+ If you have the following test hierarchy:
9
+
10
+ - `FitNesseRoot`
11
+ - `SeleniumTests`
12
+ - `SetUp`
13
+ - `LoginTest`
14
+
15
+ You should define the requisite `rubyslim` options in the `SeleniumTests` page content:
16
+
17
+ !define TEST_SYSTEM {slim}
18
+ !define TEST_RUNNER {rubyslim}
19
+ !define COMMAND_PATTERN {rubyslim}
20
+
21
+ If you're using Bundler, you may need to use:
22
+
23
+ !define TEST_SYSTEM {slim}
24
+ !define TEST_RUNNER {bundle exec rubyslim}
25
+ !define COMMAND_PATTERN {bundle exec rubyslim}
26
+
27
+ Finally, put this in your `SeleniumTests.SetUp` page:
28
+
29
+ !| import |
30
+ | Rsel |
31
+
32
+ Next: [Usage](usage.md)
33
+
@@ -0,0 +1,15 @@
1
+ To-do list
2
+ ----------
3
+
4
+ - Pass better error messages back to FitNesse. It seems that Slim script tables
5
+ only support true/false results, with no clear way to report on what went
6
+ wrong (aside from raising an exception, which even then curiously does not
7
+ mark the step as failed)
8
+ - Find a way to abort a test if something goes catastrophically wrong (such as
9
+ being unable to connect to the Selenium server)
10
+
11
+ Some additional step methods needed:
12
+
13
+ - dropdown_includes
14
+ - dropdown_equals
15
+
@@ -0,0 +1,50 @@
1
+ Usage
2
+ -----
3
+
4
+ Once you have created your `SetUp` page, you can create sibling pages with
5
+ tests in them. For instance, continuing with the example from
6
+ [Installation & Configuration](install.md), your `SeleniumTests.LoginTest`
7
+ might look like this:
8
+
9
+ !| script | selenium test | http://www.mysite.com |
10
+ | Open browser |
11
+ | Fill in | Username | with | castle |
12
+ | Fill in | Password | with | beckett |
13
+ | Click button | Log in |
14
+ | Page loads in | 5 | seconds or less |
15
+ | See | Logged in as castle |
16
+ | Close browser |
17
+
18
+ Before running a test, you must make sure you have Selenium Server installed and running.
19
+ Download [selenium-server-standalone-x.x.x.jar](http://seleniumhq.org/download/), and start
20
+ it up like this:
21
+
22
+ $ java -jar selenium-server-standalone-x.x.x.jar
23
+
24
+ By default, the server runs on port 4444, and this is the port that Rsel uses
25
+ unless you tell it otherwise. Rsel also assumes that you're running
26
+ selenium-server on your localhost (that is, the same host where FitNesse is
27
+ running); if you need to use a different host or port number, pass those as
28
+ arguments to the first line of the table. For example, if you are running
29
+ selenium-server on `my.selenium.host`, port `4455`, do this:
30
+
31
+ !| script | selenium test | http://www.mysite.com | my.selenium.host | 4455 |
32
+
33
+ The first argument after `selenium test` is the URL of the site you will be testing.
34
+ This URL is loaded when you call `Open browser`, and all steps that follow are
35
+ assumed to stay within the same domain. You can navigate around the site by
36
+ clicking links and filling in forms just as a human user would; you can also go
37
+ directly to a specific path within the domain with the `Visit` method:
38
+
39
+ | Visit | /some/path |
40
+ | Visit | /some/other/path |
41
+
42
+ These paths are evaluated relative to the domain your test is running in. (It's
43
+ theoretically possible to navigate to a different domain, but the Selenium
44
+ driver frowns upon it.)
45
+
46
+ See the `SeleniumTest` class documentation for a full list of available methods
47
+ and how to use them.
48
+
49
+ Next: [Customization](custom.md)
50
+
@@ -0,0 +1,4 @@
1
+ require 'rsel/selenium_test'
2
+
3
+ module Rsel
4
+ end
@@ -0,0 +1,4 @@
1
+ module Rsel
2
+ class LocatorNotFound < RuntimeError; end
3
+ class SeleniumNotRunning < RuntimeError; end
4
+ end
@@ -0,0 +1,524 @@
1
+ require File.join(File.dirname(__FILE__), 'exceptions')
2
+
3
+ require 'rubygems'
4
+ require 'xpath'
5
+ require 'selenium/client'
6
+
7
+ module Rsel
8
+
9
+ # Main Selenium-test class.
10
+ #
11
+ # @example
12
+ # !| script | selenium test | http://www.example.com/ |
13
+ #
14
+ # NOTE: Function names beginning with these words are forbidden:
15
+ #
16
+ # - check
17
+ # - ensure
18
+ # - reject
19
+ # - note
20
+ # - show
21
+ # - start
22
+ #
23
+ # This is because the above words are keywords in Slim script tables; if
24
+ # the first cell of a script table begins with any of these words, Slim tries
25
+ # to apply special handling to them, which usually doesn't do what you want.
26
+ #
27
+ class SeleniumTest
28
+
29
+ # Initialize a test, connecting to the given Selenium server.
30
+ #
31
+ # @param [String] url
32
+ # Full URL, including http://, of the system under test
33
+ # @param [String] host
34
+ # IP address or hostname where selenium-server is running
35
+ # @param [String] port
36
+ # Port number of selenium-server
37
+ # @param [String] browser
38
+ # Which browser to test with
39
+ #
40
+ # @example
41
+ # | script | selenium test | http://site.to.test/ |
42
+ # | script | selenium test | http://site.to.test/ | 192.168.0.3 | 4445 |
43
+ #
44
+ def initialize(url, host='localhost', port='4444', browser='*firefox')
45
+ @url = url
46
+ @browser = Selenium::Client::Driver.new(
47
+ :host => host,
48
+ :port => port,
49
+ :browser => browser,
50
+ :url => url)
51
+ end
52
+
53
+ attr_reader :url, :browser
54
+
55
+
56
+ # Start the session and open a browser to the URL defined at the start of
57
+ # the test.
58
+ #
59
+ # @example
60
+ # | Open browser |
61
+ #
62
+ def open_browser
63
+ begin
64
+ @browser.start_new_browser_session
65
+ rescue
66
+ # TODO: Find a way to make the test abort here
67
+ raise SeleniumNotRunning, "Could not start Selenium."
68
+ else
69
+ visit @url
70
+ end
71
+ end
72
+
73
+
74
+ # Stop the session and close the browser window.
75
+ #
76
+ # @example
77
+ # | Close browser |
78
+ #
79
+ def close_browser
80
+ @browser.close_current_browser_session
81
+ return true
82
+ end
83
+
84
+
85
+ # Load an absolute URL or a relative path in the browser.
86
+ #
87
+ # @param [String] path_or_url
88
+ # Relative path or absolute URL to load. Absolute URLs must be in the
89
+ # same domain as the URL that was passed to {#initialize}.
90
+ #
91
+ # @example
92
+ # | Visit | http://www.automation-excellence.com |
93
+ # | Visit | /software |
94
+ #
95
+ def visit(path_or_url)
96
+ return_error_status do
97
+ @browser.open(path_or_url)
98
+ end
99
+ end
100
+
101
+
102
+ # Click the Back button to navigate to the previous page.
103
+ #
104
+ # @example
105
+ # | Click back |
106
+ #
107
+ def click_back
108
+ return_error_status do
109
+ @browser.go_back
110
+ end
111
+ end
112
+
113
+
114
+ # Reload the current page.
115
+ #
116
+ # @example
117
+ # | refresh page |
118
+ #
119
+ def refresh_page
120
+ return_error_status do
121
+ @browser.refresh
122
+ end
123
+ end
124
+
125
+
126
+ # Maximize the browser window. May not work in some browsers.
127
+ #
128
+ # @example
129
+ # | maximize browser |
130
+ #
131
+ def maximize_browser
132
+ @browser.window_maximize
133
+ return true
134
+ end
135
+
136
+
137
+ # Ensure that the given text appears on the current page.
138
+ #
139
+ # @param [String] text
140
+ # Plain text that should be visible on the current page
141
+ #
142
+ # @example
143
+ # | See | Welcome, Marcus |
144
+ #
145
+ def see(text)
146
+ return @browser.text?(text)
147
+ end
148
+
149
+
150
+ # Ensure that the given text does not appear on the current page.
151
+ #
152
+ # @param [String] text
153
+ # Plain text that should not be visible on the current page
154
+ #
155
+ # @example
156
+ # | Do not see | Take a hike |
157
+ #
158
+ def do_not_see(text)
159
+ return !@browser.text?(text)
160
+ end
161
+
162
+
163
+ # Ensure that the current page has the given title text.
164
+ #
165
+ # @param [String] title
166
+ # Text of the page title that you expect to see
167
+ #
168
+ # @example
169
+ # | See title | Our Homepage |
170
+ #
171
+ def see_title(title)
172
+ return (@browser.get_title == title)
173
+ end
174
+
175
+
176
+ # Ensure that the current page does not have the given title text.
177
+ #
178
+ # @param [String] title
179
+ # Text of the page title that you should not see
180
+ #
181
+ # @example
182
+ # | Do not see title | Someone else's homepage |
183
+ #
184
+ def do_not_see_title(title)
185
+ return !(@browser.get_title == title)
186
+ end
187
+
188
+
189
+ # Type a value into the given field.
190
+ #
191
+ # @param [String] text
192
+ # What to type into the field
193
+ # @param [String] locator
194
+ # Label, name, or id of the field you want to type into
195
+ #
196
+ # @example
197
+ # | Type | Dale | into field | First name |
198
+ # | Type | Dale | into | First name | field |
199
+ #
200
+ def type_into_field(text, locator)
201
+ return_error_status do
202
+ @browser.type(xpath('field', locator), text)
203
+ end
204
+ end
205
+
206
+
207
+ # Fill in a field with the given text.
208
+ #
209
+ # @param [String] locator
210
+ # Label, name, or id of the field you want to type into
211
+ # @param [String] text
212
+ # What to type into the field
213
+ #
214
+ # @example
215
+ # | Fill in | First name | with | Eric |
216
+ #
217
+ def fill_in_with(locator, text)
218
+ type_into_field(text, locator)
219
+ end
220
+
221
+
222
+ # Verify that a text field contains the given text.
223
+ # The field may include additional text, as long as the
224
+ # expected value is in there somewhere.
225
+ #
226
+ # @param [String] locator
227
+ # Label, name, or id of the field you want to inspect
228
+ # @param [String] text
229
+ # Text to expect in the field
230
+ #
231
+ # @example
232
+ # | Field | First name | contains | Eric |
233
+ #
234
+ def field_contains(locator, text)
235
+ @browser.field(xpath('field', locator)).include?(text)
236
+ end
237
+
238
+
239
+ # Verify that a text field's value equals the given text.
240
+ # The value must match exactly.
241
+ #
242
+ # @param [String] locator
243
+ # Label, name, or id of the field you want to inspect
244
+ # @param [String] text
245
+ # Text to expect in the field
246
+ #
247
+ # @example
248
+ # | Field | First name | equals | Eric |
249
+ #
250
+ def field_equals(locator, text)
251
+ @browser.field(xpath('field', locator)) == text
252
+ end
253
+
254
+
255
+ # Click on a link or button, and wait for a page to load.
256
+ #
257
+ # @param [String] locator
258
+ # Text, value or id of the link or button to click
259
+ #
260
+ # @example
261
+ # | Click | Next |
262
+ # | Click | Logout |
263
+ #
264
+ def click(locator)
265
+ return_error_status do
266
+ @browser.click(xpath('link_or_button', locator))
267
+ end
268
+ end
269
+
270
+
271
+ # Click on a link.
272
+ #
273
+ # @param [String] locator
274
+ # Link text or id of the anchor element
275
+ #
276
+ # @example
277
+ # | Click | Logout | link |
278
+ # | Click link | Logout |
279
+ #
280
+ def click_link(locator)
281
+ return_error_status do
282
+ @browser.click(xpath('link', locator))
283
+ end
284
+ end
285
+
286
+
287
+ # Alias for {#click_link}.
288
+ #
289
+ # @example
290
+ # | Follow | Logout |
291
+ #
292
+ def follow(locator)
293
+ click_link(locator)
294
+ end
295
+
296
+
297
+ # Press a button.
298
+ #
299
+ # @param [String] locator
300
+ # Button text, value, or id of the button
301
+ #
302
+ # @example
303
+ # | Click | Login | button |
304
+ # | Click button | Login |
305
+ #
306
+ def click_button(locator)
307
+ return_error_status do
308
+ @browser.click(xpath('button', locator))
309
+ end
310
+ end
311
+
312
+
313
+ # Alias for {#click_button}
314
+ #
315
+ # @example
316
+ # | Press | Search |
317
+ #
318
+ def press(locator)
319
+ click_button(locator)
320
+ end
321
+
322
+
323
+ # Enable (check) a checkbox.
324
+ #
325
+ # @param [String] locator
326
+ # Label, value, or id of the checkbox to check
327
+ #
328
+ # @example
329
+ # | Enable | Send me spam | checkbox |
330
+ # | Enable checkbox | Send me spam |
331
+ #
332
+ def enable_checkbox(locator)
333
+ return_error_status do
334
+ @browser.check(xpath('checkbox', locator))
335
+ end
336
+ end
337
+
338
+
339
+ # Disable (uncheck) a checkbox.
340
+ #
341
+ # @param [String] locator
342
+ # Label, value, or id of the checkbox to uncheck
343
+ #
344
+ # @example
345
+ # | Disable | Send me spam | checkbox |
346
+ # | Disable checkbox | Send me spam |
347
+ #
348
+ def disable_checkbox(locator)
349
+ return_error_status do
350
+ @browser.uncheck(xpath('checkbox', locator))
351
+ end
352
+ end
353
+
354
+
355
+ # Verify that a given checkbox or radiobutton is enabled (checked)
356
+ #
357
+ # @param [String] locator
358
+ # Label, value, or id of the checkbox to inspect
359
+ #
360
+ # @example
361
+ # | Checkbox is enabled | send me spam |
362
+ # | Checkbox | send me spam | is enabled |
363
+ #
364
+ def checkbox_is_enabled(locator)
365
+ begin
366
+ enabled = @browser.checked?(xpath('checkbox', locator))
367
+ rescue
368
+ return false
369
+ else
370
+ return enabled
371
+ end
372
+ end
373
+
374
+
375
+ # Verify that a given checkbox or radiobutton is disabled (unchecked)
376
+ #
377
+ # @param [String] locator
378
+ # Label, value, or id of the checkbox to inspect
379
+ #
380
+ # @example
381
+ # | Checkbox is disabled | send me spam |
382
+ #
383
+ def checkbox_is_disabled(locator)
384
+ begin
385
+ enabled = @browser.checked?(xpath('checkbox', locator))
386
+ rescue
387
+ return false
388
+ else
389
+ return !enabled
390
+ end
391
+ end
392
+
393
+
394
+ # Select a radio button.
395
+ #
396
+ # @param [String] locator
397
+ # Label, id, or name of the radio button to select
398
+ #
399
+ # @example
400
+ # | Select | female | radio |
401
+ # | Select radio | female |
402
+ #
403
+ def select_radio(locator)
404
+ return_error_status do
405
+ @browser.click(xpath('radio_button', locator))
406
+ end
407
+ end
408
+
409
+
410
+ # Alias for {#checkbox_is_disabled}
411
+ #
412
+ # @example
413
+ # | Radio is disabled | medium |
414
+ # | Radio | medium | is disabled |
415
+ #
416
+ def radio_is_disabled(locator)
417
+ checkbox_is_disabled(locator)
418
+ end
419
+
420
+
421
+ # Alias for {#checkbox_is_enabled}
422
+ #
423
+ # @example
424
+ # | Radio is enabled | medium |
425
+ # | Radio | medium | is enabled |
426
+ #
427
+ def radio_is_enabled(locator)
428
+ checkbox_is_enabled(locator)
429
+ end
430
+
431
+
432
+ # Select a value from a dropdown/combo box.
433
+ #
434
+ # @param [String] value
435
+ # The value to choose from the dropdown
436
+ # @param [String] locator
437
+ # Label, name, or id of the dropdown
438
+ #
439
+ # @example
440
+ # | select | Tall | from | Height | dropdown |
441
+ # | select | Tall | from dropdown | Height |
442
+ #
443
+ def select_from_dropdown(value, locator)
444
+ return_error_status do
445
+ @browser.select(xpath('select', locator), value)
446
+ end
447
+ end
448
+
449
+
450
+ # Pause for a certain number of seconds.
451
+ #
452
+ # @param [String, Int] seconds
453
+ # How many seconds to pause
454
+ #
455
+ # @example
456
+ # | Pause | 5 | seconds |
457
+ #
458
+ def pause_seconds(seconds)
459
+ sleep seconds.to_i
460
+ return true
461
+ end
462
+
463
+
464
+ # Wait some number of seconds for the current page request to finish.
465
+ # Fails if the page does not finish loading within the specified time.
466
+ #
467
+ # @param [String, Int] seconds
468
+ # How long to wait for before timing out
469
+ #
470
+ # @example
471
+ # | Page loads in | 10 | seconds or less |
472
+ #
473
+ def page_loads_in_seconds_or_less(seconds)
474
+ return_error_status do
475
+ @browser.wait_for_page_to_load("#{seconds}000")
476
+ end
477
+ end
478
+
479
+
480
+ # Execute the given block, and return false if it raises an exception.
481
+ # Otherwise, return true.
482
+ #
483
+ # @example
484
+ # return_error_status do
485
+ # # some code that might raise an exception
486
+ # end
487
+ #
488
+ def return_error_status
489
+ begin
490
+ yield
491
+ rescue => e
492
+ #puts e.message
493
+ #puts e.backtrace
494
+ return false
495
+ else
496
+ return true
497
+ end
498
+ end
499
+
500
+ # Return a Selenium-style xpath for the given locator.
501
+ #
502
+ # @param [String] kind
503
+ # What kind of locator you're using (link, button, checkbox, field etc.).
504
+ # This must correspond to a method name in `XPath::HTML`.
505
+ # @param [String] locator
506
+ # Name, id, value, label or whatever locator the `XPath::HTML.<kind>`
507
+ # method accepts.
508
+ #
509
+ # @example
510
+ # xpath('link', 'Log in')
511
+ # xpath('button', 'Submit')
512
+ # xpath('field', 'First name')
513
+ #
514
+ def xpath(kind, locator)
515
+ # Doing explicit string-join here, so it'll work with older versions
516
+ # of the XPath module that don't have `Union#to_s` defined.
517
+ "xpath=" + XPath::HTML.send(kind, locator).to_xpaths.join(' | ')
518
+ end
519
+
520
+ end
521
+ end
522
+
523
+
524
+