2021.06.25

開発者が帰宅した後もパソコンに働いてもらうためのNode.jsとSeleniumとJavascriptによるテスト自動化

こんにちは、原之園です。

今回は、Selenium+javascript+Node.jsによるテスト自動化です。
画面の操作ベースでテストを自動化することで、リグレッションテストにかかる工数を削減したいという方向けの内容です。

JESTを用いた単体テストについては、以下の弊社ブログをご覧ください。
JestでLightning Web Componentのテストコードを実装する方法

テスト自動化に向いていること

テスト自動化は、以下のシチュエーションにおいて向いていると考えています。

  1. シナリオにそった画面操作のリグレッション
  2. 大量の画面、機能のテスト
  3. バージョンアップ、障害復旧時の正常性確認

特に必要がないと思われるケースは、「短期間で役目を終えるサイトの開発」ぐらいではないかと思います。

Salesforceのテスト自動化における課題

テスト自動化のコードを書く際には、HTMLの属性を利用します。idやtitle、classと呼ばれるものです。 属性の利用については、XPATHについて をご参照ください。
Salesforceの標準の画面では、環境や表示するタイミングによってHTMLの要素に含まれるIDが変わってしまったり1、そもそも前述の属性がなかったりすることがあるので、その場合にはほかに特定する要素がないか自身で検討する必要があります。 またiframe2を使っている場合は、工夫が必要になります。

ChromeでXPathを取る・検証する
【備忘録】Chrome拡張機能「Scraper」利用したスクレイピング(ページ内の特定の情報を一括で取得および躓いたポイントまとめ)

  1. 画面操作をSeleniumのテストコードに落とすものがありますが、そのあたりのツールはこの仕様についていけないことがあるかと思います。ご注意ください。 

  2. iframeの場合、iframeに指定されたURLを操作対象に設定し直して、画面操作を改めて行う実装になります。 

はじめるには

  1. git
  2. git Bash
  3. node.js
  4. sfdx cli

以下のGithubに、サンプルとある程度操作をまとめたライブラリを置きました。ローカルでサンプルを動かすだけであればGithubのページのReadmeをご確認ください。

サンプルを見ていただくとわかりますが、画面の要素をクリックするだけでも何行か処理を書いたほうが良いことが多いです。
1)要素が表示されるのを待つ 2)その要素までスクロールする(ScreenShotのため)
3)クリックする

例としてライブラリを作成して使用していますが、ご自身の環境やテストで確認したい内容によって独自にライブラリを作成するのがよいかもしれません。
3)のクリックするの前に、スクリーンショットを取ったり、文言をチェックしたりするロジックを記載するとよいと思います。

うまくいったら

あとは、Sample.jsやSelenium WebDriver and JavaScriptなどを参考にテストコードを書き上げていってください。
バージョンを固定したいのであれば、package.jsonをローカルで編集していただければと思います。

■その他

Node.jsやJavascriptについては、Qiitaなどで検索するとさまざまな情報を調べられます。
Qiita::package.jsonの中身を理解する
Qiita::Node.jsの検索結果
Qiita::Javascriptの検索結果

呼び出しコード例

以下は実際に記載してみたサンプルコードです。そのままコピペしても動かないことがあると思いますので、参考程度にご覧ください。
説明はコメントで記載しています。

// 自作ユーティリティクラスの呼び出し。
const Utils = require('./lib/TestUtil');
// Chromeを利用する場合
require('chromedriver');
// firefoxを利用する場合
require('geckodriver');

// Assertを使って検証する場合
var assert = require('assert');

// Selenium本体の宣言
const webdriver = require('selenium-webdriver');
// 利用する機能について宣言
const { Builder, By, Key,until } = webdriver;


const config = {};

// テスト対象のURL
config.url = 'https://www.google.co.jp/';
config.outdir = 'ss';

