AT.info ПОСИДЕЛКИ  vKontakte   facebook группа  
Класс

Формирование объектов на лету. Реализация на python

Выполняю очередную задачу по автоматизации решил немного упростить себе жизнь за счет формирования объектов на лету с помощью метаклассов 

'''
Created on Nov 20, 2010
@author: polusok
'''
pageClass = "LoginPage"
pageBaseClass = "Page"
elementClass = "Ok"
elementBaseClass = "Button"

class Page(object):
    def getUrl(self):
        return "http://url"

class Button(object):
    def click(self, xpath):
        print "Click is triggered on xpath="+xpath

page = eval("type(pageClass, (%(p)s,), {})()" % {'p':pageBaseClass})
page.ok = eval("type(elementClass, (%(b)s,), {})()" % {'b':elementBaseClass})

if __name__ == '__main__':
    print page.getUrl()
    page.ok.click("//xpath[condition]")

Selenium RC (Ruby): Вынесение оконных деклараций в XML-файл

Одной из наиболее серьезных проблем при автоматизации функционального тестирования на уровне GUI является высокая чувствительность тестов к изменениям GUI. По-хорошему, подобная ситуация не должна возникать, так как автоматизация подобного рода ставится тогда, когда пользовательский интерфейс более-менее стабилен. Но это идеальная ситуация. В реальности, продукт меняется по всем направлениям и в том числе это касается пользовательского интерфейса. Так или иначе какие-то мелкие изменения имеют место (поле переименовали, переместили, поменяли некоторые идентификаторы) и это уже влияет на работоспособность тестов. Частично, можно реализовать гибкий механизм поиска объектов, на который подобные изменения не повлияют, но в большинстве случаев корректировок самих реализаций тестов не избежать. Соответственно, надо как-то минимизировать трудозатраты на корректировку. Наиболее эффективным решением данной проблемы можно назвать вынесение оконных деклараций во внешний ресурс и использование "псевдонимов". Подобное реализовано в WinRunner ( GUI Map ), QTP, RFT ( в котором есть возможность маппинга и все оконные объекты можно классифицировать как mappable и non-mappable ), TestComplete ( NameMapping и Alias, появившийся в поздних версиях ). Суть подобных решений в том, чтобы некоторому оконному объекту с заданными атрибутами поставить в соответствие некоторое имя, которое и будет использовано для обращения к данному оконному объекту.

Во многих средствах подобный механизм реализован, но существует много различных решений, которые фактически представляют собой некоторую библиотеку с прикрученным тестовым движком. В этом случае приходится пользоваться возможностями языка, на котором эти тесты пишутся. В качестве примера рассмотрим язык Ruby и конкретно его порт под Selenium RC. Почему взят именно Ruby? Во-первых, на Ruby есть еще несколько решений аналогичных Selenium RC и возможности языка, там применимы в той же мере. Во-вторых, Ruby - один из примеров языков интерпретируемого типа, у которого есть возможность динамического формирования и компоновки объектов. Подобные механизмы имеются и во многих других скриптовых языках ( в частности JavaScript ), поэтому Ruby был выбран в качестве демонстрации самой возможности подобной компоновки. На других языках подобные решения реализуются по аналогии с поправкой на специфику.

Как известно, в Selenium оконные объекты распознаются с помощью локаторов - специальных строк вида:

<how>=<value>, где

how - определяет атрибут, по которому ищется объект. Это может быть id,name, dom, xpath и многие другие ( в документации по Selenium о локаторах достаточно много расписано )
value - непосредственно значение атрибута, по которому ищется объект

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

xpath=//img[@alt=‘The image alt text’]

Позволит определить, какой объект реально искать на форме, но совсем непонятно, какая форма должна быть. То есть с точки зрения удобства чтения мы не в состоянии фиксировать, на какой странице находится данная ссылка, достаточно сложно выявить логический смысл самой ссылки ( а это важно, так как при чтении кода больше упор ведется на логический смысл нежели фактические атрибуты ). Соответственно, удобно было бы сделать псевдоним вида: 

<Псевдоним страницы>.<Псевдоним объекта>

просто для удобства чтения. Для этого можно сделать классы-обертки вида:

