Come fare clic su un elemento con testo in Puppeteer


91

Esiste un metodo (non trovato nell'API) o una soluzione per fare clic su un elemento con testo?

Ad esempio ho html:

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

E voglio fare clic sull'elemento in cui è disposto il testo (fare clic sul pulsante all'interno di .elements), come:

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

2
Ho condiviso la risposta qui: stackoverflow.com/a/47829000/6161265
Md. Abu Taher

Hai trovato risposta?
Shubham Batra

Se aiuta, la pagina su cui volevo eseguire un clic ha jQuery caricato, quindi sono stato in grado di utilizzare il metodo di valutazione per eseguire il codice jQuery.
Brandito

Risposte:


84

Risposta breve

Questa espressione XPath interrogherà un pulsante che contiene il testo "Button text":

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

Per rispettare anche l' <div class="elements">ambiente circostante i pulsanti, utilizzare il codice seguente:

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

Spiegazione

Per spiegare perché text()in alcuni casi l'uso del text node ( ) è sbagliato, diamo un'occhiata a un esempio:

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

Innanzitutto, controlliamo i risultati quando si utilizza contains(text(), 'Text'):

  • //button[contains(text(), 'Start')]restituirà entrambi i due nodi (come previsto)
  • //button[contains(text(), 'End')]restituirà solo un nodo (il primo) come text()restituisce una lista con due testi ( Start e End), macontains controllerà solo il primo
  • //button[contains(text(), 'Middle')] non restituirà alcun risultato in quanto text()non include il testo dei nodi figli

Ecco le espressioni XPath per contains(., 'Text'), che funziona sull'elemento stesso, inclusi i suoi nodi figlio:

  • //button[contains(., 'Start')]restituirà entrambi i due pulsanti
  • //button[contains(., 'End')]restituirà nuovamente entrambi i due pulsanti
  • //button[contains(., 'Middle')] restituirà uno (l'ultimo pulsante)

Quindi, nella maggior parte dei casi, ha più senso usare .invece che text()in un'espressione XPath.


1
qualcosa che funziona con ogni tipo di elemento? non so se il testo è all'interno di un pulsante, ap, div, span ecc.
Andrea Bisello

6
@AndreaBisello Puoi usare //*[...]invece.
Thomas Dondorf

92

Puoi usare un selettore XPath con page. $ X (espressione) :

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

Dai un'occhiata clickByTexta questa sintesi per un esempio completo. Si occupa di sfuggire alle virgolette, il che è un po 'complicato con le espressioni XPath.


Fantastico: ho provato a farlo per altri tag, ma non riesco a farlo funzionare. (li, h1, ...) Come lo faresti?
Rune Jeppesen

3
@RuneJeppesen Sostituisci //a[containscon //*[containsper selezionare qualsiasi elemento, non solo un aelemento anchor ( ).
Unixmonkey

17

Puoi anche utilizzare page.evaluate()per fare clic su elementi ottenuti da document.querySelectorAll()che sono stati filtrati per contenuto di testo:

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

In alternativa, puoi utilizzare page.evaluate()per fare clic su un elemento in base al suo contenuto di testo utilizzando document.evaluate()e un'espressione XPath corrispondente:

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

14

fatto una soluzione rapida per poter utilizzare selettori CSS avanzati come ": contains (text)"

quindi usando questa libreria puoi semplicemente

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

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

5

Ecco la mia soluzione:

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

5

La soluzione è

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

1

Non esiste una sintassi del selettore CSS supportata per il selettore di testo o un'opzione combinatore, la mia soluzione sarebbe:

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

1

Ci sono molte risposte che suggeriscono, containsma non vedo alcuna motivazione per questa imprecisione dato il caso d'uso di OP che, a quanto pare, è una corrispondenza esatta con una stringa di destinazione di "Button text"e un elemento <button>Button text</button>.

Preferirei usare il più preciso [text()="Button text"], che evita un falso positivo quando il pulsante è, diciamo <button>Button text and more stuff</button>,:

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

Questo anticipa lo scenario opposto di questa risposta che cerca di essere il più permissiva possibile.

Molte risposte mancavano anche del .elementsrequisito della classe genitore.


-1

Dovevo:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.