What can I put as my watir-webdriver page "element" in a condition where it's not there? - ruby

I'm testing a nightmarish website that in most situations sticks all the important stuff in an iframe.
However, there are other common situations where the system will, annoyingly, open a page in a new tab, but not wrapped in the iframe.
I'm trying to figure out a conditional method that will check for the existence of the iframe and use it, otherwise not.
Here's what I've come up with, so far:
# The browser object...
#br = Watir::Browser.new
"frm" is the conditional method I'm trying to get working...
# Just an example element definition...
def click_my_button
#br.frm.button(id: "button").click
end
I define it in Watir's Container module, like so:
module Watir
module Container
def frm
if frame(id: "iframeportlet").exist?
frame(id: "iframeportlet")
else
# This is the part that I can't figure out.
end
end
end
end
That works fine when the iframe is there, but not surprisingly I get a NilClass error when it's not.
So, my question is: what can go into the else clause to make it work? More broadly, is there perhaps a better way to accomplish this? As you can imagine, I really want to avoid having to define every element in the web site twice.

I figured it out, and it's quite simple. The frm method's else clause just needs a "self"...
else
self
end
That's it. I'd love to know if there are any hidden pitfalls with this approach, though.

Related

Mocking a Browser for RSpec, Without Test Doubles Leaking