class MyPage

     def lnkLink()
          "xpath=//img[@alt='The image alt text']"
     end

end

После чего мы можем создать экземпляр данного класса:

wTestPage = MyPage.new

и вместо локатора использовать выражение вида:

wTestPage.lnkLink

Уже проще, так как в случае модификации атрибутов ссылки нам не надо будет менять локаторы во всех тестах, достаточно будет внести корректировки в объявлении класса. Также это решение удобно тем, что оконные декларации - это такая же часть програмного кода, что и непосредственно реализации тестов. Но тем не менее, немного неудобно нагромождать большое количество подобных классов, а если тестируемое приложение содержит много страниц, то классов будет много, что влечет за собой большое количество файлов. Поэтому, зачастую целесообразно отделить ресурсы ( оконные декларации ) от движка ( непосредственно програмной реализации ). В качестве аналога можно вспомнить NameMapping в TestComplete. Файл, описывающий правила маппинга - это XML-файл. Соответственно, можно попробовать сделать аналогичную реализацию на Ruby. Тем более в данном случае задача заметно проще, так как практически нет иерархии объектов, а сами объекты описываются одной строкой, а не множеством атрибутов.

Selenium RC (Java): Основные операции в действии. Часть 2

Часть 1, Часть 3

Работа с полем загрузки файлов

Одним из каверзных стандартных элементов управления на веб-страницах с точки зрения работы Selenium-a является поле загрузки файла. Фактически это текстовое поле и кнопка. В чем сложность? Сложность в том, что нажатие на кнопку Selenium-ом напрямую не делается (да и не нужно, если так посмотреть), а текстовое поле в общем случае доступно только для чтения, так что просто так ввести туда путь к файлу не получится. Это связано с тем, что Selenium-сервер взаимодействует с тестируемым веб-приложением путем посылки JavaScript-команд, а у объектов, соответствующих полям ввода файлов, атрибут value закрыт для записи. Это стандартное ограничение браузеров, блокирующее возможность автоматической загрузки произвольных файлов. Не стоит забывать, что клиентские скрипты несут в себе много чего вредоносного. Отчасти поэтому те же файлы с расширением js многими почтовиками блокируются. Это просто система защиты.

Тем не менее, данные проблемы так или иначе можно обойти. Итак, рассмотрим следующую секцию нашей тестовой страницы:

Реализуем шаг, в котором нам надо ввести некоторый текст в поле ввода файла и проверить, что текст введен корректно. Есть 2 способа ввести текст:

  • platform-specific - заключается в использовании сторонних библиотек, которые имитируют ввод с клавиатуры. В частности для Java можно воспользоваться классом java.awt.Robot.
  • browser-specific - заключается в том, что для определенных браузеров в определенном режиме поле ввода файлов доступно для записи на программном уровне. В частности, в режиме *chrome Selenium в состоянии ввести текст в поле ввода файла обычным вызовом type.

Из этих 2-х способов предпочтительнее выглядит 2-й по ряду причин. Во-первых, нет привязки к языку программирования, соответственно, данный подход универсален в контексте используемого языка программирования. Во-вторых, зачастую, использование внешних средств, имитирующих пользовательский ввод, привязывает код к конкретной группе операционных систем. И наконец, в-третьих, в большинстве случаев имитация ввода сводится к посылке некоторых сигналов "в эфир". Соответственно, если нужное нам окно не активно, то текст введется неизвестно куда. А ведь одно из удобств Selenium-a заключается в том, что тесты могут выполняться в фоновом режиме, в то время как пользователь может на той же машине заниматься своими делами.

Итак, нам нужно перейти в *chrome-режим, открыть страницу и ввести текст. Как-то так:

Selenium RC (Java): Основные операции в действии. Часть 1

Часть 2

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

Мы будем рассматривать работу селениума на примере Java клиента, но все подходы и заморочки аналогичны для других языков. Также, изначально предполагается, что селениум-сервер и клиентские библиотеки уже установлены и настроены нужным образом, так что на эти вещи мы не будем отвлекаться. Если у вас с этим есть проблемы, то для начала вам сюда. Также желательно быть знакомым с Java, хотя бы на базовом уровне, так как данный пост не предназначен для введения в Java.

