使用最小的Codeception + Gherkin + PageObject实现自动化


我还没有找到Internet上具有用于CodeceptionPage Object设计模式的Gherkin实现的单个具体示例,所以我认为向Internet讲述我们自己的这种模式的实现并不合适。

本文的目的是为那些已经稍微熟悉Codeception或类似框架,但还不知道如何使用测试对象来使测试更具可读性,简化其支持并减少额外代码量的人。 尽管如此,我还是试图逐步解释从头开始组装自动化项目的所有要点。

Page Object模板使您可以使用page元素封装工作,从而可以减少代码量并简化其支持。 UI中的所有更改都可以轻松快速地实现-只需更新描述此页面的Page Object类即可。 这种体系结构方法的另一个重要优点是,它使您不必使HTML测试脚本的细节杂乱无章,这使它更易于理解和理解。

这是不使用Page Object的情况下的外观



使用页面对象



我不会详细介绍安装基本环境,我将提供初始数据:

  • Ubuntu仿生海狸
  • PHP 7.1.19-1
  • Composer-PHP依赖管理器,已全局安装
  • PhpStorm-开发环境

要运行测试,我们还需要:


扩展代码接收


继续安装Codeception:

我们在终端中打开所需目录,在该目录中我们将收集项目,或者为该项目创建目录并转到该目录:

mkdir ProjectTutorial cd ProjectTutorial 

安装Codeception框架及其依赖项:

 composer require codeception/codeception --dev 



项目中的composer.json依赖项安装文件如下所示:

 { "require": { "php": ">=5.6.0 <8.0", "facebook/webdriver": ">=1.1.3 <2.0", "behat/gherkin": "^4.4.0", "codeception/phpunit-wrapper": "^6.0.9|^7.0.6" }, "require-dev": { "codeception/codeception": "^2.5", "codeception/base": "^2.5" } } 

展开项目:

 php vendor/bin/codecept bootstrap 



有关如何安装该项目的更多信息,请参见官方文档

在此阶段,在我们的项目中创建了三组测试。 默认情况下,Codeception将它们分为接受,功能和单位。 对于这些集合,Codeception还生成了三个yml文件。 在其中,我们指示所有必需的配置 ,连接模块和属性以运行测试。

本课基于验收测试的示例,因此我将在Acceptance.suite.yml中进行设置。

在PHP Storm(或其他喜欢的开发环境)中打开我们的项目,然后转到Acceptance.suite.yml (默认情况下,它位于tests / accepting.suite.yml文件夹中)。
我们写下最小的必要依赖项,并始终注意格式化。 模块之间用“-”号隔开,并且必须处于同一级别,否则测试开始时错误会散布。

原来:

 actor: AcceptanceTester modules: enabled: - WebDriver: url: 'http://yandex.ru/' //    ,        browser: 'chrome' - \Helper\Acceptance //          gherkin: contexts: default: - AcceptanceTester 

还有一些准备工作:

在项目根目录(我有lib)中创建一个单独的目录。
在此目录中,创建可执行文件run.sh,它将运行Selenium和Chrome驱动程序。

我们将Selenium和Chrome驱动程序放在这里,并在run.sh中编写run命令:

 java -jar -Dwebdriver.chrome.driver=chromedriver_241 selenium-server-standalone-3.14.0.jar 

在项目中的外观:



我们返回控制台并更改访问权限:

 chmod +x ./run.sh 

(注意。位于目录中的驱动程序名称必须与start命令中指定的名称完全匹配)。

您可以立即启动Selenium和Webdriver,以免再次出现此问题。 为此,请打开一个新的终端选项卡,转到run.sh文件所在的目录并编写启动命令:

 ~/AutomationProjects/ProjectTutorial/lib$ ./run.sh 

确保服务器正在运行:



我们使其处于运行状态。 至此准备工作就完成了。

编写测试脚本


我们继续为测试用例创建功能部件文件。 为此,在Codeception中提供了一个特殊的命令,在控制台中运行它:

 cept g:feature acceptance check 

(请注意“检查”-测试的名称)

我们在接受文件夹中看到了新的check.feature文件。



我们不需要默认内容,我们将其立即删除并编写测试。

为了使收集者能够识别西里尔字母,请不要忘记使用#language:ru启动脚本。
我们正在用俄语写一个简短的剧本。 谨在此提醒您,每个句子都应以以下关键字开头:“ When”,“ Then”,“ And”,符号“ *”等。

对于我的示例,我访问了Yandex网站,您可以选择任何一个。



要查看测试中有哪些步骤,我们在终端中运行脚本:

 cept dry-run acceptance check.feature 



脚本的步骤显示在控制台中,但尚不可用。

然后,我们运行一个命令,该命令将自动生成用于实现我们的方法的模板:

 cept gherkin:snippets acceptance 



脚本中所有用引号引起来的名称均替换为变量:arg。
我们从终端复制它们,然后将其粘贴到AcceptanceTester.php文件中,该文件位于使用页面元素的方法中。



将方法重命名为可读的方法,以反映其本质(可选),并编写其实现。



一切都很简单,但是如果您在智能开发环境(例如Storm)中工作,则一切都会更加简单,该环境本身会提示来自库的必要命令:



我们删除多余的部分并编写方法:

 /** * @When      */ public function step_beingOnMainPage($page) { $this->amOnPage('/'); } /** * @Then    :element */ public function step_seeElement($element) { this->seeElement($element); } /** * @Then    :button */ public function step_clickOnButton($button) { $this->click($button); } /** * @Then    :field  :text */ public function step_fillField($field, $text) { $this->fillField($field, $text); } 

让我们看看发生了什么。 我们成立了一个团队,向我们展示我们现在实现了哪些方法(步骤)。

 cept gherkin:steps acceptance 



