Protractor tests failing due to timing issues

Loading

Protractor tests failing due to timing issues is a common challenge when testing AngularJS applications end-to-end (E2E). These failures typically arise when Protractor attempts to interact with elements before they are fully rendered, bound, or visible in the DOM. This happens especially when asynchronous operations like HTTP requests, animations, or timeouts delay the readiness of elements.

Here’s a step-by-step explanation with 1000+ words, exploring the root causes, best practices, and solid techniques to resolve timing-related test failures in Protractor.


Understanding Timing Issues in Protractor

Protractor is built on top of WebDriverJS, which communicates with the browser in an asynchronous fashion. Protractor tries to manage synchronization automatically for Angular apps via Angular’s internal testability API (angular.getTestability() or $injector.get('$browser')). However, when there are non-Angular timeouts, third-party scripts, or race conditions, Protractor may get ahead of the application, leading to flaky or failing tests.

Common Symptoms

  • ElementNotVisibleError
  • NoSuchElementError
  • TimeoutError
  • Failed: stale element reference
  • Tests that pass locally but fail in CI
  • Tests passing/failing randomly

Root Causes of Timing Issues

1. Non-Angular App or Mixed Asynchronous Logic

Protractor waits for Angular, not arbitrary setTimeout or third-party AJAX like jQuery or vanilla JS fetch.

2. Slow Network or Animations

If the app has animations or slow HTTP responses, the DOM may not update in time for the test.

3. Manual async operations not awaited

Protractor doesn’t wait for setTimeout, setInterval, or custom JS events unless explicitly told.

4. Stale Elements

When the element is located before the DOM is updated, any further actions on it may fail.


Solutions and Techniques to Fix Timing Issues

1. Use ExpectedConditions (EC) Properly

Protractor offers ExpectedConditions to wait until certain states (visibility, clickability) are true.

const EC = protractor.ExpectedConditions;
const myButton = element(by.css('.submit-button'));
browser.wait(EC.elementToBeClickable(myButton), 5000, 'Button not clickable');
myButton.click();

2. Avoid Fixed Sleep Times (browser.sleep())

Using browser.sleep() may work temporarily but introduces brittleness and longer test runs.

// Not recommended
browser.sleep(3000);

Instead, always wait for a condition.

browser.wait(EC.presenceOf(element(by.css('.loader-done'))), 10000);

3. Use browser.waitForAngular() After Non-Angular Calls

If you interact with third-party libraries or non-Angular pages, tell Protractor when to wait.

browser.ignoreSynchronization = true; // For non-Angular apps
// Do your operation
browser.ignoreSynchronization = false;
browser.waitForAngular();

4. Check for Stability Before Interacting

Use stable element states: isPresent(), isDisplayed(), isEnabled(), etc.

const saveBtn = element(by.id('save'));
expect(saveBtn.isPresent()).toBe(true);
expect(saveBtn.isDisplayed()).toBe(true);

🔹 5. Wait for Animations to Complete

If your app uses Angular animations or jQuery fades, consider waiting for final state or disabling animations in test mode:

Option 1: Wait for state

browser.wait(EC.invisibilityOf(element(by.css('.ng-animating'))), 5000);

Option 2: Disable animations in test builds

angular.module('app').config(function($animateProvider) {
  $animateProvider.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/);
});

Example Scenario with Fix

Problem Code (Flaky):

it('should submit the form', function() {
  element(by.model('user.name')).sendKeys('John');
  element(by.css('.submit')).click();
  expect(element(by.css('.success')).isDisplayed()).toBe(true);
});

Fixed Code:

it('should submit the form', function() {
  const nameField = element(by.model('user.name'));
  const submitBtn = element(by.css('.submit'));
  const successMsg = element(by.css('.success'));

  nameField.sendKeys('John');

  browser.wait(EC.elementToBeClickable(submitBtn), 5000);
  submitBtn.click();

  browser.wait(EC.visibilityOf(successMsg), 5000);
  expect(successMsg.isDisplayed()).toBe(true);
});

Tips to Prevent Future Timing Issues

Always Use ExpectedConditions Instead of browser.sleep()

They’re more reliable, readable, and make tests deterministic.

Chain Promises When Necessary

Protractor supports both promise-style and async/await. Mixing the two can cause flaky tests.

// Recommended: Use async/await
it('should wait properly', async () => {
  const button = element(by.id('submit'));
  await browser.wait(EC.elementToBeClickable(button), 5000);
  await button.click();
});

Avoid Overusing element.all(...)

Filtering through multiple elements may delay the locator logic.

// Inefficient
element.all(by.css('.item')).get(3).click();

Instead, try:

element(by.css('.item:nth-child(4)')).click();

Advanced Issues and Their Fixes

Dealing with AngularJS $timeout

Protractor may not always wait for $timeout unless it’s wrapped in $apply.

Fix: Wrap your timeout code in Angular-aware functions or use browser.wait() explicitly for effects to finish.

Disabling Synchronization for Non-Angular Pages

browser.ignoreSynchronization = true;
// Do non-Angular steps
browser.ignoreSynchronization = false;
browser.waitForAngular(); // resume normal flow

Force Protractor to Wait on Element States

function waitUntilVisible(el, timeout = 5000) {
  return browser.wait(EC.visibilityOf(el), timeout, 'Element not visible');
}

Cleanup and Retry Patterns

In CI environments, if your test fails intermittently:

  • Add logging to know what step failed.
  • Use retry logic inside a custom utility function (not the test itself).
  • Use Protractor’s onPrepare() to ensure pre-test setup (login, data reset, etc.).

Summary of Best Practices

PatternBenefit
Use ExpectedConditionsReliable waits for clickability/visibility
Avoid browser.sleep()Reduces flakiness
Use async/awaitBetter flow control
Add elements to DOM before triggeringEnsures events work
Handle non-Angular apps explicitlyPrevents silent sync failures
Debug failed stepsHelps reproduce locally
Use .first() instead of hard-coded .get(n)Cleaner list filtering
Check state before interactionPrevents stale element error

Leave a Reply

Your email address will not be published. Required fields are marked *