Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use browser.wait with a callback that locates the element, reads its class attribute, and returns a promise resolving to true only when the required state exists. Capture the original class before the action if you need to prove that a transition occurred. For disappearance, decide whether the element must be removed from the DOM or merely become invisible; those are different conditions.

The core pattern: poll the element and return a Boolean promise

Protractor’s browser.wait repeatedly evaluates a condition until it returns a truthy value or the timeout expires. A condition can return a promise, so an asynchronous getAttribute('class') read fits naturally.

Locate the element with element(locator), then read its class attribute through the ElementFinder. Keep the callback limited to observation. Do not click buttons, submit forms, or mutate application state inside it, because the callback may run many times.

var target = element(by.css('.notice'));

browser.wait(function() {
  return target.getAttribute('class').then(function(className) {
    return /(^|s)is-ready(s|$)/.test(className || '');
  });
}, 5000, 'Expected the notice to gain is-ready');

The timeout is in milliseconds. If the class token appears within five seconds, the wait resolves. Otherwise Protractor throws a timeout error containing the message you supplied.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Wait for any class change

When the requirement is simply that the class value differ from its starting value, save the initial value and compare each later poll with it.

var target = element(by.css('.notice'));
var initialClass;

browser.wait(function() {
  return target.getAttribute('class').then(function(className) {
    if (initialClass === undefined) {
      initialClass = className || '';
      return false;
    }
    return (className || '') !== initialClass;
  });
}, 5000, 'Expected the notice class to change');

This version records the first value observed by the wait. That is safe only when the action causing the transition cannot finish before the first poll. If the update might happen immediately, capture the value before triggering the action.

Capture the before-state before clicking

var target = element(by.css('.notice'));
var initialClass;

target.getAttribute('class').then(function(className) {
  initialClass = className || '';
  return element(by.buttonText('Start')).click();
}).then(function() {
  return browser.wait(function() {
    return target.getAttribute('class').then(function(className) {
      return (className || '') !== initialClass;
    });
  }, 5000, 'Expected the notice class to change after Start');
});

Capturing first gives the comparison a real before-and-after boundary. Without it, a very fast transition can be observed only after it has completed, making the first poll look like the initial state.

Wait for a specific class to appear or disappear

A token-specific predicate communicates the intended UI state more precisely than “anything changed.” Match whitespace-delimited tokens rather than using indexOf; substring checks can mistake is-ready-large for is-ready.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Class token appears

var target = element(by.css('.notice'));

browser.wait(function() {
  return target.getAttribute('class').then(function(className) {
    return /(^|s)is-ready(s|$)/.test(className || '');
  });
}, 5000, 'Expected the notice to gain is-ready');

Class token is removed

var target = element(by.css('.notice'));

browser.wait(function() {
  return target.getAttribute('class').then(function(className) {
    return !/(^|s)is-loading(s|$)/.test(className || '');
  });
}, 5000, 'Expected the notice to lose is-loading');

If the class attribute is missing, className || '' treats it as an empty class list. That makes a removed token satisfy the predicate, which is usually the desired interpretation.

Re-locate when the application replaces the node

Framework updates can remove an element and insert a new element at the same selector. A previously resolved WebElement may then refer to a detached node, producing a stale-element error. Build the ElementFinder inside the callback so every poll performs a fresh lookup.

browser.wait(function() {
  return element(by.css('.notice')).getAttribute('class').then(function(className) {
    return /(^|s)is-ready(s|$)/.test(className || '');
  });
}, 5000, 'Expected the replacement notice to gain is-ready');

This does not make every stale-element race impossible, but it narrows the window in which a reference can become invalid. If replacement is itself the behavior under test, first wait for the old node to leave (or for the replacement condition your installed API supports), then locate the new node and assert its class.

“Disappear” has two meanings

Choose the assertion that matches the application contract. A hidden node can remain attached to the DOM, while a removed node cannot be queried as an existing element.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Element is removed from the DOM

Use a fresh lookup and isPresent(). The wait succeeds only when the locator no longer finds a node.

browser.wait(function() {
  return element(by.css('.notice')).isPresent().then(function(present) {
    return !present;
  });
}, 5000, 'Expected the notice to be removed from the DOM');

This is the right check for applications that destroy a component after completion, dismiss a notification by removing it, or navigate to markup where the selector no longer exists.

Element is hidden or not visible

If the node remains in the DOM but should no longer be visible, use Protractor’s expected-condition API when it is exposed by the version pinned in your project.