I find mocking things with RSpec to be entirely problematic and I often don't know how much code to include, in terms of it being diagnostic. So I'll start with the situation I have and the code that I've isolated as causing the problem.
I have tests where I need to mock a browser. I have a mock driver I set up like this:
require "watir"
def mock_driver
browser = double("watir")
allow(browser).to receive(:is_a?).with(Watir::Browser).and_return(true)
allow(browser).to receive(:driver).and_return(true)
browser
end
The only problems I have in my test suite are these two tests:
context "an empiric driver is requested" do
it "a watir browser is provided" do
allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
Empiric.set_browser mock_driver
end
it "the requested watir browser can be shut down" do
#allow(Empiric.browser).to receive(:quit)
Empiric.quit_browser
#allow(mock_browser).to receive(:new).and_return(Empiric.browser)
#Empiric.set_browser mock_driver
end
end
(The commented out bits in the second test are on purpose to illustrate what's going on.)
With that one line in place in the second test, I get the following error on that test:
<Double "watir"> was originally created in one example but has leaked into another
example and can no longer be used. rspec-mocks' doubles are designed to only last for
one example, and you need to create a new one in each example you wish to use it for.
If I entirely comment out the first test above, that error doesn't happen so I know I've isolated the two tests that are interacting with each other.
Okay, now notice the final line of my second test that is commented out. That seems to be what the error is indicating to me. It's saying I need to create a new double in the other. Okay, so I'll change my last test:
it "the requested watir browser can be shut down" do
#allow(Empiric.browser).to receive(:quit)
Empiric.quit_browser
#allow(mock_browser).to receive(:new).and_return(Empiric.browser)
Empiric.set_browser mock_driver
end
So here I've uncommented the last line so I'm establishing the mock_driver in that test and not allowing the code to leak.
That, however, returns exactly the same error on exactly the same test.
I'm not sure if it would help to see the methods that are being called in that test, but here they are. First is set_browser:
def set_browser(app = :chrome, *args)
#browser = Watir::Browser.new(app, *args)
Empiric.browser = #browser
end
And here is quit_browser:
def quit_browser
#browser.quit
end
The fact that RSpec thought one test was "leaking" into the other made me think that perhaps my #browser instance was the problem, essentially being what's persisting between the two tests. But I don't see how to get around that. I thought that maybe if I quit the browser in the first test, that would help. So I changed the first test to this:
it "a watir browser is provided" do
Empiric.quit_browser
allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
Empiric.start_browser mock_driver
end
That, however, led to the above error being shown on both tests now.
My more likely accurate guess is that I simply don't know how to provide a mock in this context.
I think you have to use allow with the mock and not Watir::Browser.
For example, what happens if you allow the mock browser to receive whatever calls the browser would and have the it return the mock browser?
Right now you're allowing the "Watir::Browser" to receive those messages and that's returning an "Empiric.browser". Looking at your code, I understand why you put that in there but I think that might be what's screwing you up here.
Mocks in RSpec are horrible things that rarely if ever work correctly in situations like this. I would entirely recommend not using the mock_driver that you have set up. Rather, for each of your tests just do something similar to what you are doing in the mock_driver. My guess is you're including the mock driver as part of a shared context and that, too, is another thing that is very fragile in RSpec. Not recommended.
Instead you might want to use contexts to break up your tests. Then for each context block have a before block. I'm not sure if you should use before:all or before:each given that you're simulating a browser. But that way you can set up the browser in the before and tear it down in an after.
But I would recommend getting it working in each test individually first. Even if it's a lot of code duplication. Then once all tests are passing, refactor to put the browser stuff in those before/after blocks.
But, again, don't use mocks. Don't use shared contexts. It never ends well and honestly it makes your tests harder to reason about.
Given some advice from Micah, I wanted to provide an answer with a solution. I ended up doing this:
context "an empiric driver is requested" do
it "a watir browser is provided" do
allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
allow(Empiric.browser).to receive(:driver).and_return(true)
expect { Empiric.start_browser :some_browser }.not_to raise_error
end
it "the requested watir browser can be shut down" do
allow(Empiric.browser).to receive(:quit)
allow(Watir::Browser).to receive(:new).and_return(Empiric.browser)
allow(Empiric.browser).to receive(:driver).and_return(true)
expect { Empiric.quit_browser }.not_to raise_error
end
end
All of that was needed as it is or I would get some error or other. I removed my mock driver and, per Micah's suggestion, simply tried to incorporate what seemed to work. The above "contraption" is what I ended up with as the sweet spot.
This works in the sense of giving coverage of the methods in question. What was interesting was that I had to add this to my RSpec configuration:
RSpec.configure do |config|
config.mock_with :rspec do |mocks|
mocks.allow_message_expectations_on_nil = true
end
end
I needed to do this because RSpec was reporting that I was calling allowing something that was nil to receive a value.
This brought up some interesting things, if you think about it. I have a test that is clearly passing. And it adds to my code coverage. But is it actually testing the quit action on a browser? Well, not really since it was testing a quit action on something that it thought was nil.
But -- it does work. And it must be calling the lines of code in question because the code coverage, as reported my SimpleCov, indicates that the statements in question have been checked.

How to call a method on a local variable in Ruby?

Probably a stupid question but I was following along this article and came across a bit of code I couldn't quite grasp. Here it is:
class CreateArticle
attr_reader :validate_article, :persist_article
def initialize(validate_article, persist_article)
#validate_article = validate_article
#persist_article = persist_article
end
def call(params)
result = validate_article.call(params)
if result.success?
persist_article.call(params)
end
end
end
More specifically, the problematic line is this:
if result.success?
Here's my problem with it: where did the success? method come from? It's not default in Ruby, and result is a local variable, so it should be nearby. But even if it's just omitted in the code sample, where would it have to be defined for that line to work? Everywhere I tried to define it just gave me an 'undefined method' error.
For example, I tried to define it both in the CreateArticle class and in the (only alluded to) ValidateArticle class, the obvious culprits, but no dice.
Update:
The reason I ask is not so much about what success? does as it is because I'm interested in using the pattern in my code. So, for example, my version of the success? method could be just checking whether a value got updated, or an item was inserted into an array. For example, let's say it's just this:
def success? # or self.success?
return true
end
Problem is, I can find no place where I can put this that works. I even created a module just for it and included it into the class, and still it doesn't work (it just returns 'undefined method'). So I'm still at a loss as to where I would have to define such a method so that it would work the way it looks like it should.
It's a method that comes with rails. It checks.for a server response with a 200 code. If it gets a 200 code it returns true else it returns false. Read the rails API docs about it... https://apidock.com/rails/v3.2.3/ActiveResource/Response/success%3F
Actually . success? is a built in ruby method. Check here. What it actually does is checking Stat and returns a boolean.
I did some more digging around the blog and from what I found I suspect that the code is probably making use of the dry-monads gem:
You can explicitly check the type by calling failure? or success? on a monadic value.
It's not explicit in the code excerpt but it's the only thing that makes sense.

Using SitePrism with Rspec and Capybara feature specs

I recently discovered SitePrism via the rubyweekly email.
It looks amazing. I can see its going to be the future.
The examples I have seen are mostly for cucumber steps.
I am trying to figure out how one would go about using SitePrism with rspec.
Assuming #home_page for the home page, and #login_page for the login_page
I can understand that
#home_page.load # => visit #home.expanded_url
however, the part I am not sure about, is if I think click on for example the "login" link, and the browser in Capybara goes to the login page - how I can then access an instance of the login page, without loading it.
#home_page = HomePage.new
#home_page.load
#home.login_link.click
# Here I know the login page should be loaded, so I can perhaps do
#login_page = LoginPage.new
#login_page.should be_displayed
#login_page.email_field.set("some#email.com")
#login_page.password_field.set("password")
#login_page.submit_button.click
etc...
That seems like it might work. So, when you know you are supposed to be on a specific page, you create an instance of that page, and somehow the capybara "page" context, as in page.find("a[href='/sessions/new']") is transferred to the last SitePrism object?
I just feel like I am missing something here.
I'll play around and see what I can figure out - just figured I might be missing something.
I am looking through the source, but if anyone has figured this out... feel free to share :)
What you've assumed turns out to be exactly how SitePrism works :) Though you may want to check the epilogue of the readme that explains how to save yourself from having to instantiate page objects all over your test code. Here's an example:
# our pages
class Home < SitePrism::Page
#...
end
class SearchResults < SitePrism::Page
#...
end
# here's the app class that represents our entire site:
class App
def home
Home.new
end
def results_page
SearchResults.new
end
end
# and here's how to use it:
#first line of the test...
#app = App.new
#app.home.load
#app.home.search_field.set "sausages"
#app.home.search_button.click
#app.results_page.should be_displayed

Why the object references are not correctly passed in this RSpec script?

