简体   繁体   English

如何实现@FindBy注释的用户类型?

[英]How to implement user types for @FindBy annotation?

I'm trying to get from this: 我试图从中得到:

@FindBy(xpath = "//div/span/img")
public WebElement addNew;

@FindBy(xpath = "//tr[2]/td[12]")
public WebElement save;

@FindBy(xpath = "//td/div/input")
public WebElement entryIdel;

@FindBy(xpath = "//textarea")
public WebElement authorFieldel;

@FindBy(xpath = "//td[3]/div/textarea")
public WebElement titleFieldel;

that: 那:

@FindBy(xpath = "//div/span/img")
public Button addNew;

@FindBy(xpath = "//tr[2]/td[12]")
public Button save;

@FindBy(xpath = "//td/div/input")
public InputBox entryIdel;

@FindBy(xpath = "//textarea")
public InputBox authorFieldel;

@FindBy(xpath = "//td[3]/div/textarea")
public InputBox titleFieldel;

I have previously created class for each element, but of course nothing happens. 我以前为每个元素创建了类,但当然没有任何反应。 How i can create my element class so that i can use it instead of WebElement? 我如何创建我的元素类,以便我可以使用它而不是WebElement?

Here the code of InputBox at this moment: 这里是InputBox的代码:

 import org.openqa.selenium.WebElement;

  public class InputBox {

protected WebElement element;

public WebElement getElement() {
    return element;
}

public InputBox(WebElement element) {
    this.element = element;
    // TODO Auto-generated constructor stub
}

public void type(String input) {
    clearText();
    element.sendKeys(input);
}

public void clearText() {
    element.clear();
}

public boolean isEditable() {
    return element.isEnabled();
}

String getText() {
    return element.getText();
}

String getValue() {
    return element.getValue();
}

}

I have found a very interesting post about how @FindBy works and how to use FieldDecorator in Selenium (WebDriver) based tests: http://habrahabr.ru/post/134462/ . 我发现了一篇非常有趣的帖子,关于@FindBy如何工作以及如何在Selenium(WebDriver)测试中使用FieldDecorator: http ://habrahabr.ru/post/134462/。

The author of the post is Роман Оразмагомедов (Roman Orazmagomedof). 这篇文章的作者是РоманОразмагомедов(Roman Orazmagomedof)。

Here I am giving more explanation on how to use FieldDecorator. 在这里,我将提供有关如何使用FieldDecorator的更多说明。 Also I will be showing the extended version of the original implementation with additional functionality which will allow waiting for a decorated field to be ready by using ExpectedCondition interface. 此外,我将展示原始实现的扩展版本以及其他功能,这将允许使用ExpectedCondition接口等待装饰字段准备就绪。

Setting Objectives 设定目标

Most illustrations of Selenium page object pattern are using WebElement interface to define the pages' fields: Selenium页面对象模式的大多数插图都使用WebElement接口来定义页面的字段:

public class APageObject {    

    @FindBy(id="fieldOne_id")  

    WebElement fieldOne;


    @FindBy(xpath="fieldTwo_xpath")

    WebElement fieldTwo;


    <RESTO OF THE Page IMPLEMENTATION>

}

I would like: 我想要:

a) A page to be a more generic container with the ability to combine several forms together. a)页面是一个更通用的容器,能够将多个表单组合在一起。

b) To use plain java objects instead of WebElement interface to declare fields on a page. b)使用普通java对象而不是WebElement接口来声明页面上的字段。

c) To have a simple way to determine if an element on a page is ready to be used or not. c)有一种简单的方法来确定页面上的元素是否可以使用。

For example: 例如:

public class PageObject  {

        private APageForm formA;

        <OTHER FORMS DECLARATIONS >

        public void init(final WebDriver driver) {

            this.driver = driver;

            formA = new APageForm());

            PageFactory.initElements(new SomeDecorator(driver), formA);

                <OTHER FORMS INITIALIZATION>

        }

        <THE REST OF  the PAGE IMPLEMENTATION>

}

Where APageForm looks similar to a APageObject, but with a little difference – each field in the form is defined by a dedicated java class. 其中APageForm看起来类似于APageObject,但略有不同 - 表单中的每个字段都由专用的java类定义。

public class APageForm {

    @FindBy(id="fieldOne_id")  

    FieldOne fieldOne;



    @FindBy(xpath="fieldTwo_xpath")

    FieldTwo fieldTwo;

    <REST OF THE FORM IMPLEMENTATION>

}

There are two more important points to remember: 还有两点需要记住:

a) This approach should use Selenium ExpectedCondition; a)这种方法应该使用Selenium ExpectedCondition;

