gem update tapestry
. Again, part of this is me showing how design evolves as you attempt to explain and/or document something. Key to that, however, is making sure that you don’t negate all of the material you have already documented.
Let’s start with this script which is what we ended up with at the end of the last post:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 |
require 'webdriver_manager' require 'rspec/expectations' include RSpec::Matchers require 'tapestry' include Tapestry class Veilus include Tapestry url_is 'https://veilus.herokuapp.com' img :test_gorilla, id: "site-image" element :open, id: "open" text_field :username, id: "username" text_field :password, id: "password" button :login, id: "login-button" element :notice, class: "notice" element :navigation, id: "navlist" link :stardate_on_nav, id: "stardate", index: 1 img :stardate_logo, id: "stardate-logo" end Tapestry.set_browser Tapestry.move_to(0, 0) Tapestry.maximize page = Veilus.new page.view expect(page.test_gorilla).to exist page.open.click page.username.set! "admin" page.password.set! "admin" page.login.click expect(page.notice.text).to eq("You are now logged in as admin.") page.navigation.wait_until(&:dom_updated?).click page.stardate_on_nav.wait_until(&:dom_updated?).click expect(Tapestry.browser.title).to match 'Stardate Calculator' expect(Tapestry.browser.url).to match 'stardate' expect(page.stardate_logo).to exist Tapestry.quit_browser |
Delegate More To The Definitions
Notice how we’ve carried along those two lines to move the browser and maximize it, currently lines 27 and 28. Do I really want every test script to have to do that? Not really. But if I delegate that to the page object, what method would I call to make that happen? After all, a page object may store different methods that are called at different times depending on the test being executed. What I need is a method that is always run. Tapestry provides such a method as a hook. That method is calledbegin_with
. If this method exists on your definition, it will be called. So let’s try this out. Change the Veilus definition accordingly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Veilus include Tapestry url_is 'https://veilus.herokuapp.com' img :test_gorilla, id: "site-image" element :open, id: "open" text_field :username, id: "username" text_field :password, id: "password" button :login, id: "login-button" element :notice, class: "notice" element :navigation, id: "navlist" link :stardate_on_nav, id: "stardate", index: 1 img :stardate_logo, id: "stardate-logo" def begin_with move_to(0, 0) maximize end end |
1 2 |
Tapestry.move_to(0, 0) Tapestry.maximize |
Framework Calls Into Tests
What you should notice here is that while your test script can be thought of as calling into Tapestry — i.e., using the framework — what is in fact happening is that Tapestry is using your test script as an interface. In other words, the micro-framework calls into any tests that include it. Consider that Tapestry was included in the page definition. Then you defined element definitions. But those weren’t calling methods on the micro-framework. Rather, those elements served as hooks that, if Tapestry found them, it would recognize them as methods for you. Then your script would simply be calling the methods that you yourself wrote. Tapestry just served as a sort of connective glue. The same applies with the “begin_with” declaration above. Tapestry will always look to see if this method exists on a definition. And if it does, it will call it and do whatever it asks for. If that method isn’t there, well, then it isn’t there! That’s it. So Tapestry, as any test micro-framework should do (in my opinion), is allowing your logic to define the parameters by which execution will take place. Tapestry simply provides an API to make that possible. Let’s see this in action in another context.Basic Checks
You’ll note that I check for that ‘test gorilla’ site image when Veilus is loaded up. I do that to make sure the script got to the right place. But I probably want this checked every time a test goes to that page. And I don’t want to have to repeat that expectation or assertion each and every time. Tapestry provides a Ready module. This allows you to put apage_ready
declaration on definitions. Modify your Veilus class to look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Veilus include Tapestry url_is 'https://veilus.herokuapp.com' img :test_gorilla, id: "site-image" element :open, id: "open" text_field :username, id: "username" text_field :password, id: "password" button :login, id: "login-button" element :notice, class: "notice" element :navigation, id: "navlist" link :stardate_on_nav, id: "stardate", index: 1 img :stardate_logo, id: "stardate-logo" def begin_with move_to(0, 0) maximize end page_ready { test_gorilla.exists? } end |
1 |
page_ready { [test_gorilla.exists?, "Veilus logo is not present"] } |
1 2 |
page_ready { [test_gorilla.exists?, "Veilus logo is not present"] } page_ready { [open.exists?, "The login open element was not present"] } |
Ready Validations
Thebegin_with
worked as it did because it was essentially providing a sort of initialization that you want to happen. Perhaps you would load up some data here or you would do as I did and get the browser into a state you wanted.
The page_ready
, however, can contain assertions or expectations about the page itself, which means the page needs to render. That means the page_ready declaration can’t just be executed the moment the page definition is instantiated. So how do you check it? Tapestry exposes two methods you can check: ready?
and ready_error
. For example, you could do this:
1 2 |
expect(page.ready?).to be_truthy expect(page).to be_ready |
1 2 3 4 |
page = Veilus.new page.view puts page.ready_error unless page.ready? |
1 2 3 4 |
puts page.ready? if page.ready_error.match("open element was not present") raise(StandardError, page.ready_error) end |
when_ready
on the definition instance and then providing a block of what you want to have happen if the page is, in fact, ready. Here’s an example:
1 2 3 4 5 6 7 8 9 |
page = Veilus.new page.view page.when_ready do |action| action.open.click action.username.set! "admin" action.password.set! "admin" action.login.click end |
1 2 3 4 5 6 7 8 |
page = Veilus.new page.view do |action| action.open.click action.username.set! "admin" action.password.set! "admin" action.login.click end |
1 2 3 4 5 6 |
page = Veilus.new.view do |action| action.open.click action.username.set! "admin" action.password.set! "admin" action.login.click end |
1 |
expect(page.test_gorilla).to exist |
Here’s the Full Logic
Lots of discussion and changes here so let me show you my final script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 |
require 'webdriver_manager' require 'rspec/expectations' include RSpec::Matchers require 'tapestry' include Tapestry class Veilus include Tapestry url_is 'https://veilus.herokuapp.com' img :test_gorilla, id: "site-image" element :open, id: "open" text_field :username, id: "username" text_field :password, id: "password" button :login, id: "login-button" element :notice, class: "notice" element :navigation, id: "navlist" link :stardate_on_nav, id: "stardate", index: 1 img :stardate_logo, id: "stardate-logo" def begin_with move_to(0, 0) maximize end page_ready { [test_gorilla.exists?, "Veilus logo is not present"] } end Tapestry.set_browser page = Veilus.new.view do |action| action.open.click action.username.set! "admin" action.password.set! "admin" action.login.click end expect(page.notice.text).to eq("You are now logged in as admin.") page.navigation.wait_until(&:dom_updated?).click page.stardate_on_nav.wait_until(&:dom_updated?).click expect(Tapestry.browser.title).to match 'Stardate Calculator' expect(Tapestry.browser.url).to match 'stardate' expect(page.stardate_logo).to exist Tapestry.quit_browser |
1 |
include Tapestry |