I have to say I am new both to Ruby and to RSpec. Anyway I completed one RSpec script but after refactoring it failed. Here is the original working version:
describe Site do
browser = Watir::Browser.new :ie
site = Site.new(browser, "http://localhost:8080/site")
it "can navigate to any page at the site" do
site.pages_names.each do |page_name|
site.goto(page_name)
site.actual_page.name.should eq page_name
end
end
browser.close
end
and here is the modified version - I wanted to have reported all the pages which were visited during the test:
describe Site do
browser = Watir::Browser.new :ie
site = Site.new(browser, "http://localhost:8080/site")
site.pages_names.each do |page_name|
it "can navigate to #{page_name}" do
site.goto(page_name)
site.actual_page.name.should eq page_name
end
end
browser.close
end
The problem in the latter case is that site gets evaluated to nil within the code block associated with 'it' method.
But when I did this:
...
s = site
it "can navigate to #{page_name}" do
s.goto(page_name)
s.actual_page.name.should eq page_name
end
...
the nil problem was gone but tests failed with the reason "browser was closed"
Apparently I am missing something very basic Ruby knowledge - because the browser reference is not working correctly in modified script. Where did I go wrong? What refactoring shall be applied to make this work?
Thanks for your help!
It's important to understand that RSpec, like many ruby programs, has two runtime stages:
During the first stage, RSpec loads each of your spec files, and executes each of the describe and context blocks. During this stage, the execution of your code defines your examples, the hooks, etc. But your examples and hooks are NOT executed during this stage.
Once RSpec has finished loading the spec files (and all examples have been defined), it executes them.
So...trimming down your example to a simpler form, here's what you've got:
describe Site do
browser = Watir::Browser.new :ie
it 'does something with the browser' do
# do something with the browser
end
browser.close
end
While visually it looks like the browser instance is instantiated, then used in the example, then closed, here's what's really happening:
The browser instance is instantiated
The example is defined (but not run)
The browser is closed
(Later, after all examples have been defined...) The example is run
As O.Powell's answer shows, you can close the browser in an after(:all) hook to delay the closing until after all examples in this example group have run. That said, I'd question if you really need the browser instance at example definition time. Generally you're best off lazily creating resources (such as the browser instance) when examples need them as they are running, rather than during the example definition phase.
I replicated your code above using fake classes for Site and Watir. It worked perfectly. My only conclusion then is that the issue must lie with either one of the above classes. I noticed the Site instance only had to visit one page in your first working version, but has to visit multiple pages in the non working version. There may be an issue there involving the mutation happening inside the instance.
See if this makes a difference:
describe Site do
uri = "http://localhost:8080/site"
browser = Watir::Browser.new :ie
page_names = Site.new(browser, uri).page_names
before(:each) { #site = Site.new(browser, uri) }
after(:all) { browser.close }
pages_names.each do |page_name|
it "can navigate to #{page_name}" do
#site.goto(page_name)
#site.actual_page.name.should eq page_name
end
end
end

How much should interfaces of elements in Page Objects be abstracted?

I have a page object called LineItemsPage
class LineItemsPage
attr_accessor :add_line_item_button
def initialize(test_env)
#browser = test_env[:browser]
#action_bar = #browser.div(:id => 'lineitems_win').div(:class => 'window-body').div(:class => 'actionbar')
#add_line_item_button = #action_bar.img(:class => 'button add')
end
def method_missing(sym, *args, &block)
#browser.send sym, *args, &block
end
end
I use it like so:
When /^I click on Add Item and enter the following values:$/ do |table|
#line_items_page = LineItemsPage.new(#test_env)
#line_items_page.add_line_item_button.when_present.click
end
I'm wondering if I should be abstracting the click, by adding something like the following to my LineItemsPage class:
def add_item
self.add_line_item_button.when_present.click
end
And then using it like so:
#line_items_page.add_item
I'm looking for best practices, either with regards to Page Object in particular or Ruby in general. I feel that encapsulating the interface by using add_item() is going a bit far, but I'm wondering if I'm unaware of issues I might run into down the road if I don't do that.
Personally, I try to make my page object methods be in the domain language with no reference to the implementation.
I used to do something like #line_items_page.add_line_item_button.when_present.click, however it has caused problems in the following scenarios:
1) The add line item was changed from a button to a link.
2) The process for adding a line item has changed - say its now done by a right-click or it has become a two step process (like open some dropdown and then click the add line).
In either case, you would have to locate all the places you add line items and update them. If you had all the logic in the add_item page object method, you would only have to update the one place.
From an implementation perspective, I have found that Cheezy's page object accessors work pretty well. However, for image buttons (or any of your app's custom controls), I would add additional methods to the PageObject::Accessors module. Or if they are one off controls, you can add the methods directly to the specific page object.
Update - Reply to Comment Regarding Some Starting Points:
I have not come across too much documentation, but here are a couple links that might help:
1) The Cheezy Page Object project wiki - Gives a simple example to get started
2) Cheezy's blog posts where the page object gem first started. Note that the content here might not be exactly how the gem is currently implemented, but I think it gives a good foundation to understanding what he is trying to achieve. This in turn makes it easier to understand what is happening when you have to open up and modify the gem to fit you needs.

Resources