AWS CodeBuildでAngularの単体テストを実行する際にヘッドレスChromeを利用する設定

# はじめに
プロジェクトでAngularで開発してCI/CDする機会があり、CodeBuildで`ng test`を実行しようとしたところ詰まったので試行錯誤を含めて備忘を残します

# 環境
Angular ver13.2.2
karma ver6.3.16
jasmine-core ver4.0.0
CodeBuild
　－ソース：CodeCommit
　－環境：aws/codebuild/amazonlinux2-x86_64-standard:3.0

# うまくいった設定
```javascript:karma.conf.js
  ...
  browsers: ['Chrome'],
  customLancghers: {
    ChromeHeadlessNoSandbox: {
      base: 'ChromeHeadless',
      flags: ['--no-sandbox'],
    },
  },
  ...
```

```yaml:buildspec.yml
phases:
  install:
    commands:
      - npm ci
      - npm i -g -unsafe-perm @angular/cli@1.4.9 # ngコマンドを使うため
      - curl https://intli.com/install-google-chrome.sh | bash # Chromeをインストール
  pre_build:
    commands:
      - ng test --no-watrch --browsers ChromeHeadlessNoSandbox
  build:
    commands:
      - ng build
  post_build:
    commands:
      - pip install --upgrade pip
      - pip install --upgrade awscli
      - aws s3 sync dist/hoge s3://hogebacket --delete
```

# 詰まった点
主に、CodeBuild内でChromeを使用する部分で詰まりました。
## `Error: Cannot find module 'fs/promises'`エラー
karma.conf.js、buildspec.ymlの初期状態は以下でした。
<details><summary>karma.conf.js</summary>

```javascript
module.exports = function (config) {
  ...
  browsers: ['Chrome'],
  ...
}
```
</details>
<details><summary>buildspec.yml</summary>

```yaml
phases:
  install:
    commands:
      - npm ci
      - npm i -g -unsafe-perm @angular/cli@1.4.9
  build:
    commands:
      - ng build
  post_build:
    commands:
      - pip install --upgrade pip
      - pip install --upgrade awscli
      - aws s3 sync dist/hoge s3://hogebacket --delete
```
</details>

そこから[こちら](https://qiita.com/kawakawaryuryu/items/f4f9149179aa5b0c12fe)の記事を参考に、puppeteerを`npm i`して以下のように変更しました。
karma.conf.js側はChromeをヘッドレスとして使用する設定、buildspec.ymlはビルド前にテストを実行するという修正です。

```diff_javascript:karma.conf.js
+ process.env.CHROME_BIN = require('puppeteer').executablePath();
  module.exports = function (config) {
    ...
    browsers: ['Chrome'],
+   customLanchers: {
+     ChromeHeadlessCI: {
+       base: 'ChromeHeadless',
+       flags: ['--no-sandbox'],
+     },
+   },
    ...
  }
```

```diff_yaml:buildspec.yml
phases:
  install:
    ...
+ pre_build:
+   commands:
+     - ng test --no-watch --browsers=ChromeHeadlessCI
  build:
    ...
  ...
```

すると以下エラーが発生しました。
`Error: Cannot find module 'fs/promises'`

<details><summary>エラー全文</summary>

```
[Container] yyyy/mm/dd HH:MM:SS Running command ng test --no-watch --browsers=ChromeHeadlessCI
Browserslist: caniuse-lite is outdated. Please run:
  npx browserslist@latest --update-db
  Why you should do it regularly: https://github.com/browserslist/browserslist#browsers-data-updating
- Generating browser application bundles (phase: setup)...
dd mm yyyy HH:MM:SS.SSS:ERROR [config]: Error in config file!
  Error: Cannot find module 'fs/promises'
Require stack:
- /codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js
- /codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/node/node.js
- /codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/puppeteer-core.js
- /codebuild/output/src000000000/src/node_modules/puppeteer/lib/cjs/puppeteer/puppeteer.js
- /codebuild/output/src000000000/src/projects/project/karma.conf.js
- /codebuild/output/src000000000/src/node_modules/karma/lib/config.js
- /codebuild/output/src000000000/src/node_modules/karma/lib/server.js
- /codebuild/output/src000000000/src/node_modules/karma/lib/index.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/build-angular/src/builders/karma/index.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/architect/node/node-modules-architect-host.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/architect/node/index.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/models/architect-command.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/commands/test-impl.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/schematics/tools/export-ref.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/schematics/tools/index.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/utilities/json-schema.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/models/command-runner.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/lib/cli/index.js
- /usr/local/lib/node_modules/@angular/cli/bin/ng
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:815:15)
    at Function.Module._load (internal/modules/cjs/loader.js:667:27)
    at Module.require (internal/modules/cjs/loader.js:887:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.<anonymous> (/codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:22:20)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Module.require (internal/modules/cjs/loader.js:887:19)
An unhandled exception occurred: Error in config file!
  Error: Cannot find module 'fs/promises'
Require stack:
- /codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js
- /codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/node/node.js
- /codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/puppeteer-core.js
- /codebuild/output/src000000000/src/node_modules/puppeteer/lib/cjs/puppeteer/puppeteer.js
- /codebuild/output/src000000000/src/projects/project/karma.conf.js
- /codebuild/output/src000000000/src/node_modules/karma/lib/config.js
- /codebuild/output/src000000000/src/node_modules/karma/lib/server.js
- /codebuild/output/src000000000/src/node_modules/karma/lib/index.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/build-angular/src/builders/karma/index.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/architect/node/node-modules-architect-host.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/architect/node/index.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/models/architect-command.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/commands/test-impl.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/schematics/tools/export-ref.js
- /codebuild/output/src000000000/src/node_modules/@angular-devkit/schematics/tools/index.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/utilities/json-schema.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/models/command-runner.js
- /codebuild/output/src000000000/src/node_modules/@angular/cli/lib/cli/index.js
- /usr/local/lib/node_modules/@angular/cli/bin/ng
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:815:15)
    at Function.Module._load (internal/modules/cjs/loader.js:667:27)
    at Module.require (internal/modules/cjs/loader.js:887:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at Object.<anonymous> (/codebuild/output/src000000000/src/node_modules/puppeteer-core/lib/cjs/puppeteer/node/ChromeLauncher.js:22:20)
    at Module._compile (internal/modules/cjs/loader.js:999:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
    at Module.load (internal/modules/cjs/loader.js:863:32)
    at Function.Module._load (internal/modules/cjs/loader.js:708:14)
    at Module.require (internal/modules/cjs/loader.js:887:19)
See "/tmp/ng-b05GQd/angular-errors.log" for further details.

[Container] 2023/05/29 02:43:17 Command did not exit successfully ng test --no-watch --browsers=ChromeHeadlessCI exit status 127
[Container] 2023/05/29 02:43:17 Phase complete: PRE_BUILD State: FAILED
[Container] 2023/05/29 02:43:17 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: ng test --no-watch --browsers=ChromeHeadlessCI. Reason: exit status 127
```
</details>

node.jsのバージョンは16であり、fs.promisesは14からnode.js内に含まれているらしいので、多分違うだろうな～と思いつつも一旦`npm i`でfm.promisesをインストールしてみましたが、エラーは変わらずでした。

また、[こちら](https://qiita.com/YamaDash82/items/f74f413e0e248222d493)の記事も参考にして以下修正も試しましたがエラーは変わりませんでした。
```diff_javascript:karma.conf.js
  ...
- browsers: ['Chrome'],
+ browsers: ['ChromeHeadless'],
  customLanchers: {
-   ChromeHeadlessCI: {
+   ChromeHeadless: {
      base: 'ChromeHeadless',
      flags: ['--no-sandbox'],
    },
  },
  ...
```
```diff_yaml:buildspec.yml
phases:
  ...
  pre_build:
    commands:
-     - ng test --no-watch --browsers=ChromeHeadlessCI
+     - ng test --no-watch --browsews=ChromeHeadless
  build:
    ...
  ...
```
</details>
試していく中で後から分かってきたのですが、どうやらcustomLanchersの中のキーは既知のもの('Chrome'や'ChromeHeadless')と重複してはいけないようでした。

(公式ドキュメントを追っていないので、既に書いてあったり、間違ったことを言っていればご指摘ください)

## Chromeインストール
そもそもCodeBuild内でChromeをどう使うのか調べたところ、ng test前にChromeをインストールしている[記事](https://dev.classmethod.jp/articles/aws-amplify-console-angular-ci-cd/)を見つけました。
※ちなみに[こちら](https://www.tdi.co.jp/miso/java-selenium-aws-codebuild#1ChromeChromeDriver)の記事によるとCodeBuild内にChromeはあるらしいのですが、情報が古いのか私のCodeBuild設定が違うのか、見つけられませんでした。

Chromeをインストールしている記事を参考に、CodeBuild内にChromeをインストールするように修正しました。
また、ng testのフラグの指定を間違っていたので修正しました。
puppeteerも使っていなさそうだったので外しました。

```diff_javascript
- process.env.CHROME_BIN = require('puppeteer').executablePath();
  module.exports = function (config) {
    ...
    browsers: ['Chrome'],
    customLanchers: {
-     ChromeHeadless: {
+     ChromeHeadlessNoSandbox: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox'],
      },
    },
    ...
  }
```

```diff_yaml:buildspec.yml
phases:
  install:
    commands:
      - npm ci
      - npm i -g -unsafe-perm @angular/cli@1.4.9
+     - curl https://intli.com/install-google-chrome.sh | bash
  pre_build:
    commands:
-     - ng test --no-watch --browsews=ChromeHeadless
+     - ng test --no-watch --browsews ChromeHeadlessNoSandbox
  build:
    ...
  ...
```
</details>
これでうまくいきました。