A következő címkéjű bejegyzések mutatása: Selenium. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: Selenium. Összes bejegyzés megjelenítése

2016. január 4., hétfő

Selenium - RemoteWebDriver + Jenkins

Egy folyamatos integrációs környezetben általában nem azon a gépen futtatjuk a felületi teszteket ahol a Jenkins is dolgozik, ezért felhúzunk egy külön Windows-os gépet ahova feltelepítünk Internet Explorer-t, Firefox-ot meg egy Chrome-ot mivel azt szeretnénk, hogy a Jenkins indította klikkelő robot már itt kattintgasson majd az eredményeket juttassa vissza a Jenkins számára.

Ez a működés a Selenium RemoteWebDriver segítségével egyszerűen megoldható. Először is rögzítsük a felületi tesztesetet a Selenium IDE Firefox plugin segítségével, majd exportáljuk Java forrásként és módosítsuk az alábbi mintakód alapján, ahol is a baseURL az alkalmazás kezdő URL-e, a távoli Windows-os gép a remoteSeleniumServer URL-en figyel, a browser pedig az a böngésző típus amivel a tesztet futtatni fogjuk. Látható, hogy ebben a minta kódban be vannak égetve az alapértelmezett értékek, de a Jenkins-ből ezeket majd felülírhatjuk.

import static org.junit.Assert.fail;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.lang3.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import com.thoughtworks.selenium.webdriven.WebDriverBackedSelenium;


public class NLRTest {
 private String baseURL = "https://webapphost:8113/";
 private String remoteSeleniumServer = "http://rsh:4444/wd/hub";
 private String browser="chrome";//firefox,internet explorer,chrome
 
 private WebDriverBackedSelenium selenium;
 private RemoteWebDriver remoteWD = null;

 @Before
 public void setUp() throws Exception {
  
  
  if(!StringUtils.isBlank(System.getProperty("baseURL")))
   baseURL=System.getProperty("baseURL");
  
  if(!StringUtils.isBlank(System.getProperty("remoteSeleniumServer")))
         remoteSeleniumServer=System.getProperty("remoteSeleniumServer");
  if(!StringUtils.isBlank(System.getProperty("browser")))
         browser=System.getProperty("browser");
   

  DesiredCapabilities capabilities = new DesiredCapabilities();
  capabilities.setJavascriptEnabled(true);
  capabilities.setBrowserName(browser);
  try {
   remoteWD = new RemoteWebDriver(new URL(remoteSeleniumServer),capabilities);
   

  } catch (MalformedURLException e) {
   e.printStackTrace();
  }

  selenium = new WebDriverBackedSelenium(remoteWD, baseURL);

 }

 @SuppressWarnings("deprecation")
 @Test
 public void testNLR1() throws Exception {

  selenium.open("/NLR/login");
  
  //IE certification ok button
  if(browser.equals("internet explorer"))
   remoteWD.navigate().to("javascript:document.getElementById('overridelink').click()");
  
  
  selenium.type("id=fnev", "myuser");
  selenium.type("id=password", "mypass");
  for (int second = 0;; second++) {
   if (second >= 60)
    fail("timeout");
   try {
    if ("felhasználónév:".equals(selenium
      .getText("css=td.left_label")))
     break;
   } catch (Exception e) {
   }
   Thread.sleep(1000);
  }

  selenium.click("css=button[type=\"button\"]");
  selenium.waitForPageToLoad("30000");
  for (int second = 0;; second++) {
   if (second >= 60)
    fail("timeout");
   try {
    if ("Munkamenet".equals(selenium.getText("link=Munkamenet")))
     break;
   } catch (Exception e) {
   }
   Thread.sleep(1000);
  }

  selenium.click("link=Munkamenet");
  ...
  selenium.click("link=Előzmények keresése");
  selenium.waitForPageToLoad("30000");
  selenium.click("link=Kilépés");
  selenium.waitForPageToLoad("30000");

 }

 @After
 public void tearDown() throws Exception {
  selenium.stop();
 }

}

A klikkelő robotnak létrehozott távoli Windows-os hosztra, ahol feltelepítettük az IE, Firefox és Chrome böngészőket, másoljuk fel a selenium-server-standalone.jar, a chromedriver.exe és IEDriverServer.exe fájlokat. A firefox driver-t nem kell külön letölteni, mert azt a selenium-server-standalone.jar már tartalmazza. Ezután ezen a dedikált Windows-os gépen indítsuk el a Selenium-ot az alábbi paranccsal, felparaméterezve a driverek elérési útvonalával. Innentől kezdve a Jenkins már képes elindítani a klikkelő robotot ezen a gépen.

java -Dwebdriver.chrome.driver="c:\\chromedriver.exe" -Dwebdriver.ie.driver = "c:\IEDriverServer.exe" -jar c:\selenium-server-standalone-2.47.1.jar 

Itt olvasható, hogy a Selenium-ot hogyan illeszthetjük be a maven-es projektünkbe, a Jenkins felületén pedig a Goals and options résznél állíthatjuk be a megfelelő paramétereket:

test -Dbuild.number=${PIPELINE_VERSION} -Dtest=hu.bakai.selenium.* -DbaseURL = "https://172.22.22.22:8113" -DrempoteSeleniumServer = "http://10.128.11.11:4444/wd/hub"  -Dbrowser = "internet explorer"

