Add Angular Tests to Netlify Deployment

For quite some time, Netlify has been my (free) tool of choice to deploy my blog and other personal projects.
It’s very simple to set-up and their documentation is also easy to follow.

But how do you make your Angular tests part of the Netlify build pipeline?

Most of the examples suggest: Build command: ng build --prod
What I need: Build command: ng test && ng build --prod

Netlify build settings sample

My first issue, ng test creates an instance of Chrome browser as specified in the karma.conf.js.
Pretty simple to solve, run Headless Chrome.

But how do I get Headless Chrome installed on Netlify’s Linux build environment? Puppeteer.

I solved it with a quick npm install puppeteer.

I’ve updated my package.json:

1
2
3
4
5
6
7
8
9
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build --prod",
"test": "ng test",
"test-headless": "ng test --watch=false --browsers=ChromeHeadless",
"lint": "ng lint",
"e2e": "ng e2e"
},

I’ve tested everything locally and Build command: npm run-script test-headless && npm run-script build executed successfully in my Windows environment.

After I’ve pushed the new changes, the build failed - “CHROME_BIN” env variable error.

1
2
3
4
5
6
7
8
9
10
11
12
{"os":"linux","arch":"x64"})
11:33:41 AM: audited 34037 packages in 15.642s
11:33:41 AM: found 0 vulnerabilities
11:33:41 AM: > beukes-bunch-health-tracker@0.0.0 test /opt/build/repo/ui
11:33:41 AM: > ng test
11:34:02 AM: 10 03 2019 03:34:02.050:INFO [karma-server]: Karma v4.0.1 server started at http://0.0.0.0:9876/
11:34:02 AM: 10 03 2019 03:34:02.053:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
11:34:02 AM: 10 03 2019 03:34:02.060:INFO [launcher]: Starting browser Chrome
11:34:02 AM: 10 03 2019 03:34:02.061:ERROR [launcher]: No binary for Chrome browser on your platform.
11:34:02 AM: Please, set "CHROME_BIN" env variable.
11:34:02 AM: npm
11:34:02 AM: ERR! Test failed. See above for more details.

Seems like karma is looking for the path of the chrome executable.

Second issue, how do I set CHROME_BIN env variable path?

Thanks to the folks at Puppeteer they made it easy by just checking the build logs:

1
2
3
4
5
1:00:59 PM: > puppeteer@1.13.0 install /opt/build/repo/ui/node_modules/puppeteer
1:00:59 PM: > node install.js
1:01:07 PM: Chromium downloaded to /opt/build/repo/ui/node_modules/puppeteer/.local-chromium/linux-637110
1:01:07 PM: > node-sass@4.11.0 postinstall /opt/build/repo/ui/node_modules/node-sass
1:01:07 PM: > node scripts/build.js

I’m not fluent in Linux, but I found the printenv command.
It prints all or part of the environment variables.

New plan: Build command: printenv && npm run-script test-headless && npm run-script build

With a bit of trail and error I discovered:
CHROME_BIN=/opt/build/repo/ui/node_modules/puppeteer/.local-chromium/linux-637110/chrome-linux/chrome

From the printenv I’ve learned that env variable PWD=/opt/build/repo/ui.

I could use the PWD variable like this:
CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/linux-637110/chrome-linux/chrome

But, linux-637110 smells like an appended build number.

I used this hack to dynamically look up the linux-xxxxxx folder name:
ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium.

My ugly CHROME_BIN env variable (but it works):
CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/$(ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium)/chrome-linux/chrome

One can set a Linux env variable with export.

My final plan:
Build command: export CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/$(ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium)/chrome-linux/chrome && printenv && npm run-script test-headless && npm run-script build

To save me the time logging into Netlify and update the build command, I’ve added the netlify.toml file.

1
2
3
4
[build]
base = "ui"
command = "export CHROME_BIN=${PWD}/node_modules/puppeteer/.local-chromium/$(ls -x -1 ${PWD}/node_modules/puppeteer/.local-chromium)/chrome-linux/chrome && printenv && npm run-script test-headless && npm run-script build"
publish = "ui/dist/beukes-bunch-health-tracker"

