Sæt strøm til webtesten med Selenium og hovedløs Chrome

21. februar 2019 kl. 05:116
Sæt strøm til webtesten med Selenium og hovedløs Chrome
Illustration: Bigstock/AlexLMX.
Det er nemt at teste webapps med open source-biblioteket Selenium, der bruger en rigtig browser - også uden at stikke hovedet frem på skrivebordet.
Artiklen er ældre end 30 dage
Manglende links i teksten kan sandsynligvis findes i bunden af artiklen.

Test af brugerflader har altid været et smertensbarn i familien af program-test. I modsætning til et api, som for eksempel et funktionskald, er der ikke nogen type, der kan fortælle, hvad man skal putte ind.

I en grafisk brugerflade er der ubehageligt mange muligheder for brugerinput i forhold til for eksempel et terminalprogram, hvor mulighederne som regel er begrænset til de taster, der sidder på brugerens tastatur.

Det er det, der har fået Facebook til at kigge på automatisk fejlfinding ved at få kunstig intelligens til at efterligne den måde, mennesker interagerer med firmaets mobil-apps på. Det fører til fejl, som oven i købet også rettes med kunstig intelligens. Med ganske pæn succes, lyder det fra firmaet. Det skal blive til open source-teknologi og hvermandseje en dag, men indtil da må vi selv smøge ærmerne op og gøre arbejdet.

Brugerflademiljøer til skrivebordsprogrammer indeholder ofte ‘robotbiblioteker,’ der kan gøre det samme, som brugere af kød og blod kan: klikke på knapper, skrive i tekstfelter og hive i ting og sager. Muligheder, som kan anvendes til at skrive automatiserede test med.

Artiklen fortsætter efter annoncen

Når det gjaldt webapplikationer, var situationen i mange år ikke god. Her måtte test-folket ty til instrumenterede makro-programmer, men så kom open source-værktøjet Selenium på banen.

Hovedløs webtest

Selenium kommer med flere tricks i posen. Man kan afvikle systemet i dets eget visuelle værktøj - IDE - der findes som plugin til Chrome og Firefox, og teste sin webapplikation på denne vis.

En anden fremgangsmåde er at benytte en browser-specifik driver, så man kan udføre tests fra det sprog og miljø, man bedst kan lide - Java, Javascript, Python, C#, PHP og så videre. Det kan udføres grafisk, hvor browseren stikker hovedet frem på skrivebordet under testen.

Men det kan også gøre i ‘headless’-tilstand, hvor browseren gør det, den skal - men uden at skrive til grafikken. Man kan altså køre testen på en server uden grafikmiljø. Det nedsætter også resurseforbruget væsentligt og øger den hastighed, hvormed testen bliver udført.

Artiklen fortsætter efter annoncen

Her skaber vi en test, som vi afvikler som en unit test i Java. Vi har haft et lille problem på Version2, hvor kodeopmærkning - syntaksfarvning af kodeeksempler og så videre - forsvandt i artiklerne under en opdatering af cms’et.

Det skulle vist være løst nu – 7-9-13, bank under bordet – men derfor er det ikke skidt at have test, der kan tjekke, at alt er OK.

Klik på GDPR-knappen

Selenium er pærenemt at have med at gøre. Til Java downloades den seneste udgave som zip-filen selenium-java-3.141.59 fra projektets hjemmeside. Den indeholder alle biblioteker, som Selenium anvender, så det er nemt at komme i gang med det samme.

Dernæst skal vi bruge en driver. Jeg har Chrome 72 installeret på min Windows-maskine, så den driver, jeg skal bruge, har nummer 72.0.3626.69 og kan downloades fra Chromiums webside.

Den til Windows hedder Chromedriver.exe, og så er vi parat til at komme i sving.

Selenium og Chromedriver kan selv finde Chrome-installationen, men man skal pege Chromedriver.exe ud for Selenium, som angivet i koden herunder.

Vi skaber en ny unit test, som beskrevet i en tidligere artikel.

Så ser vores test sådan ud:

  1. @Test
  2. public void test() throws InterruptedException {
  3. System.setProperty("webdriver.chrome.driver",
  4. "chromedriver/72.0.3626.69/chromedriver.exe");
  5. WebDriver driver = new ChromeDriver();
  6. driver.get("https://v2.dk/1087450");
  7.  
  8. WebElement gdprKnap = driver.findElement(
  9. By.className("eu-cookie-compliance-secondary-button"));
  10. gdprKnap.click();
  11.  
  12. assertTrue(driver.getPageSource().contains(
  13. "<span style=\"color: #000000; font-weight: bold;\">switch</span>"));
  14.  
  15. Thread.sleep(5000);
  16. driver.quit();
  17. }

(Program-pauserne Thread.sleep er kun indsat for demonstrationens skyld.)

Artiklen fortsætter efter annoncen

I første linje sætter vi en system-property, som er stien til chromedriver.exe. Dernæst skaber vi en ny WebDriver, som er bindeleddet til browseren, vi vil fjernstyre. Her er instansen en ChromeDriver, og den er altså så smart, at den på egen hånd finder Chromes eksekverbare fil.

Og så er det lige ud ad landevejen. Vi navigerer hen til en artikel med kodeopmærkning med linjen:

  1. driver.get("https://v2.dk/1087450");

Nu kommer der en blokerende GDPR-dialog frem på skærmen, som vi lige skal klikke væk.

Det gør vi med linjen

  1. WebElement gdprKnap = driver.findElement(
  2. By.className("eu-cookie-compliance-secondary-button"));
  3. gdprKnap.click();

(Class name-parameteren har vi som i tidligere artikler fundet med Chromes udviklingsværktøjer.)

Nu kommer vi frem til det, vi skal teste: at artiklens Java-kode er korrekt formateret. Her kigger vi på, om nøgleordet ‘switch’ ser rigtigt ud i html-kilden:

  1. <span style="color: #000000; font-weight: bold;">switch</span>

Hvis testen passerer, er alting godt:

  1. assertTrue(driver.getPageSource().contains(
  2. "<span style=\"color: #000000; font-weight: bold;\">switch</span>"));

Den sidste linje, driver.quit, er vigtig, for ellers bliver Chrome- og Chromedriveren hængende i hukommelsen.

Vi kan som tidligere nævnt også køre testen ‘headless’. Det er blot at sætte en parameter til Webdriver, og så kan vi bruge testen i en build-server som Jenkins, uden et grafisk afviklingsmiljø, på lige fod med almindelige unit tests:

  1. ChromeOptions chromeOptions = new ChromeOptions();
  2. chromeOptions.addArguments("--headless");
  3. WebDriver driver = new ChromeDriver(chromeOptions);

HTTP kan ikke klare jobbet

Men hvorfor ikke bare hente kildekoden via et HTTP-kald? Til denne test ville det nok have ordnet sagen for os - ret beset var klikket på GDPR-knappen ikke livsnødvendigt.

Men Selenium kan så meget, som man ikke kan klare med HTTP. Det handler som nævnt om at trykke på knapper og skrive i tekstfelter, som her:

  1. WebElement søgeKnap = driver.findElement(By.cssSelector("#header > div.header-global > div:nth-child(1) > span > i"));
  2. søgeKnap.click();
  3. WebElement searchBox = driver.findElement(By.name("keys_search_page"));
  4. searchBox.sendKeys("Java test");
  5. searchBox.submit();

Med HTTP er det svært til umuligt at tjekke, om adfærden er korrekt.

Et andet forhold er indhold, som er genereret med Javascript. Det kan ikke lige findes med HTTP-kaldet, men kræver en Javascript-motor.

Artiklens testeksempler kan downloades her, med alle biblioteker inkluderet. Vi har ændret en smule i koden, så der ikke kræves Junit.

6 kommentarer.  Hop til debatten
Denne artikel er gratis...

...men det er dyrt at lave god journalistik. Derfor beder vi dig overveje at tegne abonnement på Version2.

Digitaliseringen buldrer derudaf, og it-folkene tegner fremtidens Danmark. Derfor er det vigtigere end nogensinde med et kvalificeret bud på, hvordan it bedst kan være med til at udvikle det danske samfund og erhvervsliv.

Og der har aldrig været mere akut brug for en kritisk vagthund, der råber op, når der tages forkerte it-beslutninger.

Den rolle har Version2 indtaget siden 2006 - og det bliver vi ved med.

Debatten
Log ind eller opret en bruger for at deltage i debatten.
settingsDebatindstillinger
6
28. februar 2019 kl. 19:33

Tania, du burde bruge lidt tid på at få Git ind på rygradden - og så kigge på Maven eller Gradle til dependency management. Det er meget lettere for folk at tage et hurtigt kig på kode på GitHub, Gitlab eller lignende i stedet for at rode med zipfiler.

Og moderne byggeværktøjers automatiske dependency management er meget nemmere end selv at skulle downloade og redistribuere libraries. Fornuftige IDE'er supporterer både Maven og Gradle som projektmodeller, så man kan stadig bygge fra sit IDE hvis man ikke er så glad for et terminalvindue.

5
25. februar 2019 kl. 16:14

Jeg er mere fan af at bruge BrowserStack i stedet for at køre browseren på egen maskine. Man bruger stadig Selenium til at kode testen men i stedet for at køre browseren lokalt så køres den i skyen.

Det gør det lettere at få det til at virke på din CI server og du kan teste på tværs af OS. Dvs. hvis din CI server kører Linux så kan du stadig teste Windows only browsere. BrowserStack laver også en lille video med alle tests så du kan gå tilbage og se hvad der var på skærmen når testen fejler nogle gange på CI serveren men aldrig på din maskine.

I øvrigt er 'driver.findElement(By.className("eu-cookie-compliance-secondary-button"))' konstruktionen error prone og timing afhængig. For måske er browseren ikke færdig med at rendere siden når man spørger. Så bruge altid vente metoderne WebElement element = (new WebDriverWait(driver, 10)).until(ExpectedConditions.elementToBeClickable(By.className("eu-cookie-compliance-secondary-button")));

4
21. februar 2019 kl. 21:06

I python er with-keyword jo bare en (specialiseret) wrapper til try-finally, som de fleste programmingssprog understøtter (inklusiv Java og Python). Det with gør, er blot at kalde exit hvis objektet (Webdriver) er blevet oprettet, som man ville have gjort det i finally. PS Jeg er kæmpe fan af Python :-)

3
21. februar 2019 kl. 18:05

... så bruger man nightwatch.js i stedet for selenium direkte.

2
21. februar 2019 kl. 16:21

Hvad med også lige at teste at 'quoting' fungerer :).

Quoting burde betyde 'slå formatteringen fra imellem quote-markers så den ikke bare äder halvdelen af der der quotes'.

1
21. februar 2019 kl. 16:15

Det er vigtigt altid at kalde 'driver.quit()'. Hvis man körer "headless" Selenium og 'Internettet' er som det nu engang er, så er det relativt nemt at ende med en stak halvdöde Firefox og Chrome browsere på grund af Exceptions.

Selenium Webdriver for Python har en indbygget Contextmanager funktion for Webdriver så man i sit program skriver noget is stil med:

from selenium import webdriver</p>
<p>with webdriver.Chrome() as wd:
res = wd.get('https://stackoverflow.com/questions/&#039;)
print(res.page_source)

Uanset hvordan man ender sin 'with-blok' så bliver 'driver.quit()' kaldt.

Java må have noget tilsvarende.


Det der sker "indeni" er noget i stil med:

from selenium import webdriver</p>
<p>class WebDriver:
def <strong>init</strong>(self, driver):
self.driver = driver</p>
<pre><code>def __enter__(self):
return self.driver

def __exit__(self, exc_type, exc_val, exc_tb):
self.driver.quit()
</code></pre>
<p>with WebDriver(webdriver.Chrome()) as wd:
wd.get('https://stackoverflow.com/questions/&#039;)
print(wd.page_source)</p>
<p>Python runtime kalder <strong>exit</strong>(...) når man ryger ud af 'with blokken'