b) This approach should help to separate code between “data delivery” and “data assertion”. b)这种方法应该有助于在“数据传递”和“数据断言”之间分离代码。

  1. Element 元件

    public interface Element { 公共接口元素{

      public boolean isVisible(); public void click(); public ExpectedCondition<WebElement> isReady(); 

    } }

This interface should be extended for more complex elements like button, link, label etc. For example: 此接口应扩展为更复杂的元素,如按钮,链接,标签等。例如:

public interface TextField extends Element {

       public TextField clear();

       public TextField enterText(String text);

       public ExpectedCondition<WebElement> isReady();

}

Each element should provide isReady() to avoid usage of the Thread.sleep(). 每个元素都应该提供isReady()以避免使用Thread.sleep()。

Every implementation of an element should extend AbstractElement class: 元素的每个实现都应该扩展AbstractElement类:

public abstract class AbstractElement implements Element {

       protected WebElement wrappedElement;



protected AbstractElement (final WebElement el) {

              this.wrappedElement = el;

       }

       @Override

       public boolean isVisible() {

              return wrappedElement.isDisplayed();

       }     

       @Override

       public void click() {

           wrappedElement.click();    

       }     

       public abstract ExpectedCondition<WebElement> isReady();     

}

For example: 例如:

public class ApplicationTextField extends AbstractElement implements TextField {

       public ApplicationTextField(final WebElement el) {

              super(el);

       }

       @Override

       public TextField clear() {

              wrappedElement.clear();

              return this;

       }

       @Override

       public TextField enterText(String text) {

              char[] letters = text.toCharArray();

              for (char c: letters) {                 

                     wrappedElement.sendKeys(Character.toString(c));

                     // because it is typing too fast...

                     try {

                           Thread.sleep(70);

                     } catch (InterruptedException e) {

                           e.printStackTrace();

                     }

              }

              return this;

       }

       @Override

       public ExpectedCondition<WebElement> isReady() {

              return ExpectedConditions.elementToBeClickable(wrappedElement);

       }

}

The following interface describes an element factory: 以下界面描述了一个元素工厂:

public interface ElementFactory {

       public <E extends Element> E create(Class<E> containerClass, WebElement wrappedElement);

}

the implementation of the element factory is: 元素工厂的实现是:

public class DefaultElementFactory implements ElementFactory {

       @Override

       public <E extends Element> E create(final Class<E> elementClass,

                     final WebElement wrappedElement) {

              E element;

              try {

                     element = findImplementingClass(elementClass)

                                  .getDeclaredConstructor(WebElement.class)

                                  .newInstance(wrappedElement);

              }

              catch (InstantiationException e) { throw new RuntimeException(e);}

              catch (IllegalAccessException e) { throw new RuntimeException(e);}

              catch (IllegalArgumentException e) {throw new RuntimeException(e);}

              catch (InvocationTargetException e) {throw new RuntimeException(e);}

              catch (NoSuchMethodException e) { throw new RuntimeException(e);}

              catch (SecurityException e) {throw new RuntimeException(e);}

              return element;

       }



       private <E extends Element> Class<? extends E> findImplementingClass (final Class<E> elementClass) {

              String pack = elementClass.getPackage().getName();

              String className = elementClass.getSimpleName();

              String interfaceClassName = pack+"."+className;

              Properties impls = TestingProperties.getTestingProperties().getImplementations();

              if (impls == null) throw new RuntimeException("Implementations are not loaded");

              String implClassName = impls.getProperty(interfaceClassName);

              if (implClassName == null) throw new RuntimeException("No implementation found for interface "+interfaceClassName);

              try {

                     return (Class<? extends E>) Class.forName(implClassName);

              } catch (ClassNotFoundException e) {

                     throw new RuntimeException("Unable to load class for "+implClassName,e);

              }

       }

}

The factory reads a property file to use a desired implementation for an element: 工厂读取属性文件以使用元素的所需实现:

com.qamation.web.elements.Button = tests.application.elements.ApplicationButton

com.qamation.web.elements.Link = tests.application.elements.ApplicationLink

com.qamation.web.elements.TextField = tests.application.elements.ApplicationTextField

com.qamation.web.elements.Label=tests.application.elements.ApplicationLabel

The element factory is going to be used by an implementation of the FieldDecorator interface. 元素工厂将由FieldDecorator接口的实现使用。 I will discuss this below. 我将在下面讨论这个问题。

At this point the elements' part coverage is completed. 此时元素的部分覆盖范围已完成。 Here is the summary: 以下是摘要:

Each element is described by an interface which is extending Element interface. 每个元素由扩展Element接口的接口描述。

Every element's implementation extends the AbstractElement class and completes isReady(), along with other required methods. 每个元素的实现都扩展了AbstractElement类,并完成了isReady()以及其他必需的方法。

Desired element's implementation should be defined in a property file. 应在属性文件中定义所需元素的实现。

The element factory will instantiate an element and pass it to the PageFactory.initElement() via the decorator. 元素工厂将实例化一个元素,并通过装饰器将其传递给PageFactory.initElement()。

It seems complicated at first. 起初看起来很复杂。

It becomes very convenient to create and use simple elements to model complex forms and pages. 创建和使用简单元素来模拟复杂的表单和页面变得非常方便。

  1. Container. 容器。

A container is a facility to keep elements and other containers together in order to model complex web forms and pages. 容器是将元素和其他容器保持在一起以便为复杂的Web表单和页面建模的工具。

The container structure is similar to the element however it's simpler. 容器结构类似于元素,但它更简单。

A container is defined by an interface: 容器由接口定义:

public interface Container  {

       public void init(WebElement wrappedElement);

       public ExpectedCondition<Boolean> isReady(WebDriverWait wait);

}

The container has its AbstractContainer base class: 容器有AbstractContainer基类:

public abstract class AbstractContainer implements Container{

       private WebElement wrappedElement;

       @Override

       public void init(WebElement wrappedElement) {

              this.wrappedElement = wrappedElement;

       }     

       public abstract ExpectedCondition<Boolean> isReady(final WebDriverWait wait);

}

It is important to pay attention to the container's init method: method's parameter is an instance of the WebElement interface. 注意容器的init方法很重要:method的参数是WebElement接口的一个实例。

Similar to an element, a container should implement the isReady() method. 与元素类似,容器应该实现isReady()方法。 The difference is in the return type: ExpectedCondition. 不同之处在于返回类型:ExpectedCondition。

The “Ready” condition of a container depends on the combination of the elements included into the container. 容器的“就绪”状态取决于容器中包含的元素的组合。

It is logical to combine several conditions into one using Boolean type. 使用布尔类型将几个条件组合成一个条件是合乎逻辑的。

Here is an example of a container: 以下是容器的示例:

public class LoginContainer extends AbstractContainer{

       @FindBy(id="Email")

       private TextField username;

       @FindBy(id="Passwd" )

       private TextField password;

       @FindBy(id="signIn")

       private Button submitButton;

       public void login(final String username, final String password) {

              this.username.clear().enterText(username);

              this.password.clear().enterText(password);

              this.submitButton.press();

       }

       @Override

       public ExpectedCondition<Boolean> isReady(final WebDriverWait wait) {     

              return new ExpectedCondition<Boolean>() {

                     @Override

                     public Boolean apply(final WebDriver driver) {

                           ExpectedCondition isUserNameFieldReady = username.isReady();

                            ExpectedCondition isPasswordFieldReady = password.isReady();

                            ExpectedCondition isSubmitButtonReady = submitButton.isReady();

                           try {

                                  wait.until(isUserNameFieldReady);

                                  wait.until(isPasswordFieldReady);

                                  wait.until(isSubmitButtonReady);

                                  return new Boolean(true);

                           }

                           catch (TimeoutException ex) {

                                  return new Boolean(false);

                            }                         

                     }

              };

       }

}

Container factory defined by an interface: 容器工厂由接口定义:

public interface ContainerFactory {

       public <C extends Container> C create(Class<C> wrappingClass, WebElement wrappedElement);

}

container factory's implementation is much simpler than element's factory: 容器工厂的实现比元素工厂简单得多:

public class DefaultContainerFactory implements ContainerFactory {

       @Override

       public <C extends Container> C create(final Class<C> wrappingClass,

                     final WebElement wrappedElement) {

              C container;

              try {

                     container = wrappingClass.newInstance();

              }

catch (InstantiationException e){throw new RuntimeException(e);}

catch (IllegalAccessException e){throw new RuntimeException(e);}

              container.init(wrappedElement);

              return container;

       }

}

Here is a short summary for the container: 以下是容器的简短摘要:

A container is used to combine elements and other containers into one unit. 容器用于将元件和其他容器组合成一个单元。

A container's implementation should extend from the AbstructContainer class. 容器的实现应该从AbstructContainer类扩展。 It should implement isReady() and other methods required by the container. 它应该实现isReady()和容器所需的其他方法。

A container will be instantiated and passed to the PageFactory.initElement() by the container factory through a decorator. 容器将被实例化,并由容器工厂通过装饰器传递给PageFactory.initElement()。

  1. Page