В качестве основы теста мы возьмем пример отсюда, оставим каркас и наполним конструкциями для нашего теста. Об этом чуть позже.

Для нашего примера нужно создать тестовый HTML файл, в котором будут основные элементы, с которыми мы будем работать. Создадим файл index.htm и поместим его в корневой каталог диска C: . Если вы хотите поместить этот файл в другой каталог, то вам надо будет просто поменять абсолютный путь к файлу и учитывать это вдальнейшем при рассмотрении данного примера.

Итак, поместим в наш HTML-файл содержимое вида:

<html>
<body>
	<form name="sample_form">
		<table>
		<tr><td width="100%"><fieldset><legend>Text fields</legend>
			<table>
				<tr>
					<td><label for="text_field">Simple text field:</td>
					<td><input type=text id="text_field" value="" /></td>
				</tr>
				<tr>
					<td valign=top><label for="text_area">Text Area:</td>
					<td><textarea id="text_area" name="text_area" cols=30 rows=5></textarea></td>
				</tr>
			</table>
		</fieldset></td></tr>
		<tr><td width="100%"><fieldset><legend>Radio buttons/Check boxes</legend>
			<table>
				<tr>
					<td><label for="radio_1">Item 1:</td>
					<td><input type=radio id="radio_1" name="radio_btn" checked /></td>
				</tr>
				<tr>
					<td><label for="radio_2">Item 2:</td>
					<td><input type=radio id="radio_2" name="radio_btn"/></td>
				</tr>
				<tr>
					<td valign=top><label for="check_box">Check box:</td>
					<td><input type=checkbox name="check_box" /></td>
				</tr>
				<tr>
					<td valign=top><label for="ev_check_box">Check box with event:</td>
					<td><input type=checkbox name="ev_check_box" onclick="deactivated_btn.disabled=!this.checked;"/></td>
				</tr>

			</table>
		</fieldset></td></tr>

		<tr><td width="100%"><fieldset><legend>Buttons</legend>
			<table>
				<tr>
					<td><b><div id="clicked_text"></div></b></td>
				</tr>
				<tr>
					<td>
					<input type=button id="deactivated_btn" value="Deactivated" onclick="clicked_text.innerHTML=deactivated_btn.value;"/>
					<input type=button id="btn_1" value="First"  onclick="clicked_text.innerHTML=btn_1.value;"/>
					<button id="btn_2" onclick="clicked_text.innerHTML=btn_2.innerHTML;return false;">Second</button>
					</td>
				</tr>

			</table>
		</fieldset></td></tr>
		<tr><td width="100%"><fieldset><legend>Lists</legend>
			<table>
				<tr>
					<td><label for="single_list">Single selection list:</td>
					<td>
					<select id="single_list" />
						<option selected>Option 1</option>
						<option>Option 2</option>
						<option>Option 3</option>
					</select>
					</td>
				</tr>
				<tr>
					<td valign=top><label for="multi_list">Multi selection list:</td>
					<td>
						<select id="multi_list" MULTIPLE>
						<option>Multi Option 1</option>
						<option>Multi Option 2</option>
						<option>Multi Option 3</option>
						</select>
					</td>
				</tr>
			</table>
		</fieldset></td></tr>
		<tr width="100%"><td width="100%"><fieldset><legend>File input</legend>
			<table>
				<tr>
					<td valign=top><label for="file_input">File upload:</td>
					<td><input type=file name="file_field" /></td>
				</tr>
			</table>

		</fieldset></td></tr>

		<tr><td><fieldset><legend>Dialog boxes</legend>
			<table>
				<tr>
					<td>
						<input type=button id="alert_btn" value="Call Alert"onclick="alert('This is Alert');"/>
						<input type=button id="conf_btn" value="Call Confirmation"  onclick="confirm('This is confirmation');"/>
						<input type=button id="prompt_btn" value="Call Prompt"  onclick="prompt('Enter prompt text')"/></td>
				</tr>
			</table>

		</fieldset></td></tr>

		</table>
	</form>
</body>
</html>

Достаточно просто скопировать данный текст и вставить его в нужный HTML-файл.

Осталось только создать тестовый класс, в котором мы будем добавлять код. Создайте пакет com.example.tests и создайте в нем класс NewTest, который наследуется от SeleneseTestCase класса. После этого каркас теста имеет вид:

