点击弹出窗口或框架创建按钮后,使用木偶操纵器访问变量



经过长时间的编程,我现在正在尝试使用Javascript。我想加载一个页面,点击一个按钮,然后检查可用的变量,以测试它们是否看起来不错。

我只在一次正常的页面访问中就成功地做到了这一点,但在模拟了一次按钮点击后,我再也无法获得该变量了。

以下是我想采取的步骤:

  1. 打开页面,接受cookie
  2. 检查数据层
  3. 单击按钮将产品添加到购物篮中(在正常页面上生成某种弹出窗口/其他内容(
  4. 再次检查dataLayer

步骤2有效,但步骤4返回Undefined。我怀疑这可能是因为它专注于错误的事情,但我没有找到解决这个问题的方法。

这是重现这个问题的最小代码,为了安全起见,我去掉了example.com的链接。

// Loading dependencies
const puppeteer = require('puppeteer');
// Opening the browser
(async () => {
const browser = await puppeteer.launch({
headless: false,
defaultViewport: false
})
// Simulating the user behavior
const page = await browser.newPage()
await page.goto('https://www.example.com/')
await page.click('button#onetrust-accept-btn-handler')
let dataLayer1 = await page.evaluate(() => {
return window.dataLayer
})
await page.click('button.button.button--solid.button--custom.button--color-primary.add-to-cart')
let dataLayer2 = await page.evaluate(() => {
return window.dataLayer
})

console.log('First output')
console.log(dataLayer1)
console.log('Second output')
console.log(dataLayer2)
console.log('Finished')
})();

我不确定它是否相关,但我正在windows上运行,并将所有内容更新到最新版本。

这里的问题非常简单。从evaluate回调返回的所有数据都需要是可序列化的。假设DOM元素是可序列化的,这是一个经典的Puppeteer难题,但这并不是因为循环引用和对本地浏览器对象的依赖,这些对象在Node中不起作用,比如documentwindow

棘手的部分是,当你试图返回一个带有循环引用的对象时,Puppeter会无声地失败,默认为undefined。就是一个最小的例子

const o = await page.evaluate(() => {
const o = {};
o.o = o; // circular reference
return o;
});
console.log(o); // => undefined

事实证明dataLayer是一个与谷歌标签管理器(GTM(相关的数据结构。在单击之前,嵌套结构中没有DOM节点,因此它可以很好地序列化,但在单击之后,会出现一个"gtm.element"键,指向与事件关联的元素。这导致evaluate处的第二次尝试由于上述循环引用原因而未能串行化。

一个解决方案是在序列化过程中简单地省略这个"gtm.element"属性:

const fs = require("fs").promises;
const puppeteer = require("puppeteer"); // ^18.0.4
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
const url = "https://www.visionexpress.com/sunglasses/gucci-gg-0631s-001/8056376305852";
await page.goto(url, {waitUntil: "domcontentloaded"});
await (await page.waitForSelector("#onetrust-accept-btn-handler")).click();
await page.click(".add-to-cart");
const dataLayer = await page.evaluate(`
JSON.stringify(
dataLayer,
(k, v) => k === "gtm.element" ? undefined : v,
2
)
`);
await fs.writeFile("dataLayer.json", dataLayer);
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;

OP在一条评论中提到,他们正在寻找一个名为(或带有子字符串("becomingSkynet"的键或值,但在使用循环引用序列化程序(如MDN中的序列化程序(时产生的2.5MB结构中,该属性不存在(大多数数据都没有意义(。因此,听起来他们需要检查自己的假设,也许还需要在页面上采取其他行动来显示该属性。

最新更新