【问题标题】:How to implement user types for @FindBy annotation?如何为@FindBy 注解实现用户类型?
【发布时间】:2012-02-28 08:27:36
【问题描述】:

我试图从中得到:

@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;

那个:

@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;

我之前已经为每个元素创建了类,但是当然什么也没有发生。我如何创建我的元素类以便我可以使用它而不是 WebElement?

此时输入框的代码如下:

 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();
}

}

【问题讨论】:

标签: java selenium webdriver


【解决方案1】:

创建一个新的 FieldDecorator 实现。

当您使用 PageFactory 时,您可能正在调用

 public static void initElements(ElementLocatorFactory factory, Object page)

这会变成

 public static void initElements(FieldDecorator decorator, Object page)

您的 FieldDecorator 的行为可能与 DefaultFieldDecorator 类似,只是将代理包装在您的自定义类型中。

在这里查看课程[source]

【讨论】:

  • 谢谢,我一直在寻找这个。但我仍然无法意识到。我正在调用 PageFactory.initElements(driver, this);但我不知道如何调用我的自定义装饰器
  • 我还没有测试过这个,但这就是我想象的班级的样子。 pastebin.com/1ufk2n8Z 你可以用 PageFactory.initElements(new CustomFieldDecorator(new DefaultElementLocatorFactory(driver)), this) 来调用它
  • 好的,这很清楚,但是我的用户类应该如何?里面一定有什么特别的吗?
【解决方案2】:

我发现了一篇关于@FindBy 如何工作以及如何在基于 Selenium (WebDriver) 的测试中使用 FieldDecorator 的非常有趣的帖子:http://habrahabr.ru/post/134462/

帖子的作者是Роман Оразмагомедов (Roman Orazmagomedof)。

这里我就如何使用 FieldDecorator 做更多的解释。此外,我将展示原始实现的扩展版本,它具有额外的功能,允许通过使用 ExpectedCondition 接口等待装饰字段准备好。

设定目标

Selenium 页面对象模式的大多数插图都使用 WebElement 接口来定义页面的字段:

public class APageObject {    

    @FindBy(id="fieldOne_id")  

    WebElement fieldOne;


    @FindBy(xpath="fieldTwo_xpath")

    WebElement fieldTwo;


    <RESTO OF THE Page IMPLEMENTATION>

}

我想要:

a) 一个页面是一个更通用的容器,能够将多个表单组合在一起。

b) 使用纯 java 对象而不是 WebElement 接口在页面上声明字段。

c) 有一种简单的方法来确定页面上的元素是否可以使用。

例如:

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>

}

其中 APageForm 看起来类似于 APageObject,但有一点不同——表单中的每个字段都由专用的 java 类定义。

public class APageForm {

    @FindBy(id="fieldOne_id")  

    FieldOne fieldOne;



    @FindBy(xpath="fieldTwo_xpath")

    FieldTwo fieldTwo;

    <REST OF THE FORM IMPLEMENTATION>

}

还有两点需要记住:

a) 这种方法应该使用 Selenium ExpectedCondition;

b) 这种方法应该有助于区分“数据传递”和“数据断言”之间的代码。

  1. 元素

    公共接口元素{

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

    }

这个接口应该扩展为更复杂的元素,如按钮、链接、标签等。例如:

public interface TextField extends Element {

       public TextField clear();

       public TextField enterText(String text);

       public ExpectedCondition<WebElement> isReady();

}

每个元素都应该提供 isReady() 以避免使用 Thread.sleep()。

每个元素的实现都应该继承 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();     

}

例如:

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

       }

}

以下接口描述了一个元素工厂:

public interface ElementFactory {

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

}

元素工厂的实现是:

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

              }

       }

}

工厂读取属性文件以使用所需的元素实现:

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

元素工厂将由 FieldDecorator 接口的实现使用。我将在下面讨论这个。

此时元素的部分覆盖完成。总结如下:

每个元素都由一个扩展元素接口的接口描述。

每个元素的实现都扩展了 AbstractElement 类并完成了 isReady() 以及其他必需的方法。

所需元素的实现应在属性文件中定义。

元素工厂将实例化一个元素并通过装饰器将其传递给 PageFactory.initElement()。

起初看起来很复杂。

