Managing Flakiness in Protractor with Page Objects and Synchronization
Learn how to stabilize Protractor tests using the Page Object Model and manual synchronization techniques to overcome flakiness.
21 Aug 2026, 23:49 UTC

The most common failure in Angular automation is a flaky test that passes locally and fails in CI because the UI was not truly ready. Protractor was built to solve this with internal Angular synchronization, but tests still break when third-party scripts or non-Angular async work runs outside the Angular lifecycle.
A stable approach is to decouple selectors from test logic with the Page Object Model and add explicit synchronization hooks for work Protractor cannot see.
Architecting with Page Object Models
The Page Object Model creates a class per page or component. Selectors live in the class, not in specs. A selector change requires one edit instead of dozens.
Example login page object using Protractor locators:
class LoginPage {
constructor() {
this.usernameField = element(by.model('input[name=username]'));
this.passwordField = element(by.model('input[name=password]'));
this.loginButton = element(by.css('button[type=submit]'));
this.statusMessage = element(by.css('.alert-danger'));
}
async login(user, pass) {
await this.usernameField.sendKeys(user);
await this.passwordField.sendKeys(pass);
await this.loginButton.click();
}
}Test intent stays readable:
it('should show an error with invalid credentials', async () => {
const loginPage = new LoginPage();
await loginPage.login('admin', 'wrong-password');
const error = await loginPage.statusMessage.getText();
expect(error).toBe('Invalid credentials');
});Closing the Synchronization Gap
Protractor auto-waits for Angular AJAX requests and timeouts to resolve before executing commands. Auto-sync does not track non-Angular activity such as third-party widgets, payment iframes, or maps.
For those cases use manual waits with ExpectedConditions. browser.waitForSync checks Angular stability, but UI state needs explicit conditions.
const EC = require('protractor/expected-conditions');
async function waitForLoad() {
const loader = element(by.css('.data-row'));
await browser.wait(EC.visibilityOf(loader), 10000);
}Verification: trigger a long-running API call and observe whether commands proceed before the element is visible. Replace browser.sleep with browser.wait and confirm test duration decreases and failures reduce.
Trade-offs and Limitations
Protractor is officially deprecated by the Angular team and relies on WebDriver, which adds latency versus browser-native protocols. Over-reliance on auto-sync creates flakiness when non-Angular async runs. Large Page Object classes become brittle without clear composition.
Actionable step: audit specs for browser.sleep and replace each with browser.wait using a specific ExpectedCondition tied to a visible state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.