Selenium 中的自动化:页面对象模型和页面工厂

已发表: 2022-03-11

对于任何敏捷软件开发团队来说,编写自动化测试不仅仅是一种奢侈。 这是一种需要,也是在软件开发周期的早期阶段快速发现错误的重要工具。 当有一项新功能仍处于开发阶段时,开发人员可以运行自动化测试并查看系统的其他部分如何受到这些更改的影响。 本文将解释如何使用 Selenium 中的页面对象模型加速自动化测试。

通过测试自动化,可以降低错误修复的成本,并为软件质量保证 (QA) 流程带来整体改进。 通过适当的测试,开发人员甚至在进行 QA 之前就有机会发现和解决错误。 测试自动化进一步帮助我们自动化不断退化的测试用例和特性。 这样,QA 就有更多时间测试应用程序的其他部分。 此外,这有助于确保产品在生产版本中的质量。 因此,我们得到的产品实际上更稳定,质量保证流程更高效。

Selenium 自动化简化了 Web 应用程序的测试自动化

Selenium 简化了 Web 应用程序的测试自动化
鸣叫

尽管编写自动化测试对于开发人员和工程师来说似乎是一件容易的事,但仍有可能导致测试实施不佳,以及任何敏捷过程中代码维护的高成本。 在涉及测试时,尝试在任何敏捷开发项目中不断交付更改或功能可能会被证明是昂贵的。 更改 20 个测试所依赖的网页上的一个元素将需要一个人通过这 20 个测试例程并更新每个例程以适应这一新引入的更改。 这不仅会非常耗时,而且在早期实施自动化测试时也是一个严重的消极因素。

但是,如果我们可以只在一个地方进行更改,并让每个相关的测试例程都使用它呢? 在本文中,我们将了解 Selenium 中的自动化测试,以及如何使用页面对象模型来编写可维护和可重用的测试例程。

Selenium 中的页面对象模型

页面对象模型是 Selenium 中的一种对象设计模式,其中网页被表示为类,页面上的各种元素被定义为类上的变量。 然后,所有可能的用户交互都可以作为类上的方法实现:

 clickLoginButton(); setCredentials(user_name,user_password);

由于类中命名良好的方法易于阅读,因此这是一种实现测试例程的优雅方式,这些例程既可读又易于维护或在未来更新。 例如:

为了支持 Page Object 模型,我们使用 Page Factory。 Selenium 中的页面工厂是页面对象的扩展,可以以多种方式使用。 在这种情况下,我们将使用页面工厂来初始化在网页类或页面对象中定义的 Web 元素。

包含 Web 元素的网页类或页面对象需要先使用 Page Factory 进行初始化,然后才能使用 Web 元素变量。 这可以通过使用 PageFactory 上的initElements函数简单地完成:

 LoginPage page = new LoginPage(driver); PageFactory.initElements(driver, page);

或者,更简单:

 LoginPage page = PageFactory.intElements(driver,LoginPage.class)

或者,在网页类构造函数中:

 public LoginPage(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); }

Page Factory 将根据配置的“定位器”使用对实际网页上相应元素的引用来初始化每个WebElement变量。 这是通过使用@FindBy注释来完成的。 使用此注释,我们可以定义查找元素的策略以及识别它的必要信息:

 @FindBy(how=How.NAME, using="username") private WebElement user_name;

每次在这个WebElement变量上调用一个方法,驱动都会先在当前页面找到它,然后模拟交互。 如果我们正在处理一个简单的页面,我们知道每次查找都会在页面上找到该元素,并且我们也知道最终会导航离开该页面而不返回它,我们可以缓存通过使用另一个简单的注释查找字段:

 @FindBy(how=How.NAME, using="username") @CacheLookup private WebElement user_name;

WebElement 变量的整个定义可以用更简洁的形式替换:

 @FindBy(name="username") private WebElement user_name;

@FindBy注释支持一些其他策略,使事情变得更容易一些:

 id, name, className, css, tagName, linkText, partialLinkText, xpath
 @FindBy() private WebElement user_name; @FindBy(name="passsword") private WebElement user_password; @FindBy(className="h3") private WebElement label; @FindBy(css=”#content”) private WebElement text;

初始化后,这些 WebElement 变量可用于与页面上的相应元素进行交互。 例如,以下代码将:

 user_password.sendKeys(password);

... 将给定的击键序列发送到页面上的密码字段,它相当于:

 driver.findElement(By.name(“user_password”)).sendKeys(password);

继续前进,您经常会遇到需要在页面上查找元素列表的情况,这就是@FindBys派上用场的时候:

 @FindBys(@FindBy(css=”div[class='yt-lockup-tile yt-lockup-video']”))) private List<WebElement> videoElements;

上面的代码将找到具有两个类名“yt-lockup-tile”和“yt-lockup-video”的所有div元素。 我们可以通过将其替换为以下内容来进一步简化它:

 @FindBy(how=How.CSS,using="div[class='yt-lockup-tile yt-lockup-video']") private List<WebElement> videoElements;

