Serima Software
    Home

    chromeless on Lambda を試してみる

    最近は、E2E テストをどうにかできないかと思って、色々探っています。そのなかで、個人的にアツいなと思った chromeless の紹介と試しに使ってみたレポートを行いたいと思います。

    僕が探した限りだと、現時点(2017/10/23)で日本語の記事は、githubのTrendingになっているchromelessをWindowsで試すの1件のみに思えました。

    https://github.com/graphcool/chromeless

    AWS Lambda 上で Headless Chrome を動作させ、そちらに対してリクエストを行うということで、ブラウザテスト実行の並列性を 1000 まで上げるというアプローチを行うのがこちらのライブラリの目指すところです。

    chromeless の README にも記載がありますが、AWS Lambda 上で動作させるために、AWS のアカウントが必要になります。

    image

    Lambda 上で動作させるモードと Local で動作させるモードのふたつがあり、 Lambda 上で動作させるモードは Proxy 利用となります。

    大まかな手順としては、以下のようになります。

    1. chromeless リポジトリを clone し、Proxy 用のサービスのディレクトリで npm install2. コンフィグを設定し、自分のアカウントの Lambda 上に function をデプロイ3. デプロイが完了すると、endpoint の url と api key が発行される4. 発行された endpoint, api key を利用し、proxy に対してリクエストする

    手順はこちら。

    https://github.com/graphcool/chromeless/tree/master/serverless#chromeless-proxy-service

    ひとつハマりどころとしては、全サービス (AWS IoT, AWS Lambda, AWS S3) のリージョンは揃えておく必要があります。

    この状態で、Lambda のマネジメントコンソールを確認してみると、たしかに 4 つの関数が追加されています。

    image

    さて、次はこちらの API に対してリクエストを投げてみたいと思います。ここでは試しに iPhone の UserAgent と ViewPort を設定してみています。

    こちらを実行してみますと、ほどなくして以下のような出力があらわれます。

    s3 にスクリーンキャプチャが自動的にアップロードされ、その URL が返ってきているのがわかります。

    早速開いてみますと、ViewPort がちゃんと反映はされているものの、文字化けというか日本語が表示されるであろう箇所が豆腐になっています…。

    image
    image
    image
    image

    ちなみに、 https://www.yahoo.com/ を指定してみると

    image

    ちゃんと出ています。

    やはり日本語が表示されないようです。その後いろいろと調べていると、chromeless が依存している serverless-chrome に日本語フォントがインストールされていないため、日本語は表示されないという状況のようです。

    その issue は こちら

    こちらの issue にもコメントされていますが @fd0 さんのブログに「serverless-chrome で日本語を表示できるようにする」というエントリがあり、できないことはないようです。(lambda の 50MB 制限を超えないようにフォントをインストールした chrome のバイナリを strip して容量を減らすというの、面白かった)

    フォント問題は意外と根が深いのかもな…と思いつつ、本家の対応を待つかという気持ちになりつつあります。

    ちなみに、chromeless にももちろん click(selector: string) や type(input: string, selector?: string) などの Chrome でのメソッドが使用できます。(一部未実装があるようですが)一般的に E2E テストは実行時間が遅いという認識ですが、Lambda を利用して並列数を爆発的に増やすというアプローチはすごく面白いので、今後も非常に期待しています。

    % export AWS_PROFILE=dev
     
    % npm run deploy
     
     
    > chromeless-remotechrome-servie@1.3.0 deploy /Users/serima/chromeless-lambda/chromeless/serverless
    > serverless deploy
     
    Serverless: Compiling with Typescript...
    Serverless: Using local tsconfig.json
    /Users/serima/chromeless-lambda/chromeless/serverless/node_modules/@types/graphql/subscription/subscribe.d.ts (17,4): Cannot find name 'AsyncIterator'.
    /Users/serima/chromeless-lambda/chromeless/serverless/node_modules/@types/graphql/subscription/subscribe.d.ts (29,4): Cannot find name 'AsyncIterable'.
    Serverless: Typescript compiled.
    Serverless: Injecting Headless Chrome...
    Serverless: Packaging service...
    Serverless: Excluding development dependencies...
    Serverless: Creating Stack...
    Serverless: Checking Stack create progress...
    .....
    Serverless: Stack create finished...
    Serverless: Uploading CloudFormation file to S3...
    Serverless: Uploading artifacts...
    Serverless: Uploading service .zip file to S3 (74.27 MB)...
    Serverless: Validating template...
    Serverless: Updating Stack...
    Serverless: Checking Stack update progress...
    ..........................................................................................
    Serverless: Stack update finished...
    Service Information
    service: chromeless-serverless
    stage: dev
    region: ap-northeast-1
    stack: chromeless-serverless-dev
    api keys:
      dev-chromeless-session-key: xxxxxxxxxxxxxxxxxxx
    endpoints:
      GET - https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/version
      OPTIONS - https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
      GET - https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/
    functions:
      run: chromeless-serverless-dev-run
      version: chromeless-serverless-dev-version
      session: chromeless-serverless-dev-session
      disconnect: chromeless-serverless-dev-disconnect
    const Chromeless = require('chromeless').default
     
    async function run(url) {
      const chromeless = new Chromeless({
        remote: {
          endpointUrl: 'https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/dev',
          apiKey: 'xxxxxxxxxxxx'
        },
      })
     
      const screenshot = await chromeless
        .setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53')
        .setViewport({width: 640, height: 1136, scale: 1})
        .goto(url)
        .screenshot()
     
      console.log(url)
      console.log(screenshot) // prints local file path or S3 url
     
      await chromeless.end()
    }
     
    run('https://www.yahoo.co.jp').catch(console.error.bind(console))
    run('https://www.google.com').catch(console.error.bind(console))
    run('https://www.microsoft.com').catch(console.error.bind(console))
    run('https://www.facebook.com').catch(console.error.bind(console))
    % time node run.js
    https://www.yahoo.co.jp
    https://xxxxx-ap-northeast-1-chromeless.s3.amazonaws.com/cj946gjq6000001oy1bc2zzvg.png
    https://www.microsoft.com
    https://xxxxx-ap-northeast-1-chromeless.s3.amazonaws.com/cj946gjrb000001m9nxomxprr.png
    https://www.google.com
    https://xxxxx-ap-northeast-1-chromeless.s3.amazonaws.com/cj946gk23000001qg2zg47pe9.png
    https://www.facebook.com
    https://xxxxx-ap-northeast-1-chromeless.s3.amazonaws.com/cj946gk7n000001oydipvndx1.png
    node run.js  0.48s user 0.16s system 4% cpu 13.322 total