AWS Lambda (Node.js 4.3) で PhantomJS + WebdriverIO を使用してヘッドレスWebブラウザを操作する

  • 3
    いいね
  • 0
    コメント

目的

  • 社内グループウェア (Aipo) での出勤操作を自動化したかったんだ....
    • Amazon Dash ボタン + maddox/dasher + API Gateway + Lambda で社内グループウェア へログイン → 出勤ボタンをクリック
    • LT用のネタとして

構成

AWS Lambda (Node.js 4.3)

下記の設定でひとまず動作した。

  • メモリ: 512MB
  • タイムアウト: 3分

コードは npm でパッケージ管理を行い、Apex でデプロイを行った。

PhantomJS

実際には、下記のprebuiltなnpmモジュール: phantomjs-prebuilt を使用した。

開発時・デプロイ時は、このビルド済み PhantomJS バイナリをデプロイパッケージに含めて Lambda へアップロードするため、数十秒〜1分程度かかるつらみがある.....

WebdriverIO

phantomjs-prebuiltのREADME を見ると、下記の2通りの実行方法が記載されている。

  • Running via node: PhantomJS に処理を記載したjsファイルを食わせる方法
  • Running with WebDriver: Selenium WebDriver のプロトコルを介して (WebdriverIO から) 操る方法

主観的に、なんとなく WebdriverIO を使用した記述方法のほうが見通しが良さそうだったのでこっちの方法を選択した。

準備

package.json
.
.
.
"dependencies": {
  "phantomjs-prebuilt": "^2.1.14",
  "webdriverio": "^4.6.2"
}
.
.
.

$ env PHANTOMJS_PLATFORM="linux" PHANTOMJS_ARCH="x64" npm install

phantomjs-prebuilnpm install 時にシェルの実行環境を判定してふさわしいバイナリをダウンロードするらしい。

そのため、

macOS でデプロイパッケージ作成

Lambda (Amazon Linux? x64) へアップロード

などの状況の場合は、下記の説明のとおりに環境変数を設定して、Linux x64 用のバイナリをダウンロードさせる (もしくは、Linux x64 な環境で作業を行う)。

If you know in advance that you want to install PhantomJS for a specific architecture, you can set the environment variables: PHANTOMJS_PLATFORM (to set target platform) and PHANTOMJS_ARCH (to set target arch), where platform and arch are valid values for process.platform and process.arch.
Cross-Platform Repositories

Lambda関数

Running with WebDriver に従って記述。

'use strict';

const phantomjs = require('phantomjs-prebuilt');
const webdriverio = require('webdriverio');

const webDriverOpts = {
    desiredCapabilities: {
        browserName: 'phantomjs',
        logLevel: 'verbose',
        host: 'localhost',
        port: '4444'
    }
};

exports.handler = (event, context, callback) => {
    phantomjs
        .run('--webdriver=4444')
        .then((phantom) => {
            const client = webdriverio.remote(webDriverOpts).init();
            return client
                .url("http://example.com/aipo")
                .waitForExist('.button[value="ログイン"]', 10000)
                .setValue('#member_username', "ユーザー名")
                .setValue('#password', "パスワード")
                .click('.button[value="ログイン"]')
                .waitForExist('=Aipo', 10000)
                .click('//input[@type="button" and (@value="出勤" or @value="退勤")]')
                .pause(5000)
                .then(() => {
                    phantom.kill();
                    return Promise.resolve();
                })
                .catch((err) => {
                    console.error(`error: ${err}`);
                    phantom.kill();
                    return Promise.reject(err);
                });
        })
        .then(() => {
            console.log("done");
            context.succeed({
                statusCode: 200,
                body: JSON.stringify({ "message": "done" })
            });
        })
        .catch((err) => {
            console.error(`failed: ${err}`);
            context.fail({
                statusCode: 500,
                body: JSON.stringify({ "message": `failed:${err}` })
            });
        });
};

実際に書いたコード

hiraro/lambda-phantomjs