Clique no WebDriver () x clique no JavaScript ()

126

A história:

Aqui no StackOverflow, vi usuários relatando que não podem clicar em um elemento através do comando selenium WebDriver "click" e podem contornar isso com um clique em JavaScript executando um script.

Exemplo em Python:

element = driver.find_element_by_id("myid")
driver.execute_script("arguments[0].click();", element)

Exemplo no WebDriverJS / Transferidor:

var elm = $("#myid");
browser.executeScript("arguments[0].click();", elm.getWebElement());

A questão:

Por que clicar em "via JavaScript" funciona quando um clique normal no WebDriver não funciona? Quando exatamente isso está acontecendo e qual é a desvantagem dessa solução alternativa (se houver)?

Eu pessoalmente usei essa solução alternativa sem entender completamente por que tenho que fazer isso e quais problemas ela pode levar.

alecxe
fonte

Respostas:

150

Ao contrário do que a resposta atualmente aceita sugere, não há nada específico para o PhantomJS quando se trata da diferença entre fazer o WebDriver clicar e fazê-lo em JavaScript.

A diferença

A diferença essencial entre os dois métodos é comum a todos os navegadores e pode ser explicada de maneira bem simples:

  • WebDriver: quando o WebDriver faz o clique, ele tenta da melhor maneira possível simular o que acontece quando um usuário real usa o navegador. Suponha que você tenha um elemento A, que é um botão que diz "Clique em mim", e um elemento B, que é um divelemento que é transparente, mas tem suas dimensões e zIndexconfigurações para cobrir completamente A. Então você diz ao WebDriver para clicar em A. O WebDriver irá simule o clique para que B receba o clique primeiro . Por quê? Como B cobre A e se um usuário tentasse clicar em A, B receberia o evento primeiro. A possibilidade de A obter o evento de clique ou não depende de como B lida com o evento. De qualquer forma, o comportamento do WebDriver nesse caso é o mesmo de quando um usuário real tenta clicar em A.

  • JavaScript: Agora, suponha que você use JavaScript para fazer isso A.click(). Esse método de clicar não reproduz o que realmente acontece quando o usuário tenta clicar em A. O JavaScript envia o clickevento diretamente para A e B não recebe nenhum evento.

Por que um clique em JavaScript funciona quando um clique no WebDriver não funciona?

Como mencionei acima, o WebDriver tentará simular da melhor maneira possível o que acontece quando um usuário real está usando um navegador. O fato é que o DOM pode conter elementos com os quais um usuário não pode interagir e o WebDriver não permitirá que você clique nesses elementos. Além do caso sobreposto que mencionei, isso também implica que elementos invisíveis não podem ser clicados. Um caso comum que vejo nas perguntas de estouro de pilha é alguém que está tentando interagir com um elemento da GUI que já existe no DOM, mas só fica visível quando outro elemento foi manipulado. Às vezes isso acontece com os menus suspensos: você precisa primeiro clicar no botão para abrir o menu suspenso antes que um item de menu possa ser selecionado. Se alguém tentar clicar no item de menu antes que o menu seja visível,Se a pessoa tentar fazê-lo com JavaScript, funcionará porque o evento é entregue diretamente ao elemento, independentemente da visibilidade.

Quando você deve usar JavaScript para clicar?

Se você estiver usando o Selenium para testar um aplicativo , minha resposta a esta pergunta é "quase nunca". Em geral, seu teste Selenium deve reproduzir o que um usuário faria com o navegador. Tomando o exemplo do menu suspenso: um teste deve clicar no botão que abre o menu suspenso primeiro e depois clicar no item de menu. Se houver um problema com a GUI porque o botão está invisível ou o botão falha em mostrar os itens de menu ou algo semelhante, seu teste falhará e você detectará o erro. Se você usar o JavaScript para clicar, não poderá detectar esses erros através de testes automatizados.

Digo "quase nunca" porque pode haver exceções em que faz sentido usar JavaScript. Eles devem ser muito raros, no entanto.

Se você estiver usando o Selenium para sites de raspagem , não é tão crítico tentar reproduzir o comportamento do usuário. Portanto, usar o JavaScript para ignorar a GUI é um problema menor.

Louis
fonte
1
Resposta muito melhor, essa deve ser a aceita. A resposta acima está falando sobre um caso específico do PhantomJS, este é um IMHO muito mais preciso.
Ardesco
@Ardesco definitivamente. Marcado como aceito. Perfeito e uma resposta bastante detalhada.
alecxe
Atribuindo a recompensa a Linh como planejado, mas vou começar uma nova para agradecer por mais uma resposta impressionante. Obrigado.
alecxe
1
Resposta muito boa e compreensível. Na minha experiência, o WebDriver teve muitos problemas com as páginas do AngularJS: Com alguns elementos, os métodos do WebDriver gostam clickou sendKeysfuncionaram - mas nem sempre. Não houve problema de localização ou outra exceção, o caso de teste simplesmente não avançou mais. O registro mostrou que a ação foi executada. Às vezes, o uso Actionajudou. Se eu clicar no elemento manualmente (depois que o caso de teste parou), tudo funcionou bem. Então, mudamos para JS bruto nessas ocasiões. Não trabalho com o AngularJS há dois anos, então as coisas podem melhorar agora.
Würgspaß 22/01
1
@ Alex Eu não tenho um link acessível. Minha resposta deriva da experiência com o Selenium em particular e da simulação de eventos do usuário em geral. Comecei a usar o Selenium há 5 anos. Durante o tempo em que usei o Selenium, tive que ler o código do Selenium para corrigir alguns problemas e arquivei alguns relatórios de erros sobre o envio de eventos e discuti os erros com os desenvolvedores do Selenium. "da melhor maneira possível" é o objetivo. Certamente encontrei bugs (agora corrigidos) que o impediam de atingir esse objetivo. Alguns erros podem permanecer.
10248 Louis
30

O clique executado pelo driver tenta simular o comportamento de um usuário real o mais próximo possível, enquanto o JavaScript HTMLElement.click()executa a ação padrão para o clickevento, mesmo que o elemento não seja interativo.

As diferenças são:

  • O driver garante que o elemento seja visível rolando-o para a visualização e verifica se o elemento é interativo .

    O driver irá gerar um erro:

    • quando o elemento no topo nas coordenadas do clique não for o elemento de destino ou um descendente
    • quando o elemento não tiver um tamanho positivo ou se for totalmente transparente
    • quando o elemento é uma entrada ou botão desativado (atributo / propriedade disabledé true)
    • quando o elemento tem o ponteiro do mouse desativado (CSS pointer-eventsé none)


    Um JavaScript HTMLElement.click()sempre executará a ação padrão ou, na melhor das hipóteses, falhará silenciosamente se o elemento estiver desativado.

  • Espera-se que o driver coloque o elemento em foco, se ele puder ser focalizado .

    Um JavaScript HTMLElement.click()não.

  • Espera-se que o driver emita todos os eventos (remoção do mouse, remoção do mouse, mouse, clique, ...) como um usuário real.

    Um JavaScript HTMLElement.click()emite apenas o clickevento. A página pode confiar nesses eventos extras e pode se comportar de maneira diferente se eles não forem emitidos.

    Estes são os eventos emitidos pelo driver para um clique no Chrome:

    mouseover {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mousemove {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mousedown {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mouseup {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    click {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }

    E este é o evento emitido com uma injeção de JavaScript:

    click {target:#topic, clientX:0, clientY:0, isTrusted:false, ... }
  • O evento emitido por um JavaScript .click() não é confiável e a ação padrão não pode ser invocada:

    https://developer.mozilla.org/en/docs/Web/API/Event/isTrusted
    https://googlechrome.github.io/samples/event-istrusted/index.html

    Observe que alguns dos drivers ainda estão gerando eventos não confiáveis. Este é o caso do PhantomJS a partir da versão 2.1.

  • O evento emitido por um JavaScript .click() não possui as coordenadas do clique .

    As propriedades clientX, clientY, screenX, screenY, layerX, layerYestão definidas como 0. A página pode confiar neles e se comportar de maneira diferente.


Pode ser bom usar um JavaScript .click()para descartar alguns dados, mas não está em um contexto de teste. Isso anula o objetivo do teste, pois não simula o comportamento de um usuário. Portanto, se o clique do driver falhar, é provável que um usuário real também não consiga executar o mesmo clique nas mesmas condições.


O que faz com que o driver falhe ao clicar em um elemento quando esperamos que ele seja bem-sucedido?

  • O elemento de destino ainda não é visível / interativo devido a um atraso ou a um efeito de transição.

    Alguns exemplos :

    https://developer.mozilla.org/fr/docs/Web (menu suspenso de navegação) http://materializecss.com/side-nav.html (barra lateral suspensa)

    Soluções alternativas:

    Adicione um garçom para aguardar a visibilidade, um tamanho mínimo ou uma posição estável:

    // wait visible
    browser.wait(ExpectedConditions.visibilityOf(elem), 5000);
    
    // wait visible and not disabled
    browser.wait(ExpectedConditions.elementToBeClickable(elem), 5000);
    
    // wait for minimum width
    browser.wait(function minimumWidth() {
        return elem.getSize().then(size => size.width > 50);
    }, 5000);

    Tente clicar até que seja bem-sucedido:

    browser.wait(function clickSuccessful() {
        return elem.click().then(() => true, (ex) => false);
    }, 5000);

    Adicione um atraso correspondente à duração da animação / transição:

    browser.sleep(250);


  • O elemento de destino acaba coberto por um elemento flutuante uma vez rolado na visualização:

    O driver rola automaticamente o elemento na visualização para torná-lo visível. Se a página contiver um elemento flutuante / fixo (menu, anúncios, rodapé, notificação, política de cookies ..), o elemento poderá acabar coberto e não será mais visível / interativo.

    Exemplo: https://twitter.com/?lang=en

    Soluções alternativas:

    Defina o tamanho da janela como maior para evitar a rolagem ou o elemento flutuante.

    Passe o Ymouse sobre o elemento com um deslocamento negativo e clique nele:

      browser.actions()
         .mouseMove(elem, {x: 0, y: -250})
         .click()
         .perform();

    Role o elemento até o centro da janela antes do clique:

    browser.executeScript(function scrollCenter(elem) {
      var win = elem.ownerDocument.defaultView || window,
        box = elem.getBoundingClientRect(),
        dy = box.top - (win.innerHeight - box.height) / 2;
      win.scrollTo(win.pageXOffset, win.pageYOffset + dy);
    }, element);
    
    element.click();

    Oculte o elemento flutuante se não puder ser evitado:

    browser.executeScript(function scrollCenter(elem) {
      elem.style.display = 'none';
    }, element);
Florent B.
fonte
17

NOTA: vamos chamar 'clique' é clique do usuário final. 'js click' é clique via JS

Por que clicar em "via JavaScript" funciona quando um clique normal no WebDriver não funciona?

Existem 2 casos para que isso aconteça:

I. Se você estiver usando o PhamtomJS

Então este é o comportamento conhecido mais comum de PhantomJS. Alguns elementos às vezes não são clicáveis, por exemplo <div>. Isso ocorre porque o PhantomJSoriginal foi criado para simular o mecanismo dos navegadores (como HTML + CSS inicial -> computação CSS -> renderização). Mas isso não significa que você interaja com o usuário final (visualizando, clicando, arrastando). Portanto, PhamtomJSé apenas parcialmente suportado pela interação dos usuários finais.

POR QUE JS CLICA TRABALHO? Quanto aos cliques, todos são cliques médios. É como uma arma com 1 cano e 2 gatilhos . Um da viewport, um da JS. Como PhamtomJSótimo na simulação do mecanismo do navegador, um clique em JS deve funcionar perfeitamente.

II O manipulador de eventos de "click" foi vinculado no período incorreto.

Por exemplo, temos um <div>

  • -> Fazemos alguns cálculos

  • -> então ligamos o evento click ao <div>.

  • -> Além disso, com alguma codificação incorreta do angular (por exemplo, não manipulando o ciclo do osciloscópio corretamente)

Podemos acabar com o mesmo resultado. O clique não funcionará, porque o WebdriverJS está tentando clicar no elemento quando ele não possui um manipulador de eventos de clique.

POR QUE JS CLICA TRABALHO? O clique em Js é como injetar js diretamente no navegador. Possível de 2 maneiras,

O punho é através do console do devtools (sim, o WebdriverJS se comunica com o console do devtools).

O segundo é injetar uma <script>tag diretamente no HTML.

Para cada navegador, o comportamento será diferente. Mas, independentemente disso, esses métodos são mais complicados do que clicar no botão. O Click está usando o que já existe (clique do usuário final), o js click está passando pelo backdoor.

E para js, o clique parecerá uma tarefa assíncrona. Isso está relacionado a um tópico meio complexo de ' tarefa assíncrona do navegador e agendamento de tarefas da CPU ' (leia-a há algum tempo, não é possível encontrar o artigo novamente). Em resumo, isso resultará principalmente porque o js click precisará aguardar um ciclo de agendamento de tarefas da CPU e será executado um pouco mais devagar após a ligação do evento click. (Você pode conhecer esse caso quando encontrar o elemento algumas vezes clicável, outras vezes não.)

Quando exatamente isso está acontecendo e qual é a desvantagem dessa solução alternativa (se houver)?

=> Como mencionado acima, ambos significam um propósito, mas sobre o uso de qual entrada:

  • Clique: está usando o que fornece por padrão do navegador.
  • JS click: está passando por backdoor.

=> Para desempenho, é difícil dizer porque se baseia em navegadores. Mas geralmente:

  • Clique: não significa mais rápido, mas apenas assinou uma posição superior na lista de agendamento da tarefa de execução da CPU.
  • Clique em JS: não significa mais lento, mas apenas entrou na última posição da lista de agendamento da tarefa da CPU.

=> Desvantagens:

  • Clique: não parece ter nenhuma desvantagem, exceto que você está usando o PhamtomJS.
  • JS click: muito ruim para a saúde. Você pode clicar acidentalmente em algo que não existe na exibição. Ao usar isso, verifique se o elemento está lá e disponível para visualizar e clicar como o ponto de vista do usuário final.

PS se você estiver procurando por uma solução.

  • Usando o PhantomJS? Vou sugerir o uso do Chrome sem cabeça. Sim, você pode configurar o Chrome sem cabeça no Ubuntu. A coisa funciona exatamente como o Chrome, mas apenas não tem uma visualização e menos bugs como o PhantomJS.
  • Não está usando o PhamtomJS, mas ainda está tendo problemas? Vou sugerir o uso de ExpectedCondition of Transferidor com browser.wait()( verifique isso para obter mais informações )

(Quero abreviar, mas acabou mal. Qualquer coisa relacionada à teoria é complicada de explicar ...)

Linh Pham
fonte