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

java
selenium
Теги: #<Tag:0x00007fedbb488d60> #<Tag:0x00007fedbb488c20>

(Mykhailo Poliarush) #1

Часть 2

Миф о том, что для автоматизированного тестирования не нужны навыки программирования и тесты можно вполне легко записывать, развенчан настолько, что сам факт того, что кто-то еще так считает, уже кажется мифом или, как минимум, чем-то странным. Понятное дело, что записанные тесты и тесты, которые могли бы эффективно использоваться на регулярной основе – это, как говорят в Одессе, «две большие разницы». Очевидно, что исходный тест, да и решение по автоматизации в целом, должны пройти некоторые стадии улучшения, прежде чем прийти к пригодному к использованию виду. Давайте рассмотрим некоторые типовые шаги, которые так или иначе приходится проходить для построения стабильного, расширяемого и поддерживаемого решения.

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

Итак, путем записи и незначительной корректировки был получен следующий тест:

``` package com.mycompany.selenium.tests; import java.util.Date; import com.thoughtworks.selenium.*; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test;

/**

  • @author KaNoN
    */

public class SampleTestStage00 {
private Selenium selenium = null;
private String genTaskName() {
Date dt = new Date();
String result = dt.toString();
result = result.replaceAll( “:” , “” );
result = result.replaceAll( " " , “” );
return result;
}

        @BeforeMethod(alwaysRun=true)
        public void init() throws Exception {
                    selenium = new DefaultSelenium( "localhost" , 4444 , "*iexplore" , "http:// localhost:8080" );
                    selenium.start();
                    selenium.open("http://localhost:8080/hudson");
        }

        @Test(groups = {"sample","sample0"})
        public void testCreateJob() throws Exception {
                    selenium.click("link=New Job");
                    selenium.waitForPageToLoad("30000");
                    selenium.type("name", "SampleTask" + genTaskName() );
                    selenium.click("//input[@value='Build a free-style software project']");
                    selenium.click("//button[@type='button']");
        selenium.waitForPageToLoad("30000");
                    selenium.click("//button[text()='Save']");
                    selenium.waitForPageToLoad("30000");
                    selenium.click("link=Hudson");
                    selenium.waitForPageToLoad("30000");
        }

        @AfterMethod(alwaysRun=true)
        public void stop() throws Exception {
                    selenium.stop();
        }

}

</p><p>Что он делает? Он запускает веб-приложение (в данном примере это <a href="http://automated-testing.info/news/vybor-sredstv-nepreryvnoj-integracii-continuous-integration" target="_blank" title="Выбор средств непрерывной интеграции">Hudson</a>) и создает в нем новую задачу. Тест реализован с использованием <strong>TestNG </strong> в качестве тестового движка.</p><p>Как видно, тест еще далек от совершенства, как минимум из-за того, что много hard-coded значений. Да и вот так на первый взгляд трудно понять, что этот тест детает вообще. В-общем, непонятных вещей много, нужно усовершенствовать. Вопрос в том, что, когда и (самое главное) как.<!--break--></p><h2>Шаг 1: Вынесение величин, специфических для окружения в конфигурационный файл</h2><p>Стоит мне вот этот тест запустить с другой машины, врядли я могу расчитывать на то, что он выдаст мне тот же результат. Хотя бы потому, что на этой машине скорее всего не окажется локально установленного приложения. И не факт, что сам <a href="http://automated-testing.info/knowledgebase/lesson/selenium-server-v-interaktivnom-rezhime" target="_blank" title="Selenium Server">сервер Selenium-a</a> будет запущен локально и на том же порту. И наконец, я бы не хотел править все тесты, если я захочу запустить их под другим браузером. Для этого мне нужно сделать изменение максимум в одном файле.</p><p>Для таких задач существуют конфигурационные файлы и соответственно объекты, которые могли бы хранить эти данные. В Java для таких задач имеются *.properties файлы и соответствующие стандартные средства для работы с ними. В других языках программирования всё аналогично. Например, для Ruby используется YAML-формат. Опять же, никто не отменял те же ini-файлы. В крайнем случае, всегда можно написать свой считыватель конфигурационных данных.</p><p>Итак, нам нужно где-то задать конфигурацию и создать класс, который бы предоставлял возможность, как считывания, так и возвращения значений параметров конфигурации. В корневом каталоге проекта создаем отдельный каталог <strong>config</strong>, в нем создадим файл <strong>env.properties</strong> (можно любое другое имя, лишь бы было понятно, что там находится) и добавим туда наши параметры конфигурации. Получится что-то вида:</p><p>

host=localhost
port=4444
url=http://localhost:8080
browser=*iexplore
delay=30000

</p><p>Здесь мы задали параметры для <a href="http://automated-testing.info/knowledgebase/article/opisanie-ispolzovanie-i-opcii-zapuska-selenium-server" target="_blank" title="Запуск и опции Selenium Server">запуска Selenium-а</a>, а также время ожидания, которое достаточно интенсивно используется в методе <a href="http://automated-testing.info/knowledgebase/article/selenium-rc-obespechenie-krossbrauzernosti-testov" target="_blank" title="Как использовать waitForPageToLoad">waitForPageToLoad в предыдущем примере</a>.&nbsp;Теперь нам надо это считать. Для этого мы создадим отдельный класс <strong>Config</strong>, который содержал бы в себе следующий функционал:</p><ol><li>Загрузка всех *.properties файлов из заданного каталога и всех подкаталогов произвольной вложенности</li><li>Извлечение значения свойства по его имени</li><li>Объект данного класса должен быть один в системе и должен самостоятельно обеспечить инициализацию свойств без явного указания на данную операцию</li></ol><p>А теперь всё то же самое, но пошагово и с примерами.</p><p>            Для загрузки всех свойств нам нужен метод, которому надо указать каталог. Причем, метод должен вызываться рекурсивно (у нас произвольный уровень вложенности каталогов). Изначально получаем что-то наподобие:</p><p>

package com.mycompany.selenium.lib;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**

  • @author KaNoN
    */
    public class Config {
    private Properties prop = null;
    FilenameFilter filter = null;
    FilenameFilter dirFilter = null;
    public void loadProperties( String path ) throws Exception {
    File dir = new File( path );
    filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".properties"); } };
    dirFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return dir.isDirectory(); } };
    String files[] = dir.list(filter);
    if( prop == null ){
    prop = new Properties();
    }
    for( String file:files ){
    System.out.println( file );
    File localFile = new File( “config\” + file );
    if( localFile.isDirectory() ) continue;
    FileInputStream fis = new FileInputStream( localFile.getAbsolutePath() );
    prop.load( fis );
    fis.close();
    }
    String dirs[] = dir.list(dirFilter);
    for( String directory:dirs ){
    loadProperties( path + “\” + directory );
    }
    }
    }
</p><p>Теперь осталось выполнить второй шаг, так как это собственно один из основных функционалов, который используется при написании тестов. Собственно:</p><p>

package com.mycompany.selenium.lib;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**

  • @author KaNoN
    */
    public class Config {
    private Properties prop = null;
    FilenameFilter filter = null;
    FilenameFilter dirFilter = null;

    public void loadProperties( String path ) throws Exception {
    File dir = new File( path );
    filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".properties"); } };
    dirFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return dir.isDirectory(); } };
    String files[] = dir.list(filter);

      if( prop == null ){
          prop = new Properties();
      }
    
      for( String file:files ){
          System.out.println( file );
          File localFile = new File( "config\\" + file );
          if( localFile.isDirectory() ) continue;
          FileInputStream fis = new FileInputStream( localFile.getAbsolutePath() );
          prop.load( fis );
          fis.close();
      }
    
      String dirs[] = dir.list(dirFilter);
      for( String directory:dirs ){
          loadProperties( path + "\\" + directory );
      }
    

    }

    public String getProperty( String propertyName ) throws Exception {
    return prop.getProperty( propertyName );
    }
    }

</p><p>Теперь мы можем уже использовать данный класс в тестах. Для этого где-то вначале можно сделать вызов</p><p>

Config conf = new Config();
conf.loadProperties( “config” );

</p><p>А затем везде, где нужно использовать значение некоторого свойства, подставляем</p><p>

conf.getProperty( “some_property” );

</p><p>Но теперь представьте, что такую инициализацию и использование локальной переменной надо осуществлять во всех тестах. Как минимум это избыточные строки. Хотелось бы как-то от этого избавиться. К тому же, данный объект обычно нужен тестам в единичном экземпляре. Поэтому, подредактируем данный класс так:</p><p>

package com.mycompany.selenium.lib;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
/**

  • @author KaNoN
    */
    public class Config {
    private static Config instance = null;
    private Properties prop = null;
    FilenameFilter filter = null;
    FilenameFilter dirFilter = null;

    private void loadProperties( String path ) throws Exception {
    File dir = new File( path );
    filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".properties"); } };
    dirFilter = new FilenameFilter() { public boolean accept(File dir, String name) { return dir.isDirectory(); } };
    String files[] = dir.list(filter);

      if( prop == null ){
          prop = new Properties();
      }
    
      for( String file:files ){
          System.out.println( file );
          File localFile = new File( "config\\" + file );
          if( localFile.isDirectory() ) continue;
          FileInputStream fis = new FileInputStream( localFile.getAbsolutePath() );
          prop.load( fis );
          fis.close();
      }
    
      String dirs[] = dir.list(dirFilter);
      for( String directory:dirs ){
          loadProperties( path + "\\" + directory );
      }
    

    }

    private Config() throws Exception {
    loadProperties( “config” );
    }

    public static String getProperty( String propertyName ) throws Exception {
    if( instance == null ){
    instance = new Config();
    }
    return instance.prop.getProperty( propertyName );
    }
    }

</p><p>Как видно из изменений, у нас есть только один доступный метод <strong>getProperty</strong>, который теперь статический. Более того, конструктор объявлен как <strong>private</strong>. То есть, мы уже не сможем сделать что-то наподобие:</p><p>

Config conf = new Config();

</p><p>за пределами текущего класса. Но при этом обратите внимание на метод <strong>getProperty</strong>, а точнее на изменения, которые были внесены.</p><p>            Во-первых, данный класс сейчас содержит еще одно поле <strong>instance</strong>, которое является экземпляром данного класса. Приватный конструктор по-прежнему позволяет создавать экземпляры класса в пределах текущего класса. Во-вторых, поле <strong>instance </strong>инициализируется один раз за время работы тестов. То есть, в тесте мы можем сделать вызов вида:</p><p>

Config.getProperty( “some_property” );

</p><p> При этом нас не должно беспокоить, загружены ли свойства или нет. Об этом заботится сам класс.&nbsp;В итоге, когда мы реализовали подобный класс, мы можем переписать наш тест уже в таком виде: </p><p>

package com.mycompany.selenium.tests;
import java.util.Date;
import com.mycompany.selenium.lib.Config;
import com.thoughtworks.selenium.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.testng.Assert;
/**

  • @author KaNoN
    */
    public class SampleTestStage01 {
    private Selenium selenium = null;
    private String delay = “”;

    private String genTaskName() {
    Date dt = new Date();
    String result = dt.toString();
    result = result.replaceAll( “:” , “” );
    result = result.replaceAll( " " , “” );
    return result;
    }

    @BeforeMethod(alwaysRun=true)
    public void init() throws Exception {
    delay = Config.getProperty( “delay” );
    selenium = new DefaultSelenium( Config.getProperty( “host” ) , new Integer( Config.getProperty( “port” ) ) , Config.getProperty( “browser” ) , Config.getProperty( “url” ) );
    selenium.start();
    selenium.open( Config.getProperty( “url” ) + “/hudson/”);
    }

    @Test(groups = {“sample”,“sample1”})
    public void testCreateJob() throws Exception {
    selenium.click(“link=New Job”);
    selenium.waitForPageToLoad(delay);
    selenium.type(“name”, “SampleTask” + genTaskName() );
    selenium.click("//input[@value=‘Build a free-style software project’]");
    selenium.click("//button[@type=‘button’]");
    selenium.waitForPageToLoad(delay);
    selenium.click("//button[text()=‘Save’]");
    selenium.waitForPageToLoad(delay);
    selenium.click(“link=Hudson”);
    selenium.waitForPageToLoad(delay);
    }

    @AfterMethod(alwaysRun=true)
    public void stop() throws Exception {
    selenium.stop();
    }
    }

</p><p>Что нового здесь?</p><ol><li>Вместо явно заданного времени ожидания была зарезервирована переменная <strong>delay</strong>, которая в начале теста инициализируется значением из файла конфигурации</li><li>&nbsp;Все явные указания хостов, портов и базового адреса тестируемого приложения уже получаются через обращение к классу <strong>Config</strong>.</li></ol><p>В результате, если мы хотим, чтобы наши тесты работали с разных машин, то при их переносе достаточно просто подкорректировать конйигурационный файл.</p><h2>Шаг 2: Вынесение общих частей тестовых классов в некий базовый класс</h2><p>Теперь представим, что мы создаем еще один тест. А также представим, что в нем будет присутствовать тоже, что и в предыдущем тесте, учитывая, что работаем мы с тем же приложением?</p><ol><li>Во-первых, это будет инициализация <a href="http://automated-testing.info/knowledgebase/article/chto-takoe-selenium-i-s-chem-ego-edjat" target="_blank" title="Что такое Selenium и с чем его едят?">Selenium</a>-а, да и собственно сама переменная, которая содержит в себе Selenium client driver.</li><li>Во-вторых, это будет переменная <strong>delay</strong>, которую полезно бы использовать во всех тестах.</li><li>В-третьих, это будет вызов метода, который завершает работу <a href="http://automated-testing.info/knowledgebase/article/chto-takoe-selenium-i-s-chem-ego-edjat" target="_blank" title="Что такое Selenium и с чем его едят?">Selenium</a>-a.</li></ol><p>Создадим некоторый базовый класс для тестов и пусть наш тестовый класс будет наследником от этого базового класса. Итак, базовый класс:</p><p>

package com.mycompany.selenium.lib;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
/**

  • @author KaNoN
    */
    public class BaseTestClass extends SecurityManager {
    protected Selenium selenium = null;
    protected String delay = “”;

    @BeforeMethod(alwaysRun=true)
    public void init() throws Exception {
    delay = Config.getProperty( “delay” );
    selenium = new ExtendedSelenium( Config.getProperty( “host” ) , new Integer( Config.getProperty( “port” ) ) , Config.getProperty( “browser” ) , Config.getProperty( “url” ) );
    selenium.start();
    selenium.open( Config.getProperty( “url” ) + “/hudson” );
    mainPage = new MainPage( selenium );
    }

    @AfterMethod(lastTimeOnly=true)
    public void stop() throws Exception {
    selenium.stop();
    }
    }

</p><p>Если мы наш тест отнаследуем от данного базового класса, то у нас код упростится до такого вида:</p><p>

package com.mycompany.selenium.tests;
import com.mycompany.selenium.lib.BaseTestClass;
import java.util.Date;
import org.testng.annotations.Test;
/**

  • @author KaNoN
    */
    public class SampleTestStage02 extends BaseTestClass {

private String genTaskName() {
Date dt = new Date();
String result = dt.toString();
result = result.replaceAll( “:” , “” );
result = result.replaceAll( " " , “” );
return result;
}

@Test(groups = {"sample","sample2"})
public void testCreateJob() throws Exception {
    this.selenium.click("link=New Job");
    selenium.waitForPageToLoad(delay);
    selenium.type("name", "SampleTask" + genTaskName() );
    selenium.click("//input[@value='Build a free-style software project']");
    selenium.click("//button[@type='button']");
    selenium.waitForPageToLoad(delay);
    selenium.click("//button[text()='Save']");
    selenium.waitForPageToLoad(delay);
    selenium.click("link=Hudson");
    selenium.waitForPageToLoad(delay);
}

}

</p><p>То есть, теперь мы оперируем уже только с кодом, который специфичен именно для данного теста.</p><h2>Шаг 3: Маппинг локаторов и расширение используемых библиотек</h2><p>Поскольку наш тест работает на уровне ГУИ, то его работоспособность чувствительна к изменениям элементов страницы. Наиболее часто меняются сами <a href="http://automated-testing.info/knowledgebase/article/ispolzovanie-lokatorov-locators-v-selenium-chast-1" target="_blank" title="Локаторы Selenium">локаторы</a>. И если один и тот же локатор используется в разных тестах и по многу раз, то при изменении соответствующего элемента придется править все тесты, которые были завязаны на использование данного уже неактуального локатора. А что будет, если таких элементов будет множество? Даже представить страшно.</p><p>Другой момент – удобство понимания того, над каким элементом производится действие. Если еще первый клик по ссылке и ввод текста в данном тесте как-то можно увязать с некоторой страницей, на которой Selenium находится на момент выполнения, то конструкция вида:</p><p>

selenium.click("//button[@type=‘button’]");

</p><p>может ввести в лугкий ступор. И наконец, просто неудобно постоянно вставлять подобную конструкцию: </p><p>

selenium.waitForPageToLoad(delay);

</p><p>Особенно это неудобно, если подобная конструкция используется достаточно часто, причем в комбинации с некоторым фиксированным набором команд. В нашем случае, удобно было бы иметь разновидность метода <strong>click</strong>, которая бы еще и ждала загрузки страницы. И под шумок можно добавить еще ряд полезного функционала, которого изначально у Selenium-а нет.</p><p>            И <a href="http://automated-testing.info/knowledgebase/article/ispolzovanie-mappinga-polzovatelskogo-interfejsa-v-selenium-rc" target="_blank" title="Маппинг и UI map в Selenium">маппинг</a> и расширение Selenium-а рассматриваются в одном контексте, так как это взаимнодополняющие друг друга решения. Например, мне бы хотелось, чтобы я мог передать в метод строку и если она соответствует некоторому псевдониму элемента, то этот псевдоним подхватился бы и подставился фактический локатор. А если такого псевдонима не нашлось, то данная строка бы использовалась как явно заданный локатор.</p><p>А теперь решим эту задачу последовательно. Для маппинга нам вполне могут пригодиться те же <strong>*.properties</strong> файлы. Хотя бы потому, что по сути нам нужно некоторое представление пар: <strong>«псевдоним-локатор»,</strong> что технически эквивалентно паре <strong>«свойство-значение»</strong>. И к тому же, мы уже описали класс, который мы можем использовать для данной задачи.</p><p>            В том же каталоге config, что мы использовали для хранения конфигурационных файлов, мы можем добавить подкаталог <strong>maps</strong>, в которых будут храниться карты элементов. Поскольку псевдоним можно задавать произвольно, для удобства их использования можно выработать некоторые правила их именования, например: <strong><page_name>.<element_name></strong></p><p>То есть, мы указываем не только что это за элемент, но и к какой странице он относится. Дальше можно добавить различные соглашения по именованию страниц, элементов определенного типа и т.д. Это уже особенности конкретных проектов.</p><p>            Для примера добавим в каталог <strong>config/maps</strong> файл <strong>mainpage.properties</strong> со следующим содержимым:</p><p>

leftpanel.newjob=link=New Job
leftpanel.hudson=link=Hudson
newjobpage.name=name
newjobpage.freestyleradio=//input[@value=‘Build a free-style software project’]
newjobpage.ok=//button[@type=‘button’]
configurejob.save=//button[text()=‘Save’]

</p><p>Полей пока немного, но по мере разрастания имеет смысл разделять map-файлы по модулям, более мелким блокам, когда количество элементов маппинга будет достаточно большим. В любом случае, класс Config при инициализации подхватит эти свойства.&nbsp;Теперь надо как-то сделать так, чтобы команды Selenium-а могли работать и с картами элементов. По сути, для каждой команды должна быть конструкция вида:</p><p>

public void <selenium_command>( String arg0 ){
try {
if( Config.getProperty( arg0 ) != null ){
arg0 = Config.getProperty( arg0 );
}
}
catch( Exception e ){;}
selenium.<selenium_command>( arg0 );
}

</p><p>И подобные конструкции должны быть для всех методов. Для этого нужно расширить класс <strong>DefaultSelenium</strong>-а. Причем не просто расширить, а перегрузить практически все методы, у которых количество аргументов больше 0.</p><p>            Сразу хочу предупредить, что подобный класс будет достаточно большим по объему кода (порядка 800-900 строк), причем весьма однообразного. Поэтому, для минимизации копирования/вставки кода удобно сделать кодогенератор. В Java есть такая вещь, как рефлексия, которая позволяет получить доступ к информации о классе, его методах, полях. Для таких языков как Ruby, Python есть даже более гибкие средства. В Java нам вначале достаточно сделать что-то наподобие:</p><p>

Class<?> clazz = Selenium.class;
Method[] methods = clazz.getDeclaredMethods();

</p><p>А потом уже в цикле пройтись по всем методам и сгенерировать текст нашего класса, выведя его в консоль. То есть сам кодогенератор выглядит так:</p><p>

package com.mycompany.selenium.utils;
import com.thoughtworks.selenium.Selenium;
import java.lang.reflect.Method;
/**

  • @author KaNoN
    /
    public class ExtGenerator {
    /
    *
    • @param args
      /
      public static void main(String[] args) {
      Class<?> clazz = Selenium.class;
      Method[] methods = clazz.getDeclaredMethods();
      String classText = “package com.mycompany.selenium.lib;\r\n” +
      "\r\nimport com.thoughtworks.selenium.
      ;" +
      “\r\n\r\n\r\npublic class ExtendedSelenium{\r\n” +
      “\r\n\tprotected Selenium selenium = null;\r\n\r\n” +
      “\tpublic ExtendedSelenium(String serverHost, int serverPort,\r\n” +
      “\t\tString browserStartCommand, String browserURL) {\r\n” +
      “\t\t\tselenium = new DefaultSelenium(serverHost, serverPort, browserStartCommand, browserURL);\r\n” +
      “\t}\r\n\r\n”;

      for( Method method:methods ){
      classText = classText + “\tpublic " + method.getReturnType().getName() + " " + method.getName() + “(”;
      Class<?> classes[] = method.getParameterTypes();
      for( int i = 0 ; i < classes.length ; i++ ){
      classText = classText + " " + classes[i].getName() + " arg” + i;
      if( i < classes.length - 1 ){
      classText = classText + " , “;
      }
      }
      classText = classText + " ){\r\n”;
      if( classes.length == 0 ){
      classText = classText + “\t\t” +
      ((method.getReturnType().toString().equals( “void” ) )?( “” ):frowning: "return " )) +
      “selenium.” + method.getName() + “();\r\n\t}\r\n\r\n”;
      }
      else {
      classText = classText + “\t\ttry {\r\n\t\t\tif( Config.getProperty( arg0 ) != null )” +
      “{\r\n\t\t\t\targ0 = Config.getProperty( arg0 );\r\n\t\t\t}\r\n\t\t}\r\n” +
      “\t\tcatch( Exception e ){;}\r\n”;
      classText = classText + “\t\t” +
      ((method.getReturnType().toString().equals( “void” ) )?( “” ):frowning: “return " )) +
      “selenium.” + method.getName() + “(”;
      for( int i = 0 ; i < classes.length ; i++ ){
      classText = classText + " arg” + i;
      if( i < classes.length - 1 ){
      classText = classText + " , “;
      }
      }
      classText = classText + " );\r\n\t}\r\n\r\n”;
      }
      }

      classText = classText.replaceAll( “\[Ljava.lang.String;” , “String[]” );
      classText = classText.replaceAll( “java.lang.” , “” );
      classText = classText + “}”;
      System.out.println( classText );
      }
      }

</p><p>Запустив это приложение мы потом можем создать класс com.mycompany.selenium.lib.ExtendedSelenium. Единственное, что в этот класс еще можно добавить, это некоторые расширенные методы. Например, методы, которые при клике или выборе элемента из списка еще ждали бы загрузку страницы. Дополним этот класс следующим кодом:</p><p>

public void clickAndWait( String locator ) throws Exception {
click( locator );
waitForPageToLoad( Config.getProperty( “delay” ) );
}
public void selectAndWait( String locator , String option ) throws Exception {
select( locator , option );
waitForPageToLoad( Config.getProperty( “delay” ) );
}

</p><p>После этого мы обновим <strong>com.mycompany.selenium.lib.BaseTestClass</strong>, заменив все вхождения <strong>DefaultSelenium</strong> на <strong>ExtendedSelenium</strong>.&nbsp;В результате этих изменений, мы уже можем использовать карту элементов вместо явно заданных локаторов, а совместные вызовы click и <strong>waitForPageToLoad </strong>можно заменить на <strong>clickAndWait</strong>. После таких преобразований наш тест приобретает вид:</p><p>

package com.mycompany.selenium.tests;
import com.mycompany.selenium.lib.BaseTestClass;
import java.util.Date;
import org.testng.annotations.Test;
/**

  • @author KaNoN
    */
    public class SampleTestStage03 extends BaseTestClass {
    private String genTaskName() {
    Date dt = new Date();
    String result = dt.toString();
    result = result.replaceAll( “:” , “” );
    result = result.replaceAll( " " , “” );
    return result;
    }
    @Test(groups = {“sample”,“sample3”})
    public void testCreateJob() throws Exception {
    selenium.clickAndWait( “leftpanel.newjob” );
    selenium.type(“newjobpage.name”, “SampleTask” + genTaskName() );
    selenium.click(“newjobpage.freestyleradio”);
    selenium.clickAndWait(“newjobpage.ok”);
    selenium.clickAndWait(“configurejob.save”);
    selenium.clickAndWait(“leftpanel.hudson”);
    }
    }
</p><p>Уже намного проще читать и понимать, что же выполняется, да и объем кода теста заметно уменьшился. То есть улучшения хорошо заметны. Но на этом останавливаться рано.</p><p>(Продолжение следует…)</p><p>&nbsp;</p><h2 style="text-align: right; "><a href="http://automated-testing.info/t/selenium-rc-java-2/2258" target="_blank" title="Selenium RC (Java): Шаги усовершенствования тестов. Часть 2">Часть 2</a></h2><p>&nbsp;</p>

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