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');
  });

.then(element(by.id('pageBottomElem')).getLocation) のような、Promise 回りの省略記法は使いたくないのだけど、今回試しに使ってみた。普段は全部以下のように書くようにしていて、この方がコメントも入れやすいんだけど、行はかさむという。

.then(() => {
  // こうやって関数として return する書き方なら
  // 冗長ではあるが、ココにコメントが入れやすかったりする
  return element(by.id('pageBottomElem')).getLocation();
})

これが .then(element(by.id('pageBottomElem')).getLocation) などの部分。

つまり 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)。