A page is a bridge between a WebDriver instance and containers. 页面是WebDriver实例和容器之间的桥梁。 A Page helps to decouple WebDriver from testing activities, test data provisioning and test results verification. 页面有助于将WebDriver与测试活动,测试数据配置和测试结果验证分离。

A Page is defined by an interface, similar to the Container: 页面由接口定义,类似于Container:

public interface Page {

       public void init(WebDriver driver);

}

The difference between a container and a page is in the init(): 容器和页面之间的区别在于init():

public abstract class AbstractPage implements Page {

       protected WebDriver driver;

       @Override

       public void init(WebDriver driver) {

              this.driver = driver;

       }

}

Page's init method takes a WebDriver instance as a parameter. Page的init方法将WebDriver实例作为参数。

A page implementation should extend the AbstractPage class. 页面实现应该扩展AbstractPage类。 For example, a simple gmail page: 例如,一个简单的gmail页面:

public interface GMailPage extends Page {

       public NewEmail startNewEmail();

}

public class DefaultGMailPage extends AbstractPage implements GMailPage {

       private LeftMenueContainer leftMenue;

       public void init(final WebDriver driver) {

              this.driver = driver;

              leftMenue = new LeftMenueContainer();          

              PageFactory.initElements(new DefaultWebDecorator(driver), leftMenue);

              WebDriverWait wait = new WebDriverWait(driver,TestingProperties.getTestingProperties().getTimeOutGeneral());

        ExpectedCondition<Boolean> isEmailFormReady = leftMenue.isReady(wait);

        wait.until(isEmailFormReady);

       }

       @Override

       public NewEmail startNewEmail() {       

              leftMenue.pressCompose();

              NewEmailWindowContainer newEmail = new NewEmailWindowContainer();

        PageFactory.initElements(new DefaultWebDecorator(driver), newEmail);

        WebDriverWait wait = new WebDriverWait(driver,TestingProperties.getTestingProperties().getTimeOutGeneral());

        ExpectedCondition<Boolean> isNewEmailReady=newEmail.isReady(wait);

              wait.until(isNewEmailReady);

              return newEmail;

       }

}

Components summary: 组件摘要:

Element -> AbstractElement -> Element's Emplementations -> Element Factory 元素 - > AbstractElement - >元素的实现 - >元素工厂

Container -> AbstractContainer -> Container Factory 容器 - > AbstractContainer - >容器工厂

Page -> AbstractPage. 页面 - > AbstractPage。

  1. Decorator 装饰

The constructions described above become alive when PageFactory.initElements() calls the provided decorator. 当PageFactory.initElements()调用提供的装饰器时,上述结构变为活动状态。

A basic implementations is already exists – DefaultFieldDecorator. 基本实现已经存在 - DefaultFieldDecorator。 Lets use it. 让我们用它。

public class DefaultWebDecorator extends DefaultFieldDecorator {

       private ElementFactory elementFactory = new DefaultElementFactory();

       private ContainerFactory containerFactory = new DefaultContainerFactory();



       public DefaultWebDecorator(SearchContext context) {

              super(new DefaultElementLocatorFactory(context));

       }

       @Override

       public Object decorate(ClassLoader classLoader, Field field) {

              ElementLocator locator = factory.createLocator(field);

              WebElement wrappedElement = proxyForLocator(classLoader, locator);

              if (Container.class.isAssignableFrom(field.getType())) {

                     return decorateContainer(field, wrappedElement);

              }

              if (Element.class.isAssignableFrom(field.getType())) {

                     return decorateElement(field, wrappedElement);

              }

              return super.decorate(classLoader, field);

       }

       private Object decorateContainer(final Field field, final WebElement wrappedElement) {

              Container container = containerFactory.create((Class<? extends Container>)field.getType(), wrappedElement);

              PageFactory.initElements(new DefaultWebDecorator(wrappedElement), container);           

              return container;

       }



       private Object decorateElement(final Field field, final WebElement wrappedElement) {

              Element element = elementFactory.create((Class<? extends Element>)field.getType(), wrappedElement);

              return element;

       }

}

Note that the decorateContainer() does not exit until all sub elements and containers are not initialized. 请注意,在初始化所有子元素和容器之前,decorateContainer()不会退出。

Now, let's look at a simple test that presses Compose button on the gmail page and checks if a new email window appears on the screen: 现在,让我们看一个简单的测试,它按下gmail页面上的Compose按钮并检查屏幕上是否出现一个新的电子邮件窗口:

public class NewEmailTest {

       private WebDriver driver; 

       @BeforeTest

       public void setUp() {

              driver = new FirefoxDriver();

              driver.manage().window().maximize();

       }     

