Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

SharedStylesHost isn't cleaning up styles between Karma test specs #31834

Closed
zelliott opened this issue Jul 24, 2019 · 13 comments
Closed

SharedStylesHost isn't cleaning up styles between Karma test specs #31834

zelliott opened this issue Jul 24, 2019 · 13 comments
Assignees
Labels
area: testing Issues related to Angular testing features, such as TestBed effort1: hours freq3: high P4 A relatively minor issue that is not relevant to core functions type: bug/fix
Milestone

Comments

@zelliott
Copy link
Contributor

zelliott commented Jul 24, 2019

馃悶 Bug Report

Affected package

Latest

Is this a regression?

No

Description

Wheen testing components via TestBed.createComponent, Angular appends inline styles associated with the component via the SharedStylesHost service. These inline styles are cleaned up when the service is destroyed.

Unfortunately, these inline styles are never cleaned up in Karma unit tests. This appears to be because each SharedStylesHost service is never destroyed, apparently because each associated NgModule is never destroyed between specs. This isn't a problem when you have <10 Karma specs, but when you have >100 , and each spec appends 25 <style> tags, by the 100th spec you have 2500 redundant <style> tags in your document head.

馃敩 Minimal Reproduction

https://stackblitz.com/edit/angular-testing-exxbrh (old version of angular, but repros on latest)

馃實 Your Environment

Angular Version:

Floating version of Angular 2 RC branch. Repros on latest.

@AndrewKushnir AndrewKushnir added the area: testing Issues related to Angular testing features, such as TestBed label Jul 25, 2019
@ngbot ngbot bot added this to the needsTriage milestone Jul 25, 2019
@zelliott
Copy link
Contributor Author

Bump on this issue.

@mickaeltr
Copy link

Hello,

As a workaround, adding this to my jest.setup.ts made my whole test suite 2-3 times faster:

// Fixing this bug: https://stackoverflow.com/questions/51752862/angular-unit-tests-are-leaking-styles
afterEach(() => {
  window.document.querySelectorAll("style").forEach((style: HTMLStyleElement) => style.remove());
});

I use Jest but there should be a similar mechanism for Jasmine.

Hope this helps

@zelliott
Copy link
Contributor Author

Another bump on this issue. This seems like a relatively straightforward fix with nontrivial performance gains.

@ersimont
Copy link

ersimont commented May 9, 2020

For the same workaround zelliott just mentioned, I made a function you can use called trimLeftoverStyles(), that you can get from my open source library s-ng-dev-utils. It will only remove the styles added by your components - if you added global styles that are used in your test suite it will leave them alone. If you call it in a beforeEach as recommended, it will also leave your component correctly styled at the end of your test, so you can see it correctly for debugging.

It would be great if this could be addressed in the framework itself!

@totszwai
Copy link

I believe this also happens in production if shared component from two different angular apps are running at the same time? What is the correct way to clean up all the added styles when the component is removed?

@devversion devversion self-assigned this Jul 29, 2020
devversion added a commit to devversion/material2 that referenced this issue Jul 29, 2020
Angular's `TestBed` by default only removes a test component from the
document body and destructs individual component fixtures. It never
destroys the test module properly, but always re-creates it.

This means that providers are not necessarily cleaned up (could
leak in memory) and component styles are not cleaned up / deduped.
This results in thousands of style elements in the browsers. Ultimately
causing significantly increased memory consumption and CPU blocking.

This potentially leads to repeated crashes within browsers (as seen in
BrowserStack and Saucelabs in the past). Initial testing of this change
unveiled a reduction from 30min tests to 5min in BrowserStack. This is
a signficant improvement and we should consider moving this change
upstream with: angular/angular#31834.

Benefits are: reduced test time; increased test/browser stability;
isolated tests without leaking styles and services.
@josephperrott josephperrott self-assigned this Aug 3, 2020
mmalerba pushed a commit to angular/components that referenced this issue Aug 4, 2020
Angular's `TestBed` by default only removes a test component from the
document body and destructs individual component fixtures. It never
destroys the test module properly, but always re-creates it.

This means that providers are not necessarily cleaned up (could
leak in memory) and component styles are not cleaned up / deduped.
This results in thousands of style elements in the browsers. Ultimately
causing significantly increased memory consumption and CPU blocking.

This potentially leads to repeated crashes within browsers (as seen in
BrowserStack and Saucelabs in the past). Initial testing of this change
unveiled a reduction from 30min tests to 5min in BrowserStack. This is
a signficant improvement and we should consider moving this change
upstream with: angular/angular#31834.

Benefits are: reduced test time; increased test/browser stability;
isolated tests without leaking styles and services.
trik added a commit to gnucoop/ajf that referenced this issue Aug 10, 2020
Angular's `TestBed` by default only removes a test component from the
document body and destructs individual component fixtures. It never
destroys the test module properly, but always re-creates it.

This means that providers are not necessarily cleaned up (could
leak in memory) and component styles are not cleaned up / deduped.
This results in thousands of style elements in the browsers. Ultimately
causing significantly increased memory consumption and CPU blocking.

This potentially leads to repeated crashes within browsers (as seen in
BrowserStack and Saucelabs in the past). Initial testing of this change
unveiled a reduction from 30min tests to 5min in BrowserStack. This is
a signficant improvement and we should consider moving this change
upstream with: angular/angular#31834.

