RUTL
This is the Ruby Ui Test Library, or RUTL. Not to be confused with The Rutles.
Framework goals:
- Define what's on a page in an easy, flexible way. Easy page objects!
- Abstract away things that make tests buggy and painful to maintain.
- Write test cases for native apps, the web, and desktop apps the same way.
- Make screenshotting and diffing screenshots sane and easy.
- TODO: I'm sure I'm missing some at the moment.
- Secondary-ish goal: Make fake browser to test the framework faster.
- Tertiary-ish: Stop calling browser "fake" because I'm sick of that word. Null!
Installation
Add this line to your application's Gemfile:
$ gem rutl
And then execute:
$ bundle
Or install it yourself as:
$ gem install rutl
Usage
Page Objects
Page objects are a common paradigm in browser testing. This framework uses the following convention for page classes:
- must inherit from
rutl::BasePage
(require rutl/base_page
) - by default, the class should follow the naming convention ending with
Page
(optional?) - must have
@url
defined per page - must have a layout method such that
- field types are defined by methods
button
,checkbox
,link
, andtext
(more tbd?) - field type is followed by name as a symbol (add support for string? tbd)
- then a comma
- hash of selectors
- key is selector type as symbol (currently only
:css
) - value is string path
- key is selector type as symbol (currently only
- optional comma if there are destinations or error conditions
- optional array of destination page or error condition classes if clicking the element causes transition
- loaded? method returning boolean to determine when page is loaded
- defaults to just checking url; overide as needed
-
go_to_here
(better name?) method to navigate to the page if we can't just go to the url - your own methods because it's a plain ol' Ruby class
Example:
require 'rutl/base_page'
class MyPage < BasePage
@url = 'https://url.string.com/page.html'
def layout
text :username, { css: 'some_css_input#username' }
text :password, { css: 'css#to_password_field' }
button :log_me_in, { css: 'button#login' }, [SomeOtherPage, LoginFailurePage]
link :refresh, { css: 'link_css_to_refresh_page' }, [MyPage]
end
end
And here's some example RSpec:
require 'spec_helper'
RSpec.describe MyTest do
let!(:browser) do
Browser.new(type: :firefox)
end
it 'logs in' do
goto(MyPage)
username_text = 'drew'
password_text = 's3cr3T!'
log_me_in_button.click
expect(current_page).to be_page(SomeOtherPage)
end
end
The framework loads and manages all the pages. You just have to interact with what you can see on whatever page you're on. Let's walk through this.
- TBD: Does RUTL come with browser drivers? Browsers? What needs to be added?
- We're using let! because:
- it forces instantiation of
browser
every time - we
include DefaultRspecToBrowser
, defaulting missing methods to "browser" - thus the terse lines that follow
- it forces instantiation of
- We didn't pass named param
rutl_pages:
tobrowser
so we must have done one of:- setting environment variable
RUTL_PAGES
- setting
RUTL::PAGES
- setting environment variable
- Browser's
type:
parameter currently supports:chrome
,:firefox
, and:null
. - The first call to the browser is
goto
because it wasn't on a page. - Auto-created fields are named
#{friendly_name}_#{field_type}
. - Getting and setting text fields is as easy as calling a
String
. - When we call click, the framework polls for a next state.
- We verify that the current page is an instance of the intended page.
- Also note here that we have a matcher
be_page
which matches a page class.
- Also note here that we have a matcher
RSpec Goodies
The tests here are in RSpec and use some conventions that may be common if your tests are also RSpec.
DefaultRspecToBrowser
This is a module that allows us to skip writing browser.
in front of everything.
- We assume that
browser
is defined. - On method_missing, we try to send the method to
browser
.
It lets us turn this:
message = 'foo'
browser.field1_text = message
browser.ok_button.click
expect(browser.current_page).to be_page(NextPage)
into this:
message = 'foo'
field1_text = message
ok_button.click
expect(current_page).to be_page(NextPage)
which means less boilerplate. Because we insist on adding UI element type as part of the naming convention there's no confusion about which things are UI elements and which aren't. In this case, we don't mistake our variable "message" for a UI element.
To use it:
require 'rutl/rspec/default_rspec_to_browser'
RSpec Matcher
Currently the only has the be_page
matcher.
It lets us turn this:
expect(browser.current_page).to be_instance_of(MyPage)
into this:
expect(browser.current_page).to be_page(MyPage)
Both are acceptable but the second is more readable.
To use it:
require 'rutl/rspec/rutl_matchers'
Auto-screenshotting
If you have RUTL::SCREENSHOTS
or ENV['SCREENSHOTS']
set to a directory, RUTL
will automatically take screenshots on page transitions.
If you're using RSpec, they'll be automatically named something based on the
RSpec description with an auto-incrementing number.
If you're not using RSpec, that's not terribly useful but you can always have
your tests screenshot anyway, just less magic.
Roadmap
Coming up soon in almost no order:
- Handle other errors. Auto-screenshot on errors. Navigation errors. Unexpected exceptions?
- A test framework should have better tests.
- Diff screenshots. Make this smart so we don't have to be experts.
- Put more info in this readme.
- Move bugs and would-be features to Github Issues instead of this readme and scattered through the code.
- Make the framework make it easier to spot bugs in pages. Focus on exception-handling?
- The webdriver gem should already include InternetExplorerDriver. Maybe run tests on AppVeyor.
- Other browser drivers?
- Get this working with Appium:
- Make TK app to test on desktops and test it.
- Can Ruby TK create accesible apps? Not in the simple demos.
- Make Android example app and get this to work.
- Cordova or React Native or maybe even Flutterfor app?
- Choose (default?) emulator. Many choices.
- Same with iPhone.
- Where to build/test? CircleCI?
- Same test app as Android.
- Are there decent alternatives yet to the simulator?
- Make TK app to test on desktops and test it.
- Improve documentation.
- Add these things and treat as plugins?
- Security testing. See OWASP stuff.
- Also OWASP mobile security testing guide.
- Avoid deeper pen testing. Leave it to other toolkits.
- Other toolkits? Accessibility?
- Support web proxy and integrate out of the box. Browsermob?
- Get HARs here for perf stuff.
- Also look at browser events and tie to HARs? (Always wanted to do that.)
- Security testing. See OWASP stuff.
- Auto-screenshot support frameworks other than RSpec.
- Or just default them to screenshots at known view changes (links/buttons/other?) or errors.
- Others?
- Spidering page object maker. Or selector checker/fixer?
- Possibly pair the null browser with auto-generated pages for ______?
- Optional install of test resources based on machine type.
- Instructions about machine installs to help people using gem.
- Pair with some kind of VM, Docker container, AMI, or something.
Development
Set everything up:
1. Check out the repo.
2. `cd` to the repo.
3. `bundle install`
4. `bundle exec rake`
Great! You've checked out the code, installed everything and run the tests.
Rubocop. I still have to tweak what I want it to complain about.
bundle exec rubocop
To install this gem onto your local machine, run bundle exec rake install
.
To release a new version, update the version number in version.rb
like so
bundle exec gem bump -v [major|minor|patch|pre|release]
and then run
bundle exec rake release
which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/drewcoo/rutl.
License
The gem is available as open source under the terms of the MIT License.