2011. november 1., kedd

Adatforrás kialakítása Selenium tesztekhez

A mostani cikkemben a FitNesse és a Selenium keretrendszerek egy olyan előnyös ötvözéséről fogok blogolni, amit jómagam találtam ki a Selenium-os tesztelés megkönnyítésére!

Gyakori igény, hogy szükségünk lenne a Selenium tesztjeink eltérő adatokkal való futtatására. Az adatok tárolására elsőre jó megoldásnak tűnhet egy egyszerű csv, properties vagy excel táblázat használata, azonban több száz teszt esetén ezek nem lesznek karbantarthatóak (gondoljunk csak bele, hogy milyen nagy állományok jönnének létre), főleg amikor eltérő környezetekhez eltérő adatokat kellene használnunk. Egy javítási mód lenne ha nem egy nagy excel táblázatot kezelünk, hanem környezetenként (esetleg tesztenként) létrehoznánk egyet-egyet, azonban egy idő után ezzel is karbantartási problémák merülnének fel, mivel a sok állomány manuális kezelése elég nehézkes!

A felvázolt problémára szerintem egy webes oldalstruktúrát használó, HTML táblázat alapú adatforrás jelenthetné a megoldást. Mivel kifejezetten erre a célra nem készült még keretrendszer, úgy gondoltam, - az eredeti felhasználási területétől egy kicsit elrugaszkodva - hogy bevetem a FitNesse-t!

Excel alapú Selenium adatforrás

Bár nagyszámú teszteset esetén az excel alapú adatforrás véleményem szerint nem a legmegfelelőbb választás, bizonyos esetekben megfelelő alternatíva lehet az egyszerű és gyors kezelhetősége miatt. Az excel alapú adatforrás megvalósításának részleteiről itt olvashatsz bővebben.

FitNesse alapú Selenium adatforrás

A Selenium és a FitNesse integrációjával nemcsak az adatforrás problémakörre kapunk megoldást, hanem egy-két nagyon hasznos feature-re is szert teszünk:

  • Tesztjeinket a FitNesse webes felületéről is elindíthatjuk és megtekinthetjük lefutási eredményeket.
  • A FitNesse wiki oldalak verziózásából adódóan, visszaállhatunk egy korábbi adatforrás verzióra.
  • A tesztek definiálása és az adatok megadása mellett, akár szöveges megjegyzéseket is hozzáadhatunk az adatforrás oldalaihoz.
  • Visszakövethetők azok a felhasználók, akik az adatforrás oldalain módosításokat végeztek.
  • Az oldalak szerep alapú korlátozásának lehetősége az olvasás, írás és teszt futtatás tekintetében.

A FitNesse alapú adatforrás kialakításának további előnye, hogy az eltérő környezetekben futó tesztek (pl.: Teszt, Fejlesztői, Integrációs) nemcsak eltérő adatforrásokat, hanem egyedi paramétereket is használhatnak melyeket a hierarchia tetején is definiálhatunk az öröklődésük miatt.


A fenti ábra a FitNesse segítségével definiált, Selenium tesztekhez kapcsolódó adatforrások elhelyezkedését mutatja. A RootSuite tartalmazza a környezetek szerinti suite-okat (TestSuite, DevSuite, IntSuite), melyek a tesztesetek adatforrásait tartalmazzák. A kialakított adatforrás felépítés következtében, a tesztek a gyökér suite-nál egyszerre, vagy környezetenként külön-külön is indíthatók. Érdemes a RootSuite-nál meghatározni a Fixture classpath-t és a használni kívánt böngésző típust (driver), a környezeteknek megfelelő suite-oknál pedig a baseUrl-t, mivel a szülő csomópontoknál megadott paramétereket tovább öröklődnek a gyerekek felé.

Az adatforrások létrehozása megegyezik a FitNesse teszt táblázatok felvételével, a különbség annyi hogy a tesztmetódusok visszatérési értékét nem ellenőrizzük, így azok egységesen true-val térnek vissza. Természetesen a sikertelenül lefuttatott tesztesetek ilyenkor is kijelzésre kerülnek. 


A környezetenként eltérő ${baseUrl} és a böngésző meghajtóhoz tartozó ${driver} változóként kerül definiálásra, mivel az értékek a hierarchiában feljebb lévő suite-nál kerültek definiálásra.


Az elkészített wiki oldalhoz tartozó adatokat a LoginTest, testLoginFunction metódusa használja fel. A tesztosztály a TestBase ősosztályból származik, ahol inicializálásra kerül a baseUrl és a böngésző típus. A TestBase egyben egy ColumnFixture is, amit a RootSuite-nál definiált classpath bejegyzés köt össze a wiki oldallal.
public class TestBase extends ColumnFixture {

 protected Selenium selenium;
 protected WebDriver driver;

 protected void beforeTestCase(String[] fixtureArgs) {
        String baseUrl=fixtureArgs[0];
        String driverName=fixtureArgs[1];
        
        if(driverName.toLowerCase().equals("firefox")){
            driver = new FirefoxDriver();
        }
        else if(driverName.toLowerCase().equals("chrome")){
            driver = new ChromeDriver();
        } 
        //...

        selenium = new WebDriverBackedSelenium(driver, baseUrl);
    }

 protected void afterTestCase() {
  driver.close();
 }
}
Az így kialakított felépítés nem egy hagyományos JUnit teszteset, így a beforeTestCase() ill. afterTestCase() metódusokat a tesztmetódusban kell meghívni. Az afterTestCase() metódus a finally blokkban kapott helyet, így annak lefutása mindig garantált, azaz a Selenium által megnyitott böngésző egy esetleges kivétel esetén is bezárásra kerül. A teszt elindításával a LoginTest publikus mezői megkapják - a táblázat minden sorára lefutva - a wiki táblázat megfelelő mezőinek értékét, megoldva a külső adatforrás értékeinek a felhasználását.
public class LoginTest extends TestBase {

 public String userName;
 public String password;

 public boolean testLoginFunction() throws Exception {
  beforeTestCase(getArgs());
  try {
   selenium.open("/portal/ep/home.do");
   //További selenium parancsok vagy PageObject-ek használata
  } finally {
   afterTestCase();
  }
  return true;
 }
} 
Ezzel egyelőre véget is ért a Selenium-os cikksorozatom. Ha hasznos infókat tartalmazott a számodra, esetleg további kérdéseid lennének a Seleniummal kapcsolatban, írj egy kommentet!

2011. október 17., hétfő

Page Object minta alkalmazása Seleniummal

A múltkori elméleti bevezető után jöjjön egy kis gyakorlat a Page Object tervezési minta Seleniummal való használatához.

Nézzünk is meg egyből egy rövid kódrészletet, amit mindenféle módosítás nélkül a Selenium IDE-ből exportáltam ki! Látható hogy a Selenium parancsok és az ellenőrzési feltételek nem válnak el egymástól, továbbá a várakozási feltétel implementációja sem valami jól olvasható, ami pedig kifejezetten zavaró ha a teszteseteink ezzel vannak tele.
selenium.open("/portal/ep/home.do");
selenium.type("password", password);
selenium.type("username", userName);
selenium.click("css=input[type=image]");
for (int second = 0;; second++) {
 if (second >= 60)
  fail("timeout");
 try {
  if ("szöveg1"
    .equals(selenium.getText("css=#sfeBranchDipatch > div")))
   break;
 } catch (Exception e) {
 }
 Thread.sleep(1000);
}
assertEquals(selenium.getText("css=strong"), "Szia " + userName);

Miután a fenti kódot átírtam a Page Object minta alkalmazásával, egy jól olvasható tesztesetet kaptam. Azt hiszem a két kódrészletet összehasonlítva az eredmény magáért beszél. A továbbiakban lássuk mi is bújik meg a Seleniumos Page Objectek mögött!
Home home = new Home(selenium);
InfoPortal infoPortal = home.loginUser(userName, password);
assertEquals(infoPortal.getHeaderMsg(), "Szia " + userName);
AppPage appPage = infoPortal.getMenu().goToAppPage(selenium);
Először is érdemes minden Page Object-et egy Page osztályból származtatni, ahova az oldalakkal kapcsolatos közös részeket emeljük ki. A valódi webes oldalakhoz hasonlóan ahol is minden oldalon megjelenik egy menüsor a Page osztályból is elérhető egy Menu objektum, amely a navigációhoz szükséges menüelemeket tartalmazza. Ezenkívül itt kap helyet egy Selenium objektum is, melyet a Page Object-ek konstruktoraiban adunk majd mindig át.
public class Page {
 protected Selenium selenium;
 private Menu menu;

 public Page(Selenium selenium) {
  this.selenium = selenium;
  menu = Menu.getInstance();
 }

 public Menu getMenu() {
  return menu;
 }

 public void setMenu(Menu menu) {
  this.menu = menu;
 }
}
A Menu osztály a Singleton tervezési mintára épül, mivel a tesztesetek egészére nézve globálisan csak egy példányra lesz belőle szükségünk! 
public class Menu {
 private static Menu menu = null;

 private Menu() {
 }

 public static Menu getInstance() {
  if (menu == null) {
   menu = new Menu();
  }
  return menu;
 }

 public AppPage goToAppPage(Selenium selenium) {
  selenium.click("css=#mainTab3 > span");
  return new AppPage(selenium);
 }
}
Az adatforrásból kinyert mezők paraméterátadás útján jutnak el a Page Object-ekben található akció metódusokhoz, mint amilyen az Home loginUser() metódusa. Fontos megkötés, hogy az akciók visszatérési értéke mindig a következő oldalt jelentő page osztály legyen, ami jelen esetben az InfoPortal.
public class Home extends Page {
 public Home(Selenium selenium) {
  super(selenium);
 }

 public InfoPortal loginUser(String userName, String password)
   throws Exception {
  selenium.open("/portal/ep/home.do");
  selenium.type("password", password);
  selenium.type("username", userName);
  selenium.click("css=input[type=image]");
  waitForElementPresent("css=#sfeBranchDipatch > div",
    "Az kívánt oldal ….");
  return new InfoPortal(selenium);
 }
}
A Page Object minta egyik alapvető koncepciója, hogy a Page osztályok ne tartalmazzanak ellenőrzési feltételeket, azaz asserteket. Ez alól kivételt jelentenek az olyan jellegű megkötések, melyeknek az oldalra lépéskor mindig teljesülnie kell. Ilyen ellenőrzési feltétel található az InfoPortal konstruktorában, amely feltételezi hogy a kijelentkezés link mindig elérhető az adott oldalon.
public class InfoPortal extends Page {
 public InfoPortal(Selenium selenium) {
  super(selenium);
  assertEquals(
  selenium.getText("//table[@id='countDownTable']/tbody/tr/td[2]/a"),
  "Kijelentkezés");
 }

 public String getHeaderMsg() {
  return selenium.getText("css=strong");
 }
}
Ahogy korábban említettem, a waitFor kezdetű parancsokhoz a Selenium IDE nem generál valami szép kódot, így a kód olvashatósága miatt és a duplikációk megszüntetése érdekében ezeket érdemes kiemelni egy külön segéd osztályba vagy akár a közös Page osztályba.

Röviden ennyi a Page Object minta implementálásáról, a következő bejegyzésem pedig már a zárócikke lesz a Selenium-os sorozatomnak. Elöljáróban annyit, hogy a Selenium-os adatforrás kialakításról lesz szó, azonban egy olyan egyedi megoldást fogok bemutatni, amivel máshol még biztos nem találkoztál!

2011. október 2., vasárnap

Karbantartható Selenium tesztek készítése

A Selenium IDE segítségével kiexportálható teszteset tipikusan struktúra nélküli "spagetti kód", melyet nehéz olvasni és bővíteni, továbbá az oldalon végrehajtott módosítások is könnyen elronthatják az összes eddigi tesztjeinket a megváltozott lokátorok miatt.

Tegyük fel, hogy készítettünk 100 Selenium tesztesetet melyek mindegyike egy bejelentkező oldalról indul ki. Ha a bejelentkező oldalon, a felhasználónév megadására szolgáló input mező fölé bekerülne egy új input mező és az XPath position alapú lokátor stratégiát használtuk, akkor a //div/input[0] helyett //div/input[1] –re változik a felhasználó név mezőhöz tartozó relatív XPath kifejezés. Mivel mind a 100 teszteset erről az oldalról indul ki, ezért az összes tesztünket módosítani kellene, ami elég időigényes feladat.

Az előbb vázolt problémából kiindulva, az első gondolat az Extract Method minta alkalmazása lenne, miszerint a tesztosztályon belüli ismétlődő kódrészeket kiszervezhetnénk külön private metódusokba. Ezzel viszont az lenne a gond, hogy a kód duplikációt csak az osztályon belül kezelnénk le, azaz a tesztosztályok közötti kódismétlődés ettől még nem szűnne meg. A megoldás a Page Object minta alkalmazása, amellyel az oldalakhoz tartozó kódrészek, a nekik megfelelő un. page osztályokba kerülnek kiszervezésre.

Ha megvizsgálunk néhány Selenium IDE által rögzített tesztesetet, megállapítható hogy akciók hatására oldalakra navigálunk és az oldalakon ellenőrző feltételeket értékelünk ki. Az alábbi ábrán az akciókat a piros dobozok, míg az oldalakat a kék karikák szemléltetik.


A Page Object minta szerint, az egyes oldalakhoz tartozó műveleteket külön Page osztályokba kell kiszervezni (pl.: bejelentkeztetés -> LoginPage), az ellenőrző feltételek és az oldalak közti navigációs kódok pedig a tesztmetódusokban kapnak helyet, mivel így biztosítható a Page Objectek későbbi újrahasználhatósága. Az alábbi ábrán látható szekvencia diagram jól szemlélteti a Page Object minta Selenium környezetben való használatát.


A fenti ábra alapján, a tesztmetódus meghívja a LoginPage "Page Object" bejelentkeztető metódusát átadva a felhasználó nevet és a jelszót, majd a beléptetés után a következő oldalt jelentő HomePage "Page Object" példánnyal tér vissza, melyen a tesztmetódus meghívja annak openTab() metódusát. Bár az ábrán nincs jelölve, a bejelentkezés után visszakapott HomePage objektumon a tesztmetódus ellenőrzési feltételeket végezhet a kezdőlapon megjelenő felhasználó név helyességére vagy a felhasználó által látható menüelemekre vonatkozólag.

Visszatérve a fejezet elején említett problémára, ha a Page Object tervezési mintát alkalmazzuk, akkor a //div/input[0]-ről, //div/input[1]-re történő megváltozás esetén, nem kell mind a 100 tesztesetünket módosítani, csupán a LoginPage "Page Object" bejelentkeztető metódusánál kell átírni a felhasználónév input mező kikeresésére szolgáló lokátort.

Összefoglalás a Page Object minta használatáról
  • Oldalanként vagy oldalon belüli komponensekre tovább bontva hozzunk létre a Page Object-eket, melyek az akciókat tartalmazzák.
  • A Page Object akció metódusainak visszatérési értéke mindig ahhoz az oldalhoz tartozó Page Object legyen, ahová a navigáció irányítja majd a felhasználót.
  • Mindig a tesztesetek tartalmazzák a visszakapott Page Objectre – vagyis az oldalra - vonatkozó ellenőrzési feltételeket.
  • A Page Object-ek ne tartalmazzanak ellenőrzési feltételeket, kivéve olyan jellegű assertek-et, melyek az egész oldalra vonatkoznak, és mindig teljesülniük kell. (pl.: az adott oldalon megjelenő böngésző címe, aktív menü ellenőrzése az oldalra lépésekor).
  • A tesztesetek ne tartalmazzanak Selenium specifikus kódot, az assertek végrehajtásához szükséges lokátor kódot illesszük be a megfelelő Page Object-be, getter metódusként.
  • Kezeljük kiemelt fontossággal a Page Object-ek névadását, hogy megkönnyítsük azok beazonosítását!

A Page Object minta előnyei és hátrányai
  • Duplikációk kiküszöbölése azáltal, hogy a módosítások esetén csak a Page Objectekhez kell hozzányúlni a teszt metódusokhoz nem.
  • A tesztesetekben nem jelennek meg Selenium specifikus kódrészletek, így a mögöttes Selenium API bármikor lecserélhető. Ez a későbbiekben megkönnyíti majd a Selenium 2 API használatára történő átállást.
  • Újrafelhasználhatóság biztosítása azáltal, hogy a tesztkód és az oldalhoz tartozó kód elválik.
  • Kellő számú Page Object létrehozása után, automatikusan kialakul egy saját API, amellyel azonnal létrehozhatunk a felületi teszteket a Selenium IDE használata nélkül is!
  • A minta hátránya, hogy a fejlesztőtől többletmunkát igényel a Page Object struktúra kialakítása, ami persze a későbbiekben bőven megtérül.

A következő bejegyzésemben a Page Object minta Selenium környezetben való alkalmazását fogom bemutatni egy konkrét példán keresztül.

2011. szeptember 15., csütörtök

Selenium - Lokátor stratégia kiválasztása

Az előző bejegyzésemben a Selenium IDE parancsairól írtam, azonban arról még nem esett szó hogy a felvett parancsok hogyan is mutatnak rá a html elemekre. A mostani bejegyzésem tehát a Selenium lokátorokról fog szólni, kiemelve a JSF-es alkalmazások lokátor stratégia választását.


A Selenium lokátorok lehetővé teszik, hogy kiválasszuk azt a html elemet amelyre a Selenium parancsot végre akarjuk hajtani. A parancshoz tartozó lokátor a Selenium IDE target mezőjében jelenik meg. A html elemek kijelölésére többféle lokátor stratégia közül is választhatunk, melyeket lokátorTípus=lokátor formában tudunk megadni, bár a lokátor típus megadása nem minden esetben kötelező. Az alábbi táblázat a fontosabb lokátor stratégiákat szemlélteti egy-egy példán keresztül:

Lokátor stratégia Példa
Identifier basedloginForm:userNameId
Name AttributeloginForm:userName
CSS basedcss=#usernameInputStyle
DOM Index document.forms[0].elements[1]
XPath Attribute//input[@id='loginForm:userNameId']
XPath Id Relative//div[@id='userNameId_inputTextOuterDiv']/input
XPath Position//div/input

A Selenium IDE Locator Assistance funkciója lehetőséget ad arra, hogy a rögzített parancsokhoz tartozó alapértelmezésként felvett lokátort egy másikra módosítsuk. Ez a feature pedig pont jól fog jönni a JSF alapú alkalmazások Selenium teszteléséhez. A Locator Assistance funkciót a Selenium IDE target combobox lenyitásával érhetjük el. A target mezőnél akár saját kifejezést is megadhatunk, de arra figyeljünk, hogyha a lokátor több html elemre is illeszkedne az oldalon, akkor a legelsőnek megtalált elem kerül kiválasztásra.



Lokátor stratégia kiválasztása JSF alapú alkalmazásokhoz

Általános esetben az azonosító alapú lokátor stratégia használata javasolt, mivel a html elem azonosítója mindig egyértelműen beazonosítja az elemet, így az oldal későbbi átstrukturálása esetén is ugyanarra az elemre fog mutatni, továbbá a html elem megtalálása szempontjából is az azonosító alapú stratégia tekinthető a leggyorsabbnak hasonlóan a name attribútum alapú lokátor stratégiához.

JSF-es alkalmazások esetén sajnos ez nem teljesen igaz, mivel a gyerek elemek azonosító képzésénél a szülő elemek azonosítói is szerepet játszanak. Nézzük az alábbi példát, ahol is egy JSF/RichFaces-es kódrészlet és az ebből kirenderelt html kód látható:

  
  

A html kimenet input azonosítóinak és name attribútumainak generálásában a szülő form azonosítója is részt vesz, így ha nem adunk meg azonosítót, az id a html kimenetnél nem fog szerepelni, valamint a name attribútum esetében kiegészül egy generált j_id32 értékkel.

A következő JSF/RichFaces ill. generált html kódrészlet egy olyan átstrukturálást szemléltet ahol is a form és az input elemek közé bekerült egy a4j:repeater elem. Ebben az esetben a generált html input azonosítói is megváltoznak, mivel az azonosítókhoz hozzáadódik egy ar:sorszám rész is az a4j:repeater azonosítójától. 

      
    
    
  

