页面对象设计问题

软件测试 硒2 测试创建 网络驱动程序 页面对象
2022-01-11 13:04:21

几周前我开始使用 Selenium 2 /Webdriver Web 自动化框架,我总体上很满意,但我发现自己不确定如何最好地设计我的页面对象。以下问题一直困扰着我:

  1. 页面 URL 是页面对象的属性吗?如果多个不同的 URL 指向同一个页面怎么办?另外,如果 URL 用于传递一些参数,你会处理吗?

  2. 除了 WebElements 和 WebDriver 实例之外,您的页面对象还有哪些字段?

  3. 你如何初始化你的页面对象?在构造函数中?使用工厂方法?您是否使用 WebDriver 提供的 PageFactory 类?

  4. 您的页面对象是否曾经负责浏览到适当的网页,或者您是否依赖“外力”进行浏览?在 WebDriver 术语中,您的对象是否曾经做过 driver.get("URL") ?

我当然对这些问题有自己的答案,但我会将它们排除在主要帖子之外以避免混乱。

4个回答

我发现 Page Object 模式非常有用,并使用了修改后的 PageFactory(为自定义超时进行了参数化)。

我还使用 WidgetObjects(基本上是通过构造函数带有父引用的 PageObjects)来表示跨页面的常见主题。

  1. 我认为并非所有页面都需要 URL 关联,因此我将其保留在我的基类之外。网址参数?我让我的 PageObjects 以用户为中心;如果用户不需要知道,PageObject 也不需要。对象状态只是顺序执行和构造的产物。

    • format(:Date): 细绳
    • select(:WebElement, :String)
    • selectMany(:WebElement, :int)
    • selectRadio(:WebElement, ...)
    • ……
    • 我还在单例实用程序类中定义了许多 Predicates、Functions 和 ExpectedConditions,它们与 FluentWait 一起使用来实现:

      • waitToBeAt(path:String, timeout:int)
      • varEquals(var:String, value:String, timeout:int)
      • switchToWindow(path:String, value:String, timeout:int): 网络驱动程序
      • windowIsClosed(winTitle:String, timeout:int)
      • elementLocated(:By, timeout:int): 网页元素
      • ……
    • 我还将 JavaScript 片段保存在离散文件中,并在启动时将它们读入一个按文件名索引的 Map。这比在源代码中编写转义源代码更易于维护。
  2. 我通常使用我的自定义 PageFactory 来初始化,或者对于复杂的对象我调用构造函数并将实例传递给 PageFactory 以进行静态元素初始化。
  3. 我的 PageObjects 总是知道如何导航到下一页,并且有返回这些 PageObjects 的方法。return this;我尽可能使用链接( ),因为它使测试代码非常流畅,这是我最喜欢使用这种模式的部分:

/**
 * Test Utility.  Create an Assembly.
 * @return A reference to the viewport of the created Assembly.
 */
public ViewAssemblyPage createAssembly(String title, String type, 
        String description, Date dueDate, int numParts) {

    AssemblyPage assy = login("JoeCoder", "foopass")
        .createRecord()
        .assembly(type)
        .formInput(title, description, dueDate);

    return assy.getTable()
        .openPartsPicker()
        .findParts(type)
        .selectSomeParts(numParts)
        .submitSelection();
}

我下面的答案是基于我们构建的框架,它使用状态机来遍历我们正在测试的应用程序。

  1. URL(或者,更确切地说,表示 URL 的已编译正则表达式)确实是我的测试套件中各个页面对象的属性。它们都来自一个与 terryp 非常相似的基本页面对象(尽管我们已经转换为 PyQuery 而不是 BeautifulSoup——主要是)。
  2. __init__中,页面被抓取以获取表单元素,并将它们包装到 .form 属性中。
  3. 状态机的 WhereAmI 方法从给定模块(例如 lib.pages)中的所有页面对象中提取正则表达式以找到最合适的一个。如果一个 url 用于许多不同的页面(ajax 等),则使用基于内容的其他识别信息(呃,我知道)。
  4. 在我们使用的状态机模型中,状态机是一个迭代器。因此,从一个页面到下一个页面的浏览发生在next()每个页面对象的方法中(通过__iter__状态机)

我在 Clojure 中编写 Selenium 测试,这是一种在 JVM 上运行的 Lisp。虽然可以在 Clojure 中定义对象,但使用函数而不是对象来组织 Lisp 代码更有意义,所以我的测试都写成带有偶尔闭包的函数。(我提到 Clojure 是为了说明为什么我不使用页面对象,而不是作为对其他人的认可或推荐。)尽管我刚才说了,我认为我的一些答案适用于您的问题。

我不测试页面的结果 URL。对于每个页面,我都有一个知道如何导航到该页面的函数。该函数知道 URI。它的参数包括基本 URL 和需要包含在最终 URL 中的任何参数。如果一个页面有多个路径,我会在原始页面的上下文中测试它们中的每一个。

我的测试通常没有特定于页面的状态。可能存在全局状态(例如,用于登录具有不同类型权限的帐户的用户名和密码),但由于这不是特定于页面的,因此我将它们存储在一个全局“上下文”中,以便传递。

我的“转到第 X 页”函数执行 driver.get(URL),或者更确切地说,它调用了一个调用 driver.get(URL) 的函数。它假定浏览器会话已经处于可以访问该页面的状态。例如,如果您必须在进入页面 X 之前登录,则外力(一个想知道当您进入页面 X 时会发生什么的测试)将首先通过登录页面登录。

我使用 Python 和 Selenium 1,发现页面对象非常有用。

  1. 通常不关心 URL。我检查页面内容以确保我在我期望的位置。

  2. 我的页面对象中的大多数字段都与页面上的表单字段相关。我还有可以读取基于 DOM 的静态文本位的对象字段。

  3. 我有一个具有各种实用功能的基类(wait_for_element、wait_for_text、wait_for_page、diff(actual、expected)、login 等)。基类还包含 Selenium 会话对象。大多数子类都是从基实例初始化的:p=HistoryPage(base),但我也可以这样做p=base.clone_to(HistoryPage)这主要是传递 Selenium 会话,这可能是一种更好的方法。

  4. 基类处理大多数导航,base.visit("link=Settings")最常见的是。一些页面对象具有进行导航并为新页面返回新页面对象的方法:transaction_page = history_page.find_transaction('canceled')