创意规划设计有限公司官网,seo做得比较好的公司,福州网站开发大概费用,设计软件有哪些软件免费selenium架构在此博客文章中#xff0c;我想介绍一种具有最佳设计模式的Selenium测试的简洁架构#xff1a;页面对象#xff0c;页面元素#xff08;通常称为HTML包装器#xff09;以及自行开发的非常小巧的框架。 该体系结构不限于示例中使用的Java#xff0c;也可以以任… selenium架构 在此博客文章中我想介绍一种具有最佳设计模式的Selenium测试的简洁架构页面对象页面元素通常称为HTML包装器以及自行开发的非常小巧的框架。 该体系结构不限于示例中使用的Java也可以以任何其他语言应用于Selenium测试。 定义和关系。 页面对象 。 页面对象封装了网页的行为。 每个网页有一个页面对象将页面的逻辑抽象到外部。 这意味着与网页的交互被封装在page对象中。 Selenium的“ 通过”定位器来查找页面上的元素也没有对外公开。 页面对象的调用者不应忙于By定位符例如By.id By.tageName By.cssSelector等。Selenium测试类对页面对象进行操作。 从网上商店举个例子页面对象类可以称为例如ProductPage ShoppingCartPage PaymentPage等。这些始终是带有自己的URL的整个网页的类。 页面元素 又名HTML包装器 。 页面元素是网页的另一个细分。 它表示一个HTML元素并封装了与此元素进行交互的逻辑。 我将页面元素称为HTML包装器。 HTML包装器是可重用的因为多个页面可以包含相同的元素。 例如用于DatepickerHTML包装器可以提供以下方法API“在输入字段中设置日期”“打开日历弹出窗口”“在日历弹出窗口中选择给定的日期”等。其他HTML包装可以例如自动完成面包屑复选框单选按钮MultiSelect消息等。HTML包装器可以是复合的。 这意味着它可以包含多个小元素。 例如产品目录由产品组成购物车由项目组成等等。内部元素的Selenium的“ 按”定位符封装在复合页面元素中。 Martin Fowler描述了页面对象和HTML包装器作为设计模式。 Selenium测试类的骨骼结构。 测试类的结构良好。 它以单个过程步骤的形式定义测试顺序。 我建议采用以下结构 public class MyTestIT extends AbstractSeleniumTest {FlowOnPage(step 1, desc Description for this method)void flowSomePage(SomePage somePage) {...}FlowOnPage(step 2, desc Description for this method)void flowAnotherPage(AnotherPage anotherPage) {...}FlowOnPage(step 3, desc Description for this method)void flowYetAnotherPage(YetAnotherPage yetAnotherPage) {...}...
} MyTestIT类是用于集成测试的JUnit测试类。 FlowOnPage是网页上测试逻辑的方法注释。 step参数定义测试序列中的序列号。 计数从1开始。这意味着将在步骤 2的方法之前处理步骤 1的带注释的方法。第二个参数desc表示描述该方法的作用。 Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
public interface FlowOnPage {int step() default 1;String desc();
} 带页面对象作为方法参数调用带注释的方法。 通常每次单击按钮或链接都会切换到下一页。 开发的框架应确保在调用带有下一步的带注释的方法之前已完全加载下一页。 下图说明了测试类页面对象和HTML包装器之间的关系。 别这样 用Test注释的JUnit方法在哪里解析FlowOnPage注释的逻辑在哪里 该代码隐藏在超类AbstractSeleniumTest中 。 public abstract class AbstractSeleniumTest {// configurable base URLprivate final String baseUrl System.getProperty(selenium.baseUrl, http://localhost:8080/contextRoot/);private final WebDriver driver;public AbstractSeleniumTest() {// create desired WebDriverdriver new ChromeDriver();// you can also set here desired capabilities and so on...}/*** The single entry point to prepare and run test flow.*/Testpublic void testIt() throws Exception {LoadablePage lastPageInFlow null;List Method methods new ArrayList();// Seach methods annotated with FlowOnPage in this and all super classesClass c this.getClass();while (c ! null) {for (Method method: c.getDeclaredMethods()) {if (method.isAnnotationPresent(FlowOnPage.class)) {FlowOnPage flowOnPage method.getAnnotation(FlowOnPage.class);// add the method at the right positionmethods.add(flowOnPage.step() - 1, method);}}c c.getSuperclass();}for (Method m: methods) {Class?[] pTypes m.getParameterTypes();LoadablePage loadablePage null;if (pTypes ! null pTypes.length 0) {loadablePage (LoadablePage) pTypes[0].newInstance();}if (loadablePage null) {throw new IllegalArgumentException(No Page Object as parameter has been found for the method m.getName() , in the class this.getClass().getName());}// initialize Page Objects Page-Objekte and set parent-child relationshiploadablePage.init(this, m, lastPageInFlow);lastPageInFlow loadablePage;}if (lastPageInFlow null) {throw new IllegalStateException(Page Object to start the test was not found);}// start testlastPageInFlow.get();}/*** Executes the test flow logic on a given page.** throws AssertionError can be thrown by JUnit assertions*/public void executeFlowOnPage(LoadablePage page) {Method m page.getMethod();if (m ! null) {// execute the method annotated with FlowOnPagetry {m.setAccessible(true);m.invoke(this, page);} catch (Exception e) {throw new AssertionError(Method invocation m.getName() , in the class page.getClass().getName() , failed, e);}}}Afterpublic void tearDown() {// close browserdriver.quit();}/*** This method is invoked by LoadablePage.*/public String getUrlToGo(String path) {return baseUrl path;}public WebDriver getDriver() {return driver;}
} 如您所见只有一种测试方法testIt可以解析批注创建具有关系的页面对象并启动测试流程。 页面对象的结构。 每个页面对象类均从LoadablePage类继承而该类又从Selenium的LoadableComponent类继承。 这篇写得很好的文章对L oadableComponent进行了很好的解释 LoadableComponent的简单和高级用法 。 LoadablePage是我们自己的类实现如下 import org.openqa.selenium.support.ui.WebDriverWait;
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.LoadableComponent;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.List;public abstract class LoadablePageT extends LoadableComponentT extends LoadableComponentT {private final static Logger LOGGER LoggerFactory.getLogger(LoadablePage.class);private AbstractSeleniumTest seleniumTest;private String pageUrl;private Method method;private LoadablePage parent;/*** Init method (invoked by the framework).** param seleniumTest instance of type AbstractSeleniumTest* param method to be invoked method annotated with FlowOnPage* param parent parent page of type LoadablePage*/void init(AbstractSeleniumTest seleniumTest, Method method, LoadablePage parent) {this.seleniumTest seleniumTest;this.pageUrl seleniumTest.getUrlToGo(getUrlPath());this.method method;this.parent parent;PageFactory.initElements(getDriver(), this);}/*** Path of the URL without the context root for this page.** return String path of the URL*/protected abstract String getUrlPath();/**** Specific check which has to be implemented by every page object.* A rudimentary check on the basis of URL is undertaken by this class.* This method is doing an extra check if the page has been proper loaded.** throws Error thrown when the check fails*/protected abstract void isPageLoaded() throws Error;Overrideprotected void isLoaded() throws Error {// min. check against the page URLString url getDriver().getCurrentUrl();Assert.assertTrue(You are not on the right page., url.equals(pageUrl));// call specific check which has to be implemented on every pageisPageLoaded();}Overrideprotected void load() {if (parent ! null) {// call the logic in the parent pageparent.get();// parent page has navigated to this page (via click on button or link).// wait until this page has been loaded.WebDriverWait wait new WebDriverWait(getDriver(), 20, 250);wait.until(new ExpectedConditionBoolean () {Overridepublic Boolean apply(WebDriver d) {try {isLoaded();return true;} catch (AssertionError e) {return false;}}});} else {// Is there no parent page, the page should be navigated directlyLOGGER.info(Browser: {}, GET {}, getDriver(), getPageUrl());getDriver().get(getPageUrl());}}/*** Ensure that this page has been loaded and execute the test code on the this page.** return T LoadablePage*/public T get() {T loadablePage super.get();// execute flow logicseleniumTest.executeFlowOnPage(this);return loadablePage;}/*** See {link WebDriver#findElement(By)}*/public WebElement findElement(By by) {return getDriver().findElement(by);}/*** See {link WebDriver#findElements(By)}*/public ListWebElement findElements(By by) {return getDriver().findElements(by);}public WebDriver getDriver() {return seleniumTest.getDriver();}protected String getPageUrl() {return pageUrl;}Method getMethod() {return method;}
} 如您所见每个页面对象类都需要实现两个抽象方法 /*** Path of the URL without the context root for this page.** return String path of the URL*/
protected abstract String getUrlPath();/**** Specific check which has to be implemented by every page object.* A rudimentary check on the basis of URL is undertaken by the super class.* This method is doing an extra check if the page has been proper loaded.** throws Error thrown when the check fails*/
protected abstract void isPageLoaded() throws Error; 现在我想显示一个具体页面对象的代码和一个测试SBB Ticket Shop的测试类以便读者可以对页面对象进行测试。 页面对象TimetablePage包含基本元素HTML包装器。 public class TimetablePage extends LoadablePageTimetablePage {FindBy(id ...)private Autocomplete from;FindBy(id ...)private Autocomplete to;FindBy(id ...)private Datepicker date;FindBy(id ...)private TimeInput time;FindBy(id ...)private Button search;Overrideprotected String getUrlPath() {return pages/fahrplan/fahrplan.xhtml;}Overrideprotected void isPageLoaded() throws Error {try {assertTrue(findElement(By.id(shopForm_searchfields)).isDisplayed());} catch (NoSuchElementException ex) {throw new AssertionError();}}public TimetablePage typeFrom(String text) {from.setValue(text);return this;}public TimetablePage typeTo(String text) {to.setValue(text);return this;}public TimetablePage typeTime(Date date) {time.setValue(date);return this;}public TimetablePage typeDate(Date date) {date.setValue(date);return this;}public TimetablePage search() {search.clickAndWaitUntil().ajaxCompleted().elementVisible(By.cssSelector(...));return this;}public TimetableTable getTimetableTable() {ListWebElement element findElements(By.id(...));if (element.size() 1) {return TimetableTable.create(element.get(0));}return null;}
} 在页面对象中可以通过FindBy FindBys FindAll批注或按需动态创建HTML包装器简单或复合例如如TimetableTable.createelement 其中element是基础WebElement 。 通常注释不适用于自定义元素。 默认情况下它们仅与Selenium的WebElement一起使用。 但是让他们也使用自定义元素并不难。 您必须实现一个扩展DefaultFieldDecorator的自定义FieldDecorator 。 自定义FieldDecorator允许对自定义HTML包装程序使用FindBy FindBys或FindAll批注。 此处提供了一个示例项目其中提供了实现细节和自定义元素的示例。 您还可以赶上Selenium的臭名昭著的StaleElementReferenceException在您的自定义FieldDecorator并重新创建由最初定位的基础WebElement。 框架用户看不到StaleElementReferenceException并且即使在此期间更新了引用的DOM元素从DOM删除并再次添加新内容时也可以在WebElement上调用方法。 此处提供了带有代码段的想法。 好吧让我展示测试课程。 在测试班中我们要测试当16岁以下的儿童没有父母旅行时购物车中是否出现提示。 首先我们必须输入“从”和“到”的车站在时间表中单击所需的连接然后在下一页添加一个子项该子项显示所选连接的旅行报价。 public class HintTravelerIT extends AbstractSeleniumTest {FlowOnPage(step 1, desc Seach a connection from Bern to Zürich and click on the first Buy button)void flowTimetable(TimetablePage timetablePage) {// Type from, to, date and timetimetablePage.typeFrom(Bern).typeTo(Zürich);Date date DateUtils.addDays(new Date(), 2);timetablePage.typeDate(date);timetablePage.typeTime(date);// search for connectionstimetablePage.search();// click on the first Buy buttonTimetableTable table timetablePage.getTimetableTable();table.clickFirstBuyButton();}FlowOnPage(step 2, desc Add a child as traveler and test the hint in the shopping cart)void flowOffers(OffersPage offersPage) {// Add a childDateFormat df new SimpleDateFormat(dd.MM.yyyy, Locale.GERMAN);String birthDay df.format(DateUtils.addYears(new Date(), -10));offersPage.addTraveler(0, Max, Mustermann, birthDay);offersPage.saveTraveler();// Get hintsListString hints offersPage.getShoppingCart().getHints();assertNotNull(hints);assertTrue(hints.size() 1);assertEquals(A child can only travel under adult supervision, hints.get(0));}
}HTML包装器的结构。 我建议为所有HTML包装器创建一个抽象基类。 我们称之为HtmlWrapper 。 此类可以提供一些常用方法例如click clickAndWaitUntil findElements getParentElement getAttribute isDisplayed ……对于可编辑元素您可以创建一个继承自HtmlWrapper的类EditableWrapper 。 此类可以为可编辑元素提供一些常用方法例如 clear 清除输入 enter 按下Enter键 isEnabled 检查元素是否已启用…。所有可编辑元素都应继承自EditableWrapper 。 此外您可以分别为单值和多值元素提供两个接口EditableSingleValue和EditableMultipleValue 。 下图展示了这个想法。 它显示了三个基本HTML包装的类层次结构 日期选择器 。 它继承自EditableWrapper并实现EditableSingleValue接口。 MultiSelect 。 它继承自EditableWrapper并实现EditableMultiValue接口。 留言 。 它直接扩展HtmlWrapper因为消息不可编辑。 您是否需要更多有关HTML包装程序的实现细节 jQuery Datepicker的详细信息可以在这篇出色的文章中找到 。 MultiSelect是著名的Select2小部件的包装。 我已经通过以下方式在项目中实现了包装器 public class MultiSelect extends EditableWrapper implements EditableMultiValueString {protected MultiSelect(WebElement element) {super(element);}public static MultiSelect create(WebElement element) {assertNotNull(element);return new MultiSelect(element);}Overridepublic void clear() {JavascriptExecutor js (JavascriptExecutor) getDriver();js.executeScript(jQuery(arguments[0]).val(null).trigger(change), element);}public void removeValue(String...value) {if (value null || value.length 0) {return;}JavascriptExecutor js (JavascriptExecutor) getDriver();Object selectedValues js.executeScript(return jQuery(arguments[0]).val(), element);String[] curValue convertValues(selectedValues);String[] newValue ArrayUtils.removeElements(curValue, value);if (newValue null || newValue.length 0) {clear();} else {changeValue(newValue);}}public void addValue(String...value) {if (value null || value.length 0) {return;}JavascriptExecutor js (JavascriptExecutor) getDriver();Object selectedValues js.executeScript(return jQuery(arguments[0]).val(), element);String[] curValue convertValues(selectedValues);String[] newValue ArrayUtils.addAll(curValue, value);changeValue(newValue);}Overridepublic void setValue(String...value) {clear();if (value null || value.length 0) {return;}changeValue(value);}Overridepublic String[] getValue() {JavascriptExecutor js (JavascriptExecutor) getDriver();Object values js.executeScript(return jQuery(arguments[0]).val(), element);return convertValues(values);}private void changeValue(String...value) {Gson gson new Gson();String jsonArray gson.toJson(value);String jsCode String.format(jQuery(arguments[0]).val(%s).trigger(change), jsonArray);JavascriptExecutor js (JavascriptExecutor) getDriver();js.executeScript(jsCode, element);}SuppressWarnings(unchecked)private String[] convertValues(Object values) {if (values null) {return null;}if (values.getClass().isArray()) {return (String[]) values;} else if (values instanceof List) {ListString list (ListString ) values;return list.toArray(new String[list.size()]);} else {throw new WebDriverException(Unsupported value for MultiSelect: values.getClass());}}
} 为了完整起见还有一个Message实现的示例 public class Message extends HtmlWrapper {public enum Severity {INFO(info),WARNING(warn),ERROR(error);Severity(String severity) {this.severity severity;}private final String severity;public String getSeverity() {return severity;}}protected Message(WebElement element) {super(element);}public static Message create(WebElement element) {assertNotNull(element);return new Message(element);}public boolean isAnyMessageExist(Severity severity) {ListWebElement messages findElements(By.cssSelector(.ui-messages .ui-messages- severity.getSeverity()));return messages.size() 0;}public boolean isAnyMessageExist() {for (Severity severity: Severity.values()) {ListWebElement messages findElements(By.cssSelector(.ui-messages .ui-messages- severity.getSeverity()));if (messages.size() 0) {return true;}}return false;}public ListString getMessages(Severity severity) {ListWebElement messages findElements(By.cssSelector(.ui-messages .ui-messages- severity.getSeverity() -summary));if (messages.isEmpty()) {return null;}ListString text new ArrayList ();for (WebElement element: messages) {text.add(element.getText());}return text;}
} 消息将消息组件包装在PrimeFaces中 。 结论 完成页面对象和HTML包装程序的编写后您可以安心并专注于舒适地编写Selenium测试。 随时分享您的想法。 翻译自: https://www.javacodegeeks.com/2016/04/clean-architecture-selenium-tests.htmlselenium架构