2
respostas

Ao rodar o teste com selenium, não dá tempo de renderizar as mensagens de erro

Os métodos de teste que envolvem submit e verificação de mensagem após a resposta do servidor sempre estavam falhando. Tive que colocar um Thread.sleep(300) para aguardar a resposta da requisição e em seguida fazer o teste.

package br.com.caelum.selenium;

import static org.junit.Assert.*;
import io.github.bonigarcia.wdm.MarionetteDriverManager;

import java.io.IOException;

import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.MarionetteDriver;

public class UsuarioSystemTest {
    private WebDriver driver;

    @BeforeClass
    public static void createAndStartService() throws IOException {
        MarionetteDriverManager.getInstance().setup();
    }


    @Before
    public void setUp(){
        this.driver = new MarionetteDriver();
    }

    @Test
    public void deveAdicionarUmUsuario () throws InterruptedException{
        driver.get("http://localhost:8080/usuarios/new");

        WebElement nome = driver.findElement(By.name("usuario.nome"));
        WebElement email = driver.findElement(By.name("usuario.email"));
        WebElement botaoSalvar = driver.findElement(By.id("btnSalvar"));

        String nomeUsuario = "Adriano Xavier";
        String emailUsuario = "axavier@empresa.com.br";

        nome.sendKeys(nomeUsuario);
        email.sendKeys(emailUsuario);
        botaoSalvar.click();

        Thread.sleep(300);

        driver.get("http://localhost:8080/usuarios");
        assertTrue("Não encontrou o nome cadastrado", driver.getPageSource().contains(nomeUsuario));
        assertTrue("Não encontrou o email cadastrado", driver.getPageSource().contains(emailUsuario));
    }

    @Test
    public void deveAvisarQueNomeEhObrigatorioAoAdicionarUsuario () throws InterruptedException{
        driver.get("http://localhost:8080/usuarios/new");

        WebElement email = driver.findElement(By.name("usuario.email"));
        WebElement botaoSalvar = driver.findElement(By.id("btnSalvar"));

        String emailUsuario = "axavier@empresa.com.br";

        email.sendKeys(emailUsuario);
        botaoSalvar.click();

        Thread.sleep(300);

        assertTrue("Não encontrou aviso de que o nome é obrigatório", driver.getPageSource().contains("Nome obrigatorio!"));
    }

    @Test
    public void deveAvisarQueEmailEhObrigatorioAoAdicionarUsuario () throws InterruptedException{
        driver.get("http://localhost:8080/usuarios/new");

        WebElement nome = driver.findElement(By.name("usuario.nome"));
        WebElement botaoSalvar = driver.findElement(By.id("btnSalvar"));

        String nomeUsuario = "axavier@empresa.com.br";

        nome.sendKeys(nomeUsuario);
        botaoSalvar.click();

        Thread.sleep(300);

        assertTrue("Não encontrou aviso de que o email é obrigatório", driver.getPageSource().contains("E-mail obrigatorio!"));
    }

    @Test
    public void deveAvisarQueNomeEmailSaoObrigatorioAoAdicionarUsuario () throws InterruptedException{
        driver.get("http://localhost:8080/usuarios/new");

        WebElement botaoSalvar = driver.findElement(By.id("btnSalvar"));
        botaoSalvar.click();

        Thread.sleep(300);

        assertTrue("Não encontrou aviso de que o nome é obrigatório", driver.getPageSource().contains("Nome obrigatorio!"));
        assertTrue("Não encontrou aviso de que o email é obrigatório", driver.getPageSource().contains("E-mail obrigatorio!"));
    }

    @After
    public void tearDown() throws InterruptedException{
        if (this.driver != null) {
            this.driver.close();
        }
    }
}

Essa é a maneira mais correta de fazer? Achei estranha...

Também não consegui executar vários testes em sequência, pois o seguinte erro é apresentado:

org.openqa.selenium.remote.UnreachableBrowserException: Could not start a new session. Possible causes are invalid address of the remote server or browser start-up failure.
Build info: version: '2.53.1', revision: 'a36b8b1cd5757287168e54b817830adce9b0158d', time: '2016-06-30 19:26:09'
System info: host: 'notezim', ip: '127.0.1.1', os.name: 'Linux', os.arch: 'amd64', os.version: '4.4.0-31-generic', java.version: '1.8.0_91'
Driver info: driver.version: MarionetteDriver
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:665)
    at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:249)
    at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:234)
    at org.openqa.selenium.firefox.MarionetteDriver.run(MarionetteDriver.java:79)
    at org.openqa.selenium.firefox.MarionetteDriver.<init>(MarionetteDriver.java:73)
    at org.openqa.selenium.firefox.MarionetteDriver.<init>(MarionetteDriver.java:45)
    at br.com.caelum.selenium.UsuarioSystemTest.setUp(UsuarioSystemTest.java:28)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.openqa.selenium.WebDriverException: org.apache.http.conn.HttpHostConnectException: Connect to localhost:30643 [localhost/127.0.0.1] failed: Conexão recusada
