jsdom can run scripts which are inline inside of the document's <head>
tag, but how do you make it run scripts which are linked to in the document head via <script src="">
?
For options I'm using:
{
url: url,
resources: 'usable',
runScripts: 'dangerously',
}
// index.js
const request = require('request')
const jsdom = require('jsdom')
const {JSDOM} = jsdom
const url = 'http://localhost:8000'
request(url, (error, response, body) => {
const options = {
url: url,
resources: 'usable',
runScripts: 'dangerously',
}
const dom = new JSDOM(body, options)
console.log(dom.window.document.body.children.length) // Expecting to see `1`
console.log(dom.window.document.body.innerHTML) // Expecting to see `<h1>Hello world</h1>`
})
// external.js
document.addEventListener('DOMContentLoaded', () => {
document.write('<h1>Hello world!</h1>')
})
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JSDOM test</title>
<script src="external.js"></script>
</head>
<body>
</body>
</html>
Complete minimum working example: https://github.com/cg433n/jsdom-test
The issue here is that DOMContentLoaded
is an asynchronous event. You're logging before it has a chance to happen. You need to wait for it to fire; only then will your document.write()
happen.
Something like this should probably work:
const dom = new JSDOM(body, options)
dom.window.document.addEventListener('DOMContentLoaded', () => {
// We need to delay one extra turn because we are the first DOMContentLoaded listener,
// but we want to execute this code only after the second DOMContentLoaded listener
// (added by external.js) fires.
setImmediate(() => {
console.log(dom.window.document.body.children.length) // Expecting to see `1`
console.log(dom.window.document.body.innerHTML) // Expecting to see `<h1>Hello world</h1>`
});
});
I see! You guys are fantastic - keep up the good work!
Notice: 2 features of the demo above are risky.
request
is depreciated. npm install request
results in :setImmediate
is risky on the long run.Alternatively, I recommand using fs
to read and load the js codes from files, see Stackoverflow based on @EricRicketts' FS.read()
approach in #3023 and @Domenic's addEventListener()
in #1914. The working demo I propose includes:
fs.readFileSync
to loads external js into html as stringsThis is likely to prevent you from doing xhr / API calls and therefore only partially solve the issue.
I couldn't figure out the way to get url work.
Ideally, an updated minimal JSDOM loading external.js files via url working demo would be welcome. :heart:
Most helpful comment
The issue here is that
DOMContentLoaded
is an asynchronous event. You're logging before it has a chance to happen. You need to wait for it to fire; only then will yourdocument.write()
happen.Something like this should probably work: