Stop flaky Protractor tests with ExpectedConditions
Use Protractor's ExpectedConditions and browser.wait to synchronize Angular tests instead of arbitrary sleeps.
22 Dec 2025, 08:53 UTC

Tests fail because Angular has not finished rendering
A Protractor test clicks a button, and the run stops with “element not clickable” or “no such element.” The button exists in the HTML, but Angular has not finished attaching event handlers or enabling the control. Implicit waits do not solve this well because they poll for presence, not readiness. Arbitrary sleeps make tests slow and still fail when the network is slower than the sleep duration.
The useful takeaway is to pair Protractor’s ExpectedConditions (EC) with browser.wait. EC defines the state you need—such as an element being present, visible, or clickable—and browser.wait polls the browser until that state is true or a timeout expires.
How ExpectedConditions works with browser.wait
ExpectedConditions lives under the protractor.ExpectedConditions namespace. Common conditions include:
- elementLocated(locator): the element exists in the DOM.
- visibilityOf(element): the element is present and visible.
- elementToBeClickable(element): the element is visible and enabled.
- textToBePresentInElement(element, text): the element contains the expected text.
You pass the condition to browser.wait with a timeout in milliseconds. Protractor polls repeatedly until the condition resolves or the timeout throws a TimeoutError.
Worked example: wait for a button after an async load
Imagine a page that loads a “Submit” button only after an API call finishes. A naive test clicks immediately:
const submitButton = element(by.buttonText('Submit'));
submitButton.click(); // flaky: may run before Angular enables the button
A more reliable version wraps the locator in an ExpectedCondition:
const EC = protractor.ExpectedConditions;
const submitButton = element(by.buttonText('Submit'));
// Wait up to 10 seconds for the button to be clickable
browser.wait(
EC.elementToBeClickable(submitButton),
10000,
'Submit button did not become clickable within 10 seconds'
);
submitButton.click();
Run this from the project root where protractor.conf.js is located:
npx protractor protractor.conf.js
No special operating-system permissions are required, but the Selenium server and browser driver must be reachable. Replace 10000 with <timeout-ms> suited to your application. If the condition never resolves, Protractor throws a TimeoutError and fails the test, which is the expected behavior rather than hanging indefinitely.
Expected check: the test exits with code 0 and the click proceeds only after the button is enabled. Risk: a timeout that is too long masks slow rendering; a timeout that is too short causes intermittent TimeoutError failures.
Limitations and what to check
Protractor is officially deprecated by the Angular team in favor of Cypress or Playwright. If you are maintaining an older Angular or AngularJS suite, ExpectedConditions is still the standard synchronization tool, but new projects should consider a modern replacement.
Long timeouts can hide performance regressions. A 30-second wait may keep a test green while the application gets slower. Use the shortest timeout that reliably passes, and treat repeated timeouts as a signal to investigate the application under test.
Practical verification: run the same test five to ten times with and without the ExpectedCondition. The version using EC should pass consistently, while the version without it may fail intermittently under load. Check the Protractor API documentation for your installed version because condition names and behavior can differ between major releases.
Closing
Do not replace every implicit wait with ExpectedConditions. Use it for specific asynchronous boundaries where Angular controls when an element becomes ready. When a test fails with a timeout, inspect the application state first before increasing the limit.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.