История одного дня: PHPUnit, Selenium, Facebook

    Сегодня я хочу рассказать о том как я мучался с PHPUnit, Selenium RC и Facebook'ом :)

    Итак, мне была дана задача сделать систему ежедневного тестирования сайта и постинга багов в bugzill'у (Расскажу я только о связке PHPUnit/Selenium RC). Воспользовавшись немного гуглом я нашел две вещи подходящие для php — SimpleTest и PHPUnit. Покурив доки я остановился на PHPUnit. Выбор пал именно на него из-за поддержки Selenium'a.
    Т.к. ранее ни с PHPUnit, ни с Selenium я не работал, то пришлось искать что почитать (также большое спасибо пользователю WanderingStar за статью Юнит-тестирование в PHP). Начитавшись до красных глаз, решил сделать первый тест :) Но сначала необходимо все установить:

    pear channel-discover pear.phpunit.de
    pear install phpunit/PHPUnit-beta


    Далее скачиваем Selenium RC (selenium-remote-control-1.0.1-dist.zip), распаковываем и в директории selenium-server-1.0.1 запускаем:

    java -jar selenium-server.jar

    Теперь приступим к написанию. Первый тест как всегда был взят из документации:

    1. <?php
    2. require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
    3. class WebTest extends PHPUnit_Extensions_SeleniumTestCase
    4. {
    5.   protected function setUp()
    6.   {
    7.     $this->setBrowser('*firefox');
    8.     $this->setBrowserUrl('http://www.example.com/');
    9.   }
    10.   public function testTitle()
    11.   {
    12.     $this->open('http://www.example.com/');
    13.     $this->assertTitleEquals('Example WWW Page');
    14.   }
    15. }
    16. ?>
    * This source code was highlighted with Source Code Highlighter.


    Сохраняем в файл WebTest.php и запускаем: phpunit WebTest

    Видим как запустился FireFox, открыл страничку и убился :) Результат выполнения теста:
    PHPUnit 3.4.0beta4 by Sebastian Bergmann.

    E

    Time: 14 seconds

    There was 1 error:

    1) WebTest::testTitle
    BadMethodCallException: Method assertTitleEquals not defined.
    WebTest.php:15

    FAILURES!
    Tests: 1, Assertions: 0, Errors: 1.


    Ошибка… Не хорошо писать код с ошибками в документации… Вспомнив что это бета релиз, пошел проверить changelog. Обнаружил интересную вещь:
    Please note that the following commands had to be renamed:

    assertAlertPresent() has been renamed to assertAlert()
    assertNoAlertPresent() has been renamed to assertNotAlert()
    assertNoConfirmationPresent() has been renamed to assertConfirmationNotPresent()
    assertLocationEquals() has been renamed to assertLocation()
    assertLocationNotEquals() has been renamed to assertNotLocation()
    assertNoPromptPresent() has been renamed to assertPromptNotPresent()
    assertNothingSelected() has been renamed to assertNotSomethingSelected()
    assertTitleEquals() has been renamed to assertTitle()
    assertTitleNotEquals() has been renamed to assertNotTitle()


    Исправляем assertTitleEquals на assertTitle и заново запускаем:
    PHPUnit 3.4.0beta4 by Sebastian Bergmann.

    F

    Time: 9 seconds

    There was 1 failure:

    1) WebTest::testTitle
    Current URL: www.example.com

    Failed asserting that <string:Example Web Page> matches PCRE pattern "/Example WWW Page/".
    WebTest.php:15

    FAILURES!
    Tests: 1, Assertions: 1, Failures: 1.


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

    1. <?php
    2. require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
    3. class WebTest extends PHPUnit_Extensions_SeleniumTestCase
    4. {
    5.    protected $captureScreenshotOnFailure = TRUE;
    6.    protected $screenshotPath = 'd:\apache2\htdocs\screenshots';
    7.    protected $screenshotUrl = 'http://localhost/screenshots';
    8.  
    9.    protected function setUp()
    10.    {
    11.      $this->setBrowser('*firefox');
    12.      $this->setBrowserUrl('http://www.example.com/');
    13.    }
    14.    public function testTitle()
    15.    {
    16.      $this->open('http://www.example.com/');
    17.      $this->assertTitle('Example WWW Page');
    18.    }
    19. }
    20. ?>
    * This source code was highlighted with Source Code Highlighter.


    Запускаем:

    PHPUnit 3.4.0beta4 by Sebastian Bergmann.

    F

    Time: 8 seconds

    There was 1 failure:

    1) WebTest::testTitle
    Current URL: www.example.com
    Screenshot: localhost/screenshots/ed26432dcfb69cbbdd4e0c01fade4682.png

    Failed asserting that <string:Example Web Page> matches PCRE pattern "/Example WWW Page/".
    WebTest.php:19

    FAILURES!
    Tests: 1, Assertions: 1, Failures: 1.


    Вот так уже лучше. Пример из документации заработал :) Хотелось бы перейти к тестированию разрабатываемого сайта. Не вдаваясь в подробности сайт похож чем-то на Yahoo Answers с авторизацией через Facebook. Вот тут нас и ждет косяк…

    На сайте размещена кнопка для логина. Выглядит вот так:

    Код:
    1. <a onclick="FB.Connect.requireSession(function() { window.location='index.php'; }); return false;" href="#">
    2. <img alt="Connect" src="http://static.ak.fbcdn.net/images/fbconnect/login-buttons/connect_light_medium_long.gif" id="fb_login_image"/>
    3. </a>
    * This source code was highlighted with Source Code Highlighter.


    После клика по этой кнопке пользователь видит PopUp:


    Selenium предоставляет возможность кликнуть по элементу, для этого надо знать его id или name (также возможен поиск при помощи xpath, но мне он не подходит, т.к. элемент могут передвинуть). Как вы видите у тега id отсутствует (исправить код сайта временно нельзя). Пришлось искать другие варианты авторизации. Погуглив немного я наткнулся на такой вариант. Вкратце мы проходим авторизация на facebook'e и получаем куки, которые мы подсовываем Selenium'у. Реализовать мне это не удалось, т.к. я вспомнил что даже если пользователь авторизован на facebook'e, ему все равно надо кликнуть по кнопке «Connect with Facebook». Тут я ушел покурить :)

    На балконе мне в голову пришла идея — а почему бы не кликнуть по картинке, ведь ее id у нас есть. Решил попробовать (правда сомневался что заработает, ведь onclick висит не на картинке, но других идей не было). Вот что я накодил:

    1. <?php
    2.  
    3. require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
    4.  
    5. class LoginTest extends PHPUnit_Extensions_SeleniumTestCase
    6. {
    7.  protected $captureScreenshotOnFailure = TRUE;
    8.  protected $screenshotPath = 'd:\apache2\htdocs\screenshots';
    9.  protected $screenshotUrl = 'http://localhost/screenshots';
    10.  
    11.  protected function setUp()
    12.  {
    13.   $this->setBrowser('*firefox');
    14.   $this->setBrowserUrl('http://%site_url%/');
    15.  }
    16.  
    17.  public function testTitle()
    18.  {
    19.   $this->open('/');
    20.   
    21.   //Click Login
    22.   $this->click("fb_login_image");
    23.  }
    24. }
    25.  
    26. ?>
    * This source code was highlighted with Source Code Highlighter.


    Каково же было мое удивление, когда это заработало… Теперь нам надо ввести email и пароль и нажать Connect. Id этих элементов нам известны — email, pass, login. Делаем такой код:

    1. <?php
    2.  
    3. require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
    4.  
    5. class LoginTest extends PHPUnit_Extensions_SeleniumTestCase
    6. {
    7.  protected $captureScreenshotOnFailure = TRUE;
    8.  protected $screenshotPath = 'd:\apache2\htdocs\screenshots';
    9.  protected $screenshotUrl = 'http://localhost/screenshots';
    10.  
    11.  protected function setUp()
    12.  {
    13.   $this->setBrowser('*firefox');
    14.   $this->setBrowserUrl('http://%site_url%/');
    15.  }
    16.  
    17.  public function testTitle()
    18.  {
    19.   $this->open('/');
    20.   
    21.   //Click Login
    22.   $this->click("fb_login_image");
    23.  
    24.   $this->type("email", "test@test.com");
    25.   $this->type("pass", "паролъ");
    26.   $this->click("login");
    27.  
    28.  }
    29. }
    30.  
    31. ?>
    * This source code was highlighted with Source Code Highlighter.


    Результат:
    PHPUnit 3.4.0beta4 by Sebastian Bergmann.

    E

    Time: 25 seconds

    There was 1 error:

    1) WebTest::testTitle
    RuntimeException: Response from Selenium RC server for testComplete().
    ERROR: Element email not found.

    WebTest.php:32

    FAILURES!
    Tests: 1, Assertions: 1, Errors: 1.


    Как так? Он же есть… Немного подумав я все-таки понял почему нету. Этот элемент находиться в PopUp'е а не в главном окне. Поискав в документации как организовать переключение между окнами я получил конечный результат кода:

    1. <?php
    2.  
    3. require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
    4.  
    5. class LoginTest extends PHPUnit_Extensions_SeleniumTestCase
    6. {
    7.  protected $captureScreenshotOnFailure = TRUE;
    8.  protected $screenshotPath = 'd:\apache2\htdocs\screenshots';
    9.  protected $screenshotUrl = 'http://localhost/screenshots';
    10.  
    11.  protected function setUp()
    12.  {
    13.   $this->setBrowser('*firefox');
    14.   $this->setBrowserUrl('http://%site_url%/');
    15.  }
    16.  
    17.  public function testTitle()
    18.  {
    19.   //Открываем главную страницу
    20.   $this->open('/');
    21.   //Проверяем заголовок (на тот ли сайт мы попали?)
    22.   $this->assertTitle('%page_title%');
    23.   
    24.   //Кликаем логин
    25.   $this->click("fb_login_image");
    26.   //Ждем пока вылезет popup
    27.   $this->waitForPopUp("_blank", "30000");
    28.   
    29.   //Выбираем наш popup
    30.   $this->selectWindow("_blank");
    31.   
    32.   //заполняем поля и кликаем Connect
    33.   $this->type("email", "test@test.com");
    34.   $this->type("pass", "паролъ");
    35.   $this->click("login");
    36.  
    37.   //Выбираем главное окно
    38.   $this->selectWindow("null");
    39.   //Ждем пока оно загрузится
    40.   $this->waitForPageToLoad("30000");
    41.   
    42.   //Проверяем заголовок
    43.   $this->assertTitle('%page_title%');
    44.   
    45.   //Проверяем залогинились или нет (существует ли текст signout на странице)
    46.   $this->verifyTextPresent('signout');
    47.  }
    48. }
    * This source code was highlighted with Source Code Highlighter.


    Всем спасибо за внимание :) Любая критика приветсвуеться.

    З.Ы. Сильно не пинайте, первый пост все-таки. Если статьи такого плана интересны, могу еще написать о чем-нибудь.

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 20

      0
      Прикольно, а я еще видел страничку в вебе на которой сам руцми один раз проходишь тест, тест записывает и потом запускаешь сколько хочешь. Вот как бы еще так есть.
        0
        Можно ссылку на эту страничку?

        Кстати такая вещь возможна и в Selenium'e заметил только после написания поста. Аддон для ФФ — Selenium IDE. Но его проблема в том, что он не переключит окна (т.е. данную часть все равно руками надо доделывать)
          0
          Вот с переключениями окон не подскажу. Я как-то видел гугл показывал свои технологии тестирования… щас пороюсь покажу…
            0
            Блин, не нашел ту страничку что хотел. Там был прямо кусок гугловского кода, типа пример с картами. И Запускался скриптец который начинал протыкивать кнопки, двигать карту и т.д. Прикольно… Если найду — покажу…

            Но нарыл другого )

            googletesting.blogspot.com/
            www.youtube.com/watch?v=Vlz-WmcrBL8 — весело заканчивают доклад )
            selenium-grid.seleniumhq.org/ — для крыпных пацанов )

            А это почти то что хтел показать. Думаю будет полезным )
            cubictest.seleniumhq.org/
              0
              Спасибо :) Видео отпад.
                0
                Очень жалею что не нашел у себя в букмарках этой штуки которая в вебе дает строить тесты, даже для селениума, кажется… (
                  0
                  Фух ) нашел, но правда статью где есть скрины того интерфейса что я хочу показать.

                  Я лохонулся. Этот интерфейс для тестирования аякс-приложений. Но думаю бдет прикольно. Правда это еще не все ), демки уже не работают. Но может у вас заработают )

                  www.infoq.com/articles/testing-ajax-selenium
                    0
                    Описанный в статье метод полезен не только для ajax-приложений. Бывает применяю его и для тех случаев, когда страничка очень долго загружается.
                0
                Selenium IDE очень удобен для изготовления заготовки теста — для автоматической записи элементов, основных действий и т.п. Потом прямо в нем же конвертируешь записавшийся скрипт в нужный язык, тот же php, и доводишь до ума для запуска в Selenium RC или Core. Так что для ускорения процесса рекомендую.
                  0
                  Именно так уже и делаю, вот сейчас пробую Selenium CubicTest
                    0
                    О, про CubicTest раньше не слышал если честно. Что дает?
                      0
                      Судя по описанию, тоже самое что и IDE + графическое представление теста.
                        0
                        Есть успехи? Насколько удобнее работать с CubicTest?
                          0
                          Нету успехов :) На eclipse 3.5 очень с трудом это все поставилось и в итоге не захотело работать :) Более древний eclipse я не захотел пользовать. Зато наткнулся на еще одну интересную вещь — code.google.com/p/js-test-driver/ Как разберусь может напишу чего.
                            0
                            JsTestDriver позволяет проверить, как тот или иной js-код будет выполнен в любом браузере или что-то ещё? Пиши, если разберешься :)
              +1
              Selenium предоставляет возможность кликнуть по элементу, для этого надо знать его id или name (также возможен поиск при помощи xpath, но мне он не подходит, т.к. элемент могут передвинуть)


              Мне кажется xpath вы недооцениваете:
              //img[@id='fb_login_image']/parent::a
                0
                знаний не хватило на такое выражение :) спасибо буду знать
                  0
                  Можно еще проще: //a[img[@id='fb_login_image']]
                  // — вначале не всегда обязательно. В XSLT например не нужно, а в DOMXpath без него не катит.

                  Но так разговору уже больше полугода, думаю знаний на такое уже хватает :)
                0
                Не пробовал PHPUnit_Extensions_Story_SeleniumTestCase?
                  0
                  как-то мимо прошел :)

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

                Самое читаемое