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.
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. Only after adding an artificial delay do we see components
filled as expected. See the canonical thread 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. You can surely speed this up with a 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.