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')
Aleksandr Golubovskij
fuente
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.

Thomas Dondorf
fuente
¿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.

tokland
fuente
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();
});
Grant Miller
fuente
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()
user3493381
fuente
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
            }
        })
    });
Kamen
fuente
4

La solucion es

(await page.$$eval(selector, a => a
            .filter(a => a.textContent === 'target text')
))[0].click()
Alex A
fuente
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  
        }
    });
Ekeuwei
fuente
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.

ggorlen
fuente
-1

Tuve que:

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

Alferd Nobel
fuente