Як клацнути на елементі з текстом у Puppeteer


86

Чи є якийсь метод (не знайдений в API) або рішення натиснути на елемент із текстом?

Наприклад, у мене є html:

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

І я хочу натиснути на елемент, в який загортається текст (клацнути на кнопку всередині .elements), наприклад:

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

2
Поділився відповіддю тут: stackoverflow.com/a/47829000/6161265
Md. Abu Taher

Ви знайшли відповідь?
Шубхам Батра,

Якщо це допомагає, на сторінці, на якій я хотів виконати клацання, завантажено jQuery, тому я зміг & використав метод оцінки для виконання коду jQuery.
Брендіто

Відповіді:


78

Топової відповідь на tokland працює тільки на текстових вузлах , а не на вузлах з іншими елементами всередині.

Коротка відповідь

Цей вираз XPath запитає кнопку, яка містить текст "Текст кнопки":

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

Щоб також поважати <div class="elements">оточуючі кнопки, використовуйте такий код:

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

Пояснення

Щоб пояснити, чому використання текстового вузла ( text()) в деяких випадках є неправильним, давайте розглянемо приклад:

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

Спочатку перевіримо результати, використовуючи contains(text(), 'Text'):

  • //button[contains(text(), 'Start')]поверне обидва два вузли (як очікувалося)
  • //button[contains(text(), 'End')]поверне лише один вузол (перший), оскільки text()повертає список із двома текстами ( Startта End), але containsперевірить лише перший
  • //button[contains(text(), 'Middle')] не повертатиме НЕ результати, text()не включає в себе текст дочірніх вузлів

Ось вираз XPath для contains(., 'Text'), який працює над самим елементом, включаючи його дочірні вузли:

  • //button[contains(., 'Start')]поверне обидві дві кнопки
  • //button[contains(., 'End')]знову поверне обидві дві кнопки
  • //button[contains(., 'Middle')] поверне один (остання кнопка)

Тому в більшості випадків має сенс використовувати .замість, а не text()у виразі XPath.


чому це не головна відповідь? приємне пояснення велике спасибі!
Джанлукка,

1
щось, що працює з усіма типами елементів? я не можу знати, чи є текст усередині кнопки, ap, div, діапазону тощо
Андреа Біселло

5
@AndreaBisello Ви можете використовувати //*[...]замість цього.
Томас

90

Ви можете використовувати селектор XPath зі сторінкою. $ X (вираз) :

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

Перевірте clickByTextв цьому суть для повного прикладу. Він піклується про те, щоб уникнути лапок, що трохи складно з виразами XPath.


Чудово - я намагався зробити це для інших тегів, але не можу змусити це працювати. (li, h1, ...) Як би ви це зробили?
Rune Jeppesen

3
@RuneJeppesen Замініть //a[containsна, //*[containsщоб вибрати будь-який елемент, а не просто елемент anchor ( a).
Unixmonkey

14

Ви також можете використовувати page.evaluate()для клацання елементи, отримані з document.querySelectorAll()відфільтрованих за текстовим вмістом:

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

Крім того, ви можете використовувати page.evaluate()клацання елемента на основі його текстового вмісту за document.evaluate()допомогою відповідного виразу 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();
});

13

зробив швидке рішення, щоб мати можливість використовувати розширені селектори css, такі як ": містить (текст)"

так що користуючись цією бібліотекою ви можете просто

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

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

4

Ось моє рішення:

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

4

Рішення є

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

0

Немає підтримуваного синтаксису селектора css для селектора тексту або параметра комбінатора, я б працював над цим:

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

Існує багато відповідей, containsале я не бачу мотивації для цієї неточності, враховуючи варіант використання OP, який, за всіма видами, є точною відповідністю цільовому рядку "Button text"та елементу <button>Button text</button>.

Я вважаю за краще використовувати більш точний [text()="Button text"], що дозволяє уникнути помилкових позитивних , коли кнопка, скажімо, <button>Button text and more stuff</button>:

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

Це передбачає протилежний сценарій цієї відповіді, який намагається бути якомога дозвільнішим.

Багато відповідей також пропустили .elementsвимогу батьківського класу.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.