Jak kliknąć element z tekstem w Puppeteer

86

Czy jest jakaś metoda (nie znaleziona w API) lub rozwiązanie, aby kliknąć element z tekstem?

Na przykład mam html:

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

I chcę kliknąć element, w którym zawinięty jest tekst (kliknij przycisk wewnątrz .elementów), na przykład:

Page.click('Button text', '.elements')
Aleksandr Golubovskij
źródło
2
Tutaj udostępniliśmy odpowiedź: stackoverflow.com/a/47829000/6161265
Md. Abu Taher
Czy znalazłeś odpowiedź?
Shubham Batra
Jeśli to pomaga, strona, na którą chciałem kliknąć, ma załadowany jQuery, więc mogłem i użyłem metody oceny, aby wykonać kod jQuery.
Brandito

Odpowiedzi:

78

Aktualnym top odpowiedź przez tokland działa tylko na węzłach tekstowych, a nie na węzłach z innymi elementami wnętrza.

Krótka odpowiedź

To wyrażenie XPath będzie sprawdzać przycisk zawierający tekst „Tekst przycisku”:

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

Aby również uszanować <div class="elements">otoczenie przycisków, użyj następującego kodu:

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

Wyjaśnienie

Aby wyjaśnić, dlaczego text()w niektórych przypadkach używanie węzła tekstowego ( ) jest nieprawidłowe, spójrzmy na przykład:

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

Najpierw sprawdźmy wyniki przy użyciu contains(text(), 'Text'):

  • //button[contains(text(), 'Start')]powróci zarówno dwa węzły (prawidłowo)
  • //button[contains(text(), 'End')]zwróci tylko jeden węzeł (pierwszy) jako text()zwraca listę z dwoma tekstami ( Starti End), ale containssprawdzi tylko pierwszy
  • //button[contains(text(), 'Middle')] nie zwróci żadnych wyników, ponieważ text()nie zawiera tekstu węzłów potomnych

Oto wyrażenia XPath dla contains(., 'Text'), które działają na samym elemencie, w tym na jego węzłach podrzędnych:

  • //button[contains(., 'Start')]zwróci zarówno dwa przyciski
  • //button[contains(., 'End')]ponownie zwróci oba dwa przyciski
  • //button[contains(., 'Middle')] zwróci jeden (ostatni przycisk)

Dlatego w większości przypadków bardziej sensowne jest użycie w wyrażeniu XPath .zamiast text().

Thomas Dondorf
źródło
dlaczego to nie jest najlepsza odpowiedź? ładne wyjaśnienie wielkie dzięki!
Gianlucca
1
coś, co działa z każdym typem elementu? nie wiem, czy tekst jest wewnątrz przycisku, ap, div, span itp.
Andrea Bisello
5
@AndreaBisello Możesz użyć //*[...]zamiast tego.
Thomas Dondorf
90

Możesz użyć selektora XPath ze stroną. $ X (wyrażenie) :

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

Zapoznaj się clickByTextz tym streszczeniem, aby uzyskać pełny przykład. Dba o unikanie cudzysłowów, co jest nieco trudne w przypadku wyrażeń XPath.

tokland
źródło
Niesamowite - próbowałem to zrobić dla innych tagów, ale nie mogę tego zrobić. (li, h1, ...) Jak byś to zrobił?
Rune Jeppesen
3
@RuneJeppesen Wymień //a[containsze //*[containswybrać dowolny element, a nie tylko (anchor a) element.
Unixmonkey
14

Możesz również użyć page.evaluate()do klikania elementów uzyskanych z document.querySelectorAll()przefiltrowanych według treści tekstowej:

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

Alternatywnie możesz page.evaluate()kliknąć element na podstawie jego zawartości tekstowej przy użyciu document.evaluate()i odpowiedniego wyrażenia XPath:

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
źródło
13

stworzyliśmy szybkie rozwiązanie, aby móc korzystać z zaawansowanych selektorów css, takich jak „: zawiera (tekst)”

więc korzystając z tej biblioteki możesz po prostu

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

const element = await select(page).getElement('button:contains(Button text)');
await element.click()
user3493381
źródło
4

Oto moje rozwiązanie:

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

Rozwiązaniem jest

(await page.$$eval(selector, a => a
            .filter(a => a.textContent === 'target text')
))[0].click()
Alex A
źródło
0

Nie ma obsługiwanej składni selektora css dla selektora tekstu lub opcji kombinatora, moim obejściem byłoby:

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
źródło
0

Istnieje wiele sugerujących odpowiedzi, containsale nie widzę motywacji do tej nieprecyzyjności, biorąc pod uwagę przypadek użycia OP, który, jak się wydaje, jest dokładnym dopasowaniem z docelowym ciągiem "Button text"i elementem <button>Button text</button>.

Wolę używać bardziej precyzyjne [text()="Button text"], co pozwala uniknąć fałszywie dodatni, gdy przycisk jest, powiedzmy <button>Button text and more stuff</button>:

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

To przewiduje odwrotny scenariusz tej odpowiedzi, który stara się być jak najbardziej liberalny.

Wiele odpowiedzi nie .elementsspełniało również wymagań dotyczących klasy rodzicielskiej.

ggorlen
źródło
-1

Musiałem:

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

Alferd Nobel
źródło