ScreenshotNeo

BlogHow-to

How to Use Selenium Locators in Protractor

Learn Protractor’s Selenium locator syntax, choose stable selectors, handle AngularJS locators, and maintain legacy tests after Protractor’s end of life.

By the ScreenshotNeo team4 October 20268 min read

In Protractor, Selenium-style locators are created with the by object and passed to element() or element.all(). For example, use element(by.id('save')) for an ID or element(by.css('button.save')) for a CSS selector. Protractor also has AngularJS-aware locators such as by.model() and by.binding(); those are not general-purpose locators for modern Angular applications.

Protractor reached end of life in August 2023, so this guide is aimed at understanding and maintaining existing test suites. The project discourages adopting it for new tests. Protractor’s official site provides the lifecycle notice.

1. How Protractor locators work

A locator describes how to find an element in the page DOM. by creates the locator, and element(locator) returns an ElementFinder that test code can interact with. Use element.all(locator) when the locator can match multiple elements.

// One intended element
const saveButton = element(by.id('save'));

// A collection of matching elements
const rows = element.all(by.css('table.orders tbody tr'));

These are Protractor API examples, not claims of live testing. The Protractor locator documentation describes the available strategies and AngularJS-specific helpers.

2. Standard Selenium locator strategies

Protractor exposes familiar WebDriver locator strategies. The standard set is class name, CSS selector, ID, name, link text, partial link text, tag name, and XPath. These strategies locate ordinary DOM markup and are suitable for AngularJS, Angular, React, Vue, and non-framework pages as long as the markup matches.

Strategy Protractor example Useful when
ID by.id('save') The element has a unique, stable ID.
CSS selector by.css('button.save') You need a readable selector based on element, class, attribute, or ancestry.
Class name by.className('save-button') A single class identifies the target. Do not pass a compound class string.
Name by.name('email') A form control has a useful name attribute.
Link text by.linkText('Continue') An anchor’s complete visible text is stable.
Partial link text by.partialLinkText('Contin') Only part of an anchor’s text is suitable and remains unambiguous.
Tag name by.tagName('button') The page or a scoped parent makes that tag specific enough.
XPath by.xpath("//input[@name='email']") The relationship or condition is awkward to express clearly in CSS.

Examples for the common strategies:

const saveButton = element(by.id('save'));
const checkoutForm = element(by.css('form.checkout'));
const email = element(by.name('email'));
const button = element(by.className('save-button'));
const continueLink = element(by.linkText('Continue'));
const partialLink = element(by.partialLinkText('Contin'));
const firstButton = element(by.tagName('button'));
const emailByXPath = element(by.xpath("//input[@name='email']"));

Link-text strategies apply to links, not arbitrary buttons. If the text is on a button, use a CSS selector or a suitable XPath expression.

3. Choosing a stable locator

  1. Use a unique, predictable ID when available. It is direct and usually easy to understand.
  2. Otherwise choose a readable CSS selector. Prefer stable attributes and meaningful classes over a long chain of ancestors.
  3. Use XPath when its extra expression power helps. XPath can express relationships and conditions, but complex paths are harder to read and debug.
  4. Make the intended match explicit. If several elements match, select a collection or narrow the lookup to a meaningful parent.

Selenium’s locator guidance recommends unique IDs when available, well-written CSS selectors otherwise, and compact, readable locators. It notes that XPath is flexible but can be difficult to debug. These are documentation recommendations, not measured performance claims. See Selenium’s locator guidance.

For repeated page structures, scope a child lookup to its containing element so the relationship is clear:

const checkout = element(by.css('form.checkout'));
const email = checkout.element(by.name('email'));
const submit = checkout.element(by.css('button[type="submit"]'));

Avoid relying on positional selectors such as “the third button” unless order itself is the behavior being tested. Markup changes can otherwise silently redirect the test to a different element.

4. Using collections with element.all()

Use element.all(locator) when multiple elements are expected. Make the intended match clear by checking the collection, selecting an index only when order is meaningful, or narrowing the locator to a stable parent.

const rows = element.all(by.css('table.orders tbody tr'));

// Example interactions with a collection
rows.count().then(count => {
  console.log(`Order rows: ${count}`);
});

const firstRow = rows.first();
const rowByIndex = rows.get(1);

In asynchronous specs, await the result rather than leaving an unobserved promise:

const count = await rows.count();
expect(count).toBeGreaterThan(0);

const firstRow = rows.first();
await expect(firstRow.getText()).toContain('Order');

Use the async style supported by the project’s installed Protractor and test-runner versions. Older suites may use promise chains or framework-managed control flow, so follow the conventions already configured in that suite.

5. AngularJS-specific locator helpers

Protractor adds helpers that understand AngularJS conventions. They are not ordinary WebDriver strategies; use them only when the application and Protractor setup support AngularJS-aware locating.

// AngularJS-oriented examples
const firstNumber = element(by.model('first'));
const latestValue = element(by.binding('latest'));
const todos = element.all(by.repeater('todo in todoList.todos'));
  • by.model(expression) finds an element associated with an AngularJS ng-model expression.
  • by.binding(expression) finds content bound to an AngularJS expression.
  • by.repeater(expression) finds elements associated with an AngularJS repeater expression.

These are AngularJS-oriented examples from Protractor’s tutorial. Do not assume by.model() or by.binding() works in every Angular application: Protractor’s project says these locators are not supported for Angular applications and recommends CSS. For modern Angular markup, inspect the DOM and use a stable CSS selector such as by.css('[formcontrolname="email"]') if that attribute is present.

6. A complete legacy test example

This example shows the locator pattern in a small Protractor spec. Replace the example URL and selectors with those from the application under test. The page must expose the corresponding elements and the project must already have a working Protractor configuration.

describe('checkout form', () => {
  it('submits an email address', async () => {
    await browser.get('https://example.com/checkout');

    const form = element(by.css('form.checkout'));
    const email = form.element(by.name('email'));
    const submit = form.element(by.css('button[type="submit"]'));

    await email.clear();
    await email.sendKeys('dev@example.com');
    await submit.click();

    const confirmation = element(by.id('order-confirmation'));
    await expect(confirmation.isPresent()).toBe(true);
  });
});

The locators do not wait for arbitrary application conditions by themselves. If a target appears after client-side work, use an explicit wait for the expected condition before interacting with it. Keep the wait tied to a meaningful state rather than adding a fixed delay to every test.

7. Debugging locator failures

Symptom Likely cause What to check or change
No element found The selector does not match the rendered DOM, the page is on a different route, or the element has not appeared yet. Inspect the current DOM and route, verify spelling and attributes, then wait for the relevant state if rendering is asynchronous.
More than one element matched A broad class, tag, or partial text selector matches several elements. Use a unique ID, a more specific CSS selector, or scope the lookup under the correct parent.
Invalid selector Malformed CSS or XPath, or a class-name locator was given multiple classes. Validate the selector syntax; use by.css('.primary.save') for multiple classes rather than passing both to by.className().
Angular locator does not match The application is not AngularJS, Angular-aware locating is unavailable, or the expression does not match the AngularJS markup. Use a DOM-based CSS selector and verify the actual rendered attributes.
Element is found but interaction fails The element may be hidden, covered, disabled, or replaced between lookup and interaction. Wait for the actionable state, check visibility and enabled state, and locate the current element after rerendering.
Link text locator fails The target is a button or its visible text differs from the locator string. Use link-text locators only for anchors; inspect the actual visible text or use CSS for a button.

When debugging, first print or inspect the selector and the DOM around the expected element. Prefer fixing the selector or waiting for a real page condition over adding broad sleeps, which slow the suite and can still be flaky.

8. Performance, reliability, and maintenance

There is no source-supported universal speed ranking for these locator strategies. In practice, the clearest useful locator is usually the easiest to maintain. Keep selectors short, readable, and scoped; XPath is not inherently wrong, but an elaborate path is harder to understand when markup changes.

Reliability depends on the page’s markup and timing as well as the selector. Prefer stable attributes, avoid positional assumptions, wait for the relevant state, and make collection expectations explicit. If a UI redesign changes classes or structure, update the tests to reflect the new intended contract.

Protractor reached end of life in August 2023. For an existing suite, document its runtime and dependencies, keep its environment reproducible, and plan migration rather than treating the framework as a new long-term choice. The Protractor project’s migration discussion describes Selenium WebDriver as a close option in API terms while warning that methods do not map exactly. Playwright’s migration guide maps familiar selector forms, but migration still requires reviewing waits, test runner behavior, and application-specific locators.

9. Or skip the browser setup

If you need screenshots of the page while documenting or debugging a test, ScreenshotNeo returns a screenshot or PDF from one API request. This does not replace locator-based interaction tests, but it avoids setting up a browser just to capture a page.

See the ScreenshotNeo API documentation. Replace the target URL and use your API key:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
  • Cookie banners are accepted like a visitor, and 60+ known consent platforms, newsletter popups, and chat widgets are removed before capture; each step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents.
  • The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Sign up for 1,000 free screenshots a month with no card.

10. FAQ

Are Protractor locators the same thing as Selenium locators?

Protractor exposes standard WebDriver locator strategies through its by object and adds AngularJS-aware helpers. The AngularJS helpers are Protractor-specific.

Should a new project use Protractor?

No. Protractor reached end of life in August 2023. Use a maintained test framework for new automation and consult its migration guidance when replacing a legacy suite.

Can Protractor find an element by visible text?

For an anchor, use by.linkText() or by.partialLinkText(). For other element types, use a suitable CSS or XPath locator.

When should I use element.all()?

Use it when the locator is expected to match multiple elements, such as rows in a table or items in a list. State which item or count matters to the test.