Problem
There was a StackOverflow question today about visiting dynamically generated urls with the page object gem.
For example, the url of the page to view a StackOverflow user is in the form http://stackoverflow.com/users/1200545 where the id portion, “1200545”, is the specific user you want to view. You could create a page object to visit/interact with this page.
class StackOverflowUserPage include PageObject page_url "http://stackoverflow.com/users/1200545" end visit StackOverflowUserPage do |page| # Do stuff end
But what happens when you have multiple users? This page_url is specific to the one user. Each user would have a different id and therefore a different page_url. You could create a page object for each user, but that is a lot of duplication. As well, you might not know the required id before hand – ie it might get dynamically generated during your test.
Solution
To solve this, the page object gem allows you to create/visit dynamically generated urls. This is done by using ERB to include values from a params hash.
In the page_url, you can replace the hard-coded id value with <%=params[:id]%>. The url will be evaluated with ERB, which basically inserts the value returned from the code between the “<%=” and “%>”. The concept is similar to string interpolation.
class StackOverflowUserPage include PageObject page_url "http://stackoverflow.com/users/<%=params[:id]%>" end
You can then visit the page by specifying the dymamic portions of the url. This is done by passing in an extra :using_params hash.
visit StackOverflowUserPage, :using_params => {:id => '1200545'} do |page| # Do stuff with specific user end
If you want to add default parameter values, you can add a @params hash to the page object. For example, say by default, we want to view our user 1200545. Then we can change the page object to:
class StackOverflowUserPage include PageObject @params = {:id => '1200545'} page_url "http://stackoverflow.com/users/<%=params[:id]%>" end
Then we can visit our default user without specifying the params:
visit StackOverflowUserPage do |page| # Do stuff with default user end
For a different user, we can still specify their specific id:
visit StackOverflowUserPage, :using_params => {:id => '9999999'} do |page| # Do stuff with specific user end
Note
If part of the url needs to be dynamic but is known ahead of time and does not change, you can use string interpolation instead of ERB.
For example, say we have 2 environments to test against – http://stagestackoverflow.com and http://labstackoverflow.com. The environment is always known ahead of time and does not change during the life of the page object class.
In this case, it is easier to use string interpolation rather than ERB.
$environment = 'stage' # or 'lab' class StackOverflowUserPage include PageObject page_url "http://#{$environment}stackoverflow.com/users/1200545" end
We cannot use this approach for the dynamic id portion as the string interpolation of the page_url is evaluated (ie becomes set in stone) when the page object class is evaluated, which is before we call the visit method (ie before we know what id we want to visit).
Was looking for a solution like this. Thanks!
I do have one question. normally I would use something like:
When(/^I click on the user link on the member profile page$/) do
on(MemberProfilePage).userlink_element.when_present.click
end
Now with having to pass the parameter the code does not look as clean:
When(/^I click on the user link on the member profile page$/) do
on MemberProfilePage, :using_param {:id => ‘12345’} do |page|
page.userlink_element.when_present.click
end
end
The id is not known ahead of time so I can’t use that solution.
Once I know the id during runtime is there any other way to set the page_url? maybe with an instance variable?
Yes, you could pass instance variables to the :using_param hash:
Where @dynamic_id is an instance variable containing a string. Presumably you will have set this instance variable in a prior Cucumber step.
Thanks for the reply Justin. What about in the case where the dynamic id is not known until you are actually on the page. For example, say I place an order for an item. The id is not known until you land on the order confirmation page. Currently I don’t include the dynamic id of the order number. The page url is https://myfruits.com/fruits/65464564 and I have page_url set to ‘https://myfruits.com/fruits/’. No issues so far but is there a way to handle this? If not do you think there might be some issues that I have not run into so far?
If I recall correctly, the “page_url” (which is actually just an accessor that creates a “goto” method) is only used when you want to access the page directly – ie with the “visit” method.
In the case of a confirmation page, it seems unlikely you would be directly accessing it. Therefore, you could omit the page_url entirely.
If you did actually want to be able to “visit” the page directly later, then I think the only solution would be to scrape the dynamic id from the url (when landing on the confirmation page). Then store that id in a variable for later.
I’m trying to use this, but when I call the visit_page method I get an UndefinedMethodError. What am I doing wrong?
More Info… I’m trying to create a BasePage which can be inherited by any other Page items. The Root URL is the same, but then each other page may have a different string which needs to be added onto the Root URL to navigate to the correct page. My Root URL is defined in the env.rb file as an instance variable, but this instance variable is inaccessible by the class itself so i did the following
In BasePage class
page_url(“”)
Inherited pages have a method that is as so:
def get_url
return ‘/MyURLSuffix’
end
Then in my Step Defs I have the following step
When /I go to “(.*)”/ do |page|
active_page = Object.const_get(page).new(@browser)
visit_page active_page, :using_params => {:host => @hostURL, :url => active_page.get_url}
active_page.has_expected_title?
end
Can you please clarify which method is giving the UndefinedMethodError?
If you mean that the visit_page method is undefined, that likely means you have not included the PageFactory in your Cucumber’s World. You need to have done the following in your env.rb (or other support file):
For more details about this see the PageFactory documentation.
As an aside, the approach you are taking seems a bit odd or incomplete. Your code does not seem to be applying the concepts in this post – ie neither using the ERB or string interpolation. Assuming the base url never changes during a test run, you could write your page_url such that you do not need parameters for the visit_page method. If the base url does change, then I can see why you would use the :host parameter. However, your page_url does not seem to be using the ERB to evaluate the parameters.
very helpful thanks
Thank you for a great well-explained solution, I’ve just watched for this one.