I have a React application running. I'd like to know if it's possible to access state values using Puppeteer.
For example, in React I have:
const [gridBlocks, setGridBlocks] = useState([])
This value is later updated to set gridBlocks
to an array of values.
Then, using Puppeteer I have:
(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();
})();
The gridBlocks
state has values that I'd like to loop through to update the UI and grab screenshot of each. I won't know what the gridBlocks
values are ahead of time so I can't just "hard code" them in my Puppeteer script. I really like to be able to read it from the loaded page, from page.goto
.
Most articles I've seen deal with testing and they pass props
to ponents. I'd like to read directly from the loaded page ... if possible.
Thanks!
I have a React application running. I'd like to know if it's possible to access state values using Puppeteer.
For example, in React I have:
const [gridBlocks, setGridBlocks] = useState([])
This value is later updated to set gridBlocks
to an array of values.
Then, using Puppeteer I have:
(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();
})();
The gridBlocks
state has values that I'd like to loop through to update the UI and grab screenshot of each. I won't know what the gridBlocks
values are ahead of time so I can't just "hard code" them in my Puppeteer script. I really like to be able to read it from the loaded page, from page.goto
.
Most articles I've seen deal with testing and they pass props
to ponents. I'd like to read directly from the loaded page ... if possible.
Thanks!
Share Improve this question asked Sep 13, 2019 at 19:12 awolfe76awolfe76 2555 silver badges13 bronze badges 2-
1
If you are testing a ponent that uses the
useState
hook, the most probable scenario is that thegridBlocks
variable in your code is not accessible. What you should be testing with puppeteer is that whengridBlocks
is updated, the generated HTML corresponds to what you would expect. You can do so with the methods provided bypage
(for example page.$(selector). – mgarcia Commented Sep 13, 2019 at 19:21 -
You might be able to inject proxy functions to access the desired values. For example: Is the
gridBlocks
variable passed to any native JavaScript function? – Thomas Dondorf Commented Sep 13, 2019 at 19:41
3 Answers
Reset to default 6I found a quick and dirty way to do this on puppeteer. First you need to grab the extension file from chrome. This method is faster than cloning the react repository from github and whatnot.
There are two major steps,
- Load the extension and devtools.
- Trick the devtools into selecting the ponent.
First steps:
- First install Chrome extension source viewer
- Now download the zip file for React Developers Tools.
- Now unzip it into your project as a folder, I chose
extension
for name.
Load the extension, make sure to keep the devtools open.
const browser = await puppeteer.launch({
headless: false,
devtools: true,
args: [
'--disable-extensions-except=./extension/',
'--load-extension=./extension/',
]
});
Open the react page, and make sure it is fully loaded.
const page = await browser.newPage();
await page.goto('http://localhost:3000', {waitUntil: 'networkidle0'});
Now, we will find the child element from the root element and use the internal mands used in the devtools.
The waitFor is important as well. We should give it some time to initializing the selection.
await page.evaluate(()=>{
const rootElement = document.querySelector('#root').childNodes[0];
__REACT_DEVTOOLS_GLOBAL_HOOK__.reactDevtoolsAgent.selectNode(rootElement);
})
await page.waitFor(1000);
Finally we have the state for this particular element inside $r,
const data = await page.evaluate(()=>{
return $r;
})
I couldn't do much with it on the node context, but I'm sure you can modify and execute different stuff using this.
Full code,
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)
})();
Here is the example react app,
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;
Note:
- If you don't open the devtools or select the node, it won't work.
- It probably won't work on headless mode on current 1.19 version.
You are best off leaving puppeteer for Cypress with cypress-react-selector or Webdriver.io for accessing reacts ponents, props and state.
You could look into HTMLElement's "__reactInternalInstance$randomName" property to find the props values. You may need a help of devtools or loop through all keys in the element to get the actual key name of "__reactInternalInstance$randomName"
document.querySelector(".target-element").__reactInternalInstance$randomName.memoizedProps
In memoizedProps, keep looking and expanding "children" and "props" properties, the props are hidden somewhere in these two properties.