I have an es6 module that looks like this
// MyModulue.js
import MySubModule from '../my-sub-module';
const MyFunction => {
// accesses various DOM elements
const element = document.querySelector("#whatever");
// accesses various DOM api's
const styles = window.getComputedStyle(element)
// uses some other imported module
const result = MySubModule.someMethod(styles)
// a ton of other business logic
// ...
return someResult;
}
export default MyFunction;
I want to test MyModule.js inside of the context of a Puppeteer page (i.e. a browser tab).
Here is what I have:
await page.setContent(input);
await page.addScriptTag({
path: 'path/to/function/MyModule.js',
type: 'module',
});
const actualFieldLabel = await page.evaluate(async (selector) => {
const field = document.querySelector(selector);
const result = await MyFunction(field); // <=== How can I do this??
return result;
}
expect(actualFieldType).toBe(output);
How can I get this to work? No matter what I've tried result is either undefined or an exception gets thrown.
There are some problems that you come across that you just don't want anyone else to have to suffer through. For me this was one of them. Here is the solution to my situation I hope it can help you too:
Puppeteer is not kidding in their documentation when they say the pageFunction callback is executed in the page context. However, a far more helpful way of saying this is that pageFunction is executed inside the current HTML document and has in the window / global scope whatever you have added - and is thus missing whatever you have not provided.
There were two fixes that needed to be made:
// MyModulue.js
import MySubModule from '../my-sub-module';
const MyFunction => {
// accesses various DOM elements
const element = document.querySelector("#whatever");
// accesses various DOM api's
const styles = window.getComputedStyle(element)
// uses some other imported module
const result = MySubModule.someMethod(styles)
// a ton of other business logic
// ...
return someResult;
}
export default MyFunction;
// Problem #1: export default didn't expose MyFunction to the global
// scope so it was not accessible from the page.evaluate(() => {...})
// You can fix this by exposing it on the window global like so:
window.MyFunction = MyFunction;
The second problem was that MyModule.js dependes on another module, MySubModule.js which was simply non-existent inside page because I had only called
await page.addScriptTag({
path: 'path/to/function/MyModule.js',
type: 'module',
});
instead of something like this:
await page.addScriptTag({
path: 'path/to/function/MyModule.js',
type: 'module',
});
await page.addScriptTag({
path: 'path/to/function/my-sub-module.js',
type: 'module',
});
However, I must say in my case MyModule.js had a giant dependency tree with many imported modules and many of those modules importing other modules. As a result, I had to piggy-back off my existing webpack.config to add another entry point and output chunk. So my final solution was to import a webpack bundle for the entire dependency tree of MyModule something like my-module.bundle.js, see below:
await page.setContent(input);
await page.addScriptTag({
type: 'module',
// this is the output of webpack creating a bundle of the dependency tree
// starting at MyBundle.js
path: 'build/my-module.bundle.js',
});
const actualFieldLabel = await page.evaluate(async (selector) => {
const field = document.querySelector(selector);
const result = await MyFunction(field); // <=== How can I do this??
return result;
}
expect(actualFieldType).toBe(output);
This worked like a charm because the bundle had all the depencies and page.addScriptTag inserted them right into page!
Very greatful that jest-puppeteer worked so nicely with jest out of the box!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With