Selenium是一套支撑浏览器自动化的工具和库,它首要用于网络应用测验。Selenium的组件之一是Selenium WebDriver,它供给客户端库、JSON线协议(与浏览器驱动程序通讯的协议)和浏览器驱动程序。Selenium WebDriver的首要优势之一是它被一切首要的编程言语所支撑,而且能够在一切首要的操作系统上运转。

JUnit 5与Selenium WebDriver教程的这一部分,我将通过Selenium内置的PageFactory支撑类来完结页面目标形式。PageFactory ,供给了初始化任何声明有WebElementList<WebElement> 字段注释的页面目标的机制,@FindBy 注释。

页面目标形式介绍

咱们将为基于JavaScript的Todo应用程序创立测验,这里有:http://todomvc.com/examples/vanillajs。该应用程序被创立为一个单页应用程序(SPA),并运用本地存储作为任务库。要完结的或许场景包含添加和修改todo,删除todo,将单个或多个todos符号为已完结。该完结将运用页面目标形式完结。

页面目标形式的目标是将应用程序的页面和功能从实践测验中抽象出来。页面目标形式提高了代码在测验和固定程序中的可重用性,一起也使代码更简单保护。

页面API又称页面目标

咱们将从将TodoMVC页面建模为页面目标开始项目。这个目标将代表将在测验中运用的页面API。API自身能够运用一个接口进行建模。如果你看一下下面接口的办法,你会发现这些办法仅仅页面上可用的用户功能。用户能够创立todo,用户能够重命名todo或许他能够删除todo

public interface TodoMvc {
    void navigateTo();
    void createTodo(String todoName);
    void createTodos(String... todoNames);
    int getTodosLeft();
    boolean todoExists(String todoName);
    int getTodoCount();
    List<String> getTodos();
    void renameTodo(String todoName, String newTodoName);
    void removeTodo(String todoName);
    void completeTodo(String todoName);
    void completeAllTodos();
    void showActive();
    void showCompleted();
    void clearCompleted();
}

上述接口(明显)隐藏了一切的完结细节,但它也没有向潜在的客户(在咱们的比如中,客户=测验办法)暴露任何Selenium WebDriver的细节。事实上,它与Selenium WebDriver没有任何关系。所以从理论上讲,咱们能够为不同的设备(如移动本地应用程序、桌面应用程序和Web应用程序)供给不同的页面完结。

创立测验

随着页面API的界说,咱们能够直接跳到创立测验办法。在咱们确认API可用于创立测验后,咱们将致力于页面的完结。这种规划技术允许咱们关注应用程序的实践运用情况,而不是过早地跳到完结细节。

以下是创立的测验:

@ExtendWith(SeleniumExtension.class)
@DisplayName("Managing Todos")
class TodoMvcTests {
    private TodoMvc todoMvc;
    private final String buyTheMilk = "Buy the milk";
    private final String cleanupTheRoom = "Clean up the room";
    private final String readTheBook = "Read the book";
    @BeforeEach
    void beforeEach(ChromeDriver driver) {
        this.todoMvc = null;
        this.todoMvc.navigateTo();
    }
    @Test
    @DisplayName("Creates Todo with given name")
    void createsTodo() {
        todoMvc.createTodo(buyTheMilk);
        assertAll(
                () -> assertEquals(1, todoMvc.getTodosLeft()),
                () -> assertTrue(todoMvc.todoExists(buyTheMilk))
        );
    }
    @Test
    @DisplayName("Creates Todos all with the same name")
    void createsTodosWithSameName() {
        todoMvc.createTodos(buyTheMilk, buyTheMilk, buyTheMilk);
        assertEquals(3, todoMvc.getTodosLeft());
        todoMvc.showActive();
        assertEquals(3, todoMvc.getTodoCount());
    }
    @Test
    @DisplayName("Edits inline double-clicked Todo")
    void editsTodo() {
        todoMvc.createTodos(buyTheMilk, cleanupTheRoom);
        todoMvc.renameTodo(buyTheMilk, readTheBook);
        assertAll(
                () -> assertFalse(todoMvc.todoExists(buyTheMilk)),
                () -> assertTrue(todoMvc.todoExists(readTheBook)),
                () -> assertTrue(todoMvc.todoExists(cleanupTheRoom))
        );
    }
    @Test
    @DisplayName("Removes selected Todo")
    void removesTodo() {
        todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook);
        todoMvc.removeTodo(buyTheMilk);
        assertAll(
                () -> assertFalse(todoMvc.todoExists(buyTheMilk)),
                () -> assertTrue(todoMvc.todoExists(cleanupTheRoom)),
                () -> assertTrue(todoMvc.todoExists(readTheBook))
        );
    }
    @Test
    @DisplayName("Toggles selected Todo as completed")
    void togglesTodoCompleted() {
        todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook);
        todoMvc.completeTodo(buyTheMilk);
        assertEquals(2, todoMvc.getTodosLeft());
        todoMvc.showCompleted();
        assertEquals(1, todoMvc.getTodoCount());
        todoMvc.showActive();
        assertEquals(2, todoMvc.getTodoCount());
    }
    @Test
    @DisplayName("Toggles all Todos as completed")
    void togglesAllTodosCompleted() {
        todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook);
        todoMvc.completeAllTodos();
        assertEquals(0, todoMvc.getTodosLeft());
        todoMvc.showCompleted();
        assertEquals(3, todoMvc.getTodoCount());
        todoMvc.showActive();
        assertEquals(0, todoMvc.getTodoCount());
    }
    @Test
    @DisplayName("Clears all completed Todos")
    void clearsCompletedTodos() {
        todoMvc.createTodos(buyTheMilk, cleanupTheRoom);
        todoMvc.completeAllTodos();
        todoMvc.createTodo(readTheBook);
        todoMvc.clearCompleted();
        assertEquals(1, todoMvc.getTodosLeft());
        todoMvc.showCompleted();
        assertEquals(0, todoMvc.getTodoCount());
        todoMvc.showActive();
        assertEquals(1, todoMvc.getTodoCount());
    }
}

在上面的测验类中,咱们看到在每次测验之前,ChromeDriver都会被初始化,并由Selenium Jupiter扩展注入到setup办法中(@BeforeEach)(因此有@ExtendWith(SeleniumExtension.class) )。驱动程序目标将被用于初始化页面目标。

有不同的页面目标建模技术,这在很大程度上取决于你所做的项目的特色。你或许想运用接口,但这并不是有必要的。你或许想考虑在一个较低的抽象层次上建模,在这里API暴露了更多的细节办法,例如setTodoInput(String value),clickSubmitButton()

运用Selenium内置的PageFactory来完结页面目标形式

目前,咱们有一个接口来模仿TodoMVC页面的行为,咱们有运用API的失败测验。下一步是实践完结页面目标。为了做到这一点,咱们将运用Selenium内置的PageFactory 类和它的实用工具。

PageFactory 该类简化了页面目标形式的完结。该类供给了初始化任何声明晰 或 字段并带有 注释的页面目标的机制。 和一切其他支撑完结Page Object形式的注解都能够在 包中找到。WebElement List<WebElement> @FindBy PageFactory org.openqa.selenium.support

下面的TodoMvcPage 类完结了咱们之前创立的接口。它声明晰几个用@FindBy 注释的字段。它还声明晰一个结构函数,承受工厂用来初始化字段的WebDriver 参数:

public class TodoMvcPage implements TodoMvc {
    private final WebDriver driver;
    private static final By byTodoEdit = By.cssSelector("input.edit");
    private static final By byTodoRemove = By.cssSelector("button.destroy");
    private static final By byTodoComplete = By.cssSelector("input.toggle");
    @FindBy(className = "new-todo")
    private WebElement newTodoInput;
    @FindBy(css = ".todo-count > strong")
    private WebElement todoCount;
    @FindBy(css = ".todo-list li")
    private List<WebElement> todos;
    @FindBy(className = "toggle-all")
    private WebElement toggleAll;
    @FindBy(css = "a[href='#/active']")
    private WebElement showActive;
    @FindBy(css = "a[href='#/completed']")
    private WebElement showCompleted;
    @FindBy(className = "clear-completed")
    private WebElement clearCompleted;
    public TodoMvcPage(WebDriver driver) {
        this.driver = driver;
    }
    @Override
    public void navigateTo() {
        driver.get("http://todomvc.com/examples/vanillajs");
    }
    public void createTodo(String todoName) {
        newTodoInput.sendKeys(todoName + Keys.ENTER);
    }
    public void createTodos(String... todoNames) {
        for (String todoName : todoNames) {
            createTodo(todoName);
        }
    }
    public int getTodosLeft() {
        return Integer.parseInt(todoCount.getText());
    }
    public boolean todoExists(String todoName) {
        return getTodos().stream().anyMatch(todoName::equals);
    }
    public int getTodoCount() {
        return todos.size();
    }
    public List<String> getTodos() {
        return todos
                .stream()
                .map(WebElement::getText)
                .collect(Collectors.toList());
    }
    public void renameTodo(String todoName, String newTodoName) {
        WebElement todoToEdit = getTodoElementByName(todoName);
        doubleClick(todoToEdit);
        WebElement todoEditInput = find(byTodoEdit, todoToEdit);
        executeScript("arguments[0].value = ''", todoEditInput);
        todoEditInput.sendKeys(newTodoName + Keys.ENTER);
    }
    public void removeTodo(String todoName) {
        WebElement todoToRemove = getTodoElementByName(todoName);
        moveToElement(todoToRemove);
        click(byTodoRemove, todoToRemove);
    }
    public void completeTodo(String todoName) {
        WebElement todoToComplete = getTodoElementByName(todoName);
        click(byTodoComplete, todoToComplete);
    }
    public void completeAllTodos() {
        toggleAll.click();
    }
    public void showActive() {
        showActive.click();
    }
    public void showCompleted() {
        showCompleted.click();
    }
    public void clearCompleted() {
        clearCompleted.click();
    }
    private WebElement getTodoElementByName(String todoName) {
        return todos
                .stream()
                .filter(el -> todoName.equals(el.getText()))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Todo with name " + todoName + " not found!"));
    }
    private WebElement find(By by, SearchContext searchContext) {
        return searchContext.findElement(by);
    }
    private void click(By by, SearchContext searchContext) {
        WebElement element = searchContext.findElement(by);
        element.click();
    }
    private void moveToElement(WebElement element) {
        new Actions(driver).moveToElement(element).perform();
    }
    private void doubleClick(WebElement element) {
        new Actions(driver).doubleClick(element).perform();
    }
    private void executeScript(String script, Object... arguments) {
        ((JavascriptExecutor) driver).executeScript(script, arguments);
    }
}

