CircleCI CI/CD pipeline fails on Chrome Headless test step

See this SO entry for the same question.

A CircleCI CI/CD pipeline handling an Angular 7 application fails during the test step, with the CircleCI log suggesting the browser is not registered, although it runs once and it works!

I’m building a CircleCI CI/CD pipeline for an Angular 7 application that includes a test step which fails in a strange way. I’ve tried to change the karma.config.js parameters, to use Puppeteer, to no avail.

This is part of the WUF open source project, I’m working on the WUF-41-CI branch. Below are the details:

The CircleCI config.yml file:

version: 2.0

# Acceptance Criteria
# Given a feature branch of WUF
# When it changes on GitHub
# Then its code is built and tested but not published
# Given origin/master of WUF
# When it changes on GitHub
# Then its code is built, tested, tagged and the packages pushed to NPM, and the project’s Git pages

      - image: wasvic/node_dev:1.0.0-FM-188-SNAPSHOT
        entrypoint: /bin/sh
    working_directory: ~/wuf
      - TARGET_BRANCH: gh-pages
      - GH_EMAIL:
      - GH_NAME: anvil-open-source-machine-user
      - CHROME_BIN: /usr/lib/chromium/chrome
    # branches:
    #   only:
    #     - master
    #     - WUF-41-CI
      - run:
          name: Install OS tools
          # TODO: replace with script
          command: |
            apk update && apk add git
            apk add openssh
            apk add tar
            apk add ca-certificates
            apk update && apk upgrade && \
            echo @edge >> /etc/apk/repositories
            echo @edge >> /etc/apk/repositories
            apk add --no-cache \
               chromium@edge \
               nss@edge \
               freetype@edge \
               harfbuzz@edge \
            apk add --no-cache ttf-freefont

      #  chekout projectt
      - checkout
      - restore_cache:
            # when lock file changes, use increasingly general patterns to restore cache
            - yarn-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
            - yarn-v1-{{ .Branch }}-
            - node-v1-
      - run:
          name: Install WUF
          command: |
            yarn bootstrap
      - save_cache:
            - ./node_modules  # location depends on npm version
          key: yarn-v1-{{ .Branch }}-{{ checksum "yarn.lock" }}
      - run:
          name: Build WUF
          command: |
            ng build --aot --base-href /wuf/ --deploy-url /wuf/
      - run:
          name: Test WUF
          command: |
            # ng test --browsers CircleCI_ChromeHeadless --watch=false
            ng test --browsers CircleCI_ChromeHeadless
            # ng test
      - run:
          name: Deploy WUF
          # TODO: replace with a script
          command: |
            if [ $CIRCLE_BRANCH == $DEPLOY_BRANCH ]; then
              git config --global $GH_EMAIL
              git config --global $GH_NAME

              git clone $CIRCLE_REPOSITORY_URL out

              cd out
              git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH
              git rm -rf .
              cd ..

              cp -a dist/. out/.

              mkdir -p out/.circleci && cp -a .circleci/. out/.circleci/.
              cd out

              git add -A
              git commit -m "Automated deployment to GitHub Pages: ${CIRCLE_SHA1}" --allow-empty

              git push origin $TARGET_BRANCH

The karma.config.js file:

 * Copyright (c) 2018 Dematic, Corp.
 * Licensed under the MIT Open Source:

// Karma configuration file, see link for more information

module.exports = function (config) {
        basePath: '',
        frameworks: ['jasmine', '@angular-devkit/build-angular'],
        plugins: [
        client: {
            clearContext: false // leave Jasmine Spec Runner output visible in browser
        coverageIstanbulReporter: {
            dir: require('path').join(__dirname, '../coverage'),
            reports: ['html', 'lcovonly'],
            fixWebpackSourcePaths: true
        reporters: ['progress', 'kjhtml'],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: false,
        browsers: ['Chrome', 'CircleCI_ChromeHeadless'],
        customLaunchers: {
          CircleCI_ChromeHeadless: {
            base: 'ChromeHeadless',
            flags: [
              '--no-sandbox',  // Added to fix an issue where of Failed to connect to chrome browser
        singleRun: true

The Circle-CI log indicating that the first Chrome launch worked:

1 05 2019 00:10:47.700:INFO [launcher]: Launching browsers CircleCI_ChromeHeadless with concurrency unlimited
 11% building 9/12 modules 3 active .../root/wuf/src/app/app.component.spec.ts 11% building 10/12 modules 2 active ...assets/dummydata/branding/branding.scss11 05 2019 00:10:47.732:INFO [launcher]: Starting browser ChromeHeadless

The Circle-CI log indicating that the first execution’s tests passed:

HeadlessChrome 73.0.3683 (Linux 0.0.0): Executed 69 of 69 SUCCESS (0 secs / 12.127 secs)
HeadlessChrome 73.0.3683 (Linux 0.0.0): Executed 69 of 69 SUCCESS (12.186 secs / 12.127 secs)

The Circle-CI log indicating that the second Chrome launch FAILED:

11 05 2019 00:11:29.320:INFO [launcher]: Launching browsers CircleCI_ChromeHeadless with concurrency unlimited
11 05 2019 00:11:29.320:ERROR [launcher]: Cannot load browser "CircleCI_ChromeHeadless": it is not registered! Perhaps you are missing some plugin?
11 05 2019 00:11:29.320:ERROR [karma-server]: Error: Found 1 load error

I’m getting a similar issue, but not sure if it’s the same root problem. My hunch is it has something to do with the way CircleCI caches node_modules, and the way that Chromium sidesteps the normal npm install process (it runs as a postinstall command in its own special directory rather than being packaged like the rest of the packages).