Skip to content

feat(cdk/testing): allow disabling & batching of change detection #20464

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Sep 23, 2020

Conversation

mmalerba
Copy link
Contributor

@mmalerba mmalerba commented Aug 31, 2020

This PR introduces 2 new APIs that give users more fine-grained control over change detection when working with harnesses:

  • The first API, manualChangeDetection, can be used to disable the harness's automatic change detection for a block of code, e.g.
it('tests some things', async () => {
  await maualChangeDetection(async () => {
    someHarness.click();
    expect(...).toBe(...); // Check expectations before change detection...
    fixture.detectChanges();
    await fixture.whenStable();
    expect(...).toBe(...); // Check expectations after fixture stabilizes...
  });
});
  • The second API, parallel, can be used to resolve several async values in parallel via Promise.all while also batching change detection so that it only happens once before resolving the values and once after. This is useful for optimizing the performance of tests, e.g.
it('tests some things', async () => {
  // Read labels for all buttons simultaneously,
  // rather than waiting for one before reading the next
  const buttonLabels = await parallel(() => buttonHarnesses.map(button => button.text()));
});

Using the new parallel in the HarnessPredicate resolution code results in a signigicant performance gain:

  • finding a button by text on a page with 100 buttons: 1250.7ms => 131.8ms (89% improvement)

Using it in tests that perform many actions simultaneously again results in a significant performance gain:

  • clicking 1000 buttons: 3009.8ms => 757.8ms (75% improvement)

@mmalerba mmalerba added the P2 The issue is important to a large percentage of users, with a workaround label Aug 31, 2020
@mmalerba mmalerba requested review from devversion and a team as code owners August 31, 2020 22:45
@googlebot googlebot added the cla: yes PR author has agreed to Google's Contributor License Agreement label Aug 31, 2020
@mmalerba mmalerba added the target: minor This PR is targeted for the next minor release label Aug 31, 2020
@mmalerba mmalerba requested a review from crisbeto as a code owner September 9, 2020 20:49
@mmalerba mmalerba changed the title perf(cdk/testing): allow batching of change detection feat(cdk/testing): allow disabling & batching of change detection Sep 9, 2020
Copy link
Contributor

@wagnermaciel wagnermaciel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mmalerba
Copy link
Contributor Author

Feedback addressed

@mmalerba mmalerba force-pushed the harness-perf-2 branch 2 times, most recently from 2a93195 to 4dc6dc6 Compare September 15, 2020 23:23
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, just a few docs nits

@mmalerba mmalerba force-pushed the harness-perf-2 branch 7 times, most recently from 4df0c67 to 32ae7be Compare September 18, 2020 18:48
@mmalerba mmalerba force-pushed the harness-perf-2 branch 8 times, most recently from 0b97f1b to 5843ae7 Compare September 19, 2020 04:46
Copy link
Member

@devversion devversion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. nice work. One comment that should resolve the CI failures you were seeing

isDisabled: true,
onDetectChangesNow: resolve,
}));
const result = await fn();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the specified function fails here, we will never restore the disabled state of the auto change-detection, and change detection will never run at all. This causes the CI failures you're seeing in the gulp jobs as those run all tests (like a usual CLI project) in a single Karma process. This is actually great as it unveiled this issue that could surface for consumers.

The fix is to wrap this with a try/finally and always restore the state of change detection. In tests, it can commonly happen that errors are expected & silently handled. In such situations consecutive tests just fail randomly with all kinds of errors as change detection for harnesses is disabled permanently (as seen with the CI failures for this PR; it was hard figuring out there is actually a "silent" error). It was a test in the radio harnesses using expectAsync and toBeRejectedWith 😄

// If the function fails, we still want to restore the auto change detection state as
// otherwise change detection could be disabled unexpectedly forever. In some cases,
// errors are expected in tests and therefore silently captured. This would mean that
// consecutive tests relying on auto change detection could break without any hint about
// disabled auto change detection.
try {
  result = await fn();
} finally {
  await new Promise(resolve => autoChangeDetectionSubject.next({
    isDisabled: false,
    onDetectChangesNow: resolve,
  }));
}

We also need to do the same below with triggerBeforeAndAfter = false.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For later reference: The select tests for example failed as selectHarness.open was not reflected due to disabled change detection. The options were undefined then and .click resulted in a runtime exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for looking into this! That makes total sense. I was going crazy trying to debug this on Friday

@mmalerba mmalerba force-pushed the harness-perf-2 branch 2 times, most recently from f556fae to 733ab57 Compare September 21, 2020 19:03
@mmalerba mmalerba added the action: merge The PR is ready for merge by the caretaker label Sep 21, 2020
@annieyw annieyw merged commit 5562788 into angular:master Sep 23, 2020
wagnermaciel pushed a commit to wagnermaciel/components that referenced this pull request Sep 24, 2020
…gular#20464)

* feat(cdk/testing): allow disabling & batching of change detection

* feat(cdk/testing): address feedback

* feat(cdk/testing): split batchCD into 2 functions, `parallel` and `noAutoChangeDetection`

* feat(cdk/testing): address feedback

* feat(cdk/testing): fix some CI issues

* feat(cdk/testing): fix circular dep

* feat(cdk/testing): update docs

* feat(cdk/testing): address feedback

* feat(cdk/testing): address comments

* feat(cdk/testing): fix `parallel` to take a function

* feat(cdk/testing): ensure change detection completes even if test code throws

* feat(cdk/testing): fix misplaced tests
@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 Oct 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker cla: yes PR author has agreed to Google's Contributor License Agreement P2 The issue is important to a large percentage of users, with a workaround target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants