Pavel Müller
1.12.2010

Konfigurace Selenium testů ve Springu



V minulém článku „Selenium a návrhový vzor Page Objects“ jsem popisoval, jak strukturovat Selenium testy. Už tam ale nebylo rozebráno, jak je možné, že to celé dohromady funguje. Pokusím se tedy ukázat konfiguraci Selenium komponent ve Springu 3.0 a jak si připravit pohodlné předky pro testy a stránky.

Na unit testy používáme JUnit 4 a k jejich konfiguraci balíček Spring Test. Ten mimo jiné umožňuje vytvořit z test třídy Spring bean a následně jí konfigurovat třeba pomocí @Autowired anotace. Unit test při svém spuštění nastartuje aplikační kontext a v něm vytvoří všechny beany. Tedy ideální prostředek udělat instanci Selenium browseru a nechat si ji injectovat do test casu.

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
  <property name="location" value="classpath:selenium-${selenium.configuration:localhost}.properties" />
  <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
</bean>
<bean id="selenium" class="com.thoughtworks.selenium.DefaultSelenium" init-method="start" destroy-method="stop">
  <constructor-arg index="0" value="${selenium.host}" />
  <constructor-arg index="1" value="${selenium.port}" />
  <constructor-arg index="2" value="${selenium.browser}" />
  <constructor-arg index="3" value="${webapp.url}" />
</bean> 

Je vidět, že Selenium browser je nakonfigurován pomocí údajů z property souborů. Příjemnou vlastností je, že můžete pomocí systémové property selenium.configuration konfigurovat, jaký property soubor použít. To se hodí hlavně pro střídání prostředí. Jiné nastavení používá vývojář testů a jiné nastavení používá server na kontinuální integraci. Nyní bych měl ukázat, jak vypadá třída unit testu. Ale samotný test case není jediná komponenta potřebující nakonfigurovaný objekt Selenium. Máme tu ještě stránky podle Page Objects vzoru a další pomocné komponenty. Takže proto je tu abstraktní třída AbstractSeleniumComponent.  Vypadá nějak takto (jako vždy redakčně kráceno):

public abstract class AbstractSeleniumComponent {
  protected Logger logger = LoggerFactory.getLogger(getClass());
  
  @Autowired protected Selenium selenium;
  @Value("${webapp.context}") protected String context = "";
  @Value("${selenium.timeout:30000}") 
  protected String timeout;
  @Autowired protected ApplicationContext applicationContext;
  
  /**
    * Open URL in web application context and wait for page to load. 
    * @param url URL to open. Context relative. 
    */ 
  protected void openAndWait(String url) {
    SeleniumUtils.openAndWait(selenium, context, url, timeout);
  }
} 


Třída je jednoduchá a očekává, že budou Springem injectovány závislosti. Využívá se novinky ve Springu 3.0, anotace @Value k vyhodnocení property. Třída taky obsahuje řadu pomocných metod usnadňujících život jako je openAndWait(). Předek pro všechny testy je jednoduchý. Pouze konfiguruje Spring aplikační kontext a implementuje uložení screenshotu prohlížeče v případě neúspěšného testu. Více o této technice psal Luboš.

@TestExecutionListeners( {DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) 
@RunWith(SeleniumRunner.class) 
@ContextConfiguration(locations = { "applicationContext.xml" }) 

public abstract class AbstractSeleniumTest extends AbstractSeleniumComponent {
  @Value("${selenium.screenshot.dir:/tmp/}")
  protected String screenshotDir; 
  
  /** 
   * Capture a screen shot if test fails. 
   * @param failure failure cause 
   */ 
   @AfterFailure public void captureScreenShotOnFailure(Throwable failure) {
   // build filename ... 
   selenium.captureScreenshot(screenShotPath);
   }
} 


Ještě potřebujeme i nějakého předka pro jednotlivé stránky. I stránky musí mít přístup k Selenium objektu a musí být schopné „navigovat“ mezi sebou. Navíc by bylo dobré, aby takové stránky šly iniciovat a udržovaly konverzační stav. I stránka v browseru může být v nějakém stavu a tak i naše objekty by tomu měly odpovídat. V terminologii Springu je tedy stránka odlišná od jiných Selenium komponent, protože není singleton ale prototype. V kódu se to ale samozřejmě neprojeví.

public abstract class AbstractPage extends AbstractSeleniumComponent {
  public <T extends AbstractPage> T navigateTo(Class<T> pageClass, Object... params){
    T page = applicationContext.getBean(pageClass); 
    // initialize page 
    page.init(params);
    return page;
  }
  
  protected void init(Object... params) { } 
}

Metoda navigateTo() zajišťuje přechody mezi stránkami. Vyžádá si novou beanu od Springu (prototype) a inicializuje ji. Testy se stanou Spring beanama automaticky ale naše stránky a další Selenium komponenty je třeba nějak zařadit do aplikačního kontextu. To nejlépe provedeme tak, že najdeme všechny třídy, které jsou nějak anotované, a uděláme z nich beany. Použijeme pak klasický context:component-scan. Pro značkování tříd máme anotaci @SeleniumComponent obsahující i parametr pro definování scopu Spring beany. Můžete tak určit, jestli to má být singleton nebo prototype. Anotace @Page už je pak jen zkratkou k @SeleniumComponent(„prototype“). Navíc se hodí pro případné aspekty. Všimněte si, jak lze v novém Springu anotovat anotace.

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented public 
@interface SeleniumComponent {
  public String value() default BeanDefinition.SCOPE_SINGLETON;
}
 
@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented 
@SeleniumComponent("prototype") 
public @interface Page { }


Teď už jen říct Springu, ať hledá všechny třídy s anotací @SeleniumComponent a ať použije scope definovaný v těchto anotacích. To není zas až takový problém:

<context:component-scan base-package="com.aspectworks" use-default-filters="false" scope-resolver="com.aspectworks.awf.selenium.SeleniumComponentScopeResolver">
  <context:include-filter type="annotation" expression="com.aspectworks.awf.selenium.SeleniumComponent"/>
</context:component-scan>


Implementace scope resolveru je už jen otázkou správného použití Spring API. Jak je vidět konfigurace Selenium testů za použití JUnit 4 a Spring Frameworku je elegantní a celkem i jednoduchá. Ukázky kódu jsou pochopitelně zjednodušené, ale pokud bude zájem, není problém je zveřejnit.

Vaše emailová adresa nebude zveřejněna

Komentáře

Děkujeme za váš komentář
Další
  • Marcel Hlopko

    Skvělý článek, děkujeme :) Bylo by možné tedy zveřejnit celé ukázky nebo velmi jednoduchý hello world projektík? Dík

  • Kódy tady v tom článku jsou takové osekané ukázky z našeho frameworku, kde jsou i některé specifické věci. Přemýšlím, že bychom to uvolnili jako open source, ale je s tím nějaká práce a musel by o to být zájem.