package com.example.tests;
// We specify the package of our tests

import com.thoughtworks.selenium.*;
// This is the driver's import. You'll use this for instantiating a
// browser and making it do what you need.

import java.util.regex.Pattern;
// Selenium-IDE add the Pattern module because it's sometimes used for
// regex validations. You can remove the module if it's not used in your
// script.

public class NewTest extends SeleneseTestCase {
// We create our Selenium test case

      public void setUp() throws Exception {
        setUp("c:\\index.htm", "*firefox");
             // We instantiate and start the browser
      }

      public void testNew() throws Exception {
           selenium.open("c:\\index.htm");
     }
}

Этот каркас взят из примеров в документации к селениуму. Просто были сделаны небольшие корректировки. Итак, приступаем.

Автоматизированное тестирование автотестов

Автоматизированное тестирование програмного обеспечения может выражаться в разных формах с точки зрения целей, подходов и реализации. Но суть одна: автотесты - это програмные модули, позволяющие проверить поведение тестируемого приложения на соответствие требованиям или предоставляющие достаточно информации, чтобы осуществить подобную проверку (те же тесты на производительность вполне могут ограничиться выдачей статистики, которую потом анализирует человек ). Ключевым моментов является то, что автотесты - это такое же по сути програмное обеспечение, что и непосредственно тестируемое приложение, из чего следует, что автотесты точно так же могут содержать в себе ошибки реализации. То есть, их тоже не мешало бы периодически проверять на работоспособность или хотя бы установить какие-то средства контроля, поскольку тесты не менее чувствительны к изменениям тестируемого приложения, чем другие программные модули, затронутые в ходе изменений.

Отчасти вероятность ошибки в автотестах уменьшается за счет простоты автотестов. В частности, white-box тесты во многих случаях представляют собой несложные конструкции, которые вызывают тестируемый модуль и перехватывают исключения и/или обрабатывают код возврата. То есть, это во многом укладывается в шаблон. Функциональные тесты в большинстве случаев представляют собой линейный сценарий.

Тем не менее, автотесты могут использовать вспомогательные решения и компоненты, как непосредственно тестовый движок, дополнительные функции/методы, реализующие некоторый отдельно взятый функционал, а также оконные декларации, если речь заходит про GUI тестирование. Всё это может однажды дать сбой за счет изменений тестируемого приложения, окружения и прочих внешних факторов. И было бы очень полезно локализовать проблему непосредственно в том месте, где она возникла.

Теперь рассмотрим, что и как мы можем автоматически протестировать:

  • Непосредственно автотесты - динамически такие компоненты проверяются непосредственно во время тестовых запусков, что не выделяет тестирование автоматизированных решений из контекста автоматизированного тестирования, для которого это тесты предназначены. Проще говоря, наиболее эффективный способ проверить работоспособность автотестов - это выполнить эти автотесты.
  • Вспомогательные классы/функции/методы - поскольку подобные компоненты представляют собой некоторый программный код, то ничто не мешает применить для них традиционные практики white-box тестирования.
  • Оконные объекты - этот тип компонентов специфичен для автоматизированного тестирования, в частности GUI-level тестирования и один из наиболее критичных к изменениям тестируемого приложения. Поэтому, для тестирования подобных компонент следует выработать некоторый workflow-сценарий, который затронет все ( или хотя бы просто большинство ) оконных объектов

Как это внедрять? Внедрить это можно на этапе разработки. Например, при разработке вспомогательных классов для автотестов можно использовать практику TDD для контроля качества создаваемого компонента. Это позволит сформировать набор тестов, которые потом можно запускать непосредственно перед запусками основных автотестов. Это так называемое юнит-тестирование автоматизационного решения.

Silktest: Вывод результатов в собственном формате

При любом виде автоматизированного тестирования неотъемлемой частью является информативный вывод результатов, так как мало просто осуществить прогон, нужно еще и суметь интерпретировать результаты. Рассмотрим решение данной задачи на примере SilkTest.

