6

We have a suite of ~1100 units running in ’ng test’ which currently run to completion in ~4 minutes with no failures in Angular 7.2.5 and fail to run to completion with random failures, slowdown, and disconnect before 4 minutes in Angular 8.0.0.

The tests run successfully in Angular 7 either in Chrome or ChromeHeadless.

Already tried:

  1. To eliminate the known memory leak of style elements we implemented “Style Cleanup” here:

by running cleanStylesFromDOM in afterAll for every describe block in the 1100 tests.

  1. In Angular 8 we have tried to regress Karma from 4.1.0 to 3.0.0 and jasmine-core from 3.4.0 to 2.99.1 without success.

  2. Tried Angular 8.1.1 with Karma 4.1.0 and jasmine-core 3.4.0 without success.

  3. Increased the Karma timeouts:

browserNoActivityTimeout: 120000, captureTimeout: 60000, reportSlowerThan: 2000, browserDisconnectTolerance: 2, browserDisconnectTimeout: 20000, browserSocketTimeout: 20000, processKillTimeout: 20000

  1. Increased memory to Node.js to --max_old_space_size=8192

  2. Turned off ‘ng test’ source-map generation and watch

  3. Turned off Jasmine random in karma.conf.js: jasmine: {
  random: false,
  failFast: true,
  timeoutInterval: 1000
}

  4. For every ‘it’ in each ‘describe’ block called afterEach with fixture.destroy

  5. Tried changing beforeEach TestBed setups to beforeAll as suggested here:

    package.json

    {
      "name": "myapp",
      "version": "0.0.0",
      "scripts": {
        "ng": "ng",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
      },
      "private": true,
      "dependencies": {
        "@angular/animations": "~8.0.0",
        "@angular/common": "~8.0.0",
        "@angular/compiler": "~8.0.0",
        "@angular/core": "~8.0.0",
        "@angular/forms": "~8.0.0",
        "@angular/platform-browser": "~8.0.0",
        "@angular/platform-browser-dynamic": "~8.0.0",
        "@angular/router": "~8.0.0",
        "@ng-bootstrap/ng-bootstrap": "^4.0.0",
        "@ng-select/ng-select": "^2.20.0",
        "@ngrx/effects": "^8.0.1",
        "@ngrx/entity": "^8.0.1",
        "@ngrx/router-store": "^8.0.1",
        "@ngrx/store": "^8.0.1",
        "@ngrx/store-devtools": "^8.0.1",
        "@ngx-translate/core": "^11.0.1",
        "angular-resizable-element": "^3.2.4",
        "angular-split": "^3.0.1",
        "bootstrap": "^4.1.3",
        "core-js": "^2.6.9",
        "jquery": "^3.3.1",
        "jquery-ui": "^1.12.1",
        "jquery-ui-bundle": "^1.11.4",
        "jquery.fancytree": "^2.26.0",
        "lodash": "^4.17.11",
        "moment": "^2.17.1",
        "ngx-infinite-scroll": "^7.2.0",
        "ngx-nvd3": "^1.0.9",
        "ngx-restangular": "^5.0.0-rc1",
        "popper.js": "^1.15.0",
        "rxjs": "~6.4.0",
        "tslib": "^1.9.0",
        "ui-contextmenu": "^1.18.1",
        "urijs": "^1.18.6",
        "zone.js": "~0.9.1"
      },
      "devDependencies": {
        "@angular-devkit/build-angular": "~0.800.0",
        "@angular/cli": "~8.0.2",
        "@angular/compiler-cli": "~8.0.0",
        "@angular/language-service": "~8.0.0",
        "@types/jasmine": "~3.3.8",
        "@types/jasminewd2": "~2.0.3",
        "@types/jquery": "^3.3.29",
        "@types/jquery.fancytree": "^2.7.32",
        "@types/node": "^8.9.5",
        "codelyzer": "^5.0.0",
        "jasmine-core": "~3.4.0",
        "jasmine-marbles": "^0.6.0",
        "jasmine-spec-reporter": "~4.2.1",
        "karma": "~4.1.0",
        "karma-chrome-launcher": "~2.2.0",
        "karma-coverage-istanbul-reporter": "~2.0.1",
        "karma-jasmine": "~2.0.1",
        "karma-jasmine-html-reporter": "^1.4.0",
        "protractor": "~5.4.0",
        "ts-mockito": "^2.3.1",
        "ts-node": "~7.0.0",
        "tslint": "~5.15.0",
        "typescript": "~3.4.3",
        "webpack": "^4.37.0"
      }
    }
    

    karma.conf.js

    module.exports = function (config) {
      config.set({
        basePath: '',
        frameworks: ['jasmine', '@angular-devkit/build-angular'],
        browsers: ['ChromeHeadless'],
        plugins: [
          require('karma-jasmine'),
          require('karma-chrome-launcher'),
          require('karma-jasmine-html-reporter'),
          require('karma-coverage-istanbul-reporter'),
          require('@angular-devkit/build-angular/plugins/karma')
        ],
        client: {
          clearContext: false // leave Jasmine Spec Runner output visible in browser
        },
        coverageIstanbulReporter: {
          dir: require('path').join(__dirname, './coverage/webr3'),
          reports: ['html', 'lcovonly', 'text-summary'],
          fixWebpackSourcePaths: true
        },
        reporters: ['progress', 'kjhtml'],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: true,
        singleRun: false,
        restartOnFileChange: true
      });
    };
    

Example of a test:

describe('PageNotFoundComponent', () => {
  let component: PageNotFoundComponent;
  let fixture: ComponentFixture<PageNotFoundComponent>;
  let selectedTextElement: HTMLElement;
  let router;
  let location;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        TranslateModule.forRoot({
          loader: {
            provide: TranslateLoader,
            useClass: WebpackTranslateLoader
          }
        }),
        RouterTestingModule.withRoutes(
          [
            {
              path: 'basepath',
              redirectTo: 'nwi'
            },
            {
              path: '**',
              component: PageNotFoundComponent
            }
          ]
        ),
      ],
      declarations: [ PageNotFoundComponent ]
    })
    .compileComponents();
  }));

  afterAll(() => {
    cleanStylesFromDOM();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(PageNotFoundComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    router = TestBed.get(Router);
    location = TestBed.get(Location);
  });

  it('should show 404 text', fakeAsync(() => {
    const navigationExtras: NavigationExtras = {
      queryParams: {}
    };
    router.navigate([`/unknown`], navigationExtras);
    tick();
    expect(decodeURI(location.path())).toBe(`/unknown`);
    const textElement: HTMLElement = fixture.nativeElement;
    selectedTextElement = textElement.querySelector('p');
    expect(selectedTextElement.innerText).toEqual('404');
  }));
});

We expect the ~1100 tests to run to completion with no failures in Angular 8 as they do in Angular 7.

Here are encountered failures which should not happen:

Chrome 75.0.3770 (Mac OS X 10.13.6) VMComponent should verify the Vul Suppressions get renders correctly FAILED TypeError: Cannot read property 'className' of null at at UserContext. (http://localhost:9876/_karma_webpack_/webpack:/src/app/components/vm-disabled-risk-radius/vm-disabled-risk-radius.component.spec.ts:72:18) at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:359:1) at ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:308:1) at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:358:1) at Zone.run (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-evergreen.js:124:1) at runInTestZone (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:561:1) at UserContext. (http://localhost:9876/_karma_webpack_/webpack:/node_modules/zone.js/dist/zone-testing.js:576:1) at

... and 20 more random failures and finally disconnects:

24 07 2019 12:30:11.055:WARN [Chrome 75.0.3770 (Mac OS X 10.13.6)]: Disconnected (0 times)reconnect failed before timeout of 2Chrome 75.0.3770 (Mac OS X 10.13.6) ERROR Disconnected reconnect failed before timeout of 2000ms (transport error) Chrome 75.0.3770 (Mac OS X 10.13.6): Executed 582 of 1134 (20 FAILED) (skipped 3) DISCONNECTED (4 mins 7.005 secs / 3 mins 53.442 secs)

Community
  • 1
  • 1
steve_b
  • 61
  • 2
  • 1
    Did you find any solution for this? Would like to know, want to implement this in my project as well! – Aakash Goplani Sep 04 '20 at 20:23
  • 1
    It's amazing in 1 year nobody has a clue. A bunch of us at work have this problem. This really, in my opinion, makes karma an unacceptable tool to use. – sovemp Sep 21 '20 at 16:18

1 Answers1

1

Not sure what the cleanStylesFromDOM you mention did, but recently I've encountered a similar issue with a 1800 test large suite. The symptoms were similar:

  • Test timeouts towards the end of the run:
FAILED
Error: Timeout - Async function did not complete within 5000ms (set by jasmine.DEFAULT_TIMEOUT_INTERVAL)
at <Jasmine>
  • Perpetually unstable runs in the resource-constrained environments, i.e. CI.

Performance profiling indicated that after each test, the amount of <style> tags in the <head> steadily increased. The team was not sure if this was the cause, but we decided to address suboptimal resource management as the lead suspect, similar to your cleanStylesFromDOM. Fortunately, last year this PR was merged in, so no additional afterEach calls are required anymore. To force Angular clean up after each test, update the test bed initialization in the "test.ts" like so:

getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting(),
  {teardown: {destroyAfterEach: true}}
);

{teardown: {destroyAfterEach: true}} is the line of interest. Unfortnately, documentation does not say much about destroyAfterEach. For me, turning the setting on ultimately lead to stable, always green runs:

  1. It unveiled bugs in tests which were masked by resource reuse during individual tests. I was able to reproduce and fix numerous instances of unstable tests locally.
  2. No more timeouts, the <style>s are cleaned up. Resource consumption went down.
  3. The set of unstable tests in CI became stable, I was able to disable these in order to address them later.