       @AfterTest

       public void tearDown() {

              driver.close();

       }     

       @Test (dataProvider = "inputAndOutput", dataProviderClass = com.qamation.data.provider.TestDataProvider.class)

       public void startNewEmailTest(DataBlock data) {

              DefaultHomePage homePage = new DefaultHomePage();

              driver.manage().deleteAllCookies();     

              driver.get(data.getInput()[0]);

              homePage.init(driver);

              NewEmail newEmail = homePage.signIn().login(data.getInput()[1], data.getInput()[2]).startNewEmail();           



              for (String[] sa : data.getExpectedResults()) {

                  WebElement el = driver.findElement(By.xpath(sa[0]));

                  Assert.assertTrue(el.isDisplayed());

              }

       }

}

When running the test from Eclipse, the following VM arguments need to be used: 从Eclipse运行测试时,需要使用以下VM参数:

-DpropertiesFile=testing.properties -DpropertiesFile = testing.properties

The source and several more articles about QA and QA Automation can be found here http://qamation.blogspot.com 有关QA和QA Automation的更多文章可以在http://qamation.blogspot.com找到

Create a new implementation of FieldDecorator. 创建FieldDecorator的新实现。

When you use the PageFactory you are probably calling 当您使用PageFactory时,您可能正在调用

 public static void initElements(ElementLocatorFactory factory, Object page)

This would become 这将成为

 public static void initElements(FieldDecorator decorator, Object page)

Your FieldDecorator could behave similarly to the DefaultFieldDecorator except wrap the proxy in your custom type. 除了在自定义类型中包装代理外,FieldDecorator的行为与DefaultFieldDecorator类似。

See the classes here [source] 看这里的课程[来源]

First guess: Have you been thinking about better naming convenion. 初步猜测:您是否一直在考虑更好的命名方案。 In my class, the buttons look like this: 在我的课堂上,按钮看起来像这样:

private WebElement loginButton;

In my selenium tests I found out, that better approach is to have Class for each page, like: 在我的硒测试中,我发现,更好的方法是为每个页面设置Class,例如:

public Class LoginPage{
  private WebElement loginButton;
  private WebElement loginField;
  private WebElement passwordField;
  private WebDriver driver;

  public LoginPage(WebDriver drv){
  this.driver = drv;
  }

  public void login(String uname; String pwd){
   loginButton = driver.findElement(By.xpath("//td/div/input"));
   passwordField = driver...
   loginField = driver...
   loginField.sendKeys(uname);
   passwordField.sendkeys(pwd);      
   loginButton.click();
  }

  }

And then the test looks like this: 然后测试看起来像这样:

public void testLogin(){
 WebDriver driver = new FirefoxDriver();
 driver.get("http://the-test-page.com/login.htm");
 LoginPage loginPage = new LoginPage(driver);
 loginPage.login("username", "password");
}

but assuming that this does not work for you, there is my two guseeses: 但假设这对你不起作用,我有两个问题:

First, you can extend from WebElement: 首先,您可以从WebElement扩展:

public class Button extends WebElement{

but you will probably have to implement all the WebElement public methods even if you are not using them 但即使您不使用它们,也可能必须实现所有WebElement公共方法

Then as Second guess you can send driver and find path to the constructor 然后作为第二个猜测你可以发送驱动程序并找到构造函数的路径

public class Button {
 private WebDriver driver;
 private WebElement button;
 private WebDriver driver;

 public Button(WebDriver driver, By by){
    this,driver = driver;
    button = findElement(by);
 }

and the calling would be: 并且呼叫将是:

Button loginButton = new Button(driver, By.xpath("//td/div/input"));

BTW my assumtion here is, that you are using the WebDriver approach 顺便说一下,我的观点是,你正在使用WebDriver方法

EDIT I found out, that WebElement is interface. 编辑我发现, WebElement是界面。 So you can do something like this: 所以你可以这样做:

public class WebButton implements WebElement{

but you will have to implement all abstract methods of interface WebElement. 但是你必须实现接口WebElement的所有抽象方法。

Anyways, when I did it, it allowed me to do this annotation in my other class: 无论如何,当我这样做时,它允许我在我的其他课程中做这个注释:

@FindBy(xpath = "//textarea")
public WebButton testButton;

but I never used that approach and cannot guarantee that it will do something... 但我从来没有使用过这种方法,也无法保证它能做些什么......

BTW if interested, I pasted the example of my implementation here: BTW如果有兴趣,我在这里粘贴了我的实现示例:

http://pastebin.com/STr15UQd http://pastebin.com/STr15UQd

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM