Creating page objects with a dynamic page_url

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).

This entry was posted in Page-Object-Gem and tagged , . Bookmark the permalink.

9 Responses to Creating page objects with a dynamic page_url

  1. Jesse says:

    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?

    • Justin Ko says:

      Yes, you could pass instance variables to the :using_param hash:

      on(MemberProfilePage, :using_param => {:id => @dynamic_id})
      

      Where @dynamic_id is an instance variable containing a string. Presumably you will have set this instance variable in a prior Cucumber step.

      • Jesse says:

        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?

      • Justin Ko says:

        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.

  2. Nathan says:

    I’m trying to use this, but when I call the visit_page method I get an UndefinedMethodError. What am I doing wrong?

    • Nathan says:

      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

    • Justin Ko says:

      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):

      World PageObject::PageFactory
      

      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.

  3. Abdullah says:

    very helpful thanks

  4. Thank you for a great well-explained solution, I’ve just watched for this one.

Leave a comment