此外,您可以使用带有多个@FindBy注释的@FindAll来查找与任何给定定位器匹配的元素:

 @FindAll({@FindBy(how=How.ID, using=”username”), @FindBy(className=”username-field”)}) private WebElement user_name;

现在我们可以将网页表示为 Java 类并使用 Page Factory 轻松初始化WebElement变量,是时候看看如何使用 Page Object 模式和 Page Factory 编写简单的 Selenium 测试了。

Java 中的简单 Selenium 测试自动化项目

对于我们的页面对象模型教程,让我们自动化开发人员注册 Toptal。 为此,我们需要自动化以下步骤:

  • 访问 www.toptal.com

  • 点击“申请成为开发者”按钮

  • 在门户页面上首先检查它是否已打开

  • 点击“加入Toptal”按钮

  • 填写表格

  • 点击“加入Toptal”按钮提交表格

设置项目

  • 下载并安装 Java JDK

  • 下载并安装 InteliJ Idea

  • 创建一个新的 Maven 项目

  • 将“Project SDK”链接到您的 JDK,例如:在 Windows 上“C:\Program Files\Java\jdkxxx”

  • 设置 groupId 和 artifactId:

 <groupId>SeleniumTEST</groupId> <artifactId>Test</artifactId>
  • 在项目 POM 文件中添加依赖项 Selenium 和 JUnit Maven
 <dependencies> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- Selenium --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-support</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>${selenium.version}</version> </dependency> </dependencies>

将 Selenium 版本和 JUnit 版本替换为最新版本号,可通过在 Google 和 Selenium 网站上搜索 JUnit Maven 找到。

此时,如果启用了自动构建,依赖项应该会自动开始下载。 如果没有,只需在 IntelliJ Idea IDE 右侧的 Maven 项目面板下激活 Plugins > install > install:install。

selenium 测试教程 IDE 截图

项目启动后,我们可以开始在“src/test/java”下创建我们的测试包。 将包命名为“com.toptal”,并在其下再创建两个包:“com.toptal.webpages”和“com.toptal.tests”。

selenium 测试教程截图

我们将把我们的页面对象/页面工厂类保存在“com.toptal.webpages”下,将测试例程保存在“com.toptal.tests”下。

现在,我们可以开始创建我们的 Page Object 类了。

主页页面对象

