Vyladěné selhání testu v Codeception

28. 03. 2017

Codeception je skvělý nástroj pro psaní selenium testů. V práci jej používáme čím dál více a zvlášť náš tester si je nemůže vynachválit. Protože já píší pouze malé testy, případně nastřeluji koncept psaní, moc často testy nepouštím a neladím je. Mám vždycky radost, když s nějakou potřebnou vychytávkou přijde právě tester. Ten píše ty velké regresní testy u kterých vždycky narazí na nějaký zádrhel nebo nepříjemnost. A s jednou takovou nepříjemností byl u mě také napsoledy.

Codeception standartně ošetřuje chybný test tak, že kromě červených fontů v konzoli, vygeneruje screenshot obrazovky. Z něho je možné obvykle vyčíst, kde se asi stala chyba. Pokud ovšem potřebuji chybný test speciálně ošetřit, mám možnost použít metodou _failed.

<?php

class MojeTestovaciTridaCest {
  public function _failed(WebGuy $I) {
    ...
  }

  public function test(WebGuy $I) {
    ...
  }
}

Neštastné je, že screenshot se vygeneruje před zavoláním metody _failed. Pokud potřebujete po selhání testu ještě provést nějaké modifikace na stránce, nebo třeba upravit nastavení v zázemí aplikace, screenshot nebude již odpovídat stavu, kdy chyba vznikla. Moje typická situace je - pro některé testy mít speciální nastavení aplikace v zázemí. Poté co test skončí chci mít vše v původním stavu a také poté, když test selže.

<?php

class MojeTestovaciTridaCest {
  public function _before(WebGuy $I) {
    $this->prepare();
  }

  public function _after(WebGuy $I) {
    $this->cleanup();
  }

  public function _failed(WebGuy $I) {
    $this->cleanup();
  }

  public function test(WebGuy $I) {
    ...
  }
}

Naštestí si můžeme napsat vlastní helper, který situaci vyřeší. Helper je vlastně to samé jako modul, takže máme možnost i rozšiřovat stávající moduly, čehož také využijeme. Můj cíl je mít možnost zavolat metodu, která vygeneruje screenshot s html těsně předtím než začnu cokoliv se stránkou dělat.

public function _failed(WebGuy $I) {
  $I->makeFailedScreenshot();
  $this->cleanup();
}

Nový helper bude vypadat nějak takto:

<?php

namespace Codeception\Module;

class FailedScreenshotHelper extends \Codeception\Module {
  private $_test;

  public function _before(\Codeception\TestInterface $test) {
    $this->_test = $test;
  }

  public function makeFailedScreenshot() {
    $webDriver = $this->getModule('WebDriver');
    $webDriver->disableFailedScreenshot();
    $webDriver->_saveScreenshot($report = $this->_getFileName('.png'));
    $this->_test->getMetadata()->addReport('png', $report);
    $webDriver->_savePageSource($report = $this->_getFileName('.html'));
    $this->_test->getMetadata()->addReport('html', $report);
  }

  private function _getFileName($suffix) {
    $filename = preg_replace('~\W~', '.', $this->_test->getSignature());
    $outputDir = codecept_output_dir();
    $this->debug("Screenshot and page source will be saved into '$outputDir' dir");
    return $outputDir . mb_strcut($filename, 0, 249 - count($suffix), 'utf-8') . '.fail' . $suffix;
  }
}

Ve vytváření souboru nehledejte žádnou záhadu, je to v podstatě opsané z modulu WebDriver, tak aby názvy souborů byl totožné.

Takto nám sice vygenerování screenshotu funguje, ale má to jednu chybku. Modul WebDriver stále generuje screeshot před zavoláním _failed. A protože jsem již psal, že můžeme existující moduly modifikovat, využijeme toho a podědíme si modul WebDriver a upravíme jeho chování tak, že budeme moci určit, zda má screenshot vygenerovat nebo ne.

<?php

namespace Codeception\Module;

class ExtendedWebDriver extends \Codeception\Module\WebDriver {
  private $_enableFailedScreenshot = true;

  public function disableFailedScreenshot() {
    $this->_enableFailedScreenshot = false;
  }

  public function _failed(\Codeception\TestInterface $test, $fail) {
    if ($this->_enableFailedScreenshot) {
      parent::_failed($test, $fail);
    }
  }
}

A zároveň upravíme metodu makeFailedScreenshot, kde na začátku vypneme původní generování screenshotů:

<?php

class FailedScreenshotHelper extends \Codeception\Module {
  ...

  public function makeFailedScreenshot() {
    $webDriver = $this->getModule('ExtendedWebDriver');
    $webDriver->disableFailedScreenshot();
    ...
  }
}

A máme hotovo. Tedy skoro. Aby vše fungovalo, chybí maličkost a to upravit acceptance.suite.yml. Místo modulu WebDriver použijeme ExtendedWebDriver. A přidáme nový helper:

class_name: WebGuy
modules:
  enabled:
    - ExtendedWebDriver
    - FailedScreenshotHelper
...

A nakonec přidám ještě malou vychytávku. Časem začne narůstat složka se screenshoty selhaných testů, bylo by hezké, kdyby se smazaly ty, u kterých již testy doběhly v pořádku. Původně jsem tuto funkčnost přidal do stejného modulu, ale poté jsem zjistil, že tento modul již existuje: Codeception\Extension\RunFailed

Twitter, Facebook