WebDriver click () vs JavaScript click ()

La historia:

Aquí en StackOverflow, he visto usuarios que informan que no pueden hacer clic en un elemento a través del comando “clic” de Selenium WebDriver y que pueden solucionarlo con un clic de JavaScript ejecutando un script.

Ejemplo en Python:

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

Ejemplo en WebDriverJS / Protractor:

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

La pregunta:

¿Por qué hacer clic en “vía JavaScript” funciona cuando un clic normal en WebDriver no lo hace? ¿Cuándo sucede exactamente esto y cuál es el inconveniente de esta solución (si existe)?

Personalmente utilicé esta solución alternativa sin comprender completamente por qué tengo que hacerlo y a qué problemas puede conducir.

Al contrario de lo que sugiere la respuesta actualmente aceptada , no hay nada específico para PhantomJS cuando se trata de la diferencia entre hacer que WebDriver haga un clic y hacerlo en JavaScript.

La diferencia

La diferencia esencial entre los dos métodos es común a todos los navegadores y puede explicarse de manera bastante simple:

  • WebDriver: cuando WebDriver hace el clic, intenta, de la mejor manera posible, simular lo que sucede cuando un usuario real usa el navegador. Supongamos que tiene un elemento A que es un botón que dice “Haga clic en mí” y un elemento B que es un elemento div que es transparente pero tiene sus dimensiones y el zIndex establecido para que cubra completamente A. Luego, le dice a WebDriver que haga clic en A. WebDriver simulará el clic para que B reciba el clic primero . ¿Por qué? Debido a que B cubre A, y si un usuario intentara hacer clic en A, B obtendría el evento primero. Si A finalmente obtendría o no el evento de clic, depende de cómo B maneje el evento. En cualquier caso, el comportamiento con WebDriver en este caso es el mismo que cuando un usuario real intenta hacer clic en A.

  • JavaScript: Ahora, supongamos que utiliza JavaScript para hacer A.click() . Este método de hacer clic no reproduce lo que realmente sucede cuando el usuario intenta hacer clic en A. JavaScript envía el evento de click directamente a A, y B no obtendrá ningún evento.

¿Por qué un clic de JavaScript funciona cuando un clic de WebDriver no lo hace?

Como mencioné anteriormente, WebDriver intentará simular lo mejor posible lo que sucede cuando un usuario real está usando un navegador. El hecho del asunto es que el DOM puede contener elementos con los que un usuario no puede interactuar y WebDriver no le permitirá hacer clic en estos elementos. Además del caso de superposición que mencioné, esto también implica que no se puede hacer clic en los elementos invisibles. Un caso común que veo en las preguntas de desbordamiento de stack es alguien que está tratando de interactuar con un elemento de GUI que ya existe en el DOM pero que se vuelve visible solo cuando se ha manipulado algún otro elemento. Esto sucede a veces con los menús desplegables: primero debe hacer clic en el botón que aparece en el menú desplegable antes de poder seleccionar un elemento del menú. Si alguien intenta hacer clic en el elemento del menú antes de que el menú sea visible, WebDriver rechazará y dirá que el elemento no puede ser manipulado. Si la persona intenta hacerlo con JavaScript, funcionará porque el evento se entrega directamente al elemento, independientemente de la visibilidad.

¿Cuándo debería utilizar JavaScript para hacer clic?

Si está utilizando Selenium para probar una aplicación , mi respuesta a esta pregunta es “casi nunca”. En general, su prueba de Selenium debe reproducir lo que un usuario haría con el navegador. Tomando el ejemplo del menú desplegable: una prueba debe hacer clic en el botón que muestra el menú desplegable primero y luego hacer clic en el elemento del menú. Si hay un problema con la GUI porque el botón es invisible, o si el botón no muestra los elementos del menú, o algo similar, su prueba fallará y habrá detectado el error. Si usa JavaScript para hacer clic, no podrá detectar estos errores mediante pruebas automatizadas.

Digo “casi nunca” porque puede haber excepciones en las que tiene sentido usar JavaScript. Sin embargo, deberían ser muy raros.

Si está utilizando Selenium para raspar sitios , entonces no es tan crítico intentar reproducir el comportamiento del usuario. Por lo tanto, usar JavaScript para evitar la GUI es un problema menor.

El clic ejecutado por el controlador intenta simular el comportamiento de un usuario real lo más cerca posible mientras que HTMLElement.click() JavaScript realiza la acción predeterminada para el evento click , incluso si el elemento no es interactable.

Las diferencias son:

  • El controlador garantiza que el elemento sea ​​visible al desplazarlo hacia la vista y verifica que el elemento sea interactable .

    El controlador generará un error:

    • cuando el elemento en la parte superior en las coordenadas del clic no es el elemento objective o un descendiente
    • cuando el elemento no tiene un tamaño positivo o si es totalmente transparente
    • cuando el elemento es una entrada o botón deshabilitado (el atributo / propiedad disabled es true )
    • cuando el elemento tiene el puntero del mouse desactivado ( pointer-events CSS es none )

    Un JavaScript HTMLElement.click() siempre realizará la acción predeterminada o, en el mejor de los casos, fallará silenciosamente si el elemento está deshabilitado.

  • Se espera que el conductor enfoque el elemento si es enfocable.

    Un JavaScript HTMLElement.click() no lo hará.

  • Se espera que el controlador emita todos los eventos (mousemove, mousedown, mouseup, click, …) al igual que un usuario real.

    Un JavaScript HTMLElement.click() emite solo el evento de click . La página podría depender de estos eventos adicionales y podría comportarse de manera diferente si no se emiten.

    Estos son los eventos emitidos por el controlador para un clic con 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, ... } 

    Y este es el evento emitido con una inyección de JavaScript:

     click {target:#topic, clientX:0, clientY:0, isTrusted:false, ... } 
  • El evento emitido por un JavaScript .click() no es confiable y la acción predeterminada no puede ser invocada:

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

    Tenga en cuenta que algunos de los controladores siguen generando eventos que no son de confianza. Este es el caso de PhantomJS a partir de la versión 2.1.

  • El evento emitido por un JavaScript .click() no tiene las coordenadas del clic .

    Las propiedades clientX, clientY, screenX, screenY, layerX, layerY se establecen en 0 . La página podría confiar en ellos y podría comportarse de manera diferente.

Puede estar bien usar un .click() JavaScript para eliminar algunos datos, pero no está en un contexto de prueba. Derrota el propósito de la prueba ya que no simula el comportamiento de un usuario. Por lo tanto, si el clic del controlador falla, lo más probable es que un usuario real no realice el mismo clic en las mismas condiciones.

¿Qué hace que el controlador no haga clic en un elemento cuando esperamos que tenga éxito?

  • El elemento seleccionado aún no es visible / interactable debido a un retraso o un efecto de transición.

    Algunos ejemplos :

    https://developer.mozilla.org/fr/docs/Web (menú de navegación desplegable) http://materializecss.com/side-nav.html (barra lateral desplegable)

    Campos de trabajo:

    Agregue un camarero para esperar la visibilidad, un tamaño mínimo o una posición estable:

     // 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); 

    Vuelva a intentar hacer clic hasta que tenga éxito:

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

    Agregue un retraso que coincida con la duración de la animación / transición:

     browser.sleep(250); 

  • El elemento objective termina cubierto por un elemento flotante una vez que se desplaza en la vista:

    El controlador desplaza automáticamente el elemento en la vista para hacerlo visible. Si la página contiene un elemento flotante / adhesivo (menú, anuncios, pie de página, notificación, política de cookies …), el elemento puede quedar cubierto y ya no será visible / interactable.

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

    Soluciones:

    Establezca el tamaño de la ventana en uno más grande para evitar el desplazamiento o el elemento flotante.

    Mueva sobre el elemento con un desplazamiento negativo de Y y luego haga clic en él:

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

    Desplace el elemento hacia el centro de la ventana antes del clic:

     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(); 

    Oculta el elemento flotante si no se puede evitar:

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

NOTA: llamemos ‘clic’ es clic del usuario final. ‘js click’ es click a través de JS

¿Por qué hacer clic en “vía JavaScript” funciona cuando un clic normal en WebDriver no lo hace?

Hay 2 casos para que esto suceda:

I. Si estas usando PhamtomJS

Entonces este es el comportamiento conocido más común de PhantomJS . Algunos elementos a veces no se pueden hacer clic, por ejemplo

. Esto se debe a que PhantomJS fue PhantomJS originalmente para simular el motor de los navegadores (como HTML + CSS inicial -> computar CSS -> renderizado). Pero no significa que se pueda interactuar como usuario final (ver, hacer clic, arrastrar). Por PhamtomJS tanto, PhamtomJS solo es parcialmente compatible con la interacción del usuario final.

¿POR QUÉ FUNCIONA JS CLICK? En cuanto a cualquiera de los clics, todos son clic medio. Es como una pistola con 1 cañón y 2 disparadores . Uno desde la vista, uno desde JS. Dado que PhamtomJS excelente para simular el motor del navegador, un clic JS debería funcionar perfectamente.

II. El controlador de eventos de “clic” tiene que vincularse en el mal período de tiempo.

Por ejemplo tenemos un

  • -> Hacemos algunos cálculos

  • -> Luego enlazamos el evento de clic a la

    .

  • -> Más con un poco de mala encoding de angular (por ejemplo, no se maneja el ciclo de scope de forma popular)

Podemos terminar con el mismo resultado. El clic no funciona, porque WebdriverJS intenta hacer clic en el elemento cuando no tiene un controlador de eventos de clic.

¿POR QUÉ FUNCIONA JS CLICK? Js click es como inyectar js directamente en el navegador. Posible con 2 maneras,

El puño es a través de la consola de devtools (sí, WebdriverJS se comunica con la consola de devtools).

El segundo es inyectar una etiqueta directamente en html.

Para cada navegador el comportamiento será la diferencia. Pero independientemente, estos métodos son más complicados que hacer clic en el botón. Click está usando lo que ya está allí (click del usuario final), js click está pasando por backdoor.

Y para js click parecerá ser una tarea asíncrona. Esto se relaciona con un tema un poco complejo de " tarea de asynchronus del navegador y progtwigción de tareas de la CPU " (léalo un rato, no puedo encontrar el artículo de nuevo). Para abreviar, esto se producirá principalmente ya que js click deberá esperar un ciclo de progtwigción de tareas de la CPU y se ejecutará un poco más lento después de la vinculación del evento click. (Podría conocer este caso cuando encontró el elemento a veces pulsable, a veces no).

¿Cuándo sucede exactamente esto y cuál es el inconveniente de esta solución (si existe)?

=> Como se mencionó anteriormente, ambos significan para un propósito, pero sobre el uso de qué entrada:

  • Haga clic en: está utilizando lo que proporciona por defecto del navegador.
  • JS click: está pasando por backdoor.

=> Para el rendimiento, es difícil decirlo porque se basa en los navegadores. Pero genéricamente:

  • Haga clic en: no significa más rápido, sino que solo firmó una posición más alta en la lista de horarios de la tarea de ejecución de CPU.
  • JS click: no significa más lento, sino que solo inició sesión en la última posición de la lista de tareas de la CPU.

=> Desventajas:

  • Haga clic en: no parece tener ningún inconveniente, excepto que está utilizando PhamtomJS.
  • JS clic: muy malo para la salud. Accidentalmente puede hacer clic en algo que no aparece en la vista. Cuando use esto, asegúrese de que el elemento esté allí y esté disponible para ver y haga clic como el punto de vista del usuario final.

PD si buscas solución.

  • ¿Usando PhantomJS? Sugeriré usar Chrome sin cabeza en su lugar. Sí, puedes instalar Chrome sin cabeza en Ubuntu. Funciona igual que Chrome, pero no tiene una vista y tiene menos errores como PhantomJS.
  • ¿No está utilizando PhamtomJS pero sigue teniendo problemas? Sugeriré usar ExpectedCondition of Protractor con browser.wait() ( verifique esto para obtener más información )

(Quiero que sea breve, pero terminé mal. Cualquier cosa relacionada con la teoría es complicada de explicar ...)