我们需要实现的第一个是 Toptal 的主页 (www.toptal.com)。 在“com.toptal.webpages”下创建一个类并将其命名为“HomePage”。

 package com.toptal.webpages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.How; import org.openqa.selenium.support.PageFactory; public class HomePage { private WebDriver driver; //Page URL private static String PAGE_URL="https://www.toptal.com"; //Locators //Apply as Developer Button @FindBy(how = How.LINK_TEXT, using = "APPLY AS A DEVELOPER") private WebElement developerApplyButton; //Constructor public HomePage(WebDriver driver){ this.driver=driver; driver.get(PAGE_URL); //Initialise Elements PageFactory.initElements(driver, this); } public void clickOnDeveloperApplyButton(){ developerApplyButton.click(); } }

确定元素定位器

在 Toptal 的主页上,我们对一个元素特别感兴趣,那就是“申请成为开发者”按钮。 我们可以通过匹配文本来找到这个元素,这就是我们上面所做的。 在将网页建模为页面对象类时,查找和识别元素通常会成为一件苦差事。 使用 Google Chrome 或 Firefox 的调试工具,这可以变得更容易。 通过右键单击页面上的任何元素,您可以从上下文菜单中激活“检查元素”选项以查找有关该元素的详细信息。

一种常见的(也是我首选的)方法是使用 Firefox 的 FireBug 扩展,结合 Selenium 中的 Firefox Web 驱动程序来查找元素。 安装并启用 FireBug 扩展后,您可以右键单击页面并选择“Inspect element with FireBug”打开 FireBug。 从 FireBug 的 HTML 选项卡,您可以复制页面上任何元素的 XPath、CSS 路径、标签名称或“Id”(如果可用)。

selenium 中的页面对象模型:确定元素定位器

通过复制上面屏幕截图中元素的 XPath,我们可以在我们的 Page Object 中为其创建一个 WebElement 字段,如下所示:

 @FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1") WebElement heading;

或者为了简单起见,我们可以在这里使用标签名称“h1”,只要它唯一标识我们感兴趣的元素:

 @FindBy(tagName = "h1") WebElement heading;

DeveloperPortalPage 页面对象

接下来,我们需要一个代表开发人员门户页面的页面对象,我们可以通过单击“应用为开发人员”按钮访问该页面对象。

在这个页面上,我们有两个感兴趣的元素。 要确定页面是否已加载,我们要验证标题是否存在。 我们还需要一个用于“Join Toptal”按钮的WebElement字段。

 package com.toptal.webpages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class DeveloperPortalPage { private WebDriver driver; @FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1") private WebElement heading; @FindBy(linkText = "JOIN TOPTAL") private WebElement joinToptalButton; //Constructor public DeveloperPortalPage (WebDriver driver){ this.driver=driver; //Initialise Elements PageFactory.initElements(driver, this); } //We will use this boolean for assertion. To check if page is opened public boolean isPageOpened(){ return heading.getText().toString().contains("Developer portal"); } public void clikOnJoin(){ joinToptalButton.click(); } }

DeveloperApplyPage 页面对象

最后,对于这个项目的第三个也是最后一个页面对象,我们定义了一个代表包含开发人员申请表的页面。 由于我们必须在此处处理许多表单字段,因此我们为每个表单字段定义一个WebElement变量。 我们通过“id”找到每个字段,并为每个字段定义特殊的 setter 方法,模拟相应字段的击键。

 package com.toptal.webpages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class DeveloperApplyPage { private WebDriver driver; @FindBy(tagName = "h1") WebElement heading; @FindBy() WebElement developer_email; @FindBy() WebElement developer_password; @FindBy() WebElement developer_password_confirmation; @FindBy() WebElement developer_full_name; @FindBy() WebElement developer_skype; @FindBy() WebElement join_toptal_button; //Constructor public DeveloperApplyPage(WebDriver driver){ this.driver=driver; //Initialise Elements PageFactory.initElements(driver, this); } public void setDeveloper_email(String email){ developer_email.clear(); developer_email.sendKeys(email); } public void setDeveloper_password(String password){ developer_password.clear(); developer_password.sendKeys(password); } public void setDeveloper_password_confirmation(String password_confirmation){ developer_password_confirmation.clear(); developer_password_confirmation.sendKeys(password_confirmation); } public void setDeveloper_full_name (String fullname){ developer_full_name.clear(); developer_full_name.sendKeys(fullname); } public void setDeveloper_skype (String skype){ developer_skype.clear(); developer_skype.sendKeys(skype); } public void clickOnJoin(){ join_toptal_button.click(); } public boolean isPageOpened(){ //Assertion return heading.getText().toString().contains("Apply to join our network as a developer"); } }

编写一个简单的 Selenium 测试

使用代表我们页面的 Page Object 类,并将用户交互作为它们的方法,我们现在可以将简单的测试例程编写为一系列简单的方法调用和断言。

 package com.toptal.tests; import com.toptal.webpages.DeveloperApplyPage; import com.toptal.webpages.DeveloperPortalPage; import com.toptal.webpages.HomePage; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import java.net.URL; import java.util.concurrent.TimeUnit; public class ApplyAsDeveloperTest { WebDriver driver; @Before public void setup(){ //use FF Driver driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); } @Test public void applyAsDeveloper() { //Create object of HomePage Class HomePage home = new HomePage(driver); home.clickOnDeveloperApplyButton(); //Create object of DeveloperPortalPage DeveloperPortalPage devportal= new DeveloperPortalPage(driver); //Check if page is opened Assert.assertTrue(devportal.isPageOpened()); //Click on Join Toptal devportal.clikOnJoin(); //Create object of DeveloperApplyPage DeveloperApplyPage applyPage =new DeveloperApplyPage(driver); //Check if page is opened Assert.assertTrue(applyPage.isPageOpened()); //Fill up data applyPage.setDeveloper_email("[email protected]"); applyPage.setDeveloper_full_name("Dejan Zivanovic Automated Test"); applyPage.setDeveloper_password("password123"); applyPage.setDeveloper_password_confirmation("password123"); applyPage.setDeveloper_skype("automated_test_skype"); //Click on join //applyPage.clickOnJoin(); } @After public void close(){ driver.close(); } }

运行测试

此时,您的项目结构应如下所示:

硒测试示例

如果要运行测试,请从树中选择“ApplyAsDeveloperTest”,右键单击它,然后选择Run 'ApplyAsDeveloperTest'

硒测试示例

运行测试后,您可以在 IDE 的左下角看到结果:

硒测试示例

结论

Page Object 和 Page Factory 可以轻松地在 Selenium 中对网页进行建模并自动测试它们,并使开发人员和 QA 的生活变得更加简单。 如果做得好,这些页面对象类可以在您的整个测试套件中重用,并让您有机会在不影响敏捷开发的情况下尽早为您的项目实施自动化 Selenium 测试。 通过在您的页面对象模型中抽象出用户交互并保持您的测试例程轻巧简单,您可以轻松地调整您的测试套件以适应不断变化的需求。

我希望我已经成功地向您展示了如何编写易于维护的漂亮和干净的测试代码。 我将以我最喜欢的 QA 报价结束这篇文章:

三思而后行,编码一次!

相关:使用无头浏览器进行 Web 抓取:Puppeteer 教程