Selenium y JUnit paralelizado – instancias de WebDriver

La puesta en marcha

Entonces, básicamente estoy tratando de lograr pruebas de Selenium que se ejecutan en paralelo usando JUnit.

Para eso he encontrado este corredor JUnit . Funciona muy bien, me gusta mucho.

Sin embargo, estoy teniendo problemas con el manejo de las instancias de WebDriver.

Lo que quiero

Cada elemento WebDriver se debe crear una vez para cada clase antes de que se @Test métodos @Test .

Lógicamente, podría usar el constructor de clases para esto. En realidad, este es el requisito para mis pruebas porque necesito hacer uso de los @Parameters para que pueda crear la instancia de WebDriver en consecuencia (Chrome, FF, IE …).

El problema

El problema es que quiero que la instancia de WebDriver se driver.quit() ( driver.quit() ) después de terminar una clase y no después de cada método @Test . Pero no puedo usar @AfterClass porque no puedo hacer de WebDriver un miembro estático, ya que cada instancia de clase tiene que usar su propia cuenta (de lo contrario, las pruebas intentarán ejecutarse en el mismo navegador).

Una posible solucion

He encontrado una posible sugerencia aquí por Mrunal Gosar . Siguiendo su consejo, he cambiado WebDriver para que sea un static ThreadLocal y luego creo instancias de él en cada constructor usando

  // in the classes constructor driver = new ThreadLocal() { @Override protected WebDriver initialValue() { return new FirefoxDriver(); / } }; 

Las agujas para decir que reemplacé todos los driver.whatever driver.get().whatever llame con driver.get().whatever en mi código.

Ahora, para abordar el propósito final de esto, también escribí un método @AfterClass que llamaría driver.get().quit(); que ahora es aceptado por el comstackdor, ya que la variable es estática.

Sin embargo, probar esto conduce a un comportamiento inesperado. Tengo una configuración de Selenium Grid con 2 nodos que se ejecutan en una máquina remota. Tenía esta configuración funcionando como se esperaba antes, pero ahora los navegadores son spam y las pruebas fallan. (Mientras que 2 navegadores deberían estar funcionando, en lugar de 8+ están abiertos)

El subproceso que vinculé sugiriendo esta solución tenía alguien comentando que podría ser una mala idea manejar manualmente los subprocesos si ya está usando un marco como JUnit.

Mi pregunta

¿Cuál es el diseño correcto para hacer esto?

Solo pude pensar en

  1. Haz que lo que se sugiere aquí funcione.
  2. Escriba un solo método anotado @Test que ejecute todos los demás métodos y luego use @After para lograr lo mismo que @AfterClass
  3. Guarde el parámetro del constructor en una variable miembro y @Test el hecho de que tengo que crear un navegador antes de que se @Test cada método anotado @Test (utilizando @Before de crear la instancia de WebDriver y @After para cerrar la sesión)

No sé muy bien si la opción 3 se encuentra con posibles problemas. Si cierro la sesión después de cada método, entonces el servidor grid podría abrir una nueva sesión con una clase completamente nueva en este nodo antes de que ésta termine las anteriores. Si bien las pruebas son independientes entre sí, todavía siento que esto es un peligro potencial.

¿Alguien de aquí está usando activamente un traje de prueba Selenium multiproceso y puede guiarme en el diseño adecuado?

En general estoy de acuerdo en que:

podría ser una mala idea manejar manualmente los subprocesos si ya está utilizando un marco como JUnit

Pero, mirando el corredor Parallelized que mencionó y la implementación interna de @Parametrized en junio 4.12 es posible.

Cada caso de prueba está progtwigdo para su ejecución. Por defecto, Junit ejecuta casos de prueba en un solo hilo. Parallelized extiende el Parametrized de tal manera que el progtwigdor de pruebas de un solo hilo se reemplaza por el progtwigdor de múltiples hilos por lo que, para entender cómo afecta esto a la forma en que se ejecutan los casos de prueba Parametrized , tenemos que buscar en las fonts Parametrized JUnit:

https://github.com/junit-team/junit/blob/r4.12/src/main/java/org/junit/runners/Parameterized.java#L303

Parece:

  1. @Parametrized caso de prueba @Parametrized se divide en un grupo de TestWithParameters para cada parámetro de prueba
  2. para cada instancia de TestWithParameters de Runner se crea y se progtwig para su ejecución (en este caso, la instancia de Runner es BlockJUnit4ClassRunnerWithParameters especializada)

En efecto, cada caso de prueba @Parametrized genera un grupo de instancias de prueba para ejecutarse (instancia única para cada parámetro) y cada instancia se progtwig de manera independiente, por lo que en nuestro caso (prueba Parallelized y @Parametrized con instancias de WebDriver como parámetros) se ejecutarán múltiples pruebas independientes en hilos dedicados para cada tipo de WebDriver . Y esto es importante porque nos permite almacenar una instancia específica de WebDriver en el ámbito del subproceso actual.

Recuerde que este comportamiento se basa en los detalles de la implementación interna de Junit 4.12 y puede cambiar (por ejemplo, vea los comentarios en RunnerScheduler ).

Tomemos el siguiente ejemplo. Se basa en el comportamiento de JUnit mencionado y utiliza ThreadLocal para almacenar instancias de WebDriver compartidas entre pruebas en los mismos grupos de casos. El único truco con ThreadLocal es inicializarlo solo una vez (en @Before) y destruir cada instancia creada (en @AfterClass).

 package example.junit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.firefox.FirefoxDriver; /** * Parallel Selenium WebDriver example for http://stackoverflow.com/questions/30353996/selenium-and-parallelized-junit-webdriver-instances * Parallelized class is like http://hwellmann.blogspot.de/2009/12/running-parameterized-junit-tests-in.html */ @RunWith(Parallelized.class) public class ParallelSeleniumTest { /** Available driver types */ enum WebDriverType { CHROME, FIREFOX } /** Create WebDriver instances for specified type */ static class WebDriverFactory { static WebDriver create(WebDriverType type) { WebDriver driver; switch (type) { case FIREFOX: driver = new FirefoxDriver(); break; case CHROME: driver = new ChromeDriver(); break; default: throw new IllegalStateException(); } log(driver, "created"); return driver; } } // for description how to user Parametrized // see: https://github.com/junit-team/junit/wiki/Parameterized-tests @Parameterized.Parameter public WebDriverType currentDriverType; // test case naming requires junit 4.11 @Parameterized.Parameters(name= "{0}") public static Collection driverTypes() { return Arrays.asList(new Object[][] { { WebDriverType.CHROME }, { WebDriverType.FIREFOX } }); } private static ThreadLocal currentDriver = new ThreadLocal(); private static List driversToCleanup = Collections.synchronizedList(new ArrayList()); @BeforeClass public static void initChromeVariables() { System.setProperty("webdriver.chrome.driver", "/path/to/chromedriver"); } @Before public void driverInit() { if (currentDriver.get()==null) { WebDriver driver = WebDriverFactory.create(currentDriverType); driversToCleanup.add(driver); currentDriver.set(driver); } } private WebDriver getDriver() { return currentDriver.get(); } @Test public void searchForChromeDriver() throws InterruptedException { openAndSearch(getDriver(), "chromedriver"); } @Test public void searchForJunit() throws InterruptedException { openAndSearch(getDriver(), "junit"); } @Test public void searchForStackoverflow() throws InterruptedException { openAndSearch(getDriver(), "stackoverflow"); } private void openAndSearch(WebDriver driver, String phraseToSearch) throws InterruptedException { log(driver, "search for: "+phraseToSearch); driver.get("http://www.google.com"); WebElement searchBox = driver.findElement(By.name("q")); searchBox.sendKeys(phraseToSearch); searchBox.submit(); Thread.sleep(3000); } @AfterClass public static void driverCleanup() { Iterator iterator = driversToCleanup.iterator(); while (iterator.hasNext()) { WebDriver driver = iterator.next(); log(driver, "about to quit"); driver.quit(); iterator.remove(); } } private static void log(WebDriver driver, String message) { String driverShortName = StringUtils.substringAfterLast(driver.getClass().getName(), "."); System.out.println(String.format("%15s, %15s: %s", Thread.currentThread().getName(), driverShortName, message)); } } 

Abrirá dos navegadores y ejecutará tres casos de prueba en cada ventana del navegador simultáneamente .

La consola imprimirá algo como:

 pool-1-thread-1, ChromeDriver: created pool-1-thread-1, ChromeDriver: search for: stackoverflow pool-1-thread-2, FirefoxDriver: created pool-1-thread-2, FirefoxDriver: search for: stackoverflow pool-1-thread-1, ChromeDriver: search for: junit pool-1-thread-2, FirefoxDriver: search for: junit pool-1-thread-1, ChromeDriver: search for: chromedriver pool-1-thread-2, FirefoxDriver: search for: chromedriver main, ChromeDriver: about to quit main, FirefoxDriver: about to quit 

Puede ver que los controladores se crean una vez para cada subproceso de trabajo y se destruyen al final.

Para resumir, necesitamos algo como @BeforeParameter y @AfterParameter en el contexto del hilo de ejecución y la búsqueda rápida muestra que dicha idea ya está registrada como problema en Junit