1. 程式人生 > >我可以接受失敗,但我不能接受放棄。--邁克爾 喬丹

我可以接受失敗,但我不能接受放棄。--邁克爾 喬丹

這篇文章只是個人學習筆記,所以結構不清,資訊不全,建議閱讀原版專案手冊。

JSDOM 是用nodejs實現的用於測試的虛擬瀏覽器。

基本例子

const dom = new JSDOM(``, {
  url: "https://example.org/",
  referrer: "https://example.com/",
  contentType: "text/html",
  includeNodeLocations: true,
  storageQuota: 10000000
});
  • url 預設為 "about:blank".預設是規範化的,所以寫了https:example.com會自動變成
    https://example.com/
  • referrer just affects the value read from document.referrer. It defaults to no referrer (which reflects as the empty string).
  • contentType 預設為 "text/html".
  • includeNodeLocations 儲存頁面位置,為了效能,預設為 false 
  • storageQuota localStorage 和 sessionStorage 的最大儲存大小。預設為 5,000,000.

重要引數

runScripts

runScripts 設定為  "dangerously" 才能在頁面上執行js

pretendToBeVisual

當pretendToBeVisual設定為true時,會發生以下事情:

  • 讓 document.hidden 返回 false 而不是 true
  • 讓 document.visibilityState 返回 "visible" 而不是"prerender"
  • 啟用之前不存在的 window.requestAnimationFrame() 和 window.cancelAnimationFrame() 方法
const window = (new JSDOM(``, { pretendToBeVisual: true })).window;

window.requestAnimationFrame(timestamp => {
  console.log(timestamp > 0);
});

resources

resources設定為 usable,才會載入以下iframe, css, js引用:

  • <frame> 和 <iframe>
  • <link rel="stylesheet">
  • <script>, 不過首先你要設定 runScripts: "dangerously" 
  • <img>, 不過你首先要保證 已經安裝了canvas (or canvas-prebuilt) npm 包

JSDOM物件

屬性

  • window
  • virtualConsole
  • cookieJar

serialize方法

serialize方法可以返回序列化html。例子

const dom = new JSDOM(`<!DOCTYPE html>hello`);

dom.serialize() === "<!DOCTYPE html><html><head></head><body>hello</body></html>";

// Contrast with:
dom.window.document.documentElement.outerHTML === "<html><head></head><body>hello</body></html>";

nodeLocation方法

通過nodeLocation方法獲取當前元素在頁面上的位置(不過要記得開啟 includeNodeLocation)

const dom = new JSDOM(
  `<p>Hello
    <img src="foo.jpg">
  </p>`,
  { includeNodeLocations: true }
);

const document = dom.window.document;
const bodyEl = document.body; // implicitly created
const pEl = document.querySelector("p");
const textNode = pEl.firstChild;
const imgEl = document.querySelector("img");

console.log(dom.nodeLocation(bodyEl));   // null; it's not in the source
console.log(dom.nodeLocation(pEl));      // { startOffset: 0, endOffset: 39, startTag: ..., endTag: ... }
console.log(dom.nodeLocation(textNode)); // { startOffset: 3, endOffset: 13 }
console.log(dom.nodeLocation(imgEl));    // { startOffset: 13, endOffset: 32 }

runVMScript(script)方法

使用runVMScript去動態的執行指令碼

reconfigure(settings)方法

window的頂級屬性被標記成 Unforgeable 意思是不能改的。如果你去改就會報錯。比如

window.location.href = "https://example.com/"

會丟擲異常 jsdomError 。你也不能新建 Window或者 Document 。

不過你可以使用 reconfigure 從jsdom外部來改變這些變數

const dom = new JSDOM();

dom.window.top === dom.window;
dom.window.location.href === "about:blank";

dom.reconfigure({ windowTop: myFakeTopForTesting, url: "https://example.com/" });

dom.window.top === myFakeTopForTesting;
dom.window.location.href === "https://example.com/";

  工具API

fromURL()

使用fromURL可以快速從url中建立JSDom物件

JSDOM.fromURL("https://example.com/", options).then(dom => {
  console.log(dom.serialize());
});

第二個引數,跟jsdom建立的引數一樣,但是有以下限制:

  • 不能指定 url 和 contentType 
  • The referrer option is used as the HTTP Referer request header of the initial request.
  • The resources option also affects the initial request; this is useful if you want to, for example, configure a proxy (see above).
  • 最終的 URL, content type, 和 referrer 由 response決定
  • 由response 通過 HTTP Set-Cookie  方法設定的cookie會儲存在 jsdom's cookie jar.裡面 同樣的你預先在 cookie jar 中設定的cookie會設定在request中傳送過去.

fromFile()

跟url類似,從html檔案中建立jsdom

JSDOM.fromFile("stuff.html", options).then(dom => {
  console.log(dom.serialize());
});

fragment()

有時你不需要虛擬整個頁面,甚至你都不需要windows和document物件。

const frag = JSDOM.fragment(`<p>Hello</p><p><strong>Hi!</strong>`);

frag.childNodes.length === 2;
frag.querySelector("strong").textContent = "Why hello there!";
// etc.

因為沒有完整的dom物件所以fragment不好序列化。但是如果fragment只包含一個元素,就很容易序列化了,比如

const frag = JSDOM.fragment(`<p>Hello</p>`);
console.log(frag.firstChild.outerHTML); // logs "<p>Hello</p>"

其他注意點

如果你想使用<canvas> ,記得要安裝 canvas 外掛

關閉jsdom

如果你在jsdom裡面設定了timer,比如 window.setTimeout() or window.setInterval() 那麼jsdom會為了保證這些timer正確的被執行,而繼續執行下去。如果你要關掉它,就用window.close() 。

除錯 

由於jsdom是nodejs實現的,也可以用chrome 開發者工具來除錯。(啟動時帶上--inspect)

測試非同步載入js

通過類似requirejs來非同步載入指令碼, 你需要通知jsdom何時js載入完畢。

<!-- Inside the HTML you supply to jsdom -->
<script>
requirejs(["entry-module"], () => {
  window.onModulesLoaded();
});
</script>

然後jsdom就可以知道何時模組載入完了

// On the Node.js side:
const window = (new JSDOM(...)).window;
window.onModulesLoaded = () => {
  console.log("ready to roll!");
};

共享結構和原型

為了效能考慮,多個jsdom例項共享結構和原型,比如

const dom1 = new JSDOM();
const dom2 = new JSDOM();

dom1.window.Element.prototype.expando = "blah";
console.log(dom2.window.document.createElement("frameset").expando); // logs "blah"

jsdom沒實現的東西

沒實現的部分主要是兩個方面:

  1. 導航:比如使用 window.location.href來改變頁面
  2. 佈局:通過css來指定的dom元素位置,所以暫時無法知道dom物件的位置。

如果你想使用這些特性你可以用PhantomJS