Como clicar em um elemento com texto no Puppeteer

86

Existe algum método (não encontrado na API) ou solução para clicar no elemento com texto?

Por exemplo, tenho html:

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

E eu quero clicar no elemento no qual o texto está embrulhado (clique no botão dentro de .elementos), como:

Page.click('Button text', '.elements')
Aleksandr Golubovskij
fonte
2
Compartilhou a resposta aqui: stackoverflow.com/a/47829000/6161265
Md. Abu Taher
Você encontrou a resposta?
Shubham Batra
Se ajudar, a página na qual eu queria executar um clique carregou o jQuery, então eu pude e usei o método de avaliação para executar o código jQuery.
Brandito

Respostas:

77

A principal resposta atual de tokland funciona apenas em nós de texto e não em nós com outros elementos dentro.

Resposta curta

Esta expressão XPath consultará um botão que contém o texto "Texto do botão":

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

Para respeitar também o <div class="elements">entorno dos botões, use o seguinte código:

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

Explicação

Para explicar por que usar o nó de texto ( text()) é errado em alguns casos, vejamos um exemplo:

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

Primeiro, vamos verificar os resultados ao usar contains(text(), 'Text'):

  • //button[contains(text(), 'Start')]retornará os dois nós (como esperado)
  • //button[contains(text(), 'End')]irá retornar apenas um nó (o primeiro), pois text()retorna uma lista com dois textos ( Starte End), mas containsirá verificar apenas o primeiro
  • //button[contains(text(), 'Middle')] voltará resultados como text()não inclui o texto de nós filho

Aqui estão as expressões XPath para contains(., 'Text'), que funcionam no próprio elemento, incluindo seus nós filhos:

  • //button[contains(., 'Start')]retornará os dois botões
  • //button[contains(., 'End')]retornará novamente os dois botões
  • //button[contains(., 'Middle')] retornará um (o último botão)

Portanto, na maioria dos casos, faz mais sentido usar o em .vez de text()em uma expressão XPath.

Thomas Dondorf
fonte
por que esta não é a melhor resposta? boa explicação muito obrigado!
Gianlucca
1
algo que funciona com todo tipo de elemento? não posso saber se o texto está dentro de um botão, ap, div, um span etc.
Andrea Bisello
5
@AndreaBisello Você pode usar no //*[...]lugar.
Thomas Dondorf
90

Você pode usar um seletor XPath com page. $ X (expressão) :

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");
}

Confira clickByTextnesta essência um exemplo completo. Ele cuida do escape de aspas, o que é um pouco complicado com expressões XPath.

Tokland
fonte
Incrível - tentei fazer isso com outras tags, mas não consegui fazer funcionar. (li, h1, ...) Como você faria isso?
Rune Jeppesen
3
@RuneJeppesen Substitua //a[containspor //*[containspara selecionar qualquer elemento, não apenas um aelemento anchor ( ).
Unixmonkey
14

Você também pode usar page.evaluate()para clicar em elementos obtidos document.querySelectorAll()que foram filtrados por conteúdo de texto:

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

Como alternativa, você pode usar page.evaluate()para clicar em um elemento com base em seu conteúdo de texto usando document.evaluate()e uma expressão XPath correspondente:

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
fonte
13

fez uma solução rápida para poder usar seletores css avançados como ": contém (texto)"

então, usando esta biblioteca, você pode apenas

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

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

Aqui está minha solução:

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

A solução é

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

Não há sintaxe de seletor css compatível para seletor de texto ou uma opção de combinador, minha solução para isso seria:

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
fonte
0

Há muitas respostas sugerindo, containsmas não vejo nenhuma motivação para essa imprecisão, dado o caso de uso de OP que, ao que tudo indica, é uma correspondência exata com uma string alvo de "Button text"e um elemento <button>Button text</button>.

Prefiro usar o mais preciso [text()="Button text"], que evita um falso positivo quando o botão é, digamos <button>Button text and more stuff</button>:

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

Isso antecipa o cenário oposto a essa resposta, que tenta ser o mais permissiva possível.

Muitas respostas também não atenderam ao .elementsrequisito da classe pai.

ggorlen
fonte
-1

Eu precisei:

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

Alferd Nobel
fonte