DevTools extension that exports MemLab scenario files from the Recorder panel.
- Memlab: https://github.com/facebook/memlab
- Recorder panel: https://developer.chrome.com/docs/devtools/recorder/
- Download and unzip: https://github.com/stoyan/memlab-recorder/archive/refs/heads/main.zip
- Follow the instructions on how to install locally: https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked
This is an extension to the Recorder panel in DevTools. So Open DevTools and click on More options -> More tools -> Recorder. This is needed only once.
Then start a new recording and export the Memlab Scenario file:
// initial page load
function url() {
return 'https://www.webpagetest.org/';
}
// action where we want to detect memory leaks
async function action(page /* Puppeteer page API */) {
let el;
el = await page.waitForSelector('#analytical-review > div:nth-child(3) > label');
await el.evaluate(b => b.click());
}
// go back to the initial state
async function back(page /* Puppeteer page API */) {
const el = await page.waitForSelector('#analytical-review > div:nth-child(2) > label');
await el.evaluate(b => b.click());
}
module.exports = {action, back, url};
This tool is fairly simple and aimed at helping you get off the ground. As per Memlab's requirements it generates 3 functions:
url()
: initial URL navigationaction()
: interactionback()
to the initial state
If in your interaction recording you have more than 1 navigations, only the last one will be considered.
The last click is considered going back()
. All other clicks are part of the action()
.
This may not be true in your case so feel free to move the clicks between the "setup" (action()
) and the "teardown" (back()
).
Only clicks are generated by this extension as part of the scenario. You may need more then clicks, you can use the Puppeteer API and do-it-yourself.
Say you're testing the page https://todomvc.com/examples/react/#/
You start the recorder, click and type one todo, then another, then delete them both to restore the initial state and see if there are any leaks.
Out of the box this extension will generate:
// initial page load
function url() {
return 'https://todomvc.com/examples/react/#/';
}
// action where we want to detect memory leaks
async function action(page /* Puppeteer page API */) {
let el;
el = await page.waitForSelector('body > section > div > header > input');
await el.evaluate(b => b.click());
el = await page.waitForSelector('body > section > div > section > ul > li:nth-child(1) > div > button');
await el.evaluate(b => b.click());
}
// go back to the initial state
async function back(page /* Puppeteer page API */) {
const el = await page.waitForSelector('body > section > div > section > ul > li > div > button');
await el.evaluate(b => b.click());
}
module.exports = {action, back, url};
But the "reset" consists of the last two clicks, so you can manually move one from action()
to back()
:
// initial page load
function url() {
return 'https://todomvc.com/examples/react/#/';
}
// action where we want to detect memory leaks
async function action(page /* Puppeteer page API */) {
let el;
el = await page.waitForSelector('body > section > div > header > input');
await el.evaluate(b => b.click());
}
// go back to the initial state
async function back(page /* Puppeteer page API */) {
let el;
el = await page.waitForSelector('body > section > div > section > ul > li:nth-child(1) > div > button');
await el.evaluate(b => b.click());
el = await page.waitForSelector('body > section > div > section > ul > li > div > button');
await el.evaluate(b => b.click());
}
module.exports = {action, back, url};
You also need the keyboard typing which is not supported by the extension. Luckily, this is not too hard, given that the Recorder comes with Puppeteer export so you can copy individual steps in the recording and use the generated code.
The end result:
// initial page load
function url() {
return 'https://todomvc.com/examples/react/#/';
}
// action where we want to detect memory leaks
async function action(page /* Puppeteer page API */) {
let el;
el = await page.waitForSelector('body > section > div > header > input');
await el.evaluate(b => b.click());
await page.keyboard.type("1");
await page.keyboard.down("Enter");
await page.keyboard.type("2");
await page.keyboard.down("Enter");
}
// go back to the initial state
async function back(page /* Puppeteer page API */) {
let el;
el = await page.waitForSelector('body > section > div > section > ul > li:nth-child(1) > div > button');
await el.evaluate(b => b.click());
el = await page.waitForSelector('body > section > div > section > ul > li > div > button');
await el.evaluate(b => b.click());
}
module.exports = {action, back, url};