notebook

都内でWEB系エンジニアやってます。

Cypress + CircleCIの高速化Tips

簡単なテストケースをいくつか書いただけで5分以上掛かるようになってしまったのでチューニングの機運!

ということで今回はキャッシュについて

バイナリのキャッシュ

Cypressはバイナリのインストールをnode_modulesではないところにインストールしています

Installing Cypress | Cypress Documentation

docs.cypress.io

CircleCIでCIを回すときに使っているイメージはLinuxなので~/.cache/Cypressにインストールされるが毎度インストールが走ります

f:id:swfz:20190601002031p:plain

その結果毎度npm installに1分以上もかかるということになってしまいます

ここはCircleCIのcacheを使えば楽勝じゃん!という感じなのですがそう簡単ではありませんでした

単純に

- save_cache:
    key: sample-key-{{ .Branch }}-{{ checksum "package.json" }}
    paths:
      - "node_modules"
      - "~/.cache"

としたら下記記事のようにPermissionDiniedで失敗してしまいました

使用するイメージが違ったりすると権限の問題でPermission Diniedエラーにより失敗するようですね

今回のworkflowではCypress以外にもnodeのイメージを使っていてキャッシュ周りは共通で処理していたのでこの問題に当たりました

Cypress側でキャッシュするディレクトリを指定できれば良さそうです

公式に戻ってもう一度読んでみるとCYPRESS_CACHE_FOLDERという環境変数があります

これを設定してあげればCypressはそのディレクトリにバイナリを探しに行きます

ということで下記のような設定でキャシュできます

  • .circleci/config.yml[一部抜粋]
executors:
  cypress:
    docker:
      - image: cypress/base:10
    environment:
      - CYPRESS_CACHE_FOLDER: /tmp/.cache/Cypress
commands:
  save_cypress_binary:
    steps:
      - save_cache:
          key: sample-key-cypress-binary-{{ .Branch }}-{{ checksum "package.json" }}
          paths:
            - "/tmp/.cache/Cypress"
jobs:
  e2e:
    executor: cypress
    steps:
      - checkout
      - restore_npm
      - restore_cypress_binary
      - run: npm install
      - save_cypress_binary
      - save_npm

executorで環境変数をセットすることでCypressを実行する際のcache読み込み先と新規インストールする際の保存先を設定します

cache生成時にexecutorで指定したキャッシュディレクトリを指定することで次回実行時にキャッシュから取得することができるようになります

これでPermissionDiniedが出なくなりました

時間も短縮されました!

f:id:swfz:20190601002059p:plain

ここまでだとnodeのイメージとcypressのイメージで別々の記述が必要になってしまいます

できればまとめたいですね

環境変数をイメージ別(executor別)に用意してそれを利用することができれば実現できそうです

Cannot use circle.yml environment variables in cache keys - CircleCI 2.0 / 2.0 Feature Requests - CircleCI Discuss

discuss.circleci.com

executor側での指定

environment:
  IMAGE_NAME: 'cypress'

キャッシュキーの指定部分

    key: sample-key{{ .Environment.IMAGE_NAME }}-{{ .Branch }}-{{ checksum "package.json" }}

で、なんだ楽ちん!と思っていたら

キーに<no value>と出てくるんですね・・・

これ使えって書いてあるのに環境変数参照できないのか。。。なんて落胆していたら

Cannot use circle.yml environment variables in cache keys - CircleCI 2.0 / 2.0 Feature Requests - CircleCI Discuss discuss.circleci.com

ということだそうです

{{ .Environment.variableName }} で参照できるのはCircleCIの組み込み環境変数のみのようです

なので参考ページにもあるようにワークアラウンドではありますがechoで環境変数の中身をテキストに出力してそのファイルのchecksumをキーに含めることでやりたいことは実現できます

アンカーとエイリアスを使ったりしてゴニョゴニョした結果最終的に下記のような設定になりました

commands:
  restore_npm:
    parameters:
      prefix: &cache_key_prefix_parameter
        description: cache key prefix
        type: string
        default: sample-key
    steps:
      - run: &echo_env echo "$IMAGE_NAME" > /tmp/env_image_name.txt
      - restore_cache:
          keys:
            - &cache_key << parameters.prefix >>-{{ checksum "/tmp/env_image_name.txt" }}-{{ .Branch }}-{{ checksum "package.json" }}
            - << parameters.prefix >>-{{ checksum "/tmp/env_image_name.txt" }}-{{ .Branch }}-
            - << parameters.prefix >>-{{ checksum "/tmp/env_image_name.txt" }}-
  save_npm:
    parameters:
      prefix:
        <<: *cache_key_prefix_parameter
      with_cypress:
        description: is cypress flag
        type: boolean
        default: false
    steps:
      - run: *echo_env
      - when:
          condition: << parameters.with_cypress >>
          steps:
            - save_cache:
                key: *cache_key
                paths:
                  - "node_modules"
                  - "/tmp/.cache/Cypress"
      - unless:
          condition: << parameters.with_cypress >>
          steps:
            - save_cache:
                key: *cache_key
                paths:
                  - "node_modules"
executors:
  node:
    docker:
      - image: circleci/node:10.15.3-stretch-browsers
    environment:
      - CYPRESS_CACHE_FOLDER: /tmp/.cache/Cypress
      - IMAGE_NAME: node
  cypress:
    docker:
      - image: cypress/base:10
    environment:
      - CYPRESS_CACHE_FOLDER: /tmp/.cache/Cypress
      - IMAGE_NAME: cypress

jobs:
  # Cypressを使用するjob
  e2e:
    executor: cypress
    steps:
      - restore_npm
      .....
      .....
      - save_npm:
          with_cypress: true
      .....
      .....
  # 普通のjob
  test:
    executor: node
    steps:
      - restore_npm
      .....
      .....
      - save_npm
      .....
      .....

こんな感じですね

結局イメージによって分岐を発生させているのでちょっと微妙感ありますがrestore_cache時にはcypress用のキャッシュはない状態なので余計なリストア時間を短縮することができるようになりました

勉強になりました