Dzisiaj zamiast wywodów psychologicznych przedstawię odcinek z cyklu "poradnik młodego technika". Na tapetę pójdą testy selenium2 i generowanie "screenshotów" po błędnym teście. A że mamy już rok 2012 i JUnit wyszedł w odsłonie 4.10 to wykorzystamy sobie kilka zawartych weń bajerów.
Przykładowy test
Tutaj wielkiej filozofii nie ma. Otwieramy googla i wpisujemy nazwę jednego z ładniejszych miast w Polsce. Oczywiście test jest tak spreparowany aby nie zakończył się sukcesem (czyli zwyczajnie się wyjebie).
1: @Test
2: public void shouldFindProperPageTitleAfterSearching() throws Exception {
3: //given
4: remoteDriver.get("http://www.google.com/");
5: String searchPhrase = "Lodz";
6: //when
7: GoogleSearchPage googleSearchPage = PageFactory.initElements(remoteDriver, GoogleSearchPage.class);
8: googleSearchPage.searchFor(searchPhrase);
9: waitForSearchResult(searchPhrase);
10: //then
11: assertThat(remoteDriver.getTitle(), containsString(searchPhrase+"aaa"));
12: }
13: private void waitForSearchResult(final String searchPhrase) {
14: (new WebDriverWait(remoteDriver, 2)).until(new ExpectedCondition<Boolean>() {
15: public Boolean apply(WebDriver driver) {
16: return driver.getTitle().toLowerCase().startsWith(searchPhrase);
17: }
18: });
19: }
I jeszcze trochę inicjalizacji.
1: private static WebDriver remoteDriver;
2: @BeforeClass
3: public static void setUp() throws Exception {
4: webDriver=new FirefoxDriver();
5: }
6: @AfterClass
7: public static void tearDown() throws Exception {
8: webDriver.close();
9: }
W 21 wiek
W wersji (zdaje się) 4.7 JUnita pojawił się mechanizm "Ruli" czyli takie małe AOP na potrzeby testów. W wersji 4.10 sposób użycia Ruli poprzez implementację interfejsu "MethodRule" uległ przedawnieniu a na jego miejsce pojawił się interfejs "TestRule". Jeśli ktoś nie rozumie o czym tutaj piszę to nawet i lepiej. Niechaj paczyy na kod.
public class ScreenShotRule implements TestRule{
private WebDriver webDriver;
public ScreenShotRule(WebDriver webDriver) {
this.webDriver = webDriver;
}
public Statement apply(final Statement base, final Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try{
base.evaluate();
// Tutaj można dowolnie manipulować wyjątkami dla jakich chce się robić fotki
// Exception powinno ogarnąć zarówno problemy selenium jak i standardowe Faile testów
}catch (WebDriverException e) {
takeScreenShot(description);
}
}
private void takeScreenShot(Description description) {
try {
FileOutputStream out = openStreamToTargetFile(description);
out.write(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES));
out.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private FileOutputStream openStreamToTargetFile(Description description) throws FileNotFoundException {
File targetDirectory = new File("target/selenium/"+description.getTestClass().getSimpleName());
targetDirectory.mkdirs();
String targetFileName = description.getMethodName()+".png";
return new FileOutputStream(new File(targetDirectory,targetFileName));
}
};
}
}
Wiadomo, że w kodzie produkcyjnym trzeba by się jeszcze zająć odpowiednio zamykaniem strumieni itd. Teraz aby zastosować naszego Rula wystarczy w teście umieścić poniższe linie.
@Rule
public static ScreenShotRule screenShotRule;
@BeforeClass
public static void setUp() throws Exception {
webDriver=new FirefoxDriver();
screenShotRule=new ScreenShotRule(webDriver);
}
I wio. W przypadku błędnego zakończenia testu w katalogu target (jeśli tylko używamy Mavena) pojawi się katalog "selenium/nazwaTestu" z plikiem "nazwaMEtodyTestowej.png". Jeśli ktoś używa WebDrivera tylko lokalnie to to w zasadzie koniec zabawy. Jednakże czasami niektóre popularne zjebane przeglądarki nie są dostępne na np. takim ubuntu i trzeba wykorzystać zdalny WebDriver.
Tutaj zaczynają się schody...
Testowanie zdalne
Jeśli zapragniemy użyć poniższego kodu:
private static WebDriver remoteDriver;
@Rule
public static ScreenShotRule screenShotRule;
@BeforeClass
public static void setUp() throws Exception {
remoteDriver=new RemoteWebDriver(new URL("http://localhost:3001/wd/"),firefox());
screenShotRule=new ScreenShotRule(remoteDriver);
}
To przywita nas uroczy wyjątek:
java.lang.RuntimeException: java.lang.ClassCastException: org.openqa.selenium.remote.RemoteWebDriver cannot be cast to org.openqa.selenium.TakesScreenshot
Sytuacja jest o tyle dziwna, iż co jak za chwilę zobaczymy, RemoteWebDriver ma możliwość zwrócenia screenshotu jednakże z bliżej mi nieznanego powodu owa opcja nie jest udostępniona.
Czas na hak:
private void takeScreenShot(Description description) {
try {
FileOutputStream out = openStreamToTargetFile(description);
WebDriver augmentedDriver = new Augmenter().augment(webDriver);
out.write(((TakesScreenshot) augmentedDriver).getScreenshotAs(OutputType.BYTES));
out.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Odpalamy i... działa!!
Ale nie do końca. Niestety jeśli teraz znowu użyjemy lokalnego WebDrivera to zauważymy, że każdy nieudany test pozostawia po sobie otwarte okno przeglądarki.
Kolejna modyfikacja.
FileOutputStream out = openStreamToTargetFile(description);
if(!(webDriver instanceof TakesScreenshot)){
webDriver = new Augmenter().augment(webDriver);
}
out.write(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES));
out.close();
Powyższy kod spełnia swoją rolę i nie narzuca wysokiego kosztu utrzymaniowego bo pewnie nigdy tego nie będziemy modyfikować. Jednakże ów kod może powodować wrogie nastawienie fundamentalistów obiektowych.
Rozwiązanie (być może) zadowalające Policję Obiektową
public class RemoteWebDriverWithScreenShots extends RemoteWebDriver implements TakesScreenshot{
public RemoteWebDriverWithScreenShots(URL url,DesiredCapabilities desiredCapabilities) {
super(url, desiredCapabilities);
}
public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException {
String base64Str = execute(DriverCommand.SCREENSHOT).getValue().toString();
return target.convertFromBase64Png(base64Str);
}
}
A nasz Rul może powrócić do formy
private void takeScreenShot(Description description) {
try {
FileOutputStream out = openStreamToTargetFile(description);
out.write(((TakesScreenshot) webDriver).getScreenshotAs(OutputType.BYTES));
out.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
NOOOO
I teraz możemy powiedzieć:
Czytam Time i Epokę,
pijam tylko Ballantine'a,
palę Winstony,
programuję Obiektowo
Zdejm kapelusz
Polecam spojrzec na projekt o filozoficznie brzmiacej nazwie "Thucydides" :)
OdpowiedzUsuńhttp://drdobbs.com/testing/232300277
Pozdrawiam,
Bartek
Cześć,
OdpowiedzUsuńJakiś czas temu zrobiłem sobie małą aplikację-demo z użyciem tego narzędzia.
Niestety!
Mniejsza o to, że narzuca ono pewien sposób tworzenia testów który nie koniecznie może być wygodny dla grupy developerskiej ale co gorsza strasznie długo trwało odpalanie samych testów...
Przyznam, że nie miałem czasu się w to wgryźć i być może trochę konfiguracji by pomogło przyśpieszyć całą machinę. W każdym razie dzięki za linka - więcej pracy z tym narzędziem zaplanowałem sobie za czas jakiś.
pzdr
Niby fajna zabawka takie zrzuty ekranu, a potem okazuje sie ze niewielki z tego pozytek, bo test sie wywalil, a nim zrobil sie screenshot to juz sie DOM do konca zaladowal, i patrzysz na cos zupelnie innego niz widzial to test. I zamiast pomocy masz jeszcze większą zamotkę :)
OdpowiedzUsuń