Protractor で縦に長いページのスクリーンショットを撮るには
Appium と Protractor を使って iOS シミュレータや iOS の実機で E2E テストをしているのだが、縦に長いページをスクロールして全体のスクリーンショットを撮るのが難しかった。
const fs = require('fs');
// キャプチャを撮る関数
function writeScreenShot(data, fileName) {
const stream = fs.createWriteStream(fileName);
stream.write(new Buffer(data, 'base64'));
stream.end();
}
// 1枚目のキャプチャを撮る
browser.takeScreenshot()
.then((png) => {
// キャプチャ保存処理
writeScreenShot(png, 'ScreenShot1.png');
})
.then(element(by.id('pageBottomElem')).getLocation) // スクロールしたい付近の要素の位置を取得する
.then((elem) => {
// elem.y がページ上部からの位置を持っているので、その位置に window.scrollTo() で移動する
return browser.executeScript(`return window.scrollTo(0, ${elem.y});`);
})
.then(browser.takeScreenshot) // スクロールした後のキャプチャを撮る
.then((png) => {
// キャプチャ保存処理
writeScreenShot(png, 'ScreenShot2.png');
});
- 参考 : Protractorでスクロール関連のテスト - Qiita
getLocation()
とwindow.scrollTo()
の組み合わせを参照した。 - 参考 : Taking Screenshots with Protractor « ng-book.com – blog
writeScreenShot()
関数を参照した。
.then(element(by.id('pageBottomElem')).getLocation)
のような、Promise 回りの省略記法は使いたくないのだけど、今回試しに使ってみた。普段は全部以下のように書くようにしていて、この方がコメントも入れやすいんだけど、行はかさむという。
.then(() => {
// こうやって関数として return する書き方なら
// 冗長ではあるが、ココにコメントが入れやすかったりする
return element(by.id('pageBottomElem')).getLocation();
})
- 参考 : Promiseと仲良くなって気持ち良く非同期処理を書こう - Qiita
-
Promiseを返す非同期関数をそのまま渡すことも可能
-
これが .then(element(by.id('pageBottomElem')).getLocation)
などの部分。
- 参考 : Promise.prototype.then() - JavaScript | MDN
-
then() の引数として渡された関数(ハンドラ)が値を返した場合は、 Promise.resolve(値) によって、返値を自動的に Promise でラップします。
-
つまり return 5;
は return Promise.resolve(5);
と同じになるということ。ちなみに何も return
しなかった場合、次の .then()
で受け取れるのは undefined
となる。
「キャプチャ保存処理」をやっている then()
は Promise を return
していないので、その下で「要素の位置を取得する」ため getLocation()
を行っている then()
と並行処理になると思われる。しかし、「キャプチャ保存処理」は引数 png
で受け取ったデータを書き出すだけなので処理を待つ必要はない。そして getLocation()
は Promise を返すので、その次の「window.scrollTo()
で移動する」を行う then()
に無事繋がる、という寸法。
.then()
で繋げさえすればいいので、
browser.takeScreenshot()
.then((png) => {
// キャプチャ保存処理
writeScreenShot(png, 'ScreenShot1.png');
// スクロールしたい付近の要素の位置を取得する
return element(by.id('pageBottomElem')).getLocation();
})
.then((elem) => {
// 以下略
});
こうやって書いても動きはすると思うが、「1 処理 1 then()
」にした方が良い気がしたので止めた (「一日一善」みたいになってるw)。