A felvázolt problémakör után lássuk, hogy milyen lokátor stratégiák közül érdemes választani a JSF-es alkalmazások Selenium-os teszteléséhez. A választás függ a környezettől, így a végső döntéskor az alábbi szempontok szerint mérlegeljünk.

1. Azonosító vagy name attribútum alapú lokátor stratégia
  • A leggyorsabb lokátor stratégia.
  • Használatához az azonosítókat mindig meg kell adni a JSF kódban, különben az id attribútum nem generálódik ki, a name attribútumnál pedig j_id123 formátumú azonosítók generálódnak melyekre a tesztek készítése során nem hagyatkozhatunk a változékonyságuk miatt.
  • Az oldal módosítására és átstrukúrálására nem annyira érzékeny. Új elem felvétele esetén (pl.: új input felvétele a form alá) az azonosítók nem módosulnak (kivéve datatable, repeater beszúrásakor). A form-on kívül definiált elemek legtöbbször nincsenek hatással a form-on belüli azonosítókra.

2. XPath Position alapú lokátor stratégia
  • Internet Explorer alatt akár jelentős lassulás is tapasztalható a tesztek futtatása során, az elemek kikeresésekor.
  • Használatához az azonosítókat nem kötelező megadni, mivel ez a stratégia a html elemek sorrendiségére és elhelyezkedésére épít.
  • Az oldal módosításra teljes mértékben érzékeny. Változtatás esetén szinte mindig megváltozik a html elemek sorrendisége vagy elhelyezkedése, így az elemekhez tartozó XPath kifejezések is. Ez azt jelenti, hogy a JSF kód átstruktúrálása esetén a parancsokhoz tartozó XPath lokátorokat újra fel kell venni.
  • Abszolút XPath kifejezések helyett érdemes relatív XPath kifejezéseket használni.

3. XPath ID Relative lokátor stratégia
  • Azonosító és XPath position alapú lokátor stratégia ötvözése.
  • Használjunk relatív XPath kifejezéseket, melyeket egy fix azonosítótól kezdve adjunk meg.
  • Válasszunk az elemhez képest minél közelebbi fix azonosítót és minimalizáljuk az XPath kifejezés hosszát, így a módosításokra sem lesz annyira érzékeny.

A JSF-es alkalmazások Seleniummal való tesztelésénél - beleértve a RichFaces, IceFaces, PrimeFaces-es webes keretrendszereket - tehát annyi dolgunk van, hogy az említett három stratégia közül kiválasztjuk az igényeinknek legmegfelelőbbet, majd a Selenium Locator Assistance funkciójával átállítjuk a Selenium parancsokhoz tartozó lokátor stratégiát. Mivel a Selenium IDE-ben alapértelmezett lokátor stratégia választásra nincs lehetőség, ezért egyelőre ezt manuálisan kell elvégezni esetleg módosíthatjuk a Selenium IDE kódjában a default beállítást!


A Selenium-os cikksorozatom következő részében megmutatom, hogy hogyan készíthetünk újrafelhasználható teszteket a Selenium IDE-ből kiexportálható spagetti kód helyett!

2011. augusztus 26., péntek

Selenium IDE parancsai

A bevezető után ahogy ígértem, a Selenium IDE parancsok áttekintése következik a referencia alapján.

A Selenium IDE által rögzített teszteset Selenium parancsok sorozatából áll, melyek lehetnek akciók, állapot tárolók vagy ellenőrzési feltételek. Amíg az akciók a Selenium IDE által automatikusan rögzítésre kerülnek, addig az állapot tárolókra és az ellenőrzési feltételekre vonatkozó parancsokat a böngészőben egy kívánt elemen, a jobb egérgombos kattintással megjelenő gyorsmenüben van lehetőségünk hozzáadni.

1. Akciók

Az akciók olyan parancsok, melyek az oldal állapotát valamilyen módon megváltoztatják. Minden akcióknak van egy ”AndWait” végződésű változata is melyeket akkor kell használni, amikor az akció végrehajtása egy új oldal betöltését vagy az aktuális frissítését fogja eredményezni.

Művelet Akció Akció, majd várakozás az oldal betöltésére
Kattintás click clickAndWait
Szöveg begépelése type typeAndWait
Elem kiválasztása (pl.:checkbox) select selectAndWait

2. Állapot tárolók

Állapot tárolók használatával lehetőségünk van az oldalon lévő információkat (pl.: szöveg) elmenteni, melyekre később hivatkozhatunk ugyanezen tesztesetnél. Állapot tárolókhoz a store kezdetű parancsokat (pl.: storeText, storeTitle) használhatjuk.

3. Ellenőrzési feltételek

Az ellenőrzési feltételek feladata hogy ellenőrizzék az oldal állapotát, anélkül hogy azt módosítanák. Az ellenőrzési parancsok három csoportba sorolhatók: assert, verify és waitFor kezdetűek.

Assert és a verify használata

Az assert és verify kezdetű parancsok közt az a különbség, hogy hiba esetén az assert használatakor a teljes teszteset futása leáll, míg a verify kezdetű parancsoknál a teszt futása tovább folytatódik, a hiba pedig naplózásra kerül.
Az ellenőrzési parancsokat érdemes logikailag csoportokba szervezni, ahol is először egy assert kezdetű paranccsal ellenőrizzük, hogy a megfelelő oldalon vagyunk-e (pl.:assertTitle) a többi ellenőrzési feltétel pedig verify kezdetű parancs, melyek az oldalon megjelenő mezők helyességét vizsgálják. Tehát ha rossz oldalon vagyunk, akkor felesleges a többi mezőt is ellenőrizni, ha pedig az oldal jó, akkor egy hibás mező esetén a teszt még tovább folytatódhat a hiba kijelzésével.

Ellenőrzési módszerek

Ellenőrizendő feltétel Minta parancsok
Adott szöveg jelen van valahol az oldalon?verifyTextPresent, assertTextPresent
Adott html elem jelen van valahol az oldalon?verifyElementPresent
Adott szöveg, meghatározott helyen jelen van az oldalon?verifyText, verifyNotText, assertText

A pontosabb ellenőrzés érdekében a verifyText parancsot részesítsük előnyben.

Ajaxos oldalak ellenőrzése

Ajaxos alkalmazások tesztelésénél figyelni kell arra, hogy egy ajaxosan előtűnő elem ellenőrzését megelőzően felvegyünk egy olyan parancsot is, mellyel megvárjuk annak megjelenését. A frissülő elemek a teljes oldal újratöltése nélkül kerülnek megjelenítésre, így az akcióknál ismertetett andWait végződésű parancsok nem használhatók. A waitFor kezdetű parancsokat kell használni melyeket az alábbi táblázat foglalja össze.

Működés Minta parancs
Várakozás egy adott szöveg megjelenésére az oldalonwaitForTextPresent
Várakozás egy adott html elem megjelenésére az oldalonwaitForElementPresent
Várakozás egy adott szöveg, adott helyen történő megjelenésérewaitForText

Tehát egy ajaxosan megjelenő elem ellenőrzéséhez először mindig vegyünk fel egy waitFor kezdetű parancsot, ami után felvehetünk további verify kezdetű parancsokat.

A következő bejegyzésemben a Selenium lokátor stratégiáiról fogok blogolni és segítek eldönteni, hogy a JSF alapú alkalmazásokhoz melyiket is válasszuk!

2011. augusztus 16., kedd

Selenium - Bevezetés a webes felületi teszteléshez

Frissítve: 2011.11.04.  

Gyakori, hogy az elkészült webes felületeket az erre szakosodott tesztelő csapat egy előre meghatározott tesztforgatókönyv alapján manuálisan klikkelgeti végig, ahelyett hogy valamilyen automatizmust használnának!

A projekt kezdeti fázisában - ahol még a felület gyakran változik - a manuális felületi tesztelés lehet a célravezetőbb, a későbbiekben azonban mindenféleképpen érdemes elgondolkozni valamilyen automatizált megoldás bevezetésén. Véleményem szerint a Seleniumos tesztek készítése akkor éri meg a leginkább, amikor a felület már nem nagyon módosul, de a mögöttes logika, szolgáltatások vagy a háttér rendszerek fejlesztése még folyamatban van. Seleniumos felületi tesztek készítésével a tesztek bármikor visszajátszhatók akár Hudsonnal integrálva is, így szükség szerint folyamatosan értesülhetünk a tesztjeink lefutási eredményeiről!

A webes felületek automatizált tesztelése azonban nem tekinthető triviálisnak, gondoljunk csak a böngészőkből adódó eltérésekre, vagy az ajaxos alkalmazások újszerű kihívásaira. A következő néhány bejegyzésemben ezt a témakört fogom a Selenium keretrendszer bemutatásával körbejárni és megmutatom, hogyan is kellene újrafelhasználható és eltérő adatokkal is működő webes felületi teszteket létrehozni!


Selenium bevezető

A Selenium, Black Box jellegű integrációs felületi tesztek készítésére ad lehetőséget, ami annyit jelent, hogy az alkalmazás egészét teszteljük anélkül hogy ismernénk annak belső működését. A tesztesetek felvétele a Selenium IDE Firefox plugin segítségével történik, amely automatikusan rögzíti a böngészőben végrehajtott műveleteket. A Selenium parancsokat tartalmazó szkript többek között java forrásként is exportálható, amit némi refaktorálás és adatforrás bekötés után, eltérő környezetekben is használható tesztekké alakíthatunk!


Selenium fogalmak ismertetése

A Selenium elnevezéseit nem mindenhol használják jól, így kezdjük az alapfogalmak áttekintésével:
  • Selenium: A projekt neve.
  • Selenium 1: A Selenium projekt első verziója.
  • Selenium 2: A Selenium projekt második verziója.
  • Selenium Remote Control (RC): A Selenium 1 által használt API.
  • Selenium WebDriver: A Selenium 2 által használt alapértelmezett API.

A következő néhány Selenium-os bejegyzésemben a Selenium projekt 2-es verzióját fogom ismertetni a Selenium Remote Control API használatával, mégpedig azért mert a cikk írásakor a Selenium IDE még nem volt képes a WebDriver API parancsainak problémamentes exportálására. Szerencsére a fejlesztés jó ütemben halad, így már erre sem kell sokáig várni...


Selenium architektúrák rövid áttekintése

A Selenium RC parancsok visszajátszásához szükséges egy Selenium RC Server indítása, amely proxy-ként továbbítja a végrehajtandó műveleteket a böngészőnek. Selenium WebDriver API használatánál erre már nincs szükség, mivel a parancsok - egy gépes környezetben - közvetlenül a böngészőhöz továbbítódnak.



Selenium 2 újdonságai

A Selenium 2 legfőbb újdonságait a WebDriver projekttel történő egyesülés hozta.

  • Letisztultabb API, amit a WebDriver project biztosít. A Selenium RC parancsok Selenium 2-es környezetben való futtatásához a WebDriverBackedSelenium osztályt használhatjuk fel.
  • Beépített IPhone és az Android emulátor, melyek használatával mobil eszközök böngészőjében is futtathatók a tesztek.
  • Pop-up dialógusok bevonása a tesztesetekbe: basic authentication, fájl feltöltés pop-up dialógus.
  • Cross Domain Testing: Tipikusan a Facebook Connect-et használó alkalmazások tesztelése.
  • Amíg a Selenium 1 JavaScript-et használ a böngészőben történő tevékenységek végrehajtásához, addig a Selenium 2 - a WebDriver API felhasználásával – operációs rendszer szintű eseményekkel dolgozik a visszajátszás során, így standalone környezetben nem szükséges a Selenium Proxy Server indítása.

A Selenium lehetőséget ad arra, hogy eltérő böngészők különböző verzióival is letesztelhessük az alkalmazást. Böngésző nélküli tesztelésre is van mód a HtmlUnitDriver használatával, amely gyors teszt futtatásra ad lehetőséget, azonban a JavaScript motorja nem bizonyult megfelelőnek az összetettebb JavaScript könyvtárak használatakor (pl.: Prototype). A probléma valószínűleg a későbbi verziókban javításra kerül. A Selenium által támogatott platformok részletes listája megtalálható itt.


A Selenium-os teszteléshez szükséges eszközök

  • Szükséges böngésző a Selenium IDE kiegészítő használatához.
  • Firefox 3.6 vagy Firefox 4 is telepíthető, azonban későbbi Firefox verziókhoz a cikk írásakor még nem volt stabil verziója az összes felhasznált kiegészítőnek.
  • Firefox plugin, ami lehetővé teszi a böngészőben végrehajtott műveletek (kattintás, gépelés) felvételét, visszajátszását és exportálását. Érdemes a legfrissebb verziót telepíteni!
  • Selenium IDE kiegészítő, amely a rögzített Selenium tesztekhez ad javítási lehetőségeket ill. tippeket.
  • Selenium IDE kiegészítő, amely lehetővé teszi a rögzítet Selenium szkriptek WebDriver alapú exportálását.
  • Selenium IDE kiegészítő, amely a kiválasztott Selenium parancshoz tartozó html elemre, eltérő színezéssel rámutat a böngészőben.
  • Selenium 2 kliens oldali java könyvtár. A dokumentum írásakor a Selenium 2 rc3 (selenium-java-2.0rc3.zip) verziója már probléma nélkül használható volt.


Selenium IDE

A Selenium IDE kiegészítőt, telepítés után a Firefox/Tools/Selenium IDE menü kiválasztásával jeleníthetjük meg. A tesztek rögzítésének indítása ill. leállítása a jobb-fenti piros gombra kattintással történik. Indítás után a böngészőben végrehajtott műveletek (pl.: klikkelés, gépelés) azonnal megjelennek jobb oldali panel table tab-ja alatt. A rögzítés után a teszteset visszajátszható a ’play current test case’ gombra kattintva, melynek a naplója a Log tab-on tekinthető meg. A visszajátszás után a sikeresen végrehajtott parancsok zöld színnel, a végre nem hajtott parancsok pedig piros színnel jelennek meg. A Reference tab-ra klikkelve a táblázatban kiválasztott parancs használatáról és argumentumairól olvashatunk információkat, az Expert tabon pedig tippek és javítási ötletek jelennek meg, amelyeket azonnal elvégezhetünk az inspect, majd fix gombra kattintással.


Ha a toolbar-on bekapcsoljuk a ’HighLight Elements’ opciót, akkor a teszteset visszajátszása során az aktuálisan végrehajtott parancshoz tartozó html elem sárga háttérrel kivillan az oldalon. Hasonlóan, ha a táblázatban kiválasztunk egy sort és ráklikkelünk a target mező melletti Find gombra, akkor a böngészőben a lokátorhoz tartozó mező háttérszíne megváltozik. Ha fordított működésre lenne szükségünk, akkor használjuk az XPathFinder Firefox plugin-t, amivel a böngészőben megjelenő html elemekhez kaphatjuk meg a hozzájuk tartozó XPath kifejezést. Az elkészített tesztesetet a File/Export Test Case As… menüpont alatt többféle formátumban is exportálhatjuk.


Zárszó

A mostani bejegyzésemet leginkább egy bevezető jellegű kedvcsinálónak szántam, remélem elérte a célját! A folytatásban a Selenium parancsok használatáról fogok írni, de addig is egy kis olvasnivaló: