Saltar al contenido

¿Cuál es la diferencia entre cssSelector y Xpath y cuál es mejor con respecto al rendimiento para las pruebas entre navegadores?

Nuestro team de especialistas pasados algunos días de investigación y de juntar de información, obtuvieron la solución, queremos que te sea de utilidad en tu proyecto.

Solución:

Los selectores de CSS funcionan mucho mejor que Xpath y están bien documentados en la comunidad de Selenium. Aquí hay algunas razones

  • Los motores XPath son diferentes en cada navegador, por lo tanto, los hacen inconsistentes
  • IE no tiene un motor xpath nativo, por lo tanto, selenium inyecta su propio motor xpath para la compatibilidad de su API. Por lo tanto, perdemos la ventaja de utilizar las funciones nativas del navegador que WebDriver promueve de forma inherente.
  • Xpath tiende a volverse complejo y, por lo tanto, es difícil de leer en mi opinión.

Sin embargo, hay algunas situaciones en las que necesita usar xpath, por ejemplo, buscando un elemento padre o buscando un elemento por su texto (no recomendaría la última).

Puedes leer el blog de Simon aquí. También recomienda CSS sobre Xpath.

Si está probando contenido, no utilice selectores que dependan del contenido de los elementos. Será una pesadilla de mantenimiento para todos los lugares. Intente hablar con los desarrolladores y use técnicas que ellos usaron para externalizar el texto en la aplicación, como diccionarios o paquetes de recursos, etc. Aquí está mi blog que lo explica en detalle.

editar 1

Gracias a @parishodak, aquí está el enlace que proporciona los números que demuestran que el rendimiento de CSS es mejor

Voy a sostener la impopular opinión de la etiqueta de selenio SO que XPath es preferible a CSS a largo plazo.

Esta publicación larga tiene dos secciones: primero pondré una prueba de que la diferencia de rendimiento entre las dos es 0,1-0,3 milisegundos(sí; eso es 100 microsegundos), y luego compartiré mi opinión por qué XPath es más poderoso.


Diferencia de rendimiento

Primero abordemos “el elefante en la habitación”, que xpath es más lento que css.

Con la potencia actual de la CPU (lea: cualquier cosa x86 producida desde 2013), incluso en máquinas virtuales browserstack / saucelabs / aws, y el desarrollo de los navegadores (lea: todos los populares en los últimos 5 años) ese no es el caso. Los motores del navegador se han desarrollado, el soporte de xpath es uniforme, IE está fuera de escena (con suerte para la mayoría de nosotros). Esta comparación en la otra respuesta se cita en todas partes, pero es muy contextual: ¿cuántos están ejecutando, o les importa, la automatización contra IE8?

Si hay una diferencia, es en un fracción de milisegundo.

Sin embargo, la mayoría de los marcos de alto nivel agregan al menos 1 ms de sobrecarga sobre la llamada de selenio sin procesar de todos modos (envoltorios, controladores, almacenamiento de estado, etc.); mi arma personal de elección, RobotFramework, agrega al menos 2 ms, que estoy más que feliz de sacrificar por lo que proporciona. Un viaje de ida y vuelta de red desde un AWS us-east-1 al hub de BrowserStack suele ser 11 milisegundos.

Entonces, con los navegadores remotos, si hay una diferencia entre xpath y css, todo lo demás la eclipsa, en órdenes de magnitud.


Las medidas

No hay tantas comparaciones públicas (Realmente he visto solo el citado), entonces, aquí hay un caso simple, ficticio y simple.
Localizará un elemento por las dos estrategias X veces y comparará el tiempo promedio para eso.

El objetivo: la página de inicio de BrowserStack y su botón “Registrarse”; una captura de pantalla del html al escribir esta publicación:

ingrese la descripción de la imagen aquí

Aquí está el código de prueba (python):

from selenium import webdriver
import timeit


if __name__ == '__main__':

    xpath_locator = '//div[@class="button-section col-xs-12 row"]'
    css_locator = 'div.button-section.col-xs-12.row'

    repetitions = 1000

    driver = webdriver.Chrome()
    driver.get('https://www.browserstack.com/')

    css_time = timeit.timeit("driver.find_element_by_css_selector(css_locator)", 
                             number=repetitions, globals=globals())
    xpath_time = timeit.timeit('driver.find_element_by_xpath(xpath_locator)', 
                             number=repetitions, globals=globals())

    driver.quit()

    print("css total time  repeats: :.2fs, per find: :.2fms".
          format(repetitions, css_time, (css_time/repetitions)*1000))
    print("xpath total time for  repeats: :.2fs, per find: :.2fms".
          format(repetitions, xpath_time, (xpath_time/repetitions)*1000))

