Как нажать на элемент с текстом в 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.
Brandito

Ответы:

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.

Томас Дондорф
источник
почему это не главный ответ? хорошее объяснение, большое спасибо!
Gianlucca
1
что-то, что работает с каждым типом элемента? я не могу знать, находится ли текст внутри кнопки, ap, div, span и т. д.
Андреа Бизелло
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.

Tokland
источник
Замечательно - я пытался сделать это для других тегов, но не могу заставить его работать. (li, h1, ...) Как бы вы это сделали?
Rune Jeppesen
3
@RuneJeppesen Замените //a[containsна, //*[containsчтобы выбрать любой элемент, а не только aэлемент anchor ( ).
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, такие как ": contains (text)"

поэтому с помощью этой библиотеки вы можете просто

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

const element = await select(page).getElement('button:contains(Button text)');
await element.click()
пользователь3493381
источник
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  
        }
    });
Ekeuwei
источник
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родительского класса.

Ggorlen
источник
-1

Мне пришлось:

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

Альферд Нобель
источник