Protractor Angular Locators: Stable E2E Tests for Legacy Apps
Protractor’s Angular locators (by.model, by.binding, by.repeater) let tests find elements via data bindings, cutting flakiness in legacy AngularJS suites. They work only with Protractor ≤7.0.0 on pre‑Ivy apps and are deprecated, so verify before relying on them.
26 May 2026, 01:34 UTC

The Flakiness Problem in Angular E2E Tests
End-to-end tests for AngularJS applications often break when developers rename a CSS class or restructure a template. A test that clicks button.submit-btn fails the moment a designer changes the class to btn-primary, even though the underlying behavior hasn't changed. This brittleness erodes confidence in the test suite and wastes debugging time.
Protractor’s Angular-specific locators—by.model, by.binding, and by.repeater—address this by targeting elements through their data bindings rather than DOM structure. They remain a practical tool for teams maintaining legacy AngularJS (1.x) or early Angular (2+) test suites on Protractor ≤7.0.0.
How Angular Locators Differ from CSS and XPath
Standard WebDriver locators operate on the rendered DOM. Protractor’s extensions hook into Angular’s compiler to resolve elements by their scope properties:
by.model('userName')finds the input bound tong-model="userName".by.binding('user.name')locates any element displaying{{user.name}}.by.repeater('item in items')returns the collection of rows generated byng-repeat="item in items".
Because these locators reference the application's view-model, they survive template refactors that preserve the binding expressions. The trade-off is tight coupling to Angular's internal scope hierarchy—if the framework changes how scopes are exposed, the locators can stop working.
Worked Example: Selecting a Specific Repeater Row
Consider a component that lists orders with an ng-repeat="order in orders". You need to click the View button on the order where order.id === 42. Using CSS, you might write a fragile nth-child selector or add a test-only data-testid. With by.repeater, the intent stays clear:
const orders = element.all(by.repeater('order in orders'));
const target = orders.filter(async (row) => {
const id = await row.element(by.binding('order.id')).getText();
return id === '42';
}).first();
await target.element(by.buttonText('View')).click();
The filter callback receives each repeater row as an ElementFinder, letting you query nested bindings (by.binding('order.id')) without leaving the Angular locator API. This reads like domain logic rather than DOM traversal.
Limitations You Must Weigh
- Protractor is deprecated. The last official release is 7.0.0 (2022). No security patches or browser-driver updates are coming. Verify your version with
npx protractor --versionand plan migration to WebdriverIO, Cypress, or Playwright. - Ivy and modern Angular break scope access. Angular 9+ uses Ivy compilation, which does not expose scope properties the way the legacy compiler did.
by.modelandby.bindingoften return empty results on Ivy-rendered apps. The locators only work reliably on AngularJS (1.x) or Angular 2–8 withng-apporng-codelessbootstrap. - No TypeScript definitions in downstream forks. Community forks like
@protractor/angular-locatorsmay lack type declarations, forcing// @ts-ignoreor manual declaration files.
Actionable Next Steps
If you're on a legacy suite that still runs on Protractor 7.x with AngularJS or pre-Ivy Angular, the locators can buy you stability while you plan migration. Run this verification to confirm they work in your environment:
# 1. Confirm Protractor version
npx protractor --version
# 2. Add a minimal spec to your suite
// spec/angular-locators.e2e.ts
import { browser, by, element } from 'protractor';
describe('Angular locator sanity check', () => {
it('finds model-bound input', async () => {
await browser.get('/your-angularjs-page');
const input = element(by.model('userName'));
await expect(input.isPresent()).toBeTruthy();
});
});
# 3. Run the spec
npx protractor conf.js --specs spec/angular-locators.e2e.ts
If the spec passes, the locators are functional. Document the binding expressions your tests depend on, then evaluate WebdriverIO's angular service or Cypress's cy.get() with data attributes as migration targets. The goal isn't to stay on Protractor—it's to keep the test suite trustworthy while you move off it.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.