我已經找到了有關H非常有趣的帖子ow @FindBy的工作原理以及如何在基於Selenium(WebDriver)的測試中使用FieldDecorator:http://habrahabr.ru/post/134462/。
該文章的作者是РоманОразмагомедов(Roman Orazmagomedof)。
這裏我給出更多關於如何使用FieldDecorator的解釋。此外,我還將展示原始實現的擴展版本,並增加了一些功能,這些功能可以使用ExpectedCondition界面等待裝飾字段的準備就緒。
設定目標
大多數硒頁面對象模式的插圖使用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>
}
有兩個比較重要的點要記住:
一)這種做法應該使用Selenium ExpectedCondition;
b)這種方法應該有助於分離「數據傳遞」和「數據斷言」之間的代碼。
元
公共接口元素{
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接口的實現一起使用。我將在下面討論。
此時元素的部分覆蓋已完成。這裏是總結:
每個元素由擴展Element接口的接口描述。
每個元素的實現擴展了AbstractElement類,並完成了isReady()以及其他所需的方法。
所需元素的實現應該在屬性文件中定義。
元素工廠將實例化一個元素,並通過裝飾器將它傳遞給PageFactory.initElement()。
起初看起來很複雜。
創建和使用簡單的元素來爲複雜的表單和頁面建模變得非常方便。
- 容器。
容器是一種將元素和其他容器放在一起以便爲複雜的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 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;
}
}
由接口定義
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);
}
}
};
}
}
集裝箱工廠以下是容器的簡短摘要:
容器用於將元素和其他容器合併爲一個單元。
容器的實現應該從AbstructContainer類擴展。它應該實現容器所需的isReady()和其他方法。
容器將被實例化並由容器工廠通過裝飾器傳遞給PageFactory.initElement()。
- 頁
頁是一個實例的webdriver和容器之間的橋樑。頁面可幫助將WebDriver與測試活動,測試數據供應和測試結果驗證分離。
頁是通過一個接口,類似於容器中定義:
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;
}
}
組件摘要:
元素 - > AbstractElement - >元素的Emplementations - >元件廠
容器 - > AbstractContainer - >集裝箱廠
頁面 - >摘要頁面。
- 裝飾
的結構上面描述成爲活着時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頁面和檢查上撰寫按鈕,如果屏幕上出現一個新的電子郵件窗口:
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
我發現了這個決議:看到那裏https://github.com/oraz/selenium – Arthur 2012-02-29 10:09:58