Benefits are: reduced test time; increased test/browser stability;
isolated tests without leaking styles and services.
@zelliott
Copy link
Contributor Author

Looks like this is being fixed in #38336.

@jelbourn jelbourn added P4 A relatively minor issue that is not relevant to core functions and removed severity2: inconvenient labels Oct 1, 2020
trik added a commit to gnucoop/gngt that referenced this issue Nov 9, 2020
Angular's `TestBed` by default only removes a test component from the
document body and destructs individual component fixtures. It never
destroys the test module properly, but always re-creates it.

This means that providers are not necessarily cleaned up (could
leak in memory) and component styles are not cleaned up / deduped.
This results in thousands of style elements in the browsers. Ultimately
causing significantly increased memory consumption and CPU blocking.

This potentially leads to repeated crashes within browsers (as seen in
BrowserStack and Saucelabs in the past). Initial testing of this change
unveiled a reduction from 30min tests to 5min in BrowserStack. This is
a signficant improvement and we should consider moving this change
upstream with: angular/angular#31834.

Benefits are: reduced test time; increased test/browser stability;
isolated tests without leaking styles and services.
@cexbrayat
Copy link
Member

Until the fix lands, you can add to test.ts:

afterEach(() => {
  getTestBed().inject(傻DomSharedStylesHost).ngOnDestroy();
});

Unlike the workaround mentioned above #31834 (comment), this only cleans the necessary stylesheets and not the global ones.

This speeds up our ng test task by 2-3x (from 120s to 40s on my machine) without breaking anything.

cexbrayat added a commit to cexbrayat/angular that referenced this issue Nov 27, 2020
There is currently a known issue with the framework when testing that leaks style.
See angular#31834

A possible temporary workaround is to manually call the service that adds/aremoves the style.
See angular#31834 (comment)

This workaround will be unnecessary when angular#38336 lands.

This speeds up the `ng test` task locally by ~30%:
- before: Executed 649 of 652 (skipped 3) SUCCESS (24.161 secs / 20.072 secs)
- after: Executed 649 of 652 (skipped 3) SUCCESS (17.854 secs / 15.584 secs)
cexbrayat added a commit to cexbrayat/angular that referenced this issue Nov 29, 2020
There is currently a known issue with the framework when testing that leaks style.
See angular#31834

A possible temporary workaround is to manually call the service that adds/aremoves the style.
See angular#31834 (comment)

This workaround will be unnecessary when angular#38336 lands.

This speeds up the `ng test` task locally by ~30%:
- before: Executed 649 of 652 (skipped 3) SUCCESS (24.161 secs / 20.072 secs)
- after: Executed 649 of 652 (skipped 3) SUCCESS (17.854 secs / 15.584 secs)
wagnermaciel pushed a commit to wagnermaciel/components that referenced this issue Jan 14, 2021
Angular's `TestBed` by default only removes a test component from the
document body and destructs individual component fixtures. It never
destroys the test module properly, but always re-creates it.

This means that providers are not necessarily cleaned up (could
leak in memory) and component styles are not cleaned up / deduped.
This results in thousands of style elements in the browsers. Ultimately
causing significantly increased memory consumption and CPU blocking.

This potentially leads to repeated crashes within browsers (as seen in
BrowserStack and Saucelabs in the past). Initial testing of this change
unveiled a reduction from 30min tests to 5min in BrowserStack. This is
a signficant improvement and we should consider moving this change
upstream with: angular/angular#31834.

Benefits are: reduced test time; increased test/browser stability;
isolated tests without leaking styles and services.
@mlicciardi
Copy link

mlicciardi commented Jan 28, 2021

@cexbrayat I've tried your suggestion, but I think I've messed up since no performance change

Until the fix lands, you can add to test.ts:

afterEach(() => {
  getTestBed().inject(傻DomSharedStylesHost).ngOnDestroy();
});

Here's my test.ts file, can I have a review?

// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { 傻DomSharedStylesHost } from '@angular/platform-browser';
import {
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import 'zone.js/dist/zone.js';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy.js';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';

declare const require: {
  context(
    path: string,
    deep?: boolean,
    filter?: RegExp
  ): {
    keys(): string[];
    <T>(id: string): T;
  };
};

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
// Patch to ensure SharedStylesHost cleanup styles between Karma test specs
afterEach(() => getTestBed().inject(傻DomSharedStylesHost).ngOnDestroy());
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

Quite sure that's not how it was supposed to be added ...

@elesueur
Copy link

@AndrewKushnir what's blocking the MR in 38336 and resolution of this issue?

This issue is pretty significant and for large apps could have a MAJOR impact on unit test execution. An issue like this opened in July 2019 and still not resolved boggles my mind.

@AndrewKushnir
Copy link
Contributor

@elesueur the fix for the described issue would be a breaking change and we are looking at possible ways on how to make it less break-y for users.

@reduckted
Copy link

Am I correct in saying this has been fixed in Angular 13?

https://blog.angular.io/angular-v13-is-now-available-cce66f7bc296#1cd7

It seems that the default behavior is to remove styles. The old behavior can be kept using:

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

@cexbrayat
Copy link
Member

@reduckted Yes exactly, this issue is fixed on v13 by default (94ba59b)

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jan 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: testing Issues related to Angular testing features, such as TestBed effort1: hours freq3: high P4 A relatively minor issue that is not relevant to core functions type: bug/fix
Projects
None yet
Development

No branches or pull requests