Cómo hacer clic en un elemento con texto en Puppeteer


86

¿Existe algún método (no se encontró en la API) o solución para hacer clic en el elemento con texto?

Por ejemplo, tengo html:

<div class="elements">
    <button>Button text</button>
    <a href=#>Href text</a>
    <div>Div text</div>
</div>

Y quiero hacer clic en el elemento en el que está envuelto el texto (haga clic en el botón dentro de .elements), como:

Page.click('Button text', '.elements')

2
Compartí la respuesta aquí: stackoverflow.com/a/47829000/6161265
Md. Abu Taher

¿Encontraste la respuesta?
Shubham Batra

Si ayuda, la página en la que quería hacer un clic tiene jQuery cargado, por lo que pude y usé el método de evaluación para ejecutar el código jQuery.
Brandito

Respuestas:


78

La respuesta principal actual de tokland solo funciona en nodos de texto y no en nodos con otros elementos en su interior.

Respuesta corta

Esta expresión XPath consultará un botón que contiene el texto "Texto del botón":

const [button] = await page.$x("//button[contains(., 'Button text')]");
if (button) {
    await button.click();
}

Para respetar también el <div class="elements">entorno de los botones, utilice el siguiente código:

const [button] = await page.$x("//div[@class='elements']/button[contains(., 'Button text')]");

Explicación

Para explicar por qué el uso del nodo de texto ( text()) es incorrecto en algunos casos, veamos un ejemplo:

<div>
    <button>Start End</button>
    <button>Start <em>Middle</em> End</button>
</div>

Primero, verifiquemos los resultados al usar contains(text(), 'Text'):

  • //button[contains(text(), 'Start')]devolverá ambos dos nodos (como se esperaba)
  • //button[contains(text(), 'End')]solo devolverá un nodo (el primero) como text()devuelve una lista con dos textos ( Starty End), pero containssolo comprobará el primero
  • //button[contains(text(), 'Middle')] volverá no hay resultados que text()no incluye el texto de los nodos secundarios

Aquí están las expresiones XPath para contains(., 'Text'), que funcionan en el propio elemento, incluidos sus nodos secundarios:

  • //button[contains(., 'Start')]devolverá los dos botones
  • //button[contains(., 'End')]volverá a devolver ambos dos botones
  • //button[contains(., 'Middle')] devolverá uno (el último botón)

Entonces, en la mayoría de los casos, tiene más sentido usar el en .lugar de text()en una expresión XPath.


¿Por qué esta no es la respuesta principal? bonita explicación muchas gracias!
Gianlucca

1
algo que funcione con todo tipo de elemento? No puedo saber si el texto está dentro de un botón, ap, div, span, etc.
Andrea Bisello

5
@AndreaBisello Puedes usar //*[...]en su lugar.
Thomas Dondorf

90

Puede usar un selector XPath con page. $ X (expresión) :

const linkHandlers = await page.$x("//a[contains(text(), 'Some text')]");

if (linkHandlers.length > 0) {
  await linkHandlers[0].click();
} else {
  throw new Error("Link not found");
}

Echa un vistazo a clickByTextesta esencia para ver un ejemplo completo. Se encarga de escapar de las comillas, lo cual es un poco complicado con las expresiones XPath.


Impresionante: intenté hacerlo con otras etiquetas, pero no puedo hacerlo funcionar. (li, h1, ...) ¿Cómo harías eso?
Rune Jeppesen

3
@RuneJeppesen Reemplazar //a[containscon //*[containspara seleccionar cualquier elemento, no solo un aelemento anchor ( ).
Unixmonkey

14

También puede utilizar page.evaluate()para hacer clic en elementos obtenidos de los document.querySelectorAll()que se han filtrado por contenido de texto:

await page.evaluate(() => {
  [...document.querySelectorAll('.elements button')].find(element => element.textContent === 'Button text').click();
});

Alternativamente, puede usar page.evaluate()para hacer clic en un elemento en función de su contenido de texto usando document.evaluate()y una expresión XPath correspondiente:

await page.evaluate(() => {
  const xpath = '//*[@class="elements"]//button[contains(text(), "Button text")]';
  const result = document.evaluate(xpath, document, null, XPathResult.ANY_TYPE, null);

  result.iterateNext().click();
});

13

hizo una solución rápida para poder usar selectores avanzados de CSS como ": contiene (texto)"

así que usando esta biblioteca puedes

const select = require ('puppeteer-select');

const element = await select(page).getElement('button:contains(Button text)');
await element.click()

4

Aquí está mi solución:

let selector = 'a';
    await page.$$eval(selector, anchors => {
        anchors.map(anchor => {
            if(anchor.textContent == 'target text') {
                anchor.click();
                return
            }
        })
    });

4

La solucion es

(await page.$$eval(selector, a => a
            .filter(a => a.textContent === 'target text')
))[0].click()

0

No hay una sintaxis de selector css compatible para el selector de texto o una opción de combinador, mi solución para esto sería:

await page.$$eval('selector', selectorMatched => {
    for(i in selectorMatched)
      if(selectorMatched[i].textContent === 'text string'){
          selectorMatched[i].click();
          break;//Remove this line (break statement) if you want to click on all matched elements otherwise the first element only is clicked  
        }
    });

0

Hay muchas respuestas que sugieren, containspero no veo ninguna motivación para esta imprecisión dado el caso de uso de OP que, según todas las apariencias, es una coincidencia exacta con una cadena de destino de "Button text"y un elemento <button>Button text</button>.

Preferiría usar el más preciso [text()="Button text"], que evita un falso positivo cuando el botón es, digamos <button>Button text and more stuff</button>,:

const [el] = await page.$x('//*[@class="elements"]//a[text()="Button text"]');
el && (await el.click());

Esto anticipa el escenario opuesto de esta respuesta que intenta ser lo más permisiva posible.

Muchas respuestas también perdieron el .elementsrequisito de la clase para padres.


-1

Tuve que:

await this.page.$eval(this.menuSelector, elem => elem.click());

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.