GREEN - build log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
...
7:13:24 PM: LANGUAGE=en_US:en
7:13:24 PM: YARN_VERSION=1.3.2
7:13:24 PM: rvm_ruby_string=ruby-2.3.6
7:13:24 PM: GIMME_GO_VERSION=1.10
7:13:24 PM: CHROME_BIN=/opt/build/repo/ui/node_modules/puppeteer/.local-chromium/linux-637110/chrome-linux/chrome
7:13:24 PM: GOCACHE=/opt/buildhome/.gimme_cache/gocache
7:13:24 PM: GEM_PATH=/opt/buildhome/.rvm/gems/ruby-2.3.6:/opt/buildhome/.rvm/gems/ruby-2.3.6@global
7:13:24 PM: > ng test --watch=false --browsers=ChromeHeadless
7:13:36 PM: 10 03 2019 11:13:36.774:INFO [karma-server]: Karma v4.0.1 server started at http://0.0.0.0:9876/
7:13:36 PM: 10 03 2019 11:13:36.776:INFO [launcher]: Launching browsers ChromeHeadless with concurrency unlimited
7:13:36 PM: 10 03 2019 11:13:36.780:INFO [launcher]: Starting browser ChromeHeadless
7:13:46 PM: 10 03 2019 11:13:46.808:INFO [HeadlessChrome 74.0.3723 (Linux 0.0.0)]: Connected on socket POrD2-FtM3GJq9JLAAAA with id 8678984
7:13:51 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 0 of 14 SUCCESS (0 secs / 0 secs)
7:13:52 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 1 of 14 SUCCESS (0 secs / 0.85 secs)
7:13:52 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 2 of 14 SUCCESS (0 secs / 1.562 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 3 of 14 SUCCESS (0 secs / 1.821 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 4 of 14 SUCCESS (0 secs / 1.85 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 5 of 14 SUCCESS (0 secs / 2.103 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 6 of 14 SUCCESS (0 secs / 2.332 secs)
7:13:53 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 7 of 14 SUCCESS (0 secs / 2.363 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 8 of 14 SUCCESS (0 secs / 2.92 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 9 of 14 SUCCESS (0 secs / 2.938 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 3.219 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 11 of 14 SUCCESS (0 secs / 3.485 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 12 of 14 SUCCESS (0 secs / 3.494 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 13 of 14 SUCCESS (0 secs / 3.501 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 SUCCESS (0 secs / 3.657 secs)
7:13:54 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 SUCCESS (3.682 secs / 3.657 secs)
7:13:54 PM: TOTAL: 14 SUCCESS
7:13:54 PM: TOTAL: 14 SUCCESS
7:13:55 PM: > ng build --prod
7:15:24 PM: Date: 2019-03-10T11:15:24.219Z
7:15:24 PM: Hash: 64ca61b254dcfd5acdc1
7:15:24 PM: Time: 85158ms
7:15:24 PM: chunk {0} runtime.a5dd35324ddfd942bef1.js (runtime) 1.41 kB [entry] [rendered]
7:15:24 PM: chunk {1} es2015-polyfills.4a4cfea0ce682043f4e9.js (es2015-polyfills) 56.4 kB [initial] [rendered]
7:15:24 PM: chunk {2} main.3abc8440f2ae7fd24e18.js (main) 1.59 MB [initial] [rendered]
7:15:24 PM: chunk {3} polyfills.7e5c029b78344f3b7d7c.js (polyfills) 41.1 kB [initial] [rendered]
7:15:24 PM: chunk {4} styles.e230360cfdba5044352a.css (styles) 61.9 kB [initial] [rendered]
...
...

RED - build log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
...
7:06:09 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 8 of 14 SUCCESS (0 secs / 2.921 secs)
7:06:09 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 9 of 14 SUCCESS (0 secs / 2.938 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 10 of 14 SUCCESS (0 secs / 3.244 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0) AuthenticationService should be read tokens FAILED
7:06:10 PM: Expected 'Bearer' to be 'BearerRuan'.
7:06:10 PM: at UserContext.<anonymous> (src/app/services/authentication.service.spec.ts:65:31)
7:06:10 PM: at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.execute (node_modules/@angular/core/fesm5/testing.js:1822:1)
7:06:10 PM: at UserContext.<anonymous> (node_modules/@angular/core/fesm5/testing.js:1991:29)
7:06:10 PM: at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:1)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 11 of 14 (1 FAILED) (0 secs / 3.671 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0) AuthenticationService should be read tokens FAILED
7:06:10 PM: Expected 'Bearer' to be 'BearerRuan'.
7:06:10 PM: at UserContext.<anonymous> (src/app/services/authentication.service.spec.ts:65:31)
7:06:10 PM: at TestBedViewEngine.push../node_modules/@angular/core/fesm5/testing.js.TestBedViewEngine.execute (node_modules/@angular/core/fesm5/testing.js:1822:1)
7:06:10 PM: at UserContext.<anonymous> (node_modules/@angular/core/fesm5/testing.js:1991:29)
7:06:10 PM: at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:1)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 12 of 14 (1 FAILED) (0 secs / 3.689 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 13 of 14 (1 FAILED) (0 secs / 3.695 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 (1 FAILED) (0 secs / 3.856 secs)
7:06:10 PM: HeadlessChrome 74.0.3723 (Linux 0.0.0): Executed 14 of 14 (1 FAILED) (3.883 secs / 3.856 secs)
7:06:10 PM: TOTAL: 1 FAILED, 13 SUCCESS
7:06:10 PM: TOTAL: 1 FAILED, 13 SUCCESS
7:06:10 PM: npm
7:06:10 PM: ERR! code ELIFECYCLE
7:06:10 PM: npm
7:06:10 PM: ERR! errno 1
7:06:10 PM: npm ERR!
7:06:10 PM: beukes-bunch-health-tracker@0.0.0 test-headless: `ng test --watch=false --browsers=ChromeHeadless`
7:06:10 PM: npm ERR!
7:06:10 PM: Exit status 1
7:06:10 PM: npm
7:06:10 PM: ERR!
7:06:10 PM: npm
7:06:10 PM: failed during stage 'building site': Build script returned non-zero exit code: 1
7:06:10 PM: ERR! Failed at the beukes-bunch-health-tracker@0.0.0 test-headless script.
7:06:10 PM: npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
7:06:10 PM: npm
7:06:10 PM: ERR! A complete log of this run can be found in:
7:06:10 PM: npm
7:06:10 PM: Shutting down logging, 17 messages pending

Please leave a comment on how to enhance my ball of mud :)

Use it…don’t use it :)

Kudos to:
Todd Palmer - Angular Testing with Headless Chrome
Alain Chautard - How-to Running Angular tests on continuous integration servers