简体   繁体   English

如何使用 puppeteer 在页面上的选项卡列表中触发链接上的单击事件

[英]How to trigger click event on link from list of tabs on page with puppeteer

I've been looking for a solution to this, and found a few here that focus on clicking an element, but none that allow for clicking an element based on a link.我一直在寻找解决方案,并在这里找到了一些专注于单击元素的解决方案,但没有一个允许基于链接单击元素。

Using puppeteer, I'm looping over an array of tabs使用 puppeteer,我正在遍历一系列选项卡

<div role="tablist">
    <div><a href="#one" tabindex="-1" role="tab" aria-selected="false" class="">One</a></div>
    <div><a href="#two" tabindex="-1" role="tab" aria-selected="false" class="">Two</a></div>
    <div><a href="#three" tabindex="0" role="tab" aria-selected="true" class="icn-cv-down">three</a></div>
</div>

and able to grab the url or hash, but getting the error link.click() is not a function .并能够获取 url 或 hash,但收到错误link.click() is not a function I believe this is due to Puppeteer not being able to trigger a click the same way as JS, but unsure of the way forward:我相信这是由于 Puppeteer 无法像 JS 一样触发点击,但不确定前进的方向:

let tabs = await page.evaluate(() => {
  var tab = [...document.querySelectorAll('[role="tablist"] a')].map(
    (el) => el.hash
  );
  return tab;
});
let components = [];
if (tabs) {
  tabs.forEach((link, index) => {
    setTimeout(() => {
      link.click();
      components.push(
        [...document.querySelectorAll(".ws-compid")]
          .map((component) => component.innerText)
          .filter((el) => el !== "")
      );
    }, 200 * index);
  });
}
console.log(components);

I believe I need an async function to be able to trigger the click event, but not sure.我相信我需要一个异步 function 才能触发点击事件,但不确定。 This should be able to click the href value of each tab, and then push values from the page into an array of components.这应该能够单击每个选项卡的 href 值,然后将页面中的值推送到组件数组中。

I can't run your page to see what the actual behavior is, but based on the limited information provided, here's my best attempt at piecing together a working example you can adapt to your use case:我无法运行您的页面以查看实际行为是什么,但根据所提供的有限信息,这是我拼凑一个可以适应您的用例的工作示例的最佳尝试:

const puppeteer = require("puppeteer"); // ^19.1.0

const html = `
<div role="tablist">
  <div><a href="#one" tabindex="-1" role="tab" aria-selected="false" class="">One</a></div>
  <div><a href="#two" tabindex="-1" role="tab" aria-selected="false" class="">Two</a></div>
  <div><a href="#three" tabindex="0" role="tab" aria-selected="true" class="icn-cv-down">three</a></div>
  <div class="ws-compid"></div>
</div>
<script>
document.querySelectorAll('[role="tablist"] a').forEach(e => 
  e.addEventListener("click", () => {
    document.querySelector(".ws-compid").textContent = e.textContent;
  })
);
</script>`;

let browser;
(async () => {
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setContent(html);
  const components = await page.evaluate(() =>
    Promise.all(
      [...document.querySelectorAll('[role="tablist"] a')].map(
        (e, i) =>
          new Promise(resolve =>
            setTimeout(() => {
              e.click();
              resolve(
                [...document.querySelectorAll(".ws-compid")]
                  .map(component => component.innerText)
                  .filter(e => e)
              );
            }, 200 * i)
          )
      )
    )
  );
  console.log(components); // => [ [ 'One' ], [ 'Two' ], [ 'three' ] ]
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close());

A lot can go wrong translating browser code to Puppeteer: asynchronous loading, bot detection, iframes, shadow DOM, to name a few obstacles, so if this doesn't work, I'll need a reproducible example. go 将浏览器代码转换为 Puppeteer 时会出现很多错误:异步加载、机器人检测、iframe、影子 DOM,仅举几例障碍,因此如果这不起作用,我将需要一个可重现的示例。

Although you claim your original code works, I don't see how that's possible.尽管您声称您的原始代码有效,但我不明白这怎么可能。 The pattern boils down to:该模式归结为:

 const tabs = [..."ABCDEF"]; let components = []; tabs.forEach((link, index) => { setTimeout(() => { components.push(link); }, 200 * index); }); console.log(components); // guaranteed to be empty // added code setTimeout(() => { console.log(components.join("")); // "ABCDEF" }, 2000);

You can see that console.log(components) runs before the setTimeout s finish.您可以看到console.log(components)setTimeout完成之前运行。 Only after adding an artificial delay do we see components filled as expected.只有在添加人为延迟后,我们才能看到components按预期填充。 See the canonical thread How do I return the response from an asynchronous call?请参阅规范线程How do I return the response from an asynchronous call? . . One solution is to promisify the callbacks as I've done above.一种解决方案是像我上面所做的那样承诺回调。

Note also that sleeping for 200 milliseconds isn't ideal.另请注意,休眠 200 毫秒并不理想。 You can surely speed this up with a waitForFunction .您当然可以使用waitForFunction加快速度。


In the comments, you shared a site that has similar tabs, but you don't need to click anything to access the text that's revealed after each click:在评论中,您共享了一个具有类似选项卡的网站,但您无需单击任何内容即可访问每次单击后显示的文本:

const puppeteer = require("puppeteer");

let browser;
(async () => {
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  const url = "https://www.w3.org/WAI/ARIA/apg/example-index/tabs/tabs-manual.html";
  await page.goto(url, {waitUntil: "domcontentloaded"});
  const text = await page.$$eval(
    '#ex1 [role="tabpanel"]',
    els => els.map(e => e.textContent.trim())
  );
  console.log(text);
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close());

So there's a good chance this is an xy problem .所以这很可能是一个xy 问题

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM