5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

E2Eテストを行うNightWatchについて調べる(随時更新)

Last updated at Posted at 2020-09-20

はじめに

この記事はWebアプリケーションのエンドトゥーエンドのテストを行うためのツールであるNightWatchについてのメモになります。
https://nightwatchjs.org/

この記事は随時更新され、更新時にストックされているユーザに通知する想定です。

NightWatchはSeleniumを基盤として作られており、Node.jsでブラウザを操作してエンドトゥーエンドのテストを行うツールです。
なお、vue-cliでプロジェクトを作成するとe2eのテストとしてインストールされています。

本記事での実行環境は以下のようになります。
OS:MacOS Catalina 10.15.6
Node.js:v14.10.1
Java:openjdk 14.0.1
ChromeとFireFoxをインストール済み

簡単なサンプルの実行

コマンド実行
npm init -y

# nightwatchのインストール
# nightwatch.conf.jsが作成される。
npm install nightwatch --save-dev

# ChromeのSeleniumドライバーのインストール
npm install chromedriver --save-dev

# FireFoxのSeleniumドライバーのインストール
npm install geckodriver --save-dev

# seleniumserverのインストール
npm install selenium-server --save-dev

# FireFoxが起動してブラウザの操作を行う
npx nightwatch node_modules/nightwatch/examples/tests/ecosia.js

ecosia.jsの内容は以下の通りです。

node_modules/nightwatch/examples/tests/ecosia.js
describe('Ecosia.org Demo', function() {

  before(browser => browser.url('https://www.ecosia.org/'));

  test('Demo test ecosia.org', function (browser) {
    browser
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
  });

  after(browser => browser.end());
});

設定ファイル

nightwatchはデフォルトではカレントディレクトリのnightwatch.jsonまたはnightwatch.conf.jsを設定ファイルとして動作します。両方のファイルが存在する場合は、nightwatch.conf.jsを優先して使用します。

また、nightwatchを実行した際に、nightwatch.jsonまたはnightwatch.conf.jsが存在しない場合、nightwatch.conf.jsが作成されます。

特定の設定ファイルを指定して動作させる場合は以下の--configオプションを使用して起動します。

npx nightwatch --config nightwatch.json node_modules/nightwatch/examples/tests/ecosia.js

nightwatch.jsonの記述例

以下の設定ファイルはchromeでテストを動かすnightwatch.jsonの設定例です。

nightwatch.json
{
  "webdriver" : {
    "start_process": true,
    "server_path": "node_modules/.bin/chromedriver",
    "port": 9515
  },

  "test_settings" : {
    "default" : {
      "desiredCapabilities": {
        "browserName": "chrome"
      }
    }
  }
}

nightwatch.conf.jsの記述例

以下の設定ファイルはfirefoxでテストを動かすnightwatch.conf.jsの設定例です。

nightwatch.conf.js
module.exports = {
  webdriver: {
    start_process: true,
    port: 4444,
    server_path: require('geckodriver').path,
  },
  test_settings: {
    default: {
      launch_url: 'https://nightwatchjs.org',
      desiredCapabilities : {
        browserName : 'firefox'
      }
    }
  }
};

よく使用する設定

名前 タイプ デフォルト 解説
src_folders Stringの配列 なし テストが配置されているフォルダーの配列。指定したフォルダのサブフォルダ中のファイルも実行されます。指定されていない場合は、テストソースをテストランナーの 2番目の引数としてインラインで渡す必要があります。

VSCodeでデバッグする方法

(1).vscode/launch.jsonを以下のように作成します。

.vscode/launch.json
 {
     "version": "0.0.1",
     "configurations": [
         {
         "type": "node",
         "request": "attach",
         "name": "Attach to Process",
         "port": 9229
         }
     ]
 }

(2)ターミナルで以下のコマンドを実行する

ターミナル
 node --inspect-brk ./node_modules/nightwatch/bin/runner.js sample.js

(3)VSCodeでAttachボタンを押下してデバッグを実行する

image.png

コマンド

コマンドキュー

公式の解説
https://github.com/nightwatchjs/nightwatch/wiki/Understanding-the-Command-Queue

Nightwatchがテストを実行すると、そのコマンドはコマンドキューと呼ばれるリストで処理されます。 コマンドキューのコマンドは非同期実行で実行されており、先入先出法(FIFO)でコマンドが実行されます。

コマンドAPIは以下のサンプルコードのように、テストケースに渡されたbrowserやclientといったオブジェクトを介してアクセス可能になっています。

サンプルコード.js
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

このコードを実行した時点では、Seleniumのclickなど処理を実行するのではなく、コマンドキューにコマンドを追加します。
nightwatchのコマンドは1つまたは複数のSeleniumのリクエストをラップしたものとなります。このSeleniumのリクエストは非同期で順番に実行されていきます。
ほぼすべてのネイティブのNightwatchコマンドは、コマンドがコマンドキュー内で実行された際のコールバック関数をサポートしています。

例えばclickコマンドは以下のようにコールバックを記述することができます。

サンプル.js
browser.click('button', function (result) {
  // コマンドキュー中のクリックコマンドが終了した場合に
  // seleniumのclick操作の応答を引数としてコールバックが実行されます。
});

コールバックの主な目的は、テストの実行中にテストからデータをキャプチャできるようにすることです。
例えば、以下の例ではgetValueにコールバックを指定して、取得した値をコンソールに出力しています。

サンプルコード.js
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .getValue('input[type=search]', function(result) {
        console.log(result);
      })
      .assert.visible('button[type=submit]')
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

また、このコールバック内でコマンドキューにコマンドを追加した場合、実行したコマンドの後に新しいコマンドが追加されることになります。以下の例では、clickをした後に5000ms処理を停止します。

コールバック中のコマンドの追加
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .click('button[type=submit]',function(result) {
        browser.pause(5000);
      })
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

コマンドのコールバックで取得した値を、別のコマンドの引数として利用するにはどうしたらいいでしょうか?
例えば、以下のコードは期待通りに動作しません。

正常に動作しない例.js
var text;
browser
  .getValue('#input', function (result) {
    text = result.value;
  })
  .setValue('#output', text); // WRONG: text is undefined

これを正しく動作させるにはgetValueのコールバック中にsetValueコマンドを実行する必要があります。

正常に動作する例.js
var text;
browser.getValue('#input', function (result) {
  text = result.value;
  browser.setValue('#output', text); // RIGHT
});

Protocol Element Commands

Protocol Element CommandsはWebElementIDをパラメータとして動作する低レベルのコマンドです。これらのコマンドは通常、カスタムコマンドやカスタムアサーションを作成するのに利用します。

WebElementIDの取得方法

nightwatchでweb elementi idを取得するにはelementコマンドまたはelementsコマンドを使用します。

サンプルコード
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .element('css selector', 'div', (result)=> {
        console.log('element', result);
      })
      .elements('css selector', 'div', (result)=> {
        console.log(result);
        result.value.forEach(elem=>{
          const key = Object.keys(elem)[0];
          const ElementIDvalue = elem[key];
          console.log(key, ElementIDvalue);
          browser.elementIdAttribute(ElementIDvalue, 'class', function(attr) {
            console.log(attr.value);
          });
        });
      })
      .end();
  }
};

第一引数にはロケーターストラテジーの種類を以下のいづれかから選択します。

  • css selector
  • link text
  • partial link text
  • tag name
  • xpath

第二引数にはロケーターストラテジーにあった要素を絞り込むための値を指定します。

第三引数はコールバック関数になります。
elemntコマンドとelementsコマンドの違いは結果の値になります。elementコマンドの場合は条件に合うどれか一つの要素のWebElementIDのみが取得できますが、elementsコマンドの場合は条件にあう全てのWebElementIDが取得できます。

elementコマンドの結果例
{
  value: {
    'element-6066-11e4-a52e-4f735466cecf': '8b24022d-1455-de40-bf78-3135a6883dfa'
  }
}
elementsコマンドの結果例
{
  value: [
    {
      'element-6066-11e4-a52e-4f735466cecf': '8b24022d-1455-de40-bf78-3135a6883dfa'
    },
    {
      'element-6066-11e4-a52e-4f735466cecf': '625db85e-9d09-cb45-b93d-ed5c1c9e7b0c'
    },
    {
      'element-6066-11e4-a52e-4f735466cecf': 'f27f778d-3a93-e64c-b5a2-dfeff18cc026'
    },
    // 略
  ]
}

Protocol Element Commandsはここで取得したWebElementIDを使用する必要があります。

Protocol Element Commandsの種類

名称 説明
elementIdAttribute 要素の属性を取得します
elementIdClear 要素を表示するようにスクロール後、要素の値をクリアします
elementIdClick 要素を表示するようにスクロール後、要素をクリックします
elementIdCssProperty 指定された要素の指定されたCSSプロパティを取得します。
elementIdDisplayed 指定された要素が現在表示されているか確認します。
elementIdEnabled 要素が有効になっているか確認する
elementIdLocation ページの左上を(0,0)とした場合の要素の位置を取得する。
elementIdLocationInView 要素がスクロールされていて表示されている場合に画面上の位置を取得する
elementIdName 指定された要素のタグ名を取得する
elementIdSelected option,radio,checkboxの場合に指定された要素が選択中か調べる
elementIdSize 要素のサイズを取得する
elementIdText 要素の表示中のテキストを取得する
elementIdValue スクロールしてフォームの要素に値を入力するかまたは現在の値を取得します
submit FORMまたはFORMの子要素を指定してsubmitを行います
elementIdValueのサンプルコード
サンプルコード
module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .element('xpath', '//input[contains(@type, "search")]', (elem)=> {
        const key = Object.keys(elem.value)[0];
        const elemId = elem.value[key];
        browser.elementIdValue(elemId, 'nightwatch', (x)=> {
          browser.elementIdValue(elemId, (v)=> {
            console.log(v);
          });
        });
      })
      .end();
  }
};

このコードはwebdriverによって挙動が変わります。
Chromeの場合elementIdValueは以下の値を返します。

結果
{
  sessionId: '1d1752ad633221dbc083acd93574b7a2',
  status: 0,
  value: 'nightwatch'
}

Firefox(geckodriver 1.20.0)の場合は以下の値を返します。

結果
{ value: '' }

なお、Firefoxの場合でもgetValueを使用した場合はnightwathが取得できます。

getValueとelementIdValueの取得の違い

WebDriverに対して発行しているコマンドが異なるため挙動が異なります。

getValueの場合
/element/${id}/property/value

elementIdValueの場合
/element/${id}/attribute/value

修正案1

elementIdValue実行時に呼び出す関数を書き換えます。
ただし、Firefoxの当該事象だけを直すだけなら以下の修正で直るが影響範囲が不明です。

nightwatch/lib/api/protocol/elementIdValue.js
module.exports = class Session extends ProtocolAction {
  command(webElementId, value, callback) {
    ProtocolAction.validateElementId(webElementId, 'elementIdValue');

    if (arguments.length === 1 || typeof arguments[1] == 'function') {
      callback = arguments[1] || function (r) {return r};
      // 以下のように修正
      //return this.transportActions.getElementAttribute(webElementId, 'value', callback);
      return this.transportActions.getElementProperty(webElementId, 'value', callback);
    }

    return this.transportActions.setElementValue(webElementId, value, callback);
  }
};
修正案2

valueの取得を行う際にelementIdValueを使用しないでelementIdProperty関数を使用します。
ただし、当該関数はアンドキュメントの関数です。

          browser.elementIdProperty(elemId, 'value', (v)=> {
            console.log(v);
          });

カスタムコマンド

nightwatchでは独自のカスタムコマンドを作成することができます。
https://nightwatchjs.org/guide/extending-nightwatch/custom-commands.html

単純な例

まず設定ファイルにカスタムコマンドを配置するフォルダを指定します。
以下の例ではcommandsというフォルダに配置します。

nightwatch.conf.js
module.exports = {
  // 略
  custom_commands_path:  'commands',
  // 略
}

次にカスタムコマンドをcommandsフォルダに作成します。以下の例ではmycmdというコマンドを作成します。このコマンドはpauseコマンドと同じで操作の待機を行います。

js/commands/mycmd.js
module.exports = class CustomPause {
  command(ms, cb) {
    // If we don't pass the milliseconds, the client will
    // be suspended indefinitely
    if (!ms) {
      return;
    }

    const returnValue = {
      value: 'something'
    };

    return new Promise((resolve) => {
      setTimeout(() => {
        // if we have a callback, call it right before the complete event
        if (cb) {
          cb.call(this.api);
        }

        resolve(returnValue);
      }, ms);
    });
  }
}

カスタムコマンドは以下のように利用します。

module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .url('https://www.ecosia.org/')
      .waitForElementVisible('body')
      .assert.titleContains('Ecosia')
      .assert.visible('input[type=search]')
      .setValue('input[type=search]', 'nightwatch')
      .assert.visible('button[type=submit]')
      .mycmd(5000) // カスタムコマンド
      .click('button[type=submit]')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

既存のコマンドを組み合わせたカスタムコマンド

カスタムコマンドはthis.apiを経由してbrowserオブジェクトにアクセスできます。これを利用して既存のコマンドを組み合わせたカスタムコマンドを作成できます。

commands/mysearch.js
module.exports = class CustomSearch {
  command(term, cb) {
    const returnValue = {
      value: 'something'
    };

    return new Promise((resolve) => {
      this.api.url('https://www.ecosia.org/')
              .waitForElementVisible('input[type=search]')
              .setValue('input[type=search]', term)
              .click('button[type=submit]', ()=> {
                if (cb) {
                  cb.call(this.api);
                }
                resolve(returnValue);
              })
    });
  }
}

カスタムコマンドの使用例は以下のようになります。


module.exports = {
  'Demo test ecosia.org' : function(browser) {
    browser
      .mysearch('nightwatch')
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

ページオブジェクトの利用

SeleniumでE2Eテストを行う場合、ページの操作をまとめるページオブジェクトパターンがよく利用されます。
https://www.selenium.dev/documentation/ja/guidelines_and_recommendations/page_object_models/

nightwatchでもページオブジェクトを利用するための機能が提供されています。
https://nightwatchjs.org/guide/working-with-page-objects/

単純な例

まず設定ファイルにページオブジェクトを配置するためのフォルダをpage_objects_pathを利用して指定します。

nightwatch.conf.js
//略
  page_objects_path: 'pageobjects',
//略

次に指定したフォルダにページオブジェクトを格納します。この例ではpageobjects/ecosiaPage.jsを作成します。

pageobjects/ecosiaPage.js
var customCommands = {
  search: function(term) {
    return this.setValue('@searchText', term)
      .click('@searchButton')
  }
};

module.exports = {
  url: 'https://www.ecosia.org/',
  commands: [customCommands],
  elements: {
    searchText: {
      selector: 'input[type=search]'
    },
    searchButton: {
      selector: 'button[type=submit]'
    }
  }
};

ページオブジェクトではelementsで指定したserchText,serchButtonのセレクタに@を付与してアクセスすることができます。
このページオブジェクトを利用したテストコードは以下のようになります。

module.exports = {
  'pageobject test' : function(browser) {
     const targetPage = browser.page.ecosiaPage()
     targetPage.navigate()
      .waitForElementVisible('@searchText')
      .search('nightwatch')
     browser
      .assert.containsText('.mainline-results', 'Nightwatch.js')
      .end();
  }
};

リモートの環境でテストする

リモートのseleniumでテストするための設定と手順

(1)設定ファイルを以下のように設定する

nightwatch.conf.js
module.exports = {
  src_folders: ['tests'],

  webdriver: {
    // ローカルのwebdriverを起動しないようにする
    "start_process" : false
  },

  test_settings: {
    default: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false
        }
      },
      // リモートのwebdriverのipとportを指定する
      selenium_port :4444,
      selenium_host : "192.168.0.18"
    }
  }
};

(2)ブラウザを動作させたい端末でseleniumserverを起動する。

java -jar selenium-server-standalone-3.141.59.jar

*この例ではchromedriverとselenium-serverを同じフォルダに格納している。

(3)nightwatchでテストを開始する。

自己認証をしているサーバーにアクセスする場合の設定

chromeの場合、起動オプションに「-ignore-certificate-errors」を付与すると自己認証のエラーページをスキップできる

nightwatch.conf.js
// 略
    seleniumC: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false,
          args : [
            '--use-fake-ui-for-media-stream',
            '--use-fake-device-for-media-stream',
            '--no-sandbox',
            '-ignore-certificate-errors'
          ]
        }
      },
// 略

チャットプログラムのようなアプリのテスト

以下のチャットアプリをテストする例を考えてみます。
https://github.com/mima3/webrtc_sample/tree/master/webrtc02
image.png

複数のseleniumを起動する環境の作成

まず複数のseleniumを起動するための環境を作成します。

docker run --name seleniumA -d -p 14444:4444 -p 15900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug

docker run --name seleniumB -d -p 24444:4444 -p 25900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug

macosの場合、作成したseleniumの環境にはFinder→移動→サーバーへ接続で接続が可能です。

接続先:vnc://localhost:15900
パスワード:secret

image.png

nightwatchで複数のseleniumを同時に操作する方法

seleniumでテストコードを記述した場合と違って、nightwatchのテストコード中からseleniumを操作するためのbrowserオブジェクトを複数操作することはできません。
その代わり、nightwatchでは複数の環境で同時に同じテストを動かすということが可能です。

まず、設定ファイルのtest_settingsに作成したdocker中のseleniumAとseleniumBへの接続情報を記載します。

nightwatch.conf.js
  test_settings: {
    seleniumA: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false
        }
      },
      // リモートのwebdriverのipとportを指定する
      selenium_port :14444,
      selenium_host : "localhost"
    },
    seleniumB: {
      desiredCapabilities: {
        browserName : 'chrome',
        chromeOptions : {
          w3c : false
        }
      },
      // リモートのwebdriverのipとportを指定する
      selenium_port :24444,
      selenium_host : "localhost"
    }
  }

// 略

設定ファイルを更新後、以下のように-eコマンドで実行したい環境を複数指定することで同じテストを同時に複数の環境で動かすことができます。

npx nightwatch tests/webrtc.js -e seleniumA,seleniumB

複数環境を指定して起動した場合は以下のようにnightwatchは環境ごとに子プロセスを作成して動作します。

+npx nightwatch tests/webrtc.js -e seleniumA,seleniumB
  |--- nightwatch tests/webrtc.js --env seleniumA --parallel-mode
  \--- nightwatch tests/webrtc.js --env seleniumB --parallel-mode

そのため、「process.env.__NIGHTWATCH_ENV」を使用して、現在のプロセスで動作している環境を取得して、環境ごとに処理を記述することでチャットプログラムのテストコードを記載することができます。

以下のコードはそのサンプルコードとなります。

webrtc.js
module.exports = {
  'WebRTC' : function(browser) {
    const seleniumA = process.env.__NIGHTWATCH_ENV === 'seleniumA';
    browser
      .url('http://192.168.10.239:3000/')
      .waitForElementVisible('xpath', '//button[contains(.,"公開")]')
      .pause(10000)  // 全ての環境が起動するまで待機する。
    if(seleniumA) {
      browser.click('xpath', '//button[contains(.,"公開")]')
             .waitForElementVisible('xpath', '//button[contains(.,"送信")]')
             .waitForElementVisible('xpath', '//ul/li[contains(.,"テスト入力B")]')
             .setValue('xpath', '//input', 'テスト入力A')
             .click('xpath', '//button[contains(.,"送信")]')
             .pause(5000)
    } else {
      browser.waitForElementVisible('xpath', '//button[contains(.,"offer")]')
             .click('xpath', '//button[contains(.,"offer")]')
             .waitForElementVisible('xpath', '//button[contains(.,"送信")]')
             .waitForElementVisible('xpath', '//input')
             .setValue('xpath', '//input', 'テスト入力B')
             .click('xpath', '//button[contains(.,"送信")]')
             .waitForElementVisible('xpath', '//ul/li[contains(.,"テスト入力A")]')
             .pause(5000)
    }
    browser
      .end();
  }
};

さて、このプログラムでは複数の環境でブラウザが起動するというケースをpauseで待機していますが、本来であれば、プロセス間通信とかを利用して待ち合わせを行った方が望ましいでしょう。

以下のテストコードは上記のテストコードを発展させて、node-ipcを使用してプロセス間通信を行い環境間のタイミングを考慮しながらテストを行うためのサンプルコードになっています。

トラブルシュート

Selenium Gridを使うケースでうまく行かない。

問題
以下のようなSeleniumGridを使うDockerでwaitForElementVisible等が失敗する。
https://github.com/elgalu/docker-selenium

全く同じなのにFireFox80は動作したがChrome85では動作しない

対応
w3cフラグをオンにする
image.png

経緯
どうも、JsonWireProtocolだとfindElementで要素が取得できてもresult.statusに値が入っていなくてエラーになる。
https://github.com/nightwatchjs/nightwatch/blob/master/lib/transport/jsonwire.js#L54

以下のコードでWebdriverProtocolを作成するルートになると正常に動作する
https://github.com/nightwatchjs/nightwatch/blob/dae6f9ddfcb9cbf0c7b32944a110f6ed8ecbc84c/lib/transport/transport.js#L311

Safariで複数インスタンスを起動すると動きません。

Safariの仕様です。
Safariの場合、Chromeと同じノリで複数インスタンスを起動してテストはできません。
https://developer.apple.com/documentation/webkit/about_webdriver_for_safari

SafariでwaitForVisibleが動きません。

まず、Mac+Safari12+ではwaitForVisibleが使用しているdisplayedを許可していません。

https://github.com/nightwatchjs/nightwatch/issues/2308
https://developer.apple.com/documentation/webkit/macos_webdriver_commands_for_safari_12_and_later

最新のコードでは以下の様に修正されていますが、これはhidden属性のみをみているので、sizeやdisplay:noneは考慮していません。

対応
MacのSafariを動かしたい場合だけなら以下の様なパッチでいけます。
多分、iosではgetElementPropertyあたりが動かないと思います。
https://github.com/nightwatchjs/nightwatch/issues/2308

node_modules/nightwatch/lib/api/element-commands/_waitForDisplayed.js
class WaitForDisplayed extends WaitForElement {
  protocolAction() {
    if (this.api.capabilities) {
      const {browserName} = this.api.capabilities;
      if (browserName.toLowerCase() === BrowserName.SAFARI) {
        /*
        return this.executeProtocolAction('getElementProperty', ['hidden']).then(result => ({
          value: result.value === false
        }));
        */
        const self = this
        return this.executeProtocolAction('getElementProperty', ['hidden']).then(async (result) => {
          if (result.value) {
            return {
              value: false
            }
          }
          const displayResult = await self.executeProtocolAction('getElementCSSValue', ['display'])
          if (displayResult && displayResult.value === 'none') {
            return {
              value: false
            }
          }
          const sizeResult = await self.executeProtocolAction('getElementSize')
          if (sizeResult.value.error) {
            console.warn('サイズ取得時にerror発生したので要素は破棄されたとみなす。' + sizeResult.value.error)
            return {
              value: false
            }
          }
          if (sizeResult && (sizeResult.value.width === 0 || sizeResult.value.height === 0 )) {
            return {
              value: false
            }
          }
          return {
            value: true
          }
        })
      }
    }

    return this.executeProtocolAction('isElementDisplayed');
  }

remoteのchromeに接続すると起動オプションが渡らない場合がある

問題
RemoteのChromeに以下の様な設定で起動オプションを渡しても渡っていない様な挙動をする

// 略
    remoteMacChrome: 
      webdriver: {
        default_path_prefix: "/wd/hub",
        use_legacy_jsonwire: false,
        start_process : false,
        host:"192.168.0.18",
        port: 4444,
      },
      desiredCapabilities: {
        browserName: 'chrome',
        javascriptEnabled : true,
        acceptSslCerts : true,
        'chromeOptions': {
          w3c: true,
          args: [
            '--use-fake-ui-for-media-stream',
            '--use-fake-device-for-media-stream',
            '--no-sandbox',
            '--ignore-certificate-errors',
            '--auto-select-desktop-capture-source=Entire',
            '--enable-usermedia-screen-capturing',
          ]
        }
      }
    },

対応
goog:chromeOptionsを使用してみます。

    remoteMacChrome: {
      webdriver: {
        default_path_prefix: "/wd/hub",
        use_legacy_jsonwire: false,
        start_process : false,
        host:"192.168.0.18",
        port: 4444,
      },
      desiredCapabilities: {
        browserName: 'chrome',
        javascriptEnabled : true,
        acceptSslCerts : true,
        'goog:chromeOptions': {
          w3c: true,
          args: [
            '--use-fake-ui-for-media-stream',
            '--use-fake-device-for-media-stream',
            '--no-sandbox',
            '--ignore-certificate-errors',
            '--auto-select-desktop-capture-source=Entire',
            '--enable-usermedia-screen-capturing',
          ]
        }
      }
    }

経緯
https://github.com/webdriverio/webdriverio/issues/2645
https://github.com/SeleniumHQ/docker-selenium/issues/674

DeviceFarmのChromeに引数が渡らない。

問題
DeviceFarmにChromeの引数を渡しても正常に受け付けてくれない。

対応
いくつかの引数はDeviceFarm側で禁止しています。
https://docs.aws.amazon.com/devicefarm/latest/testgrid/techref-support.html

それ以外の引数が正常に渡らない場合は、以下の様にalwaysMatchの配下に記述してみましょう。

    default: {
      desiredCapabilities: {
        alwaysMatch: {
          browserVersion: 'latest',
          browserName : 'chrome',
     
          'goog:chromeOptions': {
             args:[
               '--no-sandbox',
               '--disable-gpu',
               '--window-size=300,400',
               '--use-fake-ui-for-media-stream',
               '--use-fake-device-for-media-stream',
               '--no-sandbox',
               '--ignore-certificate-errors',
               '--auto-select-desktop-capture-source=Entire',
               '--enable-usermedia-screen-capturing',
             ]
          }
  
        }
      }

selenium-webdriverでは動くけど、nightwatchでは動作しないことがある

問題
selenium-webdriverでは動くけど、nightwatchでは動作しないことがある

対応
実はnightwatchはselenium-webdriverのコードを使わずに、独自のコードでremoteのdriverと通信しているので、同じ設定をしたつもりでも、微妙に違う内容が送信されているケースがあります。
nightwatchを実行して期待通り動作しなかった場合は、一度、selenium-webdriverで実装してみて違いをみてみるといいでしょう。

前述のDeviceFarmの問題の際は、以下にログを入れて動作を比較・検証しました。
https://github.com/nightwatchjs/nightwatch/blob/master/lib/http/request.js#L225
https://github.com/SeleniumHQ/selenium/blob/trunk/javascript/webdriver/http/http.js#L114

その他参考

run test in two different browser tabs or windows
https://groups.google.com/g/nightwatchjs/c/GAL0CfPLrMU

HOW TO EXECUTE TESTS IN MULTIPLE BROWSERS PARALLELY WITH NIGHTWATCH JS
https://testersdock.com/execute-parallel-tests-nightwatchjs/

Testing WebRTC Apps with Nightwatch
http://v09.nightwatchjs.org/blog/

5
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?