问题
I am following Page Object Model to automate a flow in one application. In one of the module I have to add a new post which have a "Title" and a "Body" field. As of now, I am able to send the text in the "Title" field as it is in the Top Window. But the "Body" is within an iframe. After passing the text in "Title" I tried to switch to the iframe before writing in the "Body". This piece of code I have written in the main file. But Selenium shows an error as org.openqa.selenium.ElementNotVisibleException: element not visible
My PageFactory code is as follows:
package com.wordpress.pom.Pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
public class AddNewPost {
WebDriver driver;
public AddNewPost(WebDriver addNewPostDriver)
{
this.driver=addNewPostDriver;
}
@FindBy(how=How.ID,using="title")
WebElement post_title;
@FindBy(how=How.XPATH,using=".//*[@id='tinymce']/p/br")
WebElement post_body;
public void construct_title()
{
post_title.sendKeys("This is the Title");
System.out.println("Title written");
}
public void construct_body()
{
post_body.sendKeys("This is the body");
System.out.println("Body written");
}
}
I am using testNG to schedule the testcases. Here is my main file code:
package com.wordpress.pom.Testcase;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.wordpress.pom.Helper.BrowserFactory;
import com.wordpress.pom.Pages.AddNewPost;
import com.wordpress.pom.Pages.Dashboard;
import com.wordpress.pom.Pages.LoginPageNew;
import com.wordpress.pom.Pages.Posts;
public class VerifyValidLogin
{
WebDriver driver;
//code ommitted
@Test (priority=3)
public void construct_title()
{
//Created Page Object using Page Factory
AddNewPost add_new_post = PageFactory.initElements(driver, AddNewPost.class);
//Call the method
add_new_post.construct_title();
}
@Test (priority=4)
public void construct_body()
{
//Created Page Object using Page Factory
AddNewPost add_new_post = PageFactory.initElements(driver, AddNewPost.class);
driver.switchTo().frame("content_ifr");
//Call the method
add_new_post.construct_body();
}
}
The HTML DOM is as:
<iframe id="content_ifr" src="javascript:""" allowtransparency="true" title="Rich Text Area Press ALT F10 for toolbar. Press ALT 0 for help." style="width: 100%; height: 330px; display: block;" frameborder="0">
<!DOCTYPE >
<html>
<head xmlns="http://www.w3.org/1999/xhtml">
<body id="tinymce" class="mceContentBody content post-type-post wp-editor" onload="window.parent.tinyMCE.get('content').onLoad.dispatch();" dir="ltr" contenteditable="true">
<p>
<br data-mce-bogus="1">
</p>
</body>
</html>
</iframe>
I feel the elements identified in the PageFactory for the body is not incorrect. Can someone help me out please?
Update:
Add a Thread.sleep(3000) in the main class before switching to the iframe.
Thread.sleep(3000); driver.switchTo().frame("content_ifr");
Changed the XPATH of the "Body" field in Page Factory.
@FindBy(how=How.XPATH,using="//html/body/p") WebElement post_body;
On debugging found that WebDriver does clicks on the Body field and I can sysout but won't send the keys in the Body field. So I was forced to use Action class.
public void construct_body() { Actions actions = new Actions(driver); actions.moveToElement(post_body); actions.click(); actions.sendKeys("Some Name"); actions.build().perform(); System.out.println("Body written"); }
I am able to pass the text now. But I am still not sure why would I need the Action class even after clicking on the Body element. Finally would like your opinion if my approach is correct here following the Page Object Model.
回答1:
All the Things going on here:
1) You were receiving Element Not Visible exception
that means element is present in the DOM, but by the time you want to interact, it is just not rendered yet hence you need to use the wait
.
2) You were not able to FOCUS on the element on the rendered page, which can happen due to various reasons; that is why we use Action.moveToElement(elementToBeInFocus)
to explicitly deliver focus to that particular element
回答2:
I'll give you several advice of how to try to deal with it. Hope it will help you.
- Add
driver.switchTo().frame("content_ifr");
code to theconstruct_body
method to avoid mistakes in future when you forget to switch to the frame before using this method. - Probably you haven't switched to the correct frame. To check this get outerHTML of the frame or get any other element that is easy to locate. If you can't locate any elements then the frame is wrong. But it's better to look at its outerHTML go make sure it's a correct frame. There are several methods to get outerHTML Get HTML Source of WebElement in Selenium WebDriver using Python
- Make sure your xPath is relative to your iframe, not to your main HTML document. The start of the frame should be its beginning point.
And when you manage to switch to your frame and to get element, don't forget to switch back!)
回答3:
IF you are asking about strategy there are different ways to achieve this, but what I do is I simple ask this question to my self. What is this component?
i.e. What is body?
body is part of New Post (right?). And so simply you can create a new Page Object class just for body and create a object inside your NewPost Class. This is Has-A Relationship in my opinion.
You can read more about this in below post. http://www.w3resource.com/java-tutorial/inheritance-composition-relationship.php
About your particular problem, few things.
- Do not directly intereact with the element in the test, like switching to frame. Handle that inside a class or a function. in you case may be in a construct_body function.
- You are not getting an error that Element is Not visible, means Element is present and it's a good thing. You can use Explicit Wait to wait until element is visible. Read following article for more details. http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp
Following is just an example how you can handle this scenario.
Base Page: Initialize the driver and some global function
abstract public class BasePage {
}
Body Class: Responsible for handling all the function related to body part of post. It's not public so if tests are in different package they won't have direct access to this class. They will have to get access through Post Class only.
class BodyPage extends BasePage {
public BodyPage(WebDriver driver){
PageFactory.initElements(driver, this);
//Switch to frame
}
public void save(){
//Do some action here
// Switch Back to main page
}
}
Post Class: Responsible for iterating with whole page. BodyPage object is private, so you can't directly interact it in test. Now those who are writing test cases, they only know about Post Class.
public class PostPage extends BasePage {
private BodyPage bodyPage;
public PostPage(WebDriver driver){
PageFactory.initElements(driver, this);
//Or Switch to frame here
bodyPage = PageFactory.initElements(driver, BodyPage.class);
}
public void enterBody(){
//Call other functions
bodyPage.save();
//you can switch back to main window here as well.
}
}
来源:https://stackoverflow.com/questions/43001795/selenium-webdriver-java-how-do-i-store-the-elements-of-an-iframe-in-a-pagefact