成功!

但是,功能文件中的步骤仍未被识别为方法。



为了让Storm了解如何执行这些步骤,我们将通过在名称空间Gherkin Context中实现Context接口来做一个技巧。

 namespace Behat\Behat\Context { interface Context {} } 

将我们的AcceptanceTester类包装在名称空间中并从Context继承

 implements \Behat\Behat\Context\Context 



现在,功能文件的所有步骤都与它们的实现相关联:



为了使Webdriver了解单击的内容和查找的位置,您需要用相应的定位符和URL替换可读元素的名称和页面地址,这些将作为参数放入方法中。

然后我们得到以下形式的测试:



您可以运行:

 cept run acceptance 



已通过

大约 如果页面元素需要很长时间加载,则可以将wait添加到所需的方法中:

 $this->waitForElementVisible($element); 

我们回到测试场景。 我们感到沮丧的是,由于我们看到的不是HTML元素和url,而是元素和页面的明确名称,因此失去了整个测试的可读性。

如果我们要解决此问题,是时候继续执行Page Object模式的实现了。

转到页面对象


在_support目录中,创建Page目录,我们将在其中放置我们的类页面:

 php vendor/bin/codecept generate:pageobject MainPage 

第一个Page Object是Yandex的主页,我们称它为MainPage,我们将类称为相同:



在这里,我们声明静态字段和方法,以便可以在不创建对象的情况下调用它们。

由于在Acceptance.suite.yml的配置中,我们已经指定了起始页面网址: yandex.ru ,对于主页而言,足以指定

 public static $URL = '/'; 

接下来是页面元素数组。 我们描述了几个元素的定位符,并为其赋予了明确且唯一的名称。

现在,您需要添加getElement方法,该方法将通过数组中元素的名称返回定位符。

结果,我们有:

 <?php //location: tests/_support/Page/MainPage.php namespace Page; /**   */ class MainPage { public static $URL = '/'; public static $elements = array( ' ' => "//*[@id='wd-wrapper-_afisha']", ' ' => "//*[@data-statlog='afisha.title.link']", ' ' => "//*[@class='weather__icon weather__icon_ovc']|//*[@class='weather__icon weather__icon_skc_d']", ); public static function getElement($name){ return self::$elements[$name]; } } 

添加几个页面类:

/ **海报* /



/ **海报-搜索结果* /



我们返回到AcceptanceTester.php类,在其中编写了方法。
让我们在其中创建一个PageObject类的数组,在这里我们将名称分配给页面并在命名空间中指示它们的类名称:

  private $pages = array( " " => "\Page\MainPage", "" => "\Page\AfishaPage", " -  " => "\Page\AfishaResult" ); 

每个新的PageObject都类似地添加到此数组。

接下来,我们需要创建currentPage字段,该字段将存储指向当前页面的PageObject的链接:

 private $currentPage; 

现在,我们将编写一个方法,当该方法被调用时,我们可以获取currentPage并初始化所需的PageObject类。

使用“当用户导航到“页面名称”页面时”采取这样的步骤是合乎逻辑的。 然后,无需检查即可初始化PageObject类的最简单方法如下所示:

 /** * @When     :page */ public function step_beingOn($page) { //   pageObject $this->currentPage = $this->pages[$page]; } 

现在,我们编写getPageElement方法,该方法将使我们能够从当前页面获取元素,或更确切地说是其定位符:

 private function getPageElement($elementName) { //         $curPage = $this->currentPage; return $curPage::getElement($elementName); } 



对于已经实现的方法,有必要用PageObject的元素替换我们在一开始就直接从功能文本接收的参数,即:

 $arg 

将采取的形式

 getPageElement($arg)) 

然后我们的方法将采用以下形式:

 /** * @When     :page */ public function step_beingOnMainPage($page) { //      pageObject $this->currentPage = $this->pages[$page]; $curPage = $this->currentPage; $this->amOnPage($curPage::$URL); } /** * @Then    :element */ public function step_seeElement($element) { $this->waitForElementVisible($this->getPageElement($element)); $this->seeElement($this->getPageElement($element)); } /** * @Then    :button */ public function step_clickOnButton($button) { $this->click($this->getPageElement($button)); } /** * @Then    :field  :text */ public function step_fillField($field, $text) { $this->fillField($this->getPageElement($field), $text); } /** * @Then      :field */ public function step_deleteText($field) { $this->clearField($this->getPageElement($field)); } 

通过按Enter添加了另一种显示搜索结果的方法:

 /** * @Then    ENTER */ public function step_keyboardButton() { $this->pressKey('//input',WebDriverKeys::ENTER); } 

最后一步是在描述所有必需的方法和PageObjects时,您需要重构测试本身。 添加将在进入新页面时初始化PageObject的步骤。 我们有这个“ *用户转到了页面:page”。

为了清楚起见,我将添加更多步骤。 结果是这个测试:

 #language: ru :     :   .    .   .      " "     " "     " "     " "      ""     " "  " "     ENTER      " -  "     "   "       " "     " "  ""     ENTER 

这样的测试场景对于任何局外人都是可以理解和理解的。

发射!

要查看更详细的运行结果,可以使用以下命令

 cept run acceptance --debug 

我们看一下结果:



因此,使用Page Object模式可以将所有页面元素与测试脚本分开,并将它们存储在单独的目录中。

该项目本身可以在https://github.com/Remneva/ProjectTutorial中找到

作为一名自动化工程师,如果您分享您的想法,并且也许告诉我如何在逻辑上尽可能地转变和简化项目结构,我将不胜感激。

Source: https://habr.com/ru/post/zh-CN427031/


All Articles