// ブラウザと、ScreenShot保存先のフォルダを指定する
module.exports = async function (browser,path){
   // Chromeでテストする場合の設定を記載する。
    const capabilities = webdriver.Capabilities.chrome();
    capabilities.set('chromeOptions', {
        args: [
            '--headless',// 
            '--no-sandbox',
            '--disable-gpu',
            `--window-size=1980,1200`//ウインドウのサイズを指定
            // other chrome options
        ]
    });

    // ドライバーを作成。ブラウザ、設定を指定してbuild
    driver = await new Builder().forBrowser(browser).withCapabilities(capabilities).build();

    let u = new Utils(driver);
    (function ( browser ){
     // テストの実行
        test('test001');
    }(browser));

    // テストその1
    async function test(inName){
        const scrDir = path;
        const name = browser+'_'+inName;

        try {

            // 指定したURLのサイトを開く。今回はgoogle.com
            await driver.get(config.url);

            // Name属性に”q”がはいったHTML要素が表示されるのを待つ。
            await u.waitForLoadBy(By.name('q'));
            // screen shotを撮影して保存
            u.takeScr(scrDir,name+'-001.jpg');
        
            // Name属性に”q”がはいったHTML要素に 'selenium javascript'という文字を入力して、ENTERキーを押す
            await driver.findElement(By.name("q")).sendKeys('selenium javascript',Key.ENTER);
            await u.takeScr(scrDir,name+'-002.jpg');
        
            // 「次へ」が表示されたらクリックする
            await u.clickBy(By.id("pnnext"));
            await u.takeScr(scrDir,name+'-003.jpg');

            // 検索結果からリンクテキストの一部文字列に’Selenium’が入っているものを選択する。
            var elmlast = null;
            var elements = driver.findElements(By.partialLinkText("Selenium"));
            for(let elm of (await elements)) {
              elmlast = elm;
            }
            // 最後のHTML要素のクリック
            if(elmlast!= null){
                await elmlast.click();
                await u.takeScr(scrDir,name+'-004.jpg');
            }
            console.log(name+ ' done');
        }
        catch(err){
            u.log(err);
            console.log(name+ ' fault');
        }
        finally {
            await driver.quit();
        }
    }
}
TestUtil Sample.js

上記では大まかに以下のことを実施しています。

  1. Chrome用のWebdriverを生成する。
  2. 生成したWebdriverにテストしたいURLを渡して開く。ここでは、Googleの検索ページ
  3. u.waitForLoadByでname要素に’q’が設定された要素が表示されるのを待つ。
  4. "Selenium Webdriver"という単語を上記の要素に入力してEnterで検索する。
  5. "pnnext"のidの要素が表示されたらクリックする。
  6. "Selenium"の文字を持つパーシャルリンクの要素をすべて取得する。
  7. 上記の最後の要素を保存し、リンクをクリックする。

u.takeScr(scrDir,name+’-006.jpg’); 上記はscrDirで指定されたディレクトリに画像のスクリーンショットを撮影して保存する処理です。

Salesforceでは?

test.salesforce.comからログインし、テストしたいページへクリック、スクロールを駆使して進んでいきます。
ある程度URLが特定できているのであれば、ログイン→テストしたいページへ遷移しても問題ないでしょう。

sfdx cli を利用したい場合は以下の記事も参考になります。
salesforce dx
こちらを利用すれば、sfdxコマンドをJavascriptやシェルスクリプト内で呼び出すことができるようになります。
テスト中にバッチを実行したり、実際に登録更新されたデータをチェックしたりなど、テストできる範囲が広くなりますのでぜひご活用ください。

応用

git bashやsfdxコマンドが導入済みであれば、テスト中にSF上のデータを参照して挙動を変えることなどが可能になります。以前のブログなども合わせてご参照ください。
開発者が定時で帰るための8つのsfdxコマンド
Salesforce CLIをもっと活用してみよう

resemblejs

resemblejs というライブラリは、指定した2つの画像を比較して、差分個所をグラフィカルに表示してくれるライブラリです。
このライブラリを利用すれば、テストを実行して、スクリーンショットを撮影し、前回実行時のスクリーンショットと比較して結果を保存することもできるようになります。 

@slack/bolt

@slack/boltはSlackに投稿するためのライブラリです。撮影したスクリーンショットやテストの結果などをSlackに投稿することで速やかにチームメンバへ情報を共有することが可能になります。テスト結果をSlackに投稿するようにしておけば、わざわざテストを実行したマシンまで見に行かずとも全員に情報を共有できます。有効に活用したいですね。  

実際に

実際にテスト自動化を目指すとしても、すでに出来上がったシステムがある場合にはハードルが高いかと思います。
また、ずっと正常にテストさせるためのメンテナンスにもコストがかかります。
ただそれでもテスト自動化はやるべき価値があると考えています。

テストコードの作成にはスキルが必要ですが、作成されたテストのほうは、いつだれが動かしても(バグや障害が発生していない限り)同じ結果を返してくれます。 Salesforceでは定期的なバージョンアップでいろいろなところに変更が生じることがありますが、必要な機能にテスト自動化のコードを用意し、定期的に実行しておくだけでエビデンスを取得し、変更があったことをすぐに検知することもできます。 コアな部分に手を入れるときにも、人間が全機能のテストをするのは大変ですが、自動テストを用意していれば、人間は自動テストを実行するだけです。

さらに

ここまでの内容をもとに自動テストのコードを作成することができた場合、BitbucketやAzureDevOpsのPipeline上、あるいはGithubのActionsを利用してクラウド上でテストを実行させることまであと一歩です。 クラウド上で実行するところまでいけば、コミットしたときや、深夜の時間帯をトリガーに自動テストを実行することが可能になります。
エンジニアが余計なテストで時間をとられることもなく、存分に開発に専念できるわけです。
これは素晴らしいことだと思います!

みなさまも、ぜひテストの自動化にチャレンジしてみてください。
それでは、良いテスト自動化を!

11 件
     
  • banner
  • banner

関連する記事