Podczas pisania testów automatycznych niejednokrotnie spotykamy się z sytuacją, w której musimy zaimplementować złożone akcje naśladujące zachowanie żywego użytkownika. W Selenium WebDriver możemy zrealizować to zadanie na kilka sposobów. Poniżej opiszę cztery różne podejścia:

  • Standardowe metody Selenium WebDriver’a
  • Klasa Actions
  • Klasa Robot
  • JavascriptExecutor

Standardowe metody Selenium WebDriver’a

Wiele podstawowych interakcji można obsłużyć standardowymi metodami dostępnymi w interfejsach takich jak WebElement, WebDriver, Navigation. Oferują one m.in. możliwość otwierania adresu URL, klikania w elementy stron, pobierania wyświetlanego tekstu czy operowania funkcjami przeglądarki, takimi jak ‚wstecz’ i ‚dalej’. Oto przykład:

driver.findElement(By.id("textfield")).sendKeys("hello!");
driver.findElement(By.id("button")).click();
Assert.assertEquals(driver.findElement(By.id("label")).getText(), "label text");
driver.manage().deleteAllCookies();

1: wpisz „hello!” w element „textfield”
2: kliknij w „button”
3: zweryfikuj, że test elementu „label” jest równy „label text”
4: usuń wszystkie ciasteczka z obecnej domeny

Spośród wielu standardowych metod na uwagę zasługują m.in.:

  • WebDriver.get(String url) – otwórz stronę o podanym adresie
  • WebDriver.close() – zamknij aktywne okno
  • WebDriver.getCurrentUrl() – zwróć url aktualnie otwartej strony
  • WebDriver.Navigation.refresh() – odśwież stronę
  • WebDriver.Navigation.back() – nawiguj ‚wstecz’ w historii przeglądarki
  • WebDriver.Navigation.forward() – nawiguj ‚dalej’ w historii przeglądarki
  • WebElement.click() – kliknij dany element
  • WebElement.sendKeys(CharSequence… keysToSend) – wpis tekst w element (np. polu tekstowym)
  • WebElement.clear() – jeżeli elementem jest pole tekstowe, wyczyść jego wartość
  • WebElement.getText() – zwróć tekst elementu

Klasa Actions

Powyższe metody pozwalają na zasymulowanie tylko podstawowych akcji użytkownika, takich jak pojedyncze kliknięcie czy wpisywanie prostego tekstu na klawiaturze. W celu obsługi bardziej złożonych zachowań możemy skorzystać z przeznaczonej do tego celu klasy Actions. Dostarcza ona bogatą paletę akcji wykonywanych za pomocą klawiatury i myszki, które dodatkowo można łączyć w sekwencje realizowanych po sobie czynności. Tak wygląda kod, który skopiuje element (przeciągnie go trzymając wciśnięty klawisz Ctrl):

Actions builder = new Actions(driver);
builder.keyDown(Keys.CONTROL)
	.dragAndDrop(driver.findElement(By.id("source")), driver.findElement(By.id("target")))
	.keyUp(Keys.CONTROL)
	.perform();

1: stwórz instancję klasy Actions
2: wciśnij klawisz Ctrl
3: przeciągnij i upuść element
4: puść klawisz Ctrl
5. medota perform() konstruuje i wykonuje zdefiniowaną sekwencję akcji

Oto niektóre z bardziej popularnych metod:

  • Actions.contextClick(WebElement onElement) – kliknij prawym przyciskiem myszy na podanym elemencie
  • Actions.doubleClick() – wykonaj podwójne kliknięcie
  • Actions.moveToElement(WebElement toElement) – ustaw kursor myszy na wskazanym elemencie
  • Actions.keyDown(Keys theKey) – wciśnij przycisk klawiatury
  • Actions.keyUp(Keys theKey) – puść przycisk klawiatury
  • Actions.dragAndDrop(WebElement source, WebElement target) – przeciągnij element ‚source’ i upuść go na elemencie ‚target’

Oto przykład pokazujący jak zaznaczyć wszystkie elementy od ‚item1’ do ‚item10’ i przeciągnąć je do elementu ‚container’:

Actions builder = new Actions(driver);
builder.keyDown(Keys.SHIFT)
	.click(driver.findElement(By.id("item1")))
	.click(driver.findElement(By.id("item10")))
	.keyUp(Keys.SHIFT)
	.dragAndDrop(driver.findElement(By.id("item1")), driver.findElement(By.id("container")))
	.perform();

Kolejny przykład, w którym test otwiera menu kontekstowe dla elementu ‚item’, najeżdża na pozycję menu ‚menuElement’ i klika na zagdzieżdżoną pozycję menu ‚menuSubElement’:

Actions builder = new Actions(driver);
builder.contextClick(driver.findElement(By.id("item")))
	.moveToElement(driver.findElement(By.id("menuElement")))
	.click(driver.findElement(By.id("menuSubElement")))
	.perform();

Klasa Robot

Inną klasą oferującą metody symulujące myszkę i klawiaturę jest Robot. Jej użyteczność pod kątem automatyzacji testów aplikacji webowych jest ograniczona w stosunku do Selenium Actions. Jednak przewaga Robota polega na tym, że akcje, takie jak kliknięcie czy pisanie na klawiaturze, mogą być przekazywane nie tylko do testowanej aplikacji webowej, ale także do okienek systemowych. Oto jak działa Robot:

Robot robot = new Robot();
robot.mouseMove(40, 50);
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
robot.keyPress(KeyEvent.VK_7);
robot.keyRelease(KeyEvent.VK_7);

1: stwórz instancję klasy Robot
2: przesuń kursor myszy na punkt ekranu o współrzędnych 40 px, 50 px
3-4: kliknij lewym przyciskiem myszy
5-6: napisz „7”

Z dostępnych metod warto wymienić:

  • Robot.mouseMove(int x, int y) – umieść kursor myszki na wskazanych koordynatach ekranu
  • Robot.mousePress(int buttons) – kliknij jednym lub kilkoma przyciskami myszki
  • Robot.keyPress(int keycode) – wciśnij dany klawisz
  • Robot.keyRelease(int keycode) – puść dany klawisz
  • Robot.mouseWheel(int wheelAmt) – użyj scroll’a myszki
  • Robot.createScreenCapture(Rectangle screenRect) – zrób screenshot zdefiniowanego obszaru ekranu

JavascriptExecutor

Selenium umożliwia nam także wykonywanie skryptów JavaScript z poziomu testu za pomocą interfejsu JavascriptExecutor. Jest to wygodny sposób na wykonanie czynności takich jak np. ingerencja w kod html testowanej aplikacji. Wykonanie przykładowego skryptu wygląda następująco:

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("document.getElementById('element').click();");

1: stwórz instancję JavascriptExecutor
2: wykonaj skrypt podany jako argument, w tym przypadku kliknięcie w element

Zamiast identyfikować element strony w ramach wykonywanego skryptu, można przekazać obiekt WebElement jako argument i na nim wykonać operacje za pomocą JavaScript’u. Oto jak w prosty sposób można usunąć atrybut przycisku aby go aktywować:

WebElement button = driver.findElement(By.linkText("Submit"));
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].removeAttribute('disabled');", button);

Kod kolejnego przykładu przewinie stronę o 20 pixeli w lewo:

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollBy(-20,0);");

Przykładowy test

Jako podsumowanie zamieszczam przykładowy test Selenium WebDriver, który wykorzystuje wszystkie opisane sposoby symulowania akcji użytkownika. Jest on napisany jako klasa TestNG i operuje na demonstracyjnych widgetach PrimeUI ze strony PrimeFaces.

package advancedUserInteractions;

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;

import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Actions;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

public class AdvancedUserInteractions {
	
	private WebDriver driver;
	private static final String BASE_URL = "http://www.primefaces.org/primeui/";

	@BeforeClass
	public void setUp() {
		driver = new FirefoxDriver();
	}
	
	@Test
	public void standardMethods() {
		// Navigate to "autocomplete.html" page
		driver.get(BASE_URL + "autocomplete.html");
		
		// Enter "redmond" into "Basic" field
		driver.findElement(By.id("basic")).sendKeys("redmond");
		
		// Expand the dropdown and select "Argentina"
		driver.findElement(By.xpath("//input[@id='dropdown']/following-sibling::button")).click();
		driver.findElement(By.xpath("//li[text()='Argentina']")).click();
	}
	
	@Test
	public void actionsClass() {
		// Navigate to "picklist.html" page
		driver.get(BASE_URL + "picklist.html");
		
		// Locate the web elements
		WebElement basicPicklist = driver.findElement(By.id("basic"));
		WebElement ford = basicPicklist.findElement(By.xpath(".//li[text()='Ford']"));
		WebElement porsche = basicPicklist.findElement(By.xpath(".//li[text()='Porsche']"));
		WebElement moveRightButton = basicPicklist.findElement(By.xpath("(.//button)[1]"));
		
		// Select both ford and porsche and move them to the right
		new Actions(driver).keyDown(Keys.CONTROL)
			.click(ford)
			.click(porsche)
			.keyUp(Keys.CONTROL)
			.click(moveRightButton)
			.perform();
	}
	
	@Test
	public void robotClass() {
		// Navigate to "galleria.html" page
		driver.get(BASE_URL + "galleria.html");
		
		try {
			// Create an instance of a Robot and wait for a page to be loaded
			Robot robot = new Robot();
			robot.delay(3000);
			
			// Open Firefox search tool (CTRL+F)
			robot.keyPress(KeyEvent.VK_CONTROL);
			robot.keyPress(KeyEvent.VK_F);
			robot.keyRelease(KeyEvent.VK_F);
			robot.keyRelease(KeyEvent.VK_CONTROL);
			
			// Search for 'sit' word
			robot.keyPress(KeyEvent.VK_S);
			robot.keyRelease(KeyEvent.VK_S);
			robot.keyPress(KeyEvent.VK_I);
			robot.keyRelease(KeyEvent.VK_I);
			robot.keyPress(KeyEvent.VK_T);
			robot.keyRelease(KeyEvent.VK_T);
			
		} catch (AWTException e) {
			e.printStackTrace();
		}
	}
	
	@Test
	public void javascriptExecutor() {
		// Navigate to "inputtextarea.html" page
		driver.get(BASE_URL + "inputtextarea.html");
		
		// Create an instance of JavascriptExecutor
		JavascriptExecutor js = (JavascriptExecutor) driver;
		
		// Scroll the window down by 200 pixels
		js.executeScript("window.scrollBy(0, 200);");
		
		// Modify textarea size by changing "rows" attribute value
		js.executeScript("document.getElementById('resize').setAttribute('rows', '25');");
		
		// Enter text into a textarea
		js.executeScript("document.getElementById('resize').innerHTML='Text entered by JavascriptExecutor';");
	}
	
	@AfterClass
	public void tearDown() {
		driver.quit();
	}
}