创建和使用简单元素来建模复杂的表单和页面变得非常方便。

  1. 容器。

容器是一种将元素和其他容器放在一起的工具,以便为复杂的 Web 表单和页面建模。

容器结构与元素类似,但更简单。

容器由接口定义:

public interface Container  {

       public void init(WebElement wrappedElement);

       public ExpectedCondition<Boolean> isReady(WebDriverWait wait);

}

容器有它的 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);

}

注意容器的init方法很重要:方法的参数是WebElement接口的一个实例。

与元素类似,容器应该实现 isReady() 方法。不同之处在于返回类型:ExpectedCondition。

容器的“就绪”状态取决于容器中包含的元素的组合。

使用布尔类型将多个条件组合成一个是合乎逻辑的。

这是一个容器的例子:

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

                            }                         

                     }

              };

       }

}

由接口定义的容器工厂:

public interface ContainerFactory {

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

}

容器工厂的实现比元素工厂简单得多:

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;

       }

}

以下是容器的简短摘要:

容器用于将元素和其他容器组合成一个单元。

容器的实现应该从 AbstructContainer 类扩展而来。它应该实现 isReady() 和容器所需的其他方法。

容器工厂将通过装饰器实例化并传递给 PageFactory.initElement()。

  1. 页面

页面是 WebDriver 实例和容器之间的桥梁。页面有助于将 WebDriver 与测试活动、测试数据供应和测试结果验证分离。

一个Page是由一个接口定义的,类似于Container:

public interface Page {

       public void init(WebDriver driver);

}

容器和页面的区别在于init():

public abstract class AbstractPage implements Page {

       protected WebDriver driver;

       @Override

       public void init(WebDriver driver) {

              this.driver = driver;

       }

}

页面的 init 方法将 WebDriver 实例作为参数。

页面实现应该扩展 AbstractPage 类。例如,一个简单的 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;

       }

}

组件汇总:

元素 -> 抽象元素 -> 元素的实现 -> 元素工厂

容器 -> AbstractContainer -> 容器工厂

页面 -> 抽象页面。

  1. 装饰器

当 PageFactory.initElements() 调用提供的装饰器时,上述构造变得活跃。

一个基本的实现已经存在——DefaultFieldDecorator。让我们使用它。

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;

       }

}

请注意,decorateContainer() 直到所有子元素和容器都未初始化后才会退出。

现在,让我们看一个简单的测试,在 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());

              }

       }

}

从 Eclipse 运行测试时,需要使用以下 VM 参数:

-DpropertiesFile=testing.properties

可以在此处找到有关 QA 和 QA 自动化的源代码和其他几篇文章 http://qamation.blogspot.com

【讨论】:

    【解决方案3】:

    第一个猜测:您是否一直在考虑更好的命名约定。在我的课堂上,按钮如下所示:

    private WebElement loginButton;
    

    在我的 selenium 测试中我发现,更好的方法是为每个页面设置 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();
      }
    
      }
    

    然后测试是这样的:

    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");
    }
    

    但假设这对你不起作用,我有两个猜测:

    首先,您可以从 WebElement 扩展:

    public class Button extends WebElement{
    

    但您可能必须实现所有 WebElement 公共方法,即使您不使用它们

    然后作为第二个猜测,您可以发送驱动程序并找到构造函数的路径

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

    调用将是:

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

    顺便说一句,我的假设是,您正在使用 WebDriver 方法

    编辑 我发现,WebElement 是接口。所以你可以这样做:

    public class WebButton implements WebElement{
    

    但您必须实现接口 WebElement 的所有抽象方法。

    无论如何,当我这样做时,它允许我在我的其他班级中做这个注释:

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

    但我从未使用过这种方法,也不能保证它会做某事......

    顺便说一句,如果有兴趣,我在这里粘贴了我的实现示例:

    http://pastebin.com/STr15UQd

    【讨论】:

    • 很清楚。但我想通过@FindBy 注释设置我的用户类型(按钮、输入框等)
    • 我从未使用过这种方法,但会将答案更新为我刚刚测试的内容
    猜你喜欢
    • 2021-04-13
    • 1970-01-01
    • 1970-01-01
    • 2018-04-03
    • 1970-01-01
    • 1970-01-01
    • 2012-06-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多