Steve G.
Steve G.

Reputation: 33

"Element belongs to a different frame than the current one" error when I call wait_until on an element in an iframe

Given the following HTML:

<html>
   Log in:
   <iframe id="secure_iframe">
      <input id="user_login" type="text">
      <input id="user_password" type="password">
      <input id="login_dialog_submit" type="submit" name="commit" class="form-button">
   </iframe>
</html>

And the following page class code, using the page-object gem:

class LoginPage
  include PageObject

  in_iframe(id: 'secure_iframe') do |login_iframe|
     text_field(:email_field, id: 'user_login', frame: login_iframe)
     text_field(:password_field, id: 'user_password', frame: login_iframe)
     button(:login, id: 'login_dialog_submit', frame: login_iframe)
  end

  def login_as(account_object)
     wait_until { self.email_field_element.visible? }    # ERROR happens here
     self.email_field = account_object.email
     self.password_field = account_object.password
     login
  end
end

I get the following error:

Selenium::WebDriver::Error::StaleElementReferenceError: Element belongs to a different frame than the current one - switch to its containing frame to use it

Anyone else having the same issue? Thanks in advance for any and all help.

Upvotes: 2

Views: 1705

Answers (3)

Liiva
Liiva

Reputation: 338

It seems you want to make sure the email_field is shown before filling in the text_fields?

If so, an approach I use in my PageObjects is to use the initialize_page method:

class LoginPage
  include PageObject

  def initialize_page
    email_field_element.when_present
    password_field_element.when_present
  end

  in_iframe(id: 'secure_iframe') do |login_iframe|
    text_field(:email_field, id: 'user_login', frame: login_iframe)
    text_field(:password_field, id: 'user_password', frame: login_iframe)
    button(:login, id: 'login_dialog_submit', frame: login_iframe)
  end

  def login_as(account_object)
    self.email_field = account_object.email
    self.password_field = account_object.password
    login
    # optionally, you can return another PageObject here:
    PageOnceLoggedIn.new(@browser)
  end
end

The initialize_page method gets called when you use the visit(LoginPage) or on(LoginPage) from the PageObject::PageFactory module (thus this module needs to be included!)
Additional information

Upvotes: 0

Justin Ko
Justin Ko

Reputation: 46836

The exception is an issue with how the page object handles elements in a frame or iframe. Basically, when using the "[name]_element" method, the page object is not switching to the frame before interacting with the element. This is a bug that exists as Issue 224. The problem only occurs when using Selenium-Webdriver as the platform (ie does not occur with Watir-Webdriver).

In this case, since you are just checking if the element is visible, you can avoid using the "[name]_element" method (and therefore the exception) by using the "[name]?" method instead.

def login_as(account_object)
  wait_until { self.email_field? }
  self.email_field = account_object.email
  self.password_field = account_object.password
  login
end

Upvotes: 1

Steve G.
Steve G.

Reputation: 33

After spending time reading all the documentation I could get my eyes on, I figured out how to get the code to wait for an element inside an iframe:

With the login_as method looking like:

def login_as(account_type)
   show_login_dialog
   in_iframe(id: 'secure_login_iframe') do |iframe|
     wait_until { text_field_element(id: 'user_login', frame: iframe) }
   end
   self.email_field = account_object.email
   self.password_field = account_object.password
   self.login
 end

Note that the wait_until method is inside the in_iframe method, which passes the iframe into the text_field_element method as the last argument.

I don't like this solution because it breaks DRY - I'm defining the user_login field twice (and potentially more than twice) - once at the top of the page class and another time, dynamically, in the login_as method.

Upvotes: 1

Related Questions