Para aquellos que no están familiarizados con Python, abre la página y encuentra el elemento, primero con el localizador css, luego con el xpath; la operación de búsqueda se repite 1.000 veces. La salida es el tiempo total en segundos para las 1,000 repeticiones y el tiempo promedio para un hallazgo en milisegundos.

Los localizadores son:

  • para xpath – “un elemento div que tiene este valor de clase exacto, en algún lugar del DOM”;
  • el CSS es similar: “un elemento div con esta clase, en algún lugar del DOM”.

Elegido deliberadamente para no estar sobreajustado; Además, el selector de clases se cita para el CSS como “el segundo más rápido después de una identificación”.

El entorno: Chrome v66.0.3359.139, chromedriver v2.38, cpu: ULV Core M-5Y10 que normalmente se ejecuta a 1,5 GHz (sí, uno de “procesamiento de texto”, ni siquiera una bestia i7 normal).

Aquí está el resultado:

css total time 1000 repeats: 8.84s, per find: 8.84ms

xpath total time for 1000 repeats: 8.52s, per find: 8.52ms

Obviamente, los tiempos por búsqueda están bastante cerca; la diferencia es 0,32milisegundos. No salte “el xpath es más rápido” – a veces lo es, a veces es css.


Probemos con otro conjunto de localizadores, un poco más complicado: un atributo que tiene una subcadena (enfoque común al menos para mí, ir tras la clase de un elemento cuando una parte tiene un significado funcional):

xpath_locator = '//div[contains(@class, "button-section")]'
css_locator = 'div[class~=button-section]'

Los dos localizadores son nuevamente semánticamente iguales – “encuentra un elemento div que tenga en su atributo de clase esta subcadena”.
Aquí están los resultados:

css total time 1000 repeats: 8.60s, per find: 8.60ms

xpath total time for 1000 repeats: 8.75s, per find: 8.75ms

Diferencia de 0,15 ms.


Como ejercicio, la misma prueba que se hizo en el blog vinculado en los comentarios / otra respuesta, la página de prueba es pública, al igual que el código de prueba.

Están haciendo un par de cosas en el código: hacer clic en una columna para ordenar por ella, luego obtener los valores y verificar que la clasificación de la interfaz de usuario sea correcta.
Lo cortaré, después de todo, solo obtenga los localizadores, esta es la prueba de raíz, ¿verdad?

El mismo código que el anterior, con estos cambios en:

  • La URL es ahora http://the-internet.herokuapp.com/tables; hay 2 pruebas.

  • Los localizadores para el primero – “Búsqueda de elementos por ID y clase” – son:

css_locator = '#table2 tbody .dues'
xpath_locator = "//table[@id='table2']//tr/td[contains(@class,'dues')]"

Y aquí está el resultado:

css total time 1000 repeats: 8.24s, per find: 8.24ms

xpath total time for 1000 repeats: 8.45s, per find: 8.45ms

Diferencia de 0,2 milisegundos.

El “Encontrar elementos atravesando”:

css_locator = '#table1 tbody tr td:nth-of-type(4)'
xpath_locator = "//table[@id='table1']//tr/td[4]"

El resultado:

css total time 1000 repeats: 9.29s, per find: 9.29ms

xpath total time for 1000 repeats: 8.79s, per find: 8.79ms

Esta vez es 0,5 ms (a la inversa, xpath resultó “más rápido” aquí).

Entonces, 5 años después (mejores motores de navegadores) y centrándose solo en el rendimiento de los localizadores (sin acciones como ordenar en la interfaz de usuario, etc.), el mismo banco de pruebas: prácticamente no hay diferencia entre CSS y XPath.


Entonces, de xpath y css, ¿cuál de los dos elegir para el rendimiento? La respuesta es simple: elija localizar por id.

En pocas palabras, si la identificación de un elemento es única (como se supone que debe ser de acuerdo con las especificaciones), su valor juega un papel importante en la representación interna del DOM del navegador y, por lo tanto, suele ser el más rápido.

Sin embargo, único y constante (por ejemplo, no generado automáticamente) Los identificadores no siempre están disponibles, lo que nos lleva a “¿por qué XPath si hay CSS?”


La ventaja de XPath

Con el rendimiento fuera de escena, ¿por qué creo que xpath es mejor? Simple: versatilidad y potencia.