@FindBy 并非唯一用于查找页面目标中的元素的注解。还有 和 。@FindBys @FindAll

@FindBys

@FindBys 注释用于符号页面目标上的一个字段,以标明查询应该运用一系列的 标签。在这个比如中,Selenium将查找带有 的元素,该元素@FindBy class = "button" 带有 :id = "menu"

@FindBys({
  @FindBy(id = "menu"),
  @FindBy(className = "button")
})
private WebElement element;

@FindAll

@FindAll 注释用于符号页面目标上的一个字段,标明查找应该运用一系列的@FindBy标签。在这个比如中,Selenium将查找一切带有 的元素class = "button" 一切带有 的元素。不确保元素是按文档顺序排列的。id = "menu"

@FindAll({
  @FindBy(id = "menu"),
  @FindBy(className = "button")
})
private List<WebElement> webElements;

PageFactory – 初始化页面目标

PageFactory 供给了几个静态办法来初始化Page目标。在咱们的测验中,在 办法中咱们需要初始化 目标。beforeEach() TodoMvcPage

@BeforeEach
void beforeEach(ChromeDriver driver) {
    this.todoMvc = PageFactory.initElements(driver, TodoMvcPage.class);
    this.todoMvc.navigateTo();
}

PageFactory 运用反射来初始化目标,然后初始化一切标有@FindBy 注释的WebElementList<WebElement> 字段(在这个时间不做查找,字段是代理的)。运用这种办法需要页面目标有一个承受WebDriver 目标的单参数结构器。

定位元素

那么,元素何时被定位呢?查找是在每次访问字段时进行的。因此,例如,当咱们在createTodo() 办法中履行代码:newTodoInput.sendKeys(todoName + Keys.ENTER); ,实践履行的指令是:driver.findElement(By.className('new-todo')).sendKeys(todoName + Keys.ENTER) 。咱们能够预期,没有找到元素的潜在反常不是在目标初始化时抛出的,而是在第一次元素查找时抛出的。

Selenium运用Proxy形式来完结上述行为。

@CacheLookup

在有些情况下,没有必要在每次访问注释字段时都去查找元素。在这种情况下,咱们能够运用@CacheLookup 注释。在咱们的比如中,输入字段在页面上没有改变,所以它的查找能够被缓存

@FindBy(className = "new-todo")
@CacheLookup
private WebElement newTodoInput;

运转测验

现在是履行测验的时候了,它能够从IDE或运用终端完结:

./gradlew clean test --tests *TodoMvcTests

构建成功,一切测验都通过了:

> Task :test
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > editsTodo() PASSED
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > togglesTodoCompleted() PASSED
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > createsTodo() PASSED
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > removesTodo() PASSED
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > togglesAllTodosCompleted() PASSED
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > createsTodosWithSameName() PASSED
pl.codeleak.demos.selenium.todomvc.TodoMvcTests > clearsCompletedTodos() PASSED
BUILD SUCCESSFUL in 27s
3 actionable tasks: 3 executed

接下来的过程

在第三部分–改善项目装备–并行履行测验、测验履行顺序、参数化测验、AssertJ等等–你将学习如何使用JUnit 5的内置功能,在履行速度方面改善你的项目装备,但不仅如此。你还将学习通过使用某些Selenium Jupiter功能来改善项目。