По некоторым причинам, стандартный файл результатов нас мало устраивает. Это может быть связано с такими факторами как:

  1. неудобство чтения стандартного вывода
  2. необходимость использования каких-то других внешних средств для обработки результатов (например результаты некоторых тестов должны быть в дальнейшем заимпортированы в какую-то базу или в какую-то систему)
  3. нужно более наглядное представление результатов.

Этот список можно продолжать до бесконечности. 

Рассмотрим подход по организации своего вывода результатов. Рассмотрим вывод в некоторый текстовый формат, так как это наиболее частый случай использования своей системы вывода результатов. Более конкретизируем задачу: нам надо делать вывод результатов в HTML-формате. То есть, при старте скрипта у нас создается некоторый HTML-файл и по ходу выполнения кода этот файл наполняется информацией.

Итак, поскольку наш вывод фактически является дублирующим стандартный вывод, то ничего зазорного не будет, если мы сделаем заменители стандартных функций вывода в файл результатов. Но для начала рассмотрим стандартные функции вывода:

  • Print - выводит обычный текст
  • ListPrint - выводит список в виде обычного текста, где каждый элемент списка представляет собой отдельную строку
  • LogWarning - выводит текст предупреждения. Это еще не ошибка, но уже текст, который своей подсветкой обращает на себя внимание. Внутренний счетчик увеличивает количество вызванных предупреждений.
  • LogError - выводит сообщение об ошибке. При этом счетчик ошибок увеличивает свое значение на 1.
  • ExceptPrint - выводит информацию о сгенерированном исключении

То есть, для этих функций можно сделать свои аналоги. Но в отличие от стандартных функций, необходимая нам функциональность оперирует с еще одним атрибутом - имя файла, в который будет вестись запись. То есть Эти функции-аналоги и атрибут-имя файла завязаны между собой. Причем, имя файла должно быть доступно функциям-аналогам, но недоступно нигде больше. Это полностью соответствует стандартным функциям, так как в ходе выполнения скриптов мы не можем менять файл, в который выводятся результаты. Наиболее подходящим решением для данной задачи будет класс. Если обратиться к практике на других средствах функционального тестирования, то в том же TestComplete есть такой объект как Log, который отвечает за вывод в файл отчета. Можно воспользоваться подобным принципом.

Итак, нам нужно создать класс, а точнее winclass, который будет отвечать за нашу функциональность ввода-вывода. Также нам нужна глобальная переменная-экземпляр данного класса. Для этого мы создадим отдельный файл, назовем его MyLogger.inc и для старта впишем строки

[ ] private STRING sFileName
[ ] window MyLoggerClass Log
[ ]
[+] winclass MyLoggerClass
          [ ]

TestComplete ( JScript ): Описание оконных деклараций через классы-обертки

Автоматизированное тестирование на уровне GUI содержит в себе много сюрпризов, связанных с работой с оконными объектами. Причем сюрприз заключается в том, что наиболее изящное решение проблемы с поддержкой описаний оконных объектов в актуальном состоянии для каждого отдельно взятого средства может быть свое. Но это наиболее удачное решение. Тем не менее, есть ряд механизмов, имеющих аналоги в различных средствах. Одним из примеров является Mapping оконных деклараций, позволяющий некоторым оконным объектам ставить в соответствие некоторый псевдоним. Подобное решение в TestComplete обладает одним ключевым недостатком: при изменении иерархии оконного объекта приходится переделывать маппинг всех дочерних объектов. Альтернативой этому стало введение с 6-й версии TestComplete такой функциональности как Alias, которая позволяла регулировать иерархию псевдонимов. Но у такого решения есть другой недостаток: низкая скорость. Соответственно, нужны механизмы, позволяющие оперативно реагировать на изменение GUI, при этом корректировки желательно минимизировать. Давайте рассмотрим конкретный пример.

У нас есть некоторое диалоговое окно, к которому в коде можно обратиться так:

Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1);

Здесь PRODUCT_NAME - это некоторая константа, содержащая в себе имя процесса. В вышеуказанном диалоге есть текстовое поле Name , к которому обратиться можно так:

Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TEdit" , "*" , 1 );

а также 2 кнопки: OK, Cancel, у которых описания имеют вид соответственно:

Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 1 )
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 2 )

