【问题标题】:How to handle shadow DOM inside iframe with Puppeteer如何使用 Puppeteer 处理 iframe 中的影子 DOM
【发布时间】:2023-02-05 00:04:38
【问题描述】:
我想单击 iframe 内影子 DOM 中的按钮。有没有办法做到这一点?
<html>
<head></head>
<body>
<iframe class="iframe_1">
#document
<div class="shadow-root">
#shadow-root (open)
<div>
<button id="btn_1"></button>
<button id="btn_2"></button>
</div>
</iframe>
</body>
</body>
我这样做了:
const frameHandle = await page.$("iframe.iframe_1");
const frame = await frameHandle.contentFrame();
var button = await frame.querySelector(".shadow-root").shadowRoot.querySelector("button[id='btn_1']");
await button.click();
但是得到以下错误:
Uncaught TypeError TypeError: frame.querySelector is not a function
我知道为什么会出现这个错误,但我想不出其他的想法。请教我。
【问题讨论】:
标签:
javascript
iframe
puppeteer
shadow-dom
【解决方案1】:
该错误告诉您您正在尝试调用frame.querySelector,但querySelector 不是frame 上存在的函数。 DOMElement#querySelector 仅在浏览器中定义,您可以使用 evaluate-family 调用通过框架访问它。
例如,如果要返回一个元素,可以使用evaluateHandle:
const button = await frame.evaluateHandle(() =>
document.querySelector(".shadow-root")
.shadowRoot
.querySelector("#btn_1")
);
这是一个最小的完整示例,您可以在提供的 DOM 结构上运行并查看实际效果:
index.html:
<!DOCTYPE html>
<html><body>
<iframe class="iframe_1"></iframe>
<script>
const html = `<!DOCTYPE html>
<div class="shadow-root"></div>
<script>
const el = document.querySelector(".shadow-root");
const root = el.attachShadow({mode: "open"});
el.shadowRoot.innerHTML = `
<button id="btn_1">A</button>
<button id="btn_2">B</button>
`;
el.shadowRoot.querySelector("#btn_1").addEventListener("click", event => {
event.target.textContent = "clicked";
});
</script>
`;
const doc = document.querySelector("iframe").contentWindow.document;
doc.open();
doc.write(html);
doc.close();
</script>
</body></html>
index.js:
const fs = require("node:fs/promises");
const puppeteer = require("puppeteer"); // ^19.6.3
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
const html = (await fs.readFile("index.html")).toString();
await page.setContent(html);
const frameHandle = await page.$("iframe.iframe_1");
const frame = await frameHandle.contentFrame();
const button = await frame.evaluateHandle(() =>
document.querySelector(".shadow-root")
.shadowRoot
.querySelector("#btn_1")
);
await button.click();
const result = await button.evaluate(el => el.textContent);
console.log(result); // => clicked
})()
.catch(err => console.error(err))
.finally(() => browser?.close());