AT.info ПОСИДЕЛКИ  vKontakte   facebook группа  
Знаешь как делать? Можешь рассказать? Да! Мы тебя ждем.
view counter
Очень хочется узнать его. Форум ждет тебя!
view counter
Ищешь как решить проблему с Selenium?! Спроси людей. Они все знают!
view counter
Локатор

Основные команды в Selenium IDE / RC

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

Базовую информацию о инструментах автоматизации Selenium вы можете получить перейдя по этой ссылке.

В Selenium существует три типа команд:

  • Действия – функциональное действие над тестируемым веб-приложением в браузере. Например, заполнение полей, нажатие на кнопку и другие;
  • Проверки – выполнение проверок на тестируемой странице. Например, проверка того, что определенное поле формы имеет указанное значение, или проверка заголовка окна;
  • Ожидания – организация как, сколько и какое событие Selenium будет дожидаться (ожидания загрузки страницы, ajax и т.д.).

at.info workshop #2: отчет

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

Мы начали с знакомства и определения уровня собравшейся аудитории. Разъяснили базовые моменты работы Selenium и перешли к основной теме воркшопа - Локаторам.

Мы поддерживаем наш формат 95% практики и лишь 5% теории. Потому, весь материал был подкреплен практическими заданиями, с которыми ребята успешно справились. И ушли домой со всеми материалами, полученными во время встречи. 

Почему CSS локаторы работают быстрее чем XPath?

Посмотрел я видео, где рассказывают о CSS vs XPath. 

http://www.youtube.com/watch?v=6vPu3TO6XZ4&feature=channel_video_title

Мне стало интересно, почему практически во всех случаях XPath работает медленее (пуская в доли секунды для FireFox или секунды для Internet Explorer) чем CSS?

Selenium: Подбираем локаторы

Знание типов локаторов - это только первый шаг к умению рационально их использовать. Умение же ими пользоваться - один из ключевых навыков работы с Selenium-ом, так как всё остальное, что необходимо знать, сводится к изучению библиотеки (а основного функционала там немного) и ряда частных случаев, как правило, обходных маневров. Всё остальное уже больше относится к умению работать с тем или иным языком программирования. Поэтому в данном разделе мы рассмотрим, какой локатор и в каком случае удобнее подобрать.

В принципе, локаторы можно расположить по приоритетам использования в следующем порядке:

  • link= (только для ссылок, естественно, причем при условии, что данная ссылка одна, а не серия)
  • id=
  • name=
  • dom=
  • css=
  • xpath=

Соответственно, когда мы подбираем локатор для некоторого элемента, мы смотрим на его HTML-код и ищем реквизиты:

  • Текст элемента (для статических ссылок это чуть ли не ключевой элемент, для других элементов это как минимум основа для XPath, но вначале лучше смотреть на что-то другое)
  • Атрибут id
  • Атрибут name
  • Соседние или вышестоящие по иерархии элементы, у которых более-менее четко находится хотя бы один из вышепереисленных атрибутов

То есть это как бы основной шаблон, по которому можно подбирать локаторы, соблюдая наиболее оптимальное соотношение точность/скорость выполнения. Но есть несколько типовых случаев, для которых рассматривается некоторое подмножество локаторов, вплоть до программного вычисления нужного элемента.

К этим частным случаям можно отнести следующие:

  1. Отдельная ссылка либо с фиксированным текстом, либо с некоторой фиксированной частью
  2. Стандартный элемент управления формы
  3. Некоторый элемент в таблице, содержащей множество таких же однородных элементов

Теперь можно рассмотреть эти подмножества элементов поотдельности.

Отдельная ссылка либо с фиксированным текстом, либо с некоторой фиксированной частью

Отдельная ссылка с фиксированным текстом

Итак, у нас есть ссылка на странице и мы четко знаем, что она такая одна. Для примера, допустим, у нас есть ссылка

<a href="www.somedomain.com">Sample Link</a> 

Теперь подберем локатор, наиболее подходящий для нее, исходя из приоритетов выбора локаторов выше. Что у нас там? Локатор вида link=.

  1. Применяется для ссылок? Да
  2. Использует фиксированный текст ссылки? Да
  3. Идентифицирует ли он этот объект уникально? Да (по условию, такая ссылка одна на странице).

Selenium-RC: дружим с CSS

Безусловно, XPath-локатор является одним из наиболее универсальных и наиболее точных локаторов. Но к этой универсальности добавляется один из главнейших недостатков данного типа локаторов - низкая скорость обнаружения данного элемента. Это наиболее хорошо проявляется под IE, в то время как тот же Firefox работает нормально. Это связано с внутренними особенностями браузеров, в частности в способе аллоцирования элементов страницы. Но суть не в этом. Суть в том, что Селениум-тесты, которые интенсивно используют XPath работают крайне медленно под IE. И это вызывает ряд проблем как со скоростью выполнения тестов, так и с качеством этих тестов, особенно при работе с динамическим контентом. Как альтернатива XPath могут рассматриваться CSS локаторы. Что с ними можно делать?

Во-первых, CSS-локаторы тоже позволяют привязаться к иерархии объекта. Например, такой XPath-локатор:

xpath=//div//span/a

может быть описан через CSS вот так:

css=div * span > a

Отсюда четко прослеживается аналогия элементов XPath и CSS, а именно:

  1. Знак "/", означающий следующий уровень иерархии объекта, соответствует оператору ">" в CSS.
  2. Знак "//", означающий любой элемент, находящийся по иерархии ниже текущего, соответствует оператору "*" в CSS

Во-вторых, как и в XPath, CSS-локаторы могут привязываться к значениям атрибутов. Например, такой XPath-локатор:

xpath=//div[@id="some_id"]

может быть описан через CSS вот так:

css=div[id=some_id]

Также есть возможность проверки на частичное совпадение значения атрибута. Но здесь CSS немного ограничен. Есть возможность проверки, что значение атрибута содержит несколько слов, разделенных пробелами, но одно из них четко соответствует значению. То есть, то, что в XPath может быть выражено вот так:

xpath=//div[contains(@title="title")]

через CSS выражается вот так:

css=div[@title~=title]

Selenium RC: Дружим с XPath

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

  • на странице есть несколько полей с одинаковым атрибутом Name, но у разных форм и нужно работать с конкретным полем конкретной формы.
  • на странице множество объектов сходной структуры и их надо обработать одинаково (например, очистить все текстовые поля)
  • нужно обработать одинаковым образом все объекты, которые характеризуются определенным текстов некоторых дочерних объектов (например, мы знаем заголовки таблицы, а нужно сделать клик на ссылке, которая находится на том же уровне)

Каждый отдельно взятый случай решает данные проблемы своими путями, но более-менее универсальным решением является использование XPath. В чем его удобство.

  1. Во-первых, данный способ задания местоположения объекта оперирует с фактическими HTML-тегами, что дает возможность формировать локаторы исходя из непосредственно HTML-кода страницы, который можно просмотреть.
  2. Во-вторых, есть возможность задать некоторую иерархию объектов, при этом пропустить варьируемые элементы (удобно, когда надо вычислить элемент внутри таблицы, не привязываясь к конкретным ячейкам).
  3. В-третьих, элемент можно задать используя как теги, так и определенные значения атрибутов, причем можно проверить на частичное соответствие (удобно, когда элемент уникально идентифицируется обработчиком некоторого события). 
  4. В-четвертых, в Selenium есть отдельный метод, который позволит нам узнать количество элементов, удовлетворяющих заданному XPath, а такде возможность использовать индексы, что позволяет выделить и перебирать целую коллекцию элементов.

А теперь перейдем к практической составляющей. Допустим, у нас есть набор различных графиков в виде bar-chart или pie-chart, причем при клике на каждый элемент происходит переход на некоторую страницу. Реализация в HTML подобного имеет вид:

<map>
      <area href="ref1">
      <area href="ref2">
      <area href="ref3">
      ...
      <area href="ref1">
      <img src="some_img.gif"> 
</map>

вот таких map-блоков произвольное количество. В каждом из этих блоков произвольное количество элементов area. Но везде есть ссылки и на эти area-объекты мы можем сделать клик. Итак, как можно организовать обработку всех активных областей всех диаграмм. Вначале, мы узнаем, сколько же всего этих диаграмм присутствует. Предположим, что у нас уже есть проинициализированный объект Selenium-a и мы уже на нужной странице. Соответственно, реализация имеет вид (далее все примеры приводятся с использованием синтаксиса Java, но по аналогии переносится на остальные языки, на которых реализован Selenium-клиент):

int chartsCount = selenium.getXPathCount( "//map" ).intValue();

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: знакомимся с локаторами

При работе с Селениумом практически первое, с чем придется столкнуться - это локаторы. Что это такое? Локатор - это строка, уникально идентифицирующая UI-элемент. Когда вы делаете клик мышкой, ввод текста и прочие действия, вы эти действия выполняете над вполне конкретным объектом. Селениум поступает так же. Но поскольку он не умеет читать ваши мысли, то ему надо четко указать объект, для которого надо применить то или иное действие.

Где локаторы используются? Посмотрите на методы, отвечающие за ввод/извлечение данных из элементов управления (это методы click, type, select, isElementPresent, keyPress и т.п.). Первым параметром подобных методов всегда идет локатор.

Итак, какие виды локаторов бывают:

  • id=<element_id> - соответствует элементу, у которого атрибут id равен значению element_id. Например, у нас есть элемент, который в HTML записывается так:
    <input type=text id='some_input_id' name='some_input_name' value='' />
    В этом случае локатор будет иметь вид: id=some_input_id.
    Также следует отметить, что данный вид локаторов является одним из самых быстрых в нахождении и одним из самых уникальных. Это связано с тем, что в DOM-структуре ссылки на элементы, у которых задан ID, хранятся в отдельной таблице и через JScript (собственно именно через него осуществляется доступ к элементам на конечном уровне) обращение к элементам по ID идет достаточно короткой инструкцией, наподобие some_input_id.
  • name=<element_name> - соответствует элементу, у которого атрибут name равен значению element_name. Эффективно применяется при работе с полями ввода формы (кнопки, текстовые поля, выпадающие списки). Как правило, значения элементов формы используются в запросах, которые идут на сервер и как раз атрибут name в этих запросах ставит в соответствие поле и его значение. Если брать предыдущий пример:
    <input type=text id='some_input_id' name='some_input_name' value='' />
    то данный элемент может быть также идентифицирован локатором вида name=some_input_name.
    Данный тип локаторов тоже является достаточно быстрым в нахождении, но менее уникальным, так как на странице может быть несколько форм, у которых могут быть элементы с одинаковым именем.
  • dom=<dom_object> - данный тип локатора позволяет обращаться к элементу так же, как и в DHTML используя DOM-структуру. Данный тип локатора используется нечасто, так как обычно находятся более удобные аналоги, но тем не менее данная возможность есть.
  • link=<link_text> - специально для ссылок используется отдельно зарезервированный тип локаторов, который находит нужную ссылку по ее тексту. Это сделано отчасти потому, что ссылки как правило не имеют таких атрибутов как ID или name. Соответственно, ссылка, которая в HTML записывается так:
    <a href='http://some_url'>Link Text 2345</a>
    в Селениуме идентифицируется локатором link=Link Text 2345.

    И небольшой частный случай: у ссылки есть фиксированная часть и есть часть, которая может варьироваться. Допустим, в предыдущем примере у нас число может варьироваться. В этом случае мы можем использовать wildcards, в частности '*'. В этом случае, мы можем идентифицировать ссылку локатором вида: link=Link Text* и данный локатор будет соответствовать первой ссылке, текст которой будет начинаться с 'Link Text'.
  • xpath=<xpath_locator> - наиболее универсальный тип локаторов. Как XPath формируется. HTML, как и его более обобщенная форма - XML, представляет собой различное сочетание тегов, которые могут содержать вложенные теги, а те в свою очередь тоже могут содержать теги и т.д. То есть ,выстраивается определенная иерархия, наподобие структуры каталогов в файловой системе. И задача XPath - отразить подобный путь к нужному элементу, с учетом иерархии. Например, XPath вида:
    A/B/C/D
    указывает на некоторый элемент с тегом D, который находится внутри тега C, а тот в свою очередь - внутри тега B, который находится внутри тега A, который находится на самом верхнем уровне иерархии.

    Если брать использование XPath в Селениуме, то там зачастую полный путь указывать не нужно, более того, вредно, особенно, если вложенность тега нужного елемента достаточно высока. Как правило, удобно указывать путь, начиная с некоторого промежуточного элемента, пропуская теги более высокого порядка. Например, такой XPath: //table/tbody/tr/td/a ссылается на первую ссылку в первой строке тела первой таблицы. Обратите внимание на начало данной записи. Строка '//' означает, что поиск элемента начинается с некоторого произвольного места.

    У XPath есть много удобств, но есть и основной недостаток - низкая скорость нахождения объекта. В частности, с подобной проблемой можно столкнуться при работе с IE, так как при работе с XPath под IE используются JScript-библиотеки, которые не отличаются высокой скоростью выполнения. В таких случаях рекомендуется воспользоваться CSS-локаторами (см. ниже), но в некоторых случаях от XPath уйти не получится.

    Более подробно про синтаксис XPath можно почитать, например, здесь.
  • css=<css_path> - данный тип локаторов основан на описаниях таблиц стилей (CSS), соответственно и синтаксис такой же. В отличие от локаторов по ID, по имени или по тексту ссылки, данный тип локаторов может учитывать иерархию объектов, а также значения атрибутов, что делает его ближайшим аналогом XPath. А в силу того, что объект находится по данному локатору быстрее, чем XPath, рекомендуется прибегать к помощи CSS вместо XPath.

    Более подробно о синтаксисе CSS можно узнать здесь.

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-материал
© 2009-2010 Портал для автоматизаторов тестирования ПО
Автор проекта Поляруш Михаил | При использовании материалов ссылка на www.automated-testing.info обязательна.
Все замечания и пожелания присылайте на webmaster@automated-testing.info.
Яндекс.Метрика