Как видно из описаний, объекты не очень компактно описаны, более того, достаточно нестабильно, поскольку используются индексы. Часто может возникнуть ситуация, когда имена объектов в разной среде разные. Самый простой пример: системные окна сообщений для разных локализаций операционной системы имеют кнопки с текстом на локальном языке. Если заказчик из англоязычной страны, то проблем не возникнет. Но если же автотесты создаются для продукта, который идет и для неанглоязычных потребителей, то тут надо уменьшить привязку к тексту объектов, что было сделано для объектов выше. Теперь, давайте рассмотрим некоторый типичный тестовый сценарий, использующий эти объекты. Допустим нам надо подождать появления диалога в течение 5 секунд. Если он появился, то вводим текст ( неважно какой ) в текстовое поле Name и жмем кнопку OK. В чистом виде это выглядит так:

if( Sys.Process("Inventory").WaitWindow("TForm", "New Text Style", 1 , 5000 ).Exists ) {
        Log.Error( "No Text Style dialog available" );
}
else {
      Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TEdit" , "*" , 1 ).wText = "Some Text";
      Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 1 ).Click();
}

Первое, что бросается в глаза - это громоздкость кода. Слишком длинные конструкции. Далее, можно представить, что случиться, если поменяются атрибуты диалогового окна, например, текст заголовка. В результате, данный объект будет требовать несколько другого описания, из-за чего надо будет редактировать обращения и к элементам управления внутри диалога. Если прибавить к этому тот факт, что подобных конструкций в тестах может быть много и разбросаны они по разным частям файлов, то при необходимости корректировки оконных деклараций объем исправлений будет значительным. Как минимизировать требуемый объем исправлений. Одним из решений будет предоставление интерфейса для работы с окнами, который инкапсулирует непосредственное обращение к объектам. То есть можно создать класс, методы которого обеспечат доступ к нужным элементам. Итак, сделаем класс-обертку и предоставим возможность доступа непосредственно к диалоговому окну. Выглядит это так:

function NewTextStyleDlg()
{
  this.Get = function ()
  {
    return Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1);
  }  
}

Уже получили уменьшение кода, более того, данный фрагмент уже требует заметно меньших модификаций в случае изменения свойств диалогового окна.

var dNewTextStyle = new NewTextStyleDlg();

if( Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1 , 5000 ).Exists ) {
        Log.Error( "No Text Style dialog available" );
}
else {
      dNewTextStyle.Get().Window( "TEdit" , "*" , 1 ).wText = "Some Text";
      dNewTextStyle.Get().Window( "TButton", "*", 1 ).Click();
}

Selenium RC (Java): Шаги усовершенствования тестов. Часть 2

Часть 1

В предыдуших шагах мы позаботились, пожалуй о самой чувствительной к изменениям части - идентификаторах объектов. Но этого еще недостаточно, есть еще несколько шагов, которые нужно сделать, чтобы минимизировать затраты на поддержку, а также время на разработку, да и просто сделать тесты более понятными и удобными для чтения. Итак, рассмотрим эти шаги.

Шаг 4: Абстрагируемся до уровня действий на странице

В предыдущем шаге мы сумели вынести локаторы в отдельный файл и, задав, понятные имена, мы можем уже проследить, над какими элементами проводятся операции. Тем не менее, мы по-прежнему работаем на уровне примитивных команд, которые мало к чему привязаны. К тому же слабо прослеживается переход с одной страницы на другую.

Другой момент заключается в том, что держать в голове все псевдонимы всех элементов неудобно, особенно для больших объемов тестов. То есть надо бы как-то сделать так, чтобы и псевдонимы элементов использовались не так интенсивно. Да и хотелось бы, чтобы тестовые инструкции выглядели более информативно, например, не

selenium.clickAndWait( "leftpanel.newjob" );

а что-то наподобие

mainPage.clickOnNewJobLink();

То есть примитивные операции обернуть в некоторый функционал, который уже отражал бы смысл операции. Это так называемый PageObject-подход, при котором некоторому отдельному окну/странице/форме соответствует некоторый класс, методы которого соответствуют либо каким-то дочерним элементам, либо примитивным действиям внутри данного окна/страницы/формы. Один из примеров подобной реализации можно описан здесь: http://autotestgroup.com/ru/blog/55.html, а точнее реализация подобного для TestComplete. Там было описано, как обернуть некоторые дочерние элементы. В нашем случае применим подход обертки действий над некоторыми элементами, так как Selenium больше ориентирован на действия, которые проводятся над объектом, а не на объекты, над которыми проводятся действия. Это достаточно тонкая грань, которую надо уметь усмотреть.