Build info: version: '2.53.1', revision: 'a36b8b1cd5757287168e54b817830adce9b0158d', time: '2016-06-30 19:26:09'
System info: host: 'notezim', ip: '127.0.1.1', os.name: 'Linux', os.arch: 'amd64', os.version: '4.4.0-31-generic', java.version: '1.8.0_91'
Driver info: driver.version: MarionetteDriver
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:91)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:644)
    ... 31 more
Caused by: org.apache.http.conn.HttpHostConnectException: Connect to localhost:30643 [localhost/127.0.0.1] failed: Conexão recusada
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:151)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:71)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
    at org.openqa.selenium.remote.internal.ApacheHttpClient.fallBackExecute(ApacheHttpClient.java:144)
    at org.openqa.selenium.remote.internal.ApacheHttpClient.execute(ApacheHttpClient.java:90)
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:142)
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:82)
    ... 32 more
Caused by: java.net.ConnectException: Conexão recusada
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at org.apache.http.conn.socket.PlainConnectionSocketFactory.connectSocket(PlainConnectionSocketFactory.java:74)
    at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
    ... 45 more

Há alguma forma de contorno para isso?

2 respostas

Olá Marcos,

de fato o Selenium tem esse comportamento de não esperar a requisição terminar antes de executar a próxima instrução. Uma das formas de contornar isso é colocando o sleep, mas imagina se a requisição demorar mais do que 300ms?

Outra forma um pouco mais elegante de fazer é pensar nesses testes literalmente como um teste de tela, simulando o comportamento do usuário. Todas as vezes que um usuário faz uma requisição, como submeter um formulário, ele espera a tela recarregar para depois fazer a próxima ação. Como o Selenium não espera a requisição terminar, precisamos escrever o código no teste para que ele espere a tela recarregar como um usuário. Para isso, o Selenium tem uma classe chamada WebDriverWait que você pode instanciar e no construtor ele recebe o driver e quantos segundos ele espera. Depois você pode chamar métodos que dizem o que o Selenium tem que esperar acontecer, como uma tela carregar. Essa classe WebDriverWait é apresentada no capítulo 5 deste curso do Selenium.

Quanto a stacktrace, é bem bizarro conseguir rodar 1 teste isolado, mas não vários de uma vez só. Provavelmente é por conta do MarionetteDriver. Dando uma pesquisada, um caso que eu achei bem parecido aponta que o problema são as portas que o driver usa na hora de rodar o teste, já que, mesmo você fazendo o close() no @After, ele mantem uma dessas portas em uso. Aqui o link que eu achei.

A classe MarionetteDriver tem um construtor que recebe qual porta ele tem que usar. Você pode usar o PortProber.findFreePort()para encontrar uma porta livre na sua máquina e passá-la como parâmetro do construtor e ver se reolve. Ou então usar o FirefoxDriver ao invés do MarionetteDriver, já que ele em geral não tem esse problema.

Passei a usar o WebDriverWait e achei melhor trabalhar com o TestNG, pois escrevi menos códigos usando a dependências entre métodos. O problema de não rodar os testes de uma vez só foi resolvido atualizando a biblioteca do selenium para a versão 3.0.0-beta1, pois nela há correções para o Firefox 47. Apesar de ter funcionado, achei melhor usar o Chrome, pois achei que os testes rodaram mais rapidamente. Continuei usando o webdrivermanager para configurar facilmente os navegadores.

pom.xml

...
<dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.0.0-beta1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>1.4.6</version>
        </dependency>
...

Abaixo estão os códigos:

package br.com.caelum.selenium;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class EditarUsuarioPage {
    public static final String TEXTO_NOME_OBRIGATORIO = "Nome obrigatorio!";
    public static final String TEXTO_EMAIL_OBRIGATORIO = "E-mail obrigatorio!";

    private WebDriver driver;
    private WebDriverWait wait;

    @FindBy(id = "content")
    private WebElement content;

    @FindBy(name = "usuario.nome")
    private WebElement inputNome;

    @FindBy(name = "usuario.email")
    private WebElement inputEmail;

    @FindBy(xpath = "//button[text() = 'Salvar!']")
    private WebElement botaoSalvar;

    public EditarUsuarioPage(WebDriver driver) {
        this.driver = driver;
        wait = new WebDriverWait(driver, Constante.TIMEOUT);
    }

    public void aguardarCarregarPagina() {
        wait.until(ExpectedConditions.visibilityOf(botaoSalvar));
    }

    public void editar(String nome, String email) {
        String urlAtual = driver.getCurrentUrl();
        inputNome.clear();
        inputNome.sendKeys(nome);
        inputEmail.clear();
        inputEmail.sendKeys(email);
        botaoSalvar.click();
        wait.until(
            ExpectedConditions.not(
                ExpectedConditions.urlToBe(urlAtual)
            )
        );
    }
}

package br.com.caelum.selenium;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class NovoUsuarioPage {
    public static final String TEXTO_NOME_OBRIGATORIO = "Nome obrigatorio!";
    public static final String TEXTO_EMAIL_OBRIGATORIO = "E-mail obrigatorio!";

    private WebDriver driver;
    private WebDriverWait wait;

    @FindBy(id = "content")
    private WebElement content;

    @FindBy(name = "usuario.nome")
    private WebElement inputNome;

    @FindBy(name = "usuario.email")
    private WebElement inputEmail;

    @FindBy(id = "btnSalvar")
    private WebElement botaoSalvar;

    public NovoUsuarioPage(WebDriver driver) {
        this.driver = driver;
        wait = new WebDriverWait(driver, Constante.TIMEOUT);
    }

    public void aguardarCarregarPagina() {
        wait.until(ExpectedConditions.visibilityOf(botaoSalvar));
    }

    public void cadastra(String nome, String email) {
        String urlAtual = driver.getCurrentUrl();
        inputNome.sendKeys(nome);
        inputEmail.sendKeys(email);
        botaoSalvar.click();
        wait.until(
            ExpectedConditions.or(
                ExpectedConditions.not(
                    ExpectedConditions.urlToBe(urlAtual)
                ),
                ExpectedConditions.textToBePresentInElement(content, TEXTO_NOME_OBRIGATORIO),
                ExpectedConditions.textToBePresentInElement(content, TEXTO_EMAIL_OBRIGATORIO)
            )
        );
    }

    public boolean exibiuMensagemNomeObrigatorio(){
        return driver.getPageSource().contains(TEXTO_NOME_OBRIGATORIO);
    }

    public boolean exibiuMensagemEmailObrigatorio(){
        return driver.getPageSource().contains(TEXTO_EMAIL_OBRIGATORIO);
    }
}

package br.com.caelum.selenium;

import java.util.List;

import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class UsuariosPage {
    private WebDriver driver;
    private WebDriverWait wait;

    @FindBy(linkText = "Novo Usuário")
    private WebElement linkTextNovoUsuario;

    public UsuariosPage(WebDriver driver) {
        this.driver = driver;
        wait = new WebDriverWait(driver, Constante.TIMEOUT);
    }

    public void visita(){
        driver.get(Constante.URL_BASE_SISTEMA + "/usuarios");
        wait.until(ExpectedConditions.visibilityOf(linkTextNovoUsuario));
    }

    public NovoUsuarioPage novo(){
        linkTextNovoUsuario.click();
        NovoUsuarioPage novoUsuarioPage = PageFactory.initElements(driver, NovoUsuarioPage.class);
        novoUsuarioPage.aguardarCarregarPagina();
        return novoUsuarioPage;
    }

    public void excluir(String nome, String email){
        List<WebElement> botoesExcluirEncontrados = driver.findElements(By.xpath("//td[preceding::td[text() = '"+email+"' and preceding::td[text() = '"+nome+"']]]/form/button[text() = 'excluir']"));
        WebElement botaoEscluirSelecionado = botoesExcluirEncontrados.get(0);
        botaoEscluirSelecionado.click();
        wait.until(ExpectedConditions.alertIsPresent());
        Alert alert = driver.switchTo().alert();
        alert.accept();
        wait.until(ExpectedConditions.stalenessOf(botaoEscluirSelecionado));
    }

    public EditarUsuarioPage editar(String nome, String email){
        List<WebElement> linksEditarEncontrados = driver.findElements(By.xpath("//td[preceding::td[text() = '"+email+"' and preceding::td[text() = '"+nome+"']]]/a[text() = 'editar']"));
        WebElement linkEditarSelecionado = linksEditarEncontrados.get(0);
        linkEditarSelecionado.click();
        wait.until(ExpectedConditions.stalenessOf(linkEditarSelecionado));
        EditarUsuarioPage editarUsuarioPage = PageFactory.initElements(driver, EditarUsuarioPage.class);
        editarUsuarioPage.aguardarCarregarPagina();
        return editarUsuarioPage;
    }

    public boolean existeNaListagem(String nome, String email){
        return driver.getPageSource().contains(nome) && driver.getPageSource().contains(email);
    }
}

