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

我试图从中得到:

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

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

创建FieldDecorator的新实现。

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

  public static void initElements(ElementLocatorFactory factory, Object page) 

这会变成

  public static void initElements(FieldDecorator decorator, Object page) 

除了在自定义类型中包装代理外,FieldDecorator的行为与DefaultFieldDecorator类似。

看这里的课程[来源]

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

这篇文章的作者是РоманОразмагомедов(Roman Orazmagomedof)。

在这里,我将提供有关如何使用FieldDecorator的更多说明。 此外,我将展示原始实现的扩展版本以及其他function,这将允许使用ExpectedCondition接口等待装饰字段准备就绪。

设定目标

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

 public class APageObject { @FindBy(id="fieldOne_id") WebElement fieldOne; @FindBy(xpath="fieldTwo_xpath") WebElement fieldTwo;  } 

我想要:

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

b)使用普通的java对象而不是WebElement接口来声明页面上的字段。

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

例如:

 public class PageObject { private APageForm formA;  public void init(final WebDriver driver) { this.driver = driver; formA = new APageForm()); PageFactory.initElements(new SomeDecorator(driver), formA);  }  } 

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

 public class APageForm { @FindBy(id="fieldOne_id") FieldOne fieldOne; @FindBy(xpath="fieldTwo_xpath") FieldTwo fieldTwo;  } 

还有两点需要记住:

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

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

  1. 元件

    public interface Element {

      public boolean isVisible(); public void click(); public ExpectedCondition isReady(); 

    }

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

 public interface TextField extends Element { public TextField clear(); public TextField enterText(String text); public ExpectedCondition 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 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 isReady() { return ExpectedConditions.elementToBeClickable(wrappedElement); } } 

以下界面描述了一个元素工厂:

 public interface ElementFactory { public  E create(Class containerClass, WebElement wrappedElement); } 

元素工厂的实现是:

 public class DefaultElementFactory implements ElementFactory { @Override public  E create(final Class 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  Class findImplementingClass (final Class 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) 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接口的实现使用。 我将在下面讨论这个问题。

此时元素的部分覆盖范围已完成。 以下是摘要:

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

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

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

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

起初看起来很复杂。

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

  1. 容器。

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

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

容器由接口定义:

 public interface Container { public void init(WebElement wrappedElement); public ExpectedCondition 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 isReady(final WebDriverWait wait); } 

注意容器的init方法很重要:method的参数是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 isReady(final WebDriverWait wait) { return new ExpectedCondition() { @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 create(Class wrappingClass, WebElement wrappedElement); } 

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

 public class DefaultContainerFactory implements ContainerFactory { @Override public  C create(final Class 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()。

页面是WebDriver实例和容器之间的桥梁。 页面有助于将WebDriver与测试活动,测试数据配置和测试结果validation分离。

页面由接口定义,类似于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; } } 

Page的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 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 isNewEmailReady=newEmail.isReady(wait); wait.until(isNewEmailReady); return newEmail; } } 

组件摘要:

元素 – > AbstractElement – >元素的实现 – >元素工厂

容器 – > AbstractContainer – >容器工厂

页面 – > AbstractPage。

  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)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)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 Automation的更多文章可以在http://qamation.blogspot.com找到

初步猜测:您是否一直在考虑更好的命名方案。 在我的课堂上,按钮看起来像这样:

 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