Xpath es un lenguaje desarrollado para trabajar con documentos XML; como tal, permite construcciones mucho más poderosas que css.
Por ejemplo, navegación en todas las direcciones en el árbol: busque un elemento, luego vaya a su abuelo y busque un hijo que tenga ciertas propiedades.
Permite condiciones booleanas incrustadas: cond1 and not(cond2 or not(cond3 and cond4)); selectores incrustados: “busque un div que tenga estos hijos con estos atributos y luego navegue de acuerdo con él”.
XPath permite la búsqueda basada en el valor de un nodo (su texto); por muy mal vista que esté esta práctica, resulta útil, especialmente en documentos mal estructurados. (no hay atributos definidos para pisar, como identificadores dinámicos y clases; ubique el elemento por su contenido de texto).

El paso a CSS es definitivamente más fácil: uno puede comenzar a escribir selectores en cuestión de minutos; pero después de un par de días de uso, el poder y las posibilidades de xpath supera rápidamente a css.
Y puramente subjetivo: un CSS complejo es mucho más difícil de leer que una expresión xpath compleja.

Outro;)

Finalmente, de nuevo muy subjetivo, ¿cuál elegir?

En mi opinión, no hay una elección correcta o incorrecta: son soluciones diferentes para el mismo problema y se debe elegir la que sea más adecuada para el trabajo.

Siendo “un fan” de XPath no soy tímido para usar en mis proyectos una combinación de ambos – diablos, a veces es mucho más rápido lanzar un CSS, si sé que funcionará bien.

El debate entre cssSelector vs XPath permanecería como uno de los debates más subjetivos en el Comunidad de selenio. Lo que ya sabemos hasta ahora se puede resumir en:

  • Gente a favor de cssSelector dicen que es más legible y más rápido (especialmente cuando se ejecuta contra Internet Explorer).
  • Mientras que los partidarios de XPath promociona su capacidad para atravesar la página (mientras cssSelector no poder).
  • Atravesar el DOM en navegadores más antiguos como IE8 no funciona con cssSelector pero esta bien con XPath.
  • XPath puede subir el DOM (por ejemplo, de niño a padre), mientras que cssSelector solo puede atravesar el DOM (por ejemplo, de padre a hijo)
  • Sin embargo, no poder atravesar el DOM con cssSelector en los navegadores más antiguos no es necesariamente algo malo, ya que es más un indicador de que su página tiene un diseño deficiente y podría beneficiarse de un marcado útil.
  • Ben Burton menciones que deberías usar cssSelector porque así es como se crean las aplicaciones. Esto hace que las pruebas sean más fáciles de escribir, hablar y hacer que otros ayuden a mantenerlas.
  • Adam Goucher dice adoptar un enfoque más híbrido, centrándose primero en las identificaciones, luego cssSelectory aprovechando XPath solo cuando lo necesite (por ejemplo, subiendo por el DOM) y eso XPath siempre será más potente para localizadores avanzados.

Dave Haeffner llevó a cabo un prueba sobre una página con dos tablas de datos HTML, una tabla está escrita sin atributos útiles (IDENTIFICACIÓN y Clase), y el otro con ellos. yo tengo analizó el procedimiento de prueba y el resultado de este experimento en detalles en la discusión ¿Por qué debería usar cssSelector selectores en lugar de XPath para pruebas automatizadas ?. Si bien este experimento demostró que cada estrategia de localización es razonablemente equivalente en todos los navegadores, no nos mostró el panorama completo de manera adecuada. Dave Haeffner en la otra discusión Css Vs. X Path, Under a Microscope mencionado, en una prueba de extremo a extremo había muchas otras variables en juego Inicio de salsa, Inicio del navegador, y latencia hacia y desde la aplicación bajo prueba. La conclusión desafortunada de ese experimento podría ser que un conductor puede ser más rápido que el otro (p. Ej. ES DECIR vs Firefox), cuando de hecho, ese no fue el caso en absoluto. Para tener una idea real de cuál es la diferencia de rendimiento entre cssSelector y XPath, necesitábamos profundizar más. Lo hicimos ejecutando todo desde una máquina local mientras usábamos una utilidad de evaluación comparativa de rendimiento. También nos enfocamos en una acción específica de Selenium en lugar de la ejecución de prueba completa, y ejecutamos cosas en numerosas ocasiones. He analizado el especifico procedimiento de prueba y el resultado de este experimento en detalle en la discusión cssSelector vs XPath para selenio. Pero a las pruebas todavía les faltaba un aspecto, es decir más cobertura del navegador (por ejemplo, Internet Explorer 9 y 10) y pruebas en una página más grande y profunda.

Dave Haeffner en otra discusión Css Vs. X Path, Under a Microscope (Parte 2) menciona, para asegurarnos de que los puntos de referencia requeridos estén cubiertos de la mejor manera posible, debemos considerar un ejemplo que demuestre una página grande y profunda.


Configuración de prueba