var target = element(by.css('.notice'));
var EC = protractor.ExpectedConditions;

browser.wait(
  EC.invisibilityOf(target),
  5000,
  'Expected the notice to become invisible'
);

Invisibility conditions generally succeed when the target is hidden or absent. Confirm the exact helper name and behavior in your installed Protractor/WebDriver version before adopting it. Do not substitute this check for a DOM-removal assertion when continued attachment matters; likewise, do not demand removal when the application intentionally keeps a hidden component for reuse.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing the right predicate

Requirement Predicate Why use it
Any class transition Current class differs from captured initial class Useful when the particular destination class is not part of the contract
A class is added Whitespace-token regular expression returns true States the exact ready, active, or completed condition
A class is removed Token regular expression returns false Checks that a loading, disabled, or pending marker is gone
Node is destroyed Fresh lookup followed by isPresent() equals false Verifies DOM absence rather than visual appearance
Node remains but is hidden ExpectedConditions.invisibilityOf, if available Checks visibility behavior without requiring removal

Reliable waits in real test suites

Use a bounded timeout and diagnostic message

Every wait should have an explicit upper bound and a message describing the expected state. A five-second timeout is only an example; choose a limit appropriate for the operation and your continuous-integration environment. Avoid an unbounded loop, which can leave a failed test hanging indefinitely.

Keep polling side-effect free

The callback may be evaluated repeatedly. Read attributes, presence, or visibility, and return a Boolean or a promise for one. Put the click or other state-changing operation before browser.wait.

Wait for the state your user needs

A class change may occur before images, data, or controls are usable. If the actual contract is “the button can be clicked,” wait for that state directly, such as an enabled class plus the relevant interaction check, rather than treating any class mutation as completion.

Use stable locators

Prefer a dedicated test attribute or a stable component selector when one exists. A selector tied to styling can change during a redesign and cause a test failure unrelated to behavior. If the selector must remain the same while the node is replaced, re-query it on every poll as shown above.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

Symptom Likely cause Fix
Timeout even though the page appears updated The predicate expects the wrong class token, or the class is applied to a different node Inspect the exact element and class string, then correct the locator or token expression
Timeout when waiting for removal The application hides the element instead of removing it Use an invisibility condition, or change the product behavior if DOM removal is required
Stale element reference The framework replaced the node between lookup and attribute read Create the ElementFinder inside the wait callback and avoid retaining resolved WebElements
“Any change” wait passes unexpectedly The initial class was captured after the triggering action Read the class before clicking or dispatching the event
Class check matches the wrong state Substring matching treats one class name as part of another Use a whitespace-delimited token expression such as /(^|s)token(s|$)/
Expected-condition helper is undefined The project’s pinned Protractor version exposes a different API Check that installed version’s ExpectedConditions implementation and use a custom browser.wait predicate if necessary
Test is flaky only in CI The timeout is too short for slower startup or the test races a node replacement Use a bounded but realistic timeout, fresh lookups, and a predicate tied to the final state rather than an arbitrary delay

Protractor’s maintenance status

These patterns are for maintaining an existing Protractor suite. The official Protractor repository was archived on July 29, 2024, and the project’s future-of-end-to-end-testing guidance encourages migration toward a modern framework-agnostic platform. The snippets above describe the documented WebDriverJS-style APIs; they are not a claim that any particular installed version has been executed here. For a new test suite, evaluate a maintained alternative and confirm its locator, wait, and visibility APIs before choosing a framework.

Or skip the browser setup

If your goal is to capture the page rather than exercise its interaction logic, ScreenshotNeo can return a screenshot or PDF from one HTTP request. It accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo documentation for all options, including full-page and selector captures, device and retina settings, waits, custom CSS or JavaScript, request blocking, cookies and headers, geolocation, PDFs, caching, signed links, webhooks, bulk capture, and usage reporting.

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}`);

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free. Start at ScreenshotNeo’s free sign-up.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Should a class wait assert the complete class attribute or one token?

Assert one token when other classes are allowed to change independently. Compare the complete string only when the exact class list is part of the component contract.

What should I do when a transition removes the selector before the final class appears?

Treat replacement as a two-stage condition: wait for the old locator to disappear, then locate the replacement and wait for its required class.

Can a CSS animation’s temporary class be a reliable test condition?

Only if that class represents a documented application state. Animation-specific markers can be timing details; prefer a semantic ready, completed, or hidden state when one exists.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.