В любом случае, нам нужен некоторый набор классов, которые могли бы соответствовать некоторым страницам. Сразу следует обратить внимание на то, что если мы скрываем действия Selenium-а внутри некоторого внешнего класса, то нам надо в этот класс передать объект Selenium-а, созданный тестом. Например, при создании любого объекта страницы в качестве параметра передается объект Selenium-a. Пожалуй, это будет наиболее общее для всех объектов страниц решение. В пакет com.mycompany.selenium.lib добавим класс BaseTestClass со следующим содержимым:

/**
 * 
 */
package com.mycompany.selenium.lib;

/**
 * @author KaNoN
 *
 */
public class PageObjectClass {
 
            protected ExtendedSelenium selenium = null;

            public PageObjectClass( ExtendedSelenium selenium ) throws Exception {
                        this.selenium = selenium;
            }
}

После этого, мы можем создавать классы страниц, которые (классы) будут отнаследованы от данного класса. Еще один момент. Когда мы работаем с объектом страницы, то в ряде случаев, когда мы делаем действие, приводящее к переходу на новую страницу, было бы полезно возвращать объект этой новой страницы. Учитывая эти пожелания, создадим отдельный пакет для классов страниц. Назовем его “com.mycompany.selenium.lib.pages” и добавим в него 3 класса страниц, с которыми работает наш тест:

MainPage:

/**
 * 
 */
package com.mycompany.selenium.lib.pages;

import com.mycompany.selenium.lib.ExtendedSelenium;
import com.mycompany.selenium.lib.PageObjectClass;
 

/**
 * @author KaNoN
 *
 */
public class MainPage extends PageObjectClass {

            /**
             * @param selenium
             * @throws Exception
             */

            public MainPage(ExtendedSelenium selenium) throws Exception {
                        super(selenium);
            }

            public NewJobPage clickOnNewJobLink() throws Exception{
                        selenium.clickAndWait( "leftpanel.newjob" );
                        return new NewJobPage( selenium );
            }

            public MainPage clickOnHudsonLink() throws Exception {
                        selenium.clickAndWait("leftpanel.hudson");
                        return this;
            }
}

NewJobPage:

/**
 * 
 */
package com.mycompany.selenium.lib.pages;

import com.mycompany.selenium.lib.ExtendedSelenium;
import com.mycompany.selenium.lib.PageObjectClass;

/**
 * @author KaNoN
 *
 */
public class NewJobPage extends PageObjectClass {

	/**
	 * @param selenium
	 * @throws Exception
	 */
	public NewJobPage(ExtendedSelenium selenium) throws Exception {
		super(selenium);
	}

	public NewJobPage typeJobName( String name ) throws Exception {
		selenium.type( "newjobpage.name", name );
		return this;
	}
	
	public NewJobPage checkFreeStyleJobRadioButton() throws Exception {
		selenium.click("newjobpage.freestyleradio");
		return this;
	}
	
	public ConfigureJobPage clickOK() throws Exception{
		selenium.clickAndWait("newjobpage.ok");
		return new ConfigureJobPage( selenium );
	}

}

ConfigureJobPage:

/**
 * 
 */
package com.mycompany.selenium.lib.pages;

import com.mycompany.selenium.lib.ExtendedSelenium;
import com.mycompany.selenium.lib.PageObjectClass;

/**
 * @author KaNoN
 *
 */
public class ConfigureJobPage extends PageObjectClass {

            /**
             * @param selenium
             * @throws Exception
             */
            public ConfigureJobPage(ExtendedSelenium selenium) throws Exception {
                        super(selenium);
            }

            public MainPage clickSave() throws Exception {
                        selenium.clickAndWait("configurejob.save");
                        return new MainPage( selenium );
            }

}

Если посмотреть внимательно, то можно увидеть, что каждое действие, которое возвращает объект страницы, либо создает новый объект, либо возвращает указатель на себя, если перехода на новую страницу не было.

RSS-материал