jsdom = function (html, options) { if (options === undefined) { options = {}; } if (options.parsingMode === undefined || options.parsingMode === "auto") { options.parsingMode = "html"; } if (options.parsingMode !== "html" && options.parsingMode !== "xml") { throw new RangeError(`Invalid parsingMode option ${JSON.stringify(options.parsingMode)}; must be either "html", ` + `"xml", "auto", or undefined`); } options.encoding = options.encoding || "UTF-8"; setGlobalDefaultConfig(options); // Back-compat hack: we have previously suggested nesting these under document, for jsdom.env at least. // So we need to support that. if (options.document) { if (options.document.cookie !== undefined) { options.cookie = options.document.cookie; } if (options.document.referrer !== undefined) { options.referrer = options.document.referrer; } } // List options explicitly to be clear which are passed through const window = new Window({ parsingMode: options.parsingMode, contentType: options.contentType, encoding: options.encoding, parser: options.parser, url: options.url, lastModified: options.lastModified, referrer: options.referrer, cookieJar: options.cookieJar, cookie: options.cookie, resourceLoader: options.resourceLoader, deferClose: options.deferClose, concurrentNodeIterators: options.concurrentNodeIterators, virtualConsole: options.virtualConsole, pool: options.pool, agent: options.agent, agentClass: options.agentClass, agentOptions: options.agentOptions, strictSSL: options.strictSSL, proxy: options.proxy, userAgent: options.userAgent }); const documentImpl = idlUtils.implForWrapper(window.document); documentFeatures.applyDocumentFeatures(documentImpl, options.features); if (options.created) { options.created(null, window.document.defaultView); } if (options.parsingMode === "html") { if (html === undefined || html === "") { html = "<html><head></head><body></body></html>"; } window.document.write(html); } else if (options.parsingMode === "xml") { if (html !== undefined) { documentImpl._htmlToDom.appendHtmlToDocument(html, documentImpl); } } if (window.document.close && !options.deferClose) { window.document.close(); } return window.document; }
...
#### Dealing with asynchronous script loading
If you load scripts asynchronously, e.g. with a module loader like RequireJS, none of the above hooks will really give you what
you want. There's nothing, either in jsdom or in browsers, to say "notify me after all asynchronous loads have completed
." The solution is to use the mechanisms of the framework you are using to notify about this finishing up. E.g., with RequireJS
, you could do
```js
// On the Node.js side:
var window = jsdom.jsdom(...).defaultView;
window.onModulesLoaded = function () {
console.log("ready to roll!");
};
```
```html
<!-- Inside the HTML you supply to jsdom -->
...
blobToBuffer = function (blob) { return Blob.is(blob) && idlUtils.implForWrapper(blob)._buffer || undefined; }
n/a
changeURL = function (window, urlString) { const doc = idlUtils.implForWrapper(window._document); const url = whatwgURL.parseURL(urlString); if (url === "failure") { throw new TypeError(`Could not parse "${urlString}" as a URL`); } doc._URL = url; doc._origin = whatwgURL.serializeURLToUnicodeOrigin(doc._URL); }
...
In the future we may expand `reconfigureWindow` to allow overriding other `[Unforgeable]` properties. Let us know if you need this
capability.
#### Changing the URL of an existing jsdom `Window` instance
At present jsdom does not handle navigation (such as setting `window.location.href === "https://example.com/"`). However
, if you'd like to change the URL of an existing `Window` instance (such as for testing purposes), you can use the `jsdom.changeURL
` method:
```js
jsdom.changeURL(window, "https://example.com/");
```
#### Running vm scripts
Although in most cases it's simplest to just insert a `<script>` element or call `window.eval`, in some cases you
want access to the raw [vm context](https://nodejs.org/api/vm.html) underlying jsdom to run scripts. You can do that like so:
```js
...
createCookieJar = function () { return new CookieJar(null, { looseMode: true }); }
...
```
- `config.html`: a HTML fragment
- `config.file`: a file which jsdom will load HTML from; the resulting document's URL will be a `file://` URL.
- `config.url`: sets the resulting document's URL, which is reflected in various properties like `document.URL` and `location
.href`, and is also used for cross-origin request restrictions. If `config.html` and `config.file` are not provided, jsdom will
load HTML from this URL.
- `config.scripts`: see `scripts` above.
- `config.src`: an array of JavaScript strings that will be evaluated against the resulting document. Similar to `scripts`, but
it accepts JavaScript instead of paths/URLs.
- `config.cookieJar`: cookie jar which will be used by document and related resource requests. Can be created by `jsdom.createCookieJar()` method. Useful to share cookie state among different documents as browsers does
.
- `config.parsingMode`: either `"auto"`, `"html"`, or `"xml"`. The default is `"auto"`,
which uses HTML behavior unless `config.url` responds with an XML `Content-Type`, or `config.file` contains a filename ending in
`.xml` or `.xhtml`. Setting to `"xml"` will attempt to parse the document as an XHTML document. (jsdom is [currently
only OK at doing that](https://github.com/tmpvar/jsdom/labels/x%28ht%29ml).)
- `config.referrer`: the new document will have this referrer.
- `config.cookie`: manually set a cookie value, e.g. `'key=value; expires=Wed, Sep 21 2011 12:00:00 GMT; path=/'`. Accepts
cookie string or array of cookie strings.
- `config.headers`: an object giving any headers that will be used while loading the HTML from `config.url`, if applicable.
- `config.userAgent`: the user agent string used in requests; defaults to `Node.js (#process.platform#; U; rv:#process.version#)`
- `config.features`: see Flexibility section below. **Note**: the default feature set for `jsdom.env` does _not_ include fetching
remote JavaScript and executing it. This is something that you will need to _carefully_ enable yourself.
- `config.resourceLoader`: a function that intercepts subresource requests and allows you to re-route them, modify, or outright
replace them with your own content. More below.
...
createVirtualConsole = function (options) { return new VirtualConsole(options); }
...
console.error("script error!!", event.error);
});
```
it is often also desirable to listen for any script errors during initialization, or errors loading scripts passed to `jsdom.env
`. To do this, use the virtual console feature, described in more detail later:
```js
var virtualConsole = jsdom.createVirtualConsole();
virtualConsole.on("jsdomError", function (error) {
console.error(error.stack, error.detail);
});
var window = jsdom.jsdom(..., { virtualConsole }).defaultView;
```
...
env = function () { const config = getConfigFromArguments(arguments); let req = null; if (config.file && canReadFilesFromFS) { req = resourceLoader.readFile(config.file, { defaultEncoding: config.defaultEncoding, detectMetaCharset: true }, (err, text, res) => { if (err) { reportInitError(err, config); return; } const contentType = parseContentType(res.headers["content-type"]); config.encoding = contentType.get("charset"); setParsingModeFromExtension(config, config.file); config.html = text; processHTML(config); }); } else if (config.html !== undefined) { processHTML(config); } else if (config.url) { req = handleUrl(config); } else if (config.somethingToAutodetect !== undefined) { const url = URL.parse(config.somethingToAutodetect); if (url.protocol && url.hostname) { config.url = config.somethingToAutodetect; req = handleUrl(config.somethingToAutodetect); } else if (canReadFilesFromFS) { req = resourceLoader.readFile(config.somethingToAutodetect, { defaultEncoding: config.defaultEncoding, detectMetaCharset: true }, (err, text, res) => { if (err) { if (err.code === "ENOENT" || err.code === "ENAMETOOLONG") { config.html = config.somethingToAutodetect; processHTML(config); } else { reportInitError(err, config); } } else { const contentType = parseContentType(res.headers["content-type"]); config.encoding = contentType.get("charset"); setParsingModeFromExtension(config, config.somethingToAutodetect); config.html = text; config.url = toFileUrl(config.somethingToAutodetect); processHTML(config); } }); } else { config.html = config.somethingToAutodetect; processHTML(config); } } function handleUrl() { config.cookieJar = config.cookieJar || exports.createCookieJar(); const options = { defaultEncoding: config.defaultEncoding, detectMetaCharset: true, headers: config.headers, pool: config.pool, strictSSL: config.strictSSL, proxy: config.proxy, cookieJar: config.cookieJar, userAgent: config.userAgent, agent: config.agent, agentClass: config.agentClass, agentOptions: config.agentOptions }; const fragment = whatwgURL.parseURL(config.url).fragment; return resourceLoader.download(config.url, options, (err, responseText, res) => { if (err) { reportInitError(err, config); return; } // The use of `res.request.uri.href` ensures that `window.location.href` // is updated when `request` follows redirects. config.html = responseText; config.url = res.request.uri.href; if (fragment) { config.url += `#${fragment}`; } if (res.headers["last-modified"]) { config.lastModified = new Date(res.headers["last-modified"]); } const contentType = parseContentType(res.headers["content-type"]); if (config.parsingMode === "auto") { if (contentType.isXML()) { config.parsingMode = "xml"; } } config.encoding = contentType.get("charset"); processHTML(config); }); } return req; }
...
You can use it with a URL
```js
// Count all of the links from the io.js build page
var jsdom = require("jsdom");
jsdom.env(
"https://iojs.org/dist/",
["http://code.jquery.com/jquery.js"],
function (err, window) {
console.log("there have been", window.$("a").length - 4, "io.js releases!");
}
);
```
...
(window, script) => { return script.runInContext(idlUtils.implForWrapper(window._document)._global); }
...
#### Running vm scripts
Although in most cases it's simplest to just insert a `<script>` element or call `window.eval`, in some cases you
want access to the raw [vm context](https://nodejs.org/api/vm.html) underlying jsdom to run scripts. You can do that like so:
```js
const script = new vm.Script("globalVariable = 5;", { filename: "test.js" });
jsdom.evalVMScript(window, script);
```
## jsdom vs. PhantomJS
Some people wonder what the differences are between jsdom and [PhantomJS](http://phantomjs.org/), and when you would use one over
the other. Here we attempt to explain some of the differences, and why we find jsdom to be a pleasure to use for testing and scraping
use cases.
PhantomJS is a complete browser (although it uses a very old and rare rendering engine). It even performs layout and rendering,
allowing you to query element positions or take a screenshot. jsdom is not a full browser: it does not perform layout or rendering
, and it does not support navigation between pages. It _does_ support the DOM, HTML, canvas, many other web platform APIs, and running
scripts.
...
getVirtualConsole = function (window) { return window._virtualConsole; }
...
virtualConsole: virtualConsole
});
```
Post-initialization, if you didn't pass in a `virtualConsole` or no longer have a reference to it, you can retrieve the `virtualConsole
` by using:
```js
var virtualConsole = jsdom.getVirtualConsole(window);
```
#### Virtual console `jsdomError` error reporting
Besides the usual events, corresponding to `console` methods, the virtual console is also used for reporting errors from jsdom itself
. This is similar to how error messages often show up in web browser consoles, even if they are not initiated by `console.error`.
So far, the following errors are output this way:
- Errors loading or parsing external resources (scripts, stylesheets, frames, and iframes)
...
jQueryify = function (window, jqueryUrl, callback) { if (!window || !window.document) { return; } const implImpl = idlUtils.implForWrapper(window.document.implementation); const features = implImpl._features; implImpl._addFeature("FetchExternalResources", ["script"]); implImpl._addFeature("ProcessExternalResources", ["script"]); const scriptEl = window.document.createElement("script"); scriptEl.className = "jsdom"; scriptEl.src = jqueryUrl; scriptEl.onload = scriptEl.onerror = () => { implImpl._features = features; if (callback) { callback(window, window.jQuery); } }; window.document.body.appendChild(scriptEl); }
...
### jQueryify
```js
var jsdom = require("jsdom");
var window = jsdom.jsdom().defaultView;
jsdom.jQueryify(window, "http://code.jquery.com/jquery-2.1.1.js", function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
console.log(window.$(".testing").text());
});
```
### Passing objects to scripts inside the page
...
nodeLocation = function (node) { return idlUtils.implForWrapper(node)[locationInfo]; }
...
</p>`);
var bodyEl = document.body; // implicitly created
var pEl = document.querySelector("p");
var textNode = pEl.firstChild;
var imgEl = document.querySelector("img");
console.log(jsdom.nodeLocation(bodyEl)); // null; it's not in the source
console.log(jsdom.nodeLocation(pEl)); // { start: 0, end: 39, startTag: ..., endTag: ... }
console.log(jsdom.nodeLocation(textNode)); // { start: 3, end: 13 }
console.log(jsdom.nodeLocation(imgEl)); // { start: 13, end: 32 }
```
This returns the [parse5 location info](https://www.npmjs.com/package/parse5#options-locationinfo) for the node.
...
reconfigureWindow = function (window, newProps) { if ("top" in newProps) { window._top = newProps.top; } }
...
This returns the [parse5 location info](https://www.npmjs.com/package/parse5#options-locationinfo) for the node.
#### Overriding `window.top`
The `top` property on `window` is marked `[Unforgeable]` in the spec, meaning it is a non-configurable own property and thus cannot
be overridden or shadowed by normal code running inside the jsdom window, even using `Object.defineProperty`. However, if you
x27;re acting from outside the window, e.g. in some test framework that creates jsdom instances, you can override it using the special
`jsdom.reconfigureWindow` function:
```js
jsdom.reconfigureWindow(window, { top: myFakeTopForTesting });
```
In the future we may expand `reconfigureWindow` to allow overriding other `[Unforgeable]` properties. Let us know if you need this
capability.
#### Changing the URL of an existing jsdom `Window` instance
At present jsdom does not handle navigation (such as setting `window.location.href === "https://example.com/"`). However
, if you'd like to change the URL of an existing `Window` instance (such as for testing purposes), you can use the `jsdom.changeURL
` method:
...
serializeDocument = function (doc) { return domToHtml([doc]); }
n/a
jsdom = function (html, options) { if (options === undefined) { options = {}; } if (options.parsingMode === undefined || options.parsingMode === "auto") { options.parsingMode = "html"; } if (options.parsingMode !== "html" && options.parsingMode !== "xml") { throw new RangeError(`Invalid parsingMode option ${JSON.stringify(options.parsingMode)}; must be either "html", ` + `"xml", "auto", or undefined`); } options.encoding = options.encoding || "UTF-8"; setGlobalDefaultConfig(options); // Back-compat hack: we have previously suggested nesting these under document, for jsdom.env at least. // So we need to support that. if (options.document) { if (options.document.cookie !== undefined) { options.cookie = options.document.cookie; } if (options.document.referrer !== undefined) { options.referrer = options.document.referrer; } } // List options explicitly to be clear which are passed through const window = new Window({ parsingMode: options.parsingMode, contentType: options.contentType, encoding: options.encoding, parser: options.parser, url: options.url, lastModified: options.lastModified, referrer: options.referrer, cookieJar: options.cookieJar, cookie: options.cookie, resourceLoader: options.resourceLoader, deferClose: options.deferClose, concurrentNodeIterators: options.concurrentNodeIterators, virtualConsole: options.virtualConsole, pool: options.pool, agent: options.agent, agentClass: options.agentClass, agentOptions: options.agentOptions, strictSSL: options.strictSSL, proxy: options.proxy, userAgent: options.userAgent }); const documentImpl = idlUtils.implForWrapper(window.document); documentFeatures.applyDocumentFeatures(documentImpl, options.features); if (options.created) { options.created(null, window.document.defaultView); } if (options.parsingMode === "html") { if (html === undefined || html === "") { html = "<html><head></head><body></body></html>"; } window.document.write(html); } else if (options.parsingMode === "xml") { if (html !== undefined) { documentImpl._htmlToDom.appendHtmlToDocument(html, documentImpl); } } if (window.document.close && !options.deferClose) { window.document.close(); } return window.document; }
...
#### Dealing with asynchronous script loading
If you load scripts asynchronously, e.g. with a module loader like RequireJS, none of the above hooks will really give you what
you want. There's nothing, either in jsdom or in browsers, to say "notify me after all asynchronous loads have completed
." The solution is to use the mechanisms of the framework you are using to notify about this finishing up. E.g., with RequireJS
, you could do
```js
// On the Node.js side:
var window = jsdom.jsdom(...).defaultView;
window.onModulesLoaded = function () {
console.log("ready to roll!");
};
```
```html
<!-- Inside the HTML you supply to jsdom -->
...
env = function () { const config = getConfigFromArguments(arguments); let req = null; if (config.file && canReadFilesFromFS) { req = resourceLoader.readFile(config.file, { defaultEncoding: config.defaultEncoding, detectMetaCharset: true }, (err, text, res) => { if (err) { reportInitError(err, config); return; } const contentType = parseContentType(res.headers["content-type"]); config.encoding = contentType.get("charset"); setParsingModeFromExtension(config, config.file); config.html = text; processHTML(config); }); } else if (config.html !== undefined) { processHTML(config); } else if (config.url) { req = handleUrl(config); } else if (config.somethingToAutodetect !== undefined) { const url = URL.parse(config.somethingToAutodetect); if (url.protocol && url.hostname) { config.url = config.somethingToAutodetect; req = handleUrl(config.somethingToAutodetect); } else if (canReadFilesFromFS) { req = resourceLoader.readFile(config.somethingToAutodetect, { defaultEncoding: config.defaultEncoding, detectMetaCharset: true }, (err, text, res) => { if (err) { if (err.code === "ENOENT" || err.code === "ENAMETOOLONG") { config.html = config.somethingToAutodetect; processHTML(config); } else { reportInitError(err, config); } } else { const contentType = parseContentType(res.headers["content-type"]); config.encoding = contentType.get("charset"); setParsingModeFromExtension(config, config.somethingToAutodetect); config.html = text; config.url = toFileUrl(config.somethingToAutodetect); processHTML(config); } }); } else { config.html = config.somethingToAutodetect; processHTML(config); } } function handleUrl() { config.cookieJar = config.cookieJar || exports.createCookieJar(); const options = { defaultEncoding: config.defaultEncoding, detectMetaCharset: true, headers: config.headers, pool: config.pool, strictSSL: config.strictSSL, proxy: config.proxy, cookieJar: config.cookieJar, userAgent: config.userAgent, agent: config.agent, agentClass: config.agentClass, agentOptions: config.agentOptions }; const fragment = whatwgURL.parseURL(config.url).fragment; return resourceLoader.download(config.url, options, (err, responseText, res) => { if (err) { reportInitError(err, config); return; } // The use of `res.request.uri.href` ensures that `window.location.href` // is updated when `request` follows redirects. config.html = responseText; config.url = res.request.uri.href; if (fragment) { config.url += `#${fragment}`; } if (res.headers["last-modified"]) { config.lastModified = new Date(res.headers["last-modified"]); } const contentType = parseContentType(res.headers["content-type"]); if (config.parsingMode === "auto") { if (contentType.isXML()) { config.parsingMode = "xml"; } } config.encoding = contentType.get("charset"); processHTML(config); }); } return req; }
...
You can use it with a URL
```js
// Count all of the links from the io.js build page
var jsdom = require("jsdom");
jsdom.env(
"https://iojs.org/dist/",
["http://code.jquery.com/jquery.js"],
function (err, window) {
console.log("there have been", window.$("a").length - 4, "io.js releases!");
}
);
```
...
jQueryify = function (window, jqueryUrl, callback) { if (!window || !window.document) { return; } const implImpl = idlUtils.implForWrapper(window.document.implementation); const features = implImpl._features; implImpl._addFeature("FetchExternalResources", ["script"]); implImpl._addFeature("ProcessExternalResources", ["script"]); const scriptEl = window.document.createElement("script"); scriptEl.className = "jsdom"; scriptEl.src = jqueryUrl; scriptEl.onload = scriptEl.onerror = () => { implImpl._features = features; if (callback) { callback(window, window.jQuery); } }; window.document.body.appendChild(scriptEl); }
...
### jQueryify
```js
var jsdom = require("jsdom");
var window = jsdom.jsdom().defaultView;
jsdom.jQueryify(window, "http://code.jquery.com/jquery-2.1.1.js", function () {
window.$("body").append('<div class="testing">Hello World, It works</div>');
console.log(window.$(".testing").text());
});
```
### Passing objects to scripts inside the page
...