【问题标题】:Access to React state with Puppeteer使用 Puppeteer 访问 React 状态
【发布时间】:2021-11-21 08:12:37
【问题描述】:

我有一个 React 应用程序正在运行。我想知道是否可以使用 Puppeteer 访问状态值。

例如,在 React 中我有:

const [gridBlocks, setGridBlocks] = useState([])

此值稍后会更新以将 gridBlocks 设置为值数组。

然后,使用 Puppeteer 我有:

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/', {
    waitUntil: 'networkidle2'
  });

  // Can I get access to React and other javascript values now?
  // Something like console.log(await page.evaluate(() => <are React variables available here>));
  // Another way?

await browser.close();
})();

gridBlocks 状态具有我想循环访问的值以更新 UI 并抓取每个值的屏幕截图。我不会提前知道gridBlocks 的值是什么,所以我不能只是在我的 Puppeteer 脚本中“硬编码”它们。我真的很喜欢能够从加载的页面中读取它,来自page.goto

我见过的大多数文章都涉及测试,它们将props 传递给组件。我想直接从加载的页面中阅读……如果可能的话。

谢谢!

【问题讨论】:

  • 如果您正在测试使用useState 挂钩的组件,最可能的情况是代码中的gridBlocks 变量不可访问。您应该使用 puppeteer 进行测试的是,当 gridBlocks 更新时,生成的 HTML 与您期望的一致。您可以使用page 提供的方法(例如page.$(selector)
  • 您可以通过inject proxy functions 访问所需的值。例如:gridBlocks 变量是否传递给任何原生 JavaScript 函数?

标签: javascript reactjs puppeteer


【解决方案1】:

我在 puppeteer 上找到了一种快速而肮脏的方法。首先,您需要从 chrome 中获取扩展文件。这种方法比从 github 等克隆 react 存储库要快。

有两个主要步骤,

  1. 加载扩展和开发工具。
  2. 欺骗开发工具选择组件。

第一步:

  1. 首先安装Chrome extension source viewer
  2. 现在下载 React Developers Tools 的 zip 文件。
  3. 现在解压到你的项目中作为一个文件夹,我选择了extension作为名称。

加载扩展,确保保持开发工具打开

const browser = await puppeteer.launch({
    headless: false,
    devtools: true,
    args: [
      '--disable-extensions-except=./extension/',
      '--load-extension=./extension/',
    ]
});

打开反应页面,并确保已完全加载

const page = await browser.newPage();
await page.goto('http://localhost:3000', {waitUntil: 'networkidle0'});

现在,我们将从根元素中找到子元素,并使用 devtools 中使用的内部命令。

waitFor 也很重要。我们应该给它一些时间来初始化选择。

await page.evaluate(()=>{
    const rootElement = document.querySelector('#root').childNodes[0];
    __REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent.selectNode(rootElement);
})

await page.waitFor(1000);

最后我们在 $r 中得到了这个特定元素的状态,

const data = await page.evaluate(()=>{
    return $r;
})

我无法在节点上下文中使用它,但我相信您可以使用它来修改和执行不同的东西。

完整代码,

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({
    headless: false,
    devtools: true,
    args: [
      '--disable-extensions-except=./extension/',
      '--load-extension=./extension/',
    ]
  });
  const page = await browser.newPage();
  await page.goto('http://localhost:3000', {waitUntil: 'networkidle0'});

  await page.evaluate(()=>{
    const rootElement = document.querySelector('#root').childNodes[0];
    __REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent.selectNode(rootElement);
  })

  await page.waitFor(1000);

  const data = await page.evaluate(()=>{
    return $r;
  })

  console.log(data)
})();

这是示例反应应用程序,

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Example;

注意:

  • 如果您不打开开发工具或选择节点,它将无法工作。
  • 在当前 1.19 版本上,它可能无法在无头模式下工作。

【讨论】:

  • 有没有办法在无头模式下做到这一点? Puppeteer 不允许在 headless 上加载扩展,但也许有一种方法可以使用 page.evaluate 来加载相关的 React DevTools 代码。
【解决方案2】:

你最好离开赛普拉斯的 puppeteer 使用 cypress-react-selector 或 Webdriver.io 来访问反应组件、道具和状态。

【讨论】:

    【解决方案3】:

    您可以查看 HTMLElement 的“__reactInternalInstance$randomName”属性来查找道具值。您可能需要 devtools 的帮助或遍历元素中的所有键以获取“__reactInternalInstance$randomName”的实际键名

    document.querySelector(".target-element").__reactInternalInstance$randomName.memoizedProps
    

    在 memoizedProps 中,不断查找和扩展“children”和“props”属性,props 隐藏在这两个属性的某处。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-26
      • 2014-11-21
      • 2016-01-02
      • 2020-04-29
      • 1970-01-01
      • 1970-01-01
      • 2020-01-21
      • 2017-12-26
      相关资源
      最近更新 更多