Search code examples
node.jsangularkarma-jasminekarma-runnergoogle-chrome-headless

Karma - Chrome Headless - test FAILED - Uncaught null thrown


While running unit tests, they fail "sometimes", always on different tests, without any meaningful error.

...
Chrome 89.0.4389.114 (Linux x86_64): Executed 1225 of 1453 SUCCESS (0 secs / 3 mins 29.829 secs)
Chrome 89.0.4389.114 (Linux x86_64): Executed 1226 of 1453 SUCCESS (0 secs / 3 mins 30.893 secs)
Chrome 89.0.4389.114 (Linux x86_64): Executed 1227 of 1453 SUCCESS (0 secs / 3 mins 31.923 secs)
Chrome 89.0.4389.114 (Linux x86_64) ChooseOfWorkplaceComponent should show institutie warning info FAILED
        Uncaught null thrown
Chrome 89.0.4389.114 (Linux x86_64): Executed 1228 of 1453 (1 FAILED) (0 secs / 3 mins 32.237 secs)
    ✗ should show institutie warning info
        Uncaught null thrown
Chrome 89.0.4389.114 (Linux x86_64): Executed 1229 of 1453 (1 FAILED) (0 secs / 3 mins 32.713 secs)
Chrome 89.0.4389.114 (Linux x86_64): Executed 1230 of 1453 (1 FAILED) (0 secs / 3 mins 33.432 secs)
Chrome 89.0.4389.114 (Linux x86_64): Executed 1231 of 1453 (1 FAILED) (0 secs / 3 mins 33.461 secs)

  • We run the tests with ng test --watch=false --browsers=ChromeHeadlessCI
  • We run the tests with the same order (no random order as you can see from the config below) but the error happens on different places always with the same error message
  • On stronger configurations (16gb ram, 8th gen i7) it won't happen as frequently as on an older config (12gb ram, 7th gen i5)
  • It happens 2 times out of 3 run in a docker environment
  • The more unit tests we have the more it happens
  • We also experience timeouts from chrome (some tests won't respond for 60 seconds: Chrome Headless 93.0.4577.82 (Linux x86_64) ERROR 14:06:45 #19 695.4 Disconnected , because no message in 60000 ms) but it happens just sometimes as well... restarting the tests makes it dissappear most of the time.

Karma.conf.js:

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage'),
      require('karma-spec-reporter'),
      require('@angular-devkit/build-angular/plugins/karma'),
    ],
    client: {
      clearContext: false, // leave FJasmine Spec Runner output visible in browser
      jasmine: {
        random: false,
      },
    },
    jasmineHtmlReporter: {
      suppressAll: true, // removes the duplicated traces
    },
    coverageReporter: {
      dir: require('path').join(__dirname, '...'),
      subdir: '.',
      reporters: [{ type: 'html' }, { type: 'text-summary' }],
      check: {
        global: {
          statements: 50,
          branches: 50, 
          functions: 50,
          lines: 50, 
        },
      },
    },
    captureTimeout: 180000,
    browserNoActivityTimeout: 60000,
    browserDisconnectTimeout: 10000,
    browserDisconnectTolerance: 3,
    reporters: ['progress', 'kjhtml', 'spec'],
    port: 9876,
    colors: true,
    logLevel: config.DEBUG,
    autoWatch: true,
    browsers: ['Chrome', 'ChromeHeadless'],
    singleRun: false,
    restartOnFileChange: true,
    customLaunchers: {
      ChromeHeadlessCI: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox'],
      },
    },
  });
};

Angular version:

$ ng --version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 13.2.2
Node: 14.17.1
Package Manager: npm 6.14.13
OS: linux x64

Angular: 13.2.1
... animations, cdk, common, compiler, compiler-cli, core, forms
... localize, material, material-moment-adapter
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1302.2
@angular-devkit/build-angular   13.2.2
@angular-devkit/core            13.2.2
@angular-devkit/schematics      13.2.2
@angular/cli                    13.2.2
@angular/flex-layout            12.0.0-beta.35
@schematics/angular             13.2.2
rxjs                            7.4.0
typescript                      4.5.5

An another error log, to illustrate how the error appears randomly, and with no additional data:

...
15:42:21  #19 381.2 [1A[2KChrome Headless 93.0.4577.82 (Linux x86_64): Executed 1253 of 1453 SUCCESS (0 secs / 3 mins 29.334 secs)
15:42:21  #19 381.2     [32m✓ [39mshould "toIdentificationMode" anim status
15:42:21  #19 381.3 [1A[2KChrome Headless 93.0.4577.82 (Linux x86_64) ERROR
15:42:21  #19 381.3   An error was thrown in afterAll
15:42:21  #19 381.3   Uncaught null thrown
15:42:21  #19 381.3 Chrome Headless 93.0.4577.82 (Linux x86_64): Executed 1253 of 1453 ERROR (0 secs / 3 mins 29.334 secs)
15:42:21  #19 381.3 Chrome Headless 93.0.4577.82 (Linux x86_64) ERROR
15:42:21  #19 381.3   An error was thrown in afterAll
15:42:21  #19 381.3   Uncaught null thrown
15:42:21  #19 381.3 [31mChrome Headless 93.0.4577.82 (Linux x86_64) ERROR[39m
15:42:21  #19 381.3   An error was thrown in afterAll
15:42:21  #19 381.3   Uncaught null thrown
15:42:21  #19 381.3 [1A[2KChrome Headless 93.0.4577.82 (Linux x86_64): Executed 1253 of 1453 ERROR (4 mins 27.037 secs / 3 mins 29.334 secs)

update: I was able to catch the error in a Chrome window.

zone.js:182 Uncaught null
runTask @ zone.js:182
invokeTask @ zone.js:487
ZoneTask.invoke @ zone.js:476
data.args.<computed> @ zone.js:2541
setTimeout (async)
scheduleTask @ zone.js:2543
scheduleTask @ zone.js:393
onScheduleTask @ zone-testing.js:306
scheduleTask @ zone.js:386
scheduleTask @ zone.js:221
scheduleMacroTask @ zone.js:244
scheduleMacroTaskWithCurrentZone @ zone.js:679
(anonymous) @ zone.js:2585
proto.<computed> @ zone.js:975
setTimeout @ timeoutProvider.js:4
reportUnhandledError @ reportUnhandledError.js:4
(anonymous) @ Subscriber.js:117
_error @ Subscriber.js:64
error @ Subscriber.js:40
_error @ Subscriber.js:64
error @ Subscriber.js:40
init @ throwError.js:5
_trySubscribe @ Observable.js:37
(anonymous) @ Observable.js:31
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
doInnerSub @ mergeInternals.js:19
outerNext @ mergeInternals.js:14
OperatorSubscriber._next @ OperatorSubscriber.js:9
next @ Subscriber.js:31
(anonymous) @ innerFrom.js:51
_trySubscribe @ Observable.js:37
(anonymous) @ Observable.js:31
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
mergeInternals @ mergeInternals.js:50
(anonymous) @ mergeMap.js:13
(anonymous) @ lift.js:10
(anonymous) @ Observable.js:26
errorContext @ errorContext.js:19
subscribe @ Observable.js:22
(anonymous) @ session-transfer.component.ts:263
(anonymous) @ Subscriber.js:110
_complete @ Subscriber.js:72
complete @ Subscriber.js:49
(anonymous) @ take.js:14
OperatorSubscriber._next @ OperatorSubscriber.js:9
next @ Subscriber.js:31
(anonymous) @ map.js:7
OperatorSubscriber._next @ OperatorSubscriber.js:9
next @ Subscriber.js:31
(anonymous) @ timer.js:23
_execute @ AsyncAction.js:53
execute @ AsyncAction.js:41
flush @ AsyncScheduler.js:18
timer @ zone.js:2561
invokeTask @ zone.js:406
onInvokeTask @ zone-testing.js:318
invokeTask @ zone.js:405
runTask @ zone.js:178
invokeTask @ zone.js:487
ZoneTask.invoke @ zone.js:476
data.args.<computed> @ zone.js:2541
Show 32 more frames

We don't have much, but it might help somebody to see what it could be.

enter image description here


Solution

  • To anyone who might stumble upon this: I was able to catch the error with the "stop on exception" feature of chrome. In it we still only saw that a "null" was thrown (and no stacttrace whatsoever) but we also saw that the error happens in a zone.js task object, which provided enough information about what causes the error.

    It was us. Sorry for the trouble. We threw a null object in one of the tests and did not catch it. That's a quite bad practice.

        if (typeof res === 'boolean') {
              return of(res);
            } else {
              return throwError(res);   // res can be null...
            }
    

    So the bottom line is, be suspicious about why the stacktrace is exaclty null...