package br.com.caelum.selenium;

import static org.testng.Assert.assertTrue;
import io.github.bonigarcia.wdm.ChromeDriverManager;

import java.io.IOException;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public class UsuarioSystemTest {
    public static final String NOME_VALIDO = "Fulano de Tal";
    public static final String EMAIL_VALIDO = "fulanodetal@empresa.com.br";

    public static final String NOME_EDITAR = "Cicrano";
    public static final String EMAIL_EDITAR = "cicrano@empresa.com.br";

    private WebDriver driver;
    private UsuariosPage usuariosPage;

    @BeforeTest
    public static void createAndStartService() throws IOException {
        ChromeDriverManager.getInstance().setup();
    }

    @BeforeClass
    public void setUp(){
        this.driver = new ChromeDriver();
        driver.get(Constante.URL_LIMPA_BASE);
        this.usuariosPage = PageFactory.initElements(driver, UsuariosPage.class);
    }

    @Test
    public void deveAdicionarUmUsuario () throws InterruptedException{
        usuariosPage.visita();
        usuariosPage.novo().cadastra(NOME_VALIDO, EMAIL_VALIDO);

        assertTrue(usuariosPage.existeNaListagem(NOME_VALIDO, EMAIL_VALIDO), "Usuário não cadastrado");
    }

    @Test
    public void deveAvisarQueNomeEhObrigatorioAoAdicionarUsuario () throws InterruptedException{
        usuariosPage.visita();
        NovoUsuarioPage novoUsuarioPage = usuariosPage.novo();
        novoUsuarioPage.cadastra("", EMAIL_VALIDO);

        assertTrue(novoUsuarioPage.exibiuMensagemNomeObrigatorio(), "Não encontrou aviso de que o nome é obrigatório");
    }

    @Test
    public void deveAvisarQueEmailEhObrigatorioAoAdicionarUsuario () throws InterruptedException{
        usuariosPage.visita();
        NovoUsuarioPage novoUsuarioPage = usuariosPage.novo();
        novoUsuarioPage.cadastra(NOME_VALIDO, "");

        assertTrue(novoUsuarioPage.exibiuMensagemEmailObrigatorio(), "Não encontrou aviso de que o nome é obrigatório");
    }

    @Test
    public void deveAvisarQueNomeEmailSaoObrigatoriosAoAdicionarUsuario () throws InterruptedException{
        usuariosPage.visita();
        NovoUsuarioPage novoUsuarioPage = usuariosPage.novo();
        novoUsuarioPage.cadastra("", "");

        assertTrue(novoUsuarioPage.exibiuMensagemNomeObrigatorio() && novoUsuarioPage.exibiuMensagemEmailObrigatorio(), "Não encontrou aviso de que o nome é obrigatório");
    }

    @Test(dependsOnMethods = { "deveAdicionarUmUsuario" })
    public void deveEditarUsuario() throws InterruptedException{
        usuariosPage.visita();
        EditarUsuarioPage editarUsuarioPage = usuariosPage.editar(NOME_VALIDO, EMAIL_VALIDO);
        editarUsuarioPage.editar(NOME_EDITAR, EMAIL_EDITAR);

        assertTrue(usuariosPage.existeNaListagem(NOME_EDITAR, EMAIL_EDITAR), "Usuário não editado");
    }

    @Test(dependsOnMethods = { "deveEditarUsuario" })
    public void deveExcluirUsuario() throws InterruptedException{
        usuariosPage.visita();
        usuariosPage.excluir(NOME_EDITAR, EMAIL_EDITAR);

        boolean existe = usuariosPage.existeNaListagem(NOME_EDITAR, EMAIL_EDITAR);

        assertTrue(!existe , "Usuário não excluído");
    }

    @AfterClass
    public void tearDown() throws InterruptedException{
        if (this.driver != null) {
            this.driver.quit();
        }
    }
}