Para demostrar este ejemplo detallado, se configuró una máquina virtual con Windows XP y se instaló Ruby (1.9.3). También se instalaron todos los navegadores disponibles y sus controladores de navegador equivalentes para Selenium. Para la evaluación comparativa, la biblioteca estándar de Ruby benchmark se utilizó.


Código de prueba

require_relative 'base'
require 'benchmark'

class LargeDOM < Base

  LOCATORS = 
    nested_sibling_traversal: 
      css: "div#siblings > div:nth-of-type(1) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3)",
      xpath: "//div[@id='siblings']/div[1]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]"
    ,
    nested_sibling_traversal_by_class: 
      css: "div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1",
      xpath: "//div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]"
    ,
    table_header_id_and_class: 
      css: "table#large-table thead .column-50",
      xpath: "//table[@id='large-table']//thead//*[@class='column-50']"
    ,
    table_header_id_class_and_direct_desc: 
      css: "table#large-table > thead .column-50",
      xpath: "//table[@id='large-table']/thead//*[@class='column-50']"
    ,
    table_header_traversing: 
      css: "table#large-table thead tr th:nth-of-type(50)",
      xpath: "//table[@id='large-table']//thead//tr//th[50]"
    ,
    table_header_traversing_and_direct_desc: 
      css: "table#large-table > thead > tr > th:nth-of-type(50)",
      xpath: "//table[@id='large-table']/thead/tr/th[50]"
    ,
    table_cell_id_and_class: 
      css: "table#large-table tbody .column-50",
      xpath: "//table[@id='large-table']//tbody//*[@class='column-50']"
    ,
    table_cell_id_class_and_direct_desc: 
      css: "table#large-table > tbody .column-50",
      xpath: "//table[@id='large-table']/tbody//*[@class='column-50']"
    ,
    table_cell_traversing: 
      css: "table#large-table tbody tr td:nth-of-type(50)",
      xpath: "//table[@id='large-table']//tbody//tr//td[50]"
    ,
    table_cell_traversing_and_direct_desc: 
      css: "table#large-table > tbody > tr > td:nth-of-type(50)",
      xpath: "//table[@id='large-table']/tbody/tr/td[50]"
    
  

  attr_reader :driver

  def initialize(driver)
    @driver = driver
    visit '/large'
    is_displayed?(id: 'siblings')
    super
  end

  # The benchmarking approach was borrowed from
  # http://rubylearning.com/blog/2013/06/19/how-do-i-benchmark-ruby-code/
  def benchmark
    Benchmark.bmbm(27) do |bm|
      LOCATORS.each do |example, data|
    data.each do |strategy, locator|
      bm.report(example.to_s + " using " + strategy.to_s) do
        begin
          ENV['iterations'].to_i.times do |count|
         find(strategy => locator)
          end
        rescue Selenium::WebDriver::Error::NoSuchElementError => error
          puts "( 0.0 )"
        end
      end
    end
      end
    end
  end

end

Resultados

NOTA: La salida está en segundos y los resultados son para el tiempo de ejecución total de 100 ejecuciones.

En forma de tabla:

css_xpath_under_microscopev2

En forma de gráfico:

  • Cromo:

gráfico-cromo

  • Firefox:

chart-firefox

  • Internet Explorer 8:

gráfico-ie8

  • Internet Explorer 9:

gráfico-ie9

  • Internet Explorer 10:

gráfico-ie10

  • Ópera:

chart-opera


Analizando los resultados

  • Chrome y Firefox están claramente sintonizados para una mayor rapidez cssSelector rendimiento.
  • Internet Explorer 8 es una bolsa de sorpresas cssSelector eso no funcionará, fuera de control XPath recorrido que toma ~ 65 segundos, y un recorrido de tabla de 38 segundos sin cssSelector resultado para compararlo.
  • En IE 9 y 10, XPath es más rápido en general. En Safari, es un lanzamiento, excepto por un par de recorridos más lentos con XPath. Y en casi todos los navegadores, el recorrido de hermanos anidado y el recorrido de celda de tabla realizado con XPath son una operación cara.
  • Estos no deberían ser tan sorprendentes ya que los localizadores son frágiles e ineficientes y debemos evitarlos.

Resumen

  • En general, hay dos circunstancias en las que XPath es notablemente más lento que cssSelector. Pero son fácilmente evitables.
  • La diferencia de rendimiento está ligeramente a favor de los selectores css para navegadores que no son IE y ligeramente a favor de xpath para navegadores IE.

Trivialidades

Puede realizar la evaluación comparativa por su cuenta, utilizando esta biblioteca donde Dave Haeffner envuelto todo el código.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *