неділя, 20 травня 2012 р.

Page mapping + Yaml + PHP

Добрый день читатель.
Сегодня хочу с вами поделится интересным подходом для мапинга елементов UI интерфейса Web страниц.

Нам с вами понадобятся всего три вещи: небольшое знание языка программирования PHP, YAML и принципов наследования классов в Java.




Начнем с последнего: Страницы и елементы дложны выглядень как конейнеры свойств с наследованием от одного предка. Основные елементы таких страниц Url, Title, Elements

Сами страницы прописываются в yaml файл как ключ-значение.

Выглядит это следующим образом:


FELogin:
  url: "login.php"
  inherit: FELayout
  elements:
    Username: "//input[@name='email_address']"
    Password: "//input[@name='password']"
    ContinueButton: "//button[contains(@class, 'login-continue')]"
    ErrorMessage: "//div[@class='errormsgBoxDiv']"
    ForgotPassword: "//table[@class='auth-form']//input[@name='new_customer']"
    ShoppingCartBox: "%{ShoppingCartBox/SELF}%"


Первым елементом в такой структуре идет название типа страницы FELogin. Первым ребенком  елемент url, который обезательный для мапинга страниц. Дальше идет необезательный елемент, который показывает от какой странцы данная должна наследовать елементы. Дальше идет массив отличных от родителя елемендов страницы. 
Необходимо еще указать, что елементы могут быть составные и включать некоторые предопределенные блоки. Например, елемент ShoppingCartBox является SELF елементом блока ShoppingCartBox. Таких составляющих может быть довольно много, например:


FECategory:
...
    RegularPriceVatNote: "%{FECategory/ProductBlock}%%{ProductImagePopup/RegularPrice}%%{Prive/VatNote}%"


Единственное ограничение,  при составление елементов из "блоков" нужно следить, чтобы они не ссылались друг на друга (не вызывали рекурсию).


В такой схеме при чтении мапинга, елси родитель и ребенок имеют елемент с одинаковым название, то будет братся значение локатора елемента с ребенка.

Само чтение мапинга реализовывается в специальном статическом классе PageHelper. Этот клас должен уметь возвращать урл страницы по ее типу, а также  возвращать определенный локатор елемента по имени и типу страницы.  Код хелпера довольно простой:


<?php
namespace Core\Features\Context;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
class PageHelper {
private $pages = array();
private static $_instance;
private function __construct() {
$yaml = new \Symfony\Component\Yaml\Parser();
try {
$dir = __DIR__;
$ymls = `cat $dir/../mappings/*.yml`;
$value = $yaml->parse($ymls);
$this->pages = $value;
} catch (ParseException $e) {
printf("Unable to parse the YAML string: %s", $e->getMessage());
}
}

public static function getPage($pageName) {
if (!isset(self::$_instance)) {
self::$_instance = new PagesHelper();
}
if (array_key_exists($pageName, self::$_instance->pages)) {
$result = self::$_instance->pages[$pageName];
} else {
throw new \Exception("Page type \"$pageName\" is not known yet. First register it in PagesHelper class.");
}
return $result;
}

public static function getPageUrl($pageName, $objectId = null) {


$page = self::getPage($pageName);
$url =  (string)$page['url'];


if (strstr($url, "%ID%")) {
if ($objectId) {
$url = str_replace("%ID%", $objectId, $url);
} else {
throw new \Exception("Url for $pageName requires object id, please pass title of object for me to get id");
}
}
return (string)$url;
}

public static function getElements($pageName) {
$page = self::getPage($pageName);
$selfElements = array();
$parentPageElements = array();
//Get all own page elements
if (array_key_exists("elements", $page)) {
$selfElements = $page['elements'];
}
//Get inherited elements
if (array_key_exists("inherit", $page)) {
$parentPageElements = self::getElements($page['inherit']);
}
if (count($selfElements) == 0 && count($parentPageElements) == 0) {
return array();
}
$elements = array_merge_recursive($selfElements, $parentPageElements);
return $elements;
}
private static function getRawElementLocator($pageName, $element){
$elements = self::getElements($pageName);
if (!array_key_exists($element, $elements)) {
throw new \Exception("Element \"$element\" is not known for page $pageName yet. First register it in pages-mappings yaml class.");
}
$elementLocator = $elements[$element];
if (is_array($elementLocator)){
  $elementLocator = $elementLocator[0];  //get first one - it means from child page
}
$result = 1;
while ($result) {
$result = preg_match("*%{(?P<page>\w+)\/(?P<elem>\w+)}%*", $elementLocator, $pageObject);
if ($result) {
$xpathPage = $pageObject['page'];
$xpathElem = $pageObject['elem'];
$elementXpathReplacement = self::getRawElementLocator($xpathPage, $xpathElem);
$replace = (string)$pageObject[0];
$elementLocator = str_replace("${replace}", $elementXpathReplacement, $elementLocator);
}
}
return (string)$elementLocator;
}
public static function getElementForObject($pageName, $element, $objectId = null, $language = null) {
$elementLocator = self::getRawElementLocator($pageName, $element);
$ID_TOKEN = "%ID%";
$LANG_TOKEN = "%LANG%";
if ($elementLocator) {
if ($objectId) {
if (strstr($elementLocator, $ID_TOKEN)) {
$elementLocator = str_replace($ID_TOKEN, $objectId, $elementLocator);
}
else {
throw new \Exception("Element for $pageName requires object id (current $objectId), please pass it");
}
}
if ($language) {
if (strstr($elementLocator, $LANG_TOKEN)) {
$elementLocator = str_replace($LANG_TOKEN, $language, $elementLocator);
} else {
throw new \Exception("Element for $pageName requires language (current $language), please pass it");
}
}
return (string)$elementLocator;
} else return null;
}
}

Как видно из кода хелпера, что в мапинг введено два токена %ID% и %LANG% которые позволяют  работать с динамическими елементами и елементами, которые зависят от языка.

Использование даных вещей очень прост:
PageHelper::getElementForObject("FELogin","Username","125","en")
 где 125 - ид юзера Vasja в базе.

Таким образом написание тестов упрощается в разы. Особенно если вы работаете с Behat. Например, шаг проверки значение елемента на странице для какого-то продукта будет выглядеть:

I should see text "1234" in "PriceRegular" for "iPhone 4" at "FECategory" on "da" language  

Это просто, гибко, и красиво. Пользуйтесь.

Немає коментарів:

Дописати коментар