The message Cannot read property 'click' of undefined means JavaScript tried to evaluate .click on a value that was undefined. In chartsMenuBtn.click(), for example, the first thing to verify is chartsMenuBtn—not the browser, the selector, or the click itself. Find the exact stack-trace expression, inspect the value immediately before .click, and then check assignment, spelling, scope, locator construction, and asynchronous resolution.
What the error actually means
Property access happens before a method call. JavaScript must first evaluate the object on the left side of the dot:
chartsMenuBtn.click();
If chartsMenuBtn is undefined, JavaScript cannot read its click property and throws the exception. This is different from a WebDriver error such as “element is not clickable” or “another element would receive the click.” Those messages occur after Protractor has a value to operate on and the browser rejects the interaction.
The exception alone cannot identify which line of your program produced the undefined value. A locator can be valid while the variable holding it is never assigned, is misspelled, is out of scope, or is overwritten. An asynchronous collection can also be mistaken for an already-resolved array.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#1 Best Overall
Start with the complete stack trace
- Open the first stack-trace line that points to your test code, rather than a framework internals line.
- Copy the entire expression containing
.click. For a chain such asmenu.items[0].click(), inspectmenu,menu.items, and the indexed value separately. - Add a temporary log immediately before the failing statement:
console.log('chartsMenuBtn:', chartsMenuBtn);
chartsMenuBtn.click();
If the log prints undefined, browser waits will not repair that JavaScript value. Fix how it is created or passed first.
Check assignment, spelling, and scope
Declare and use the same identifier
JavaScript identifiers are case-sensitive. These are three different names:
var chartsMenuBtn = element(by.linkText('Charts'));
// Correct
chartsMenuBtn.click();
// Incorrect: different capitalization
chartsMenuButton.click();
Search the file for every occurrence of the variable. Look for a declaration such as chartMenuBtn (missing “s”), a reassignment inside a conditional, or a use before the assignment runs.
Keep the value in scope
A variable declared inside a function or block is not automatically available elsewhere:
Rank #2
function openCharts() {
const chartsMenuBtn = element(by.linkText('Charts'));
}
// chartsMenuBtn is not defined here
it('opens charts', async function () {
chartsMenuBtn.click();
});
Create the ElementFinder in the scope where it is needed, or return it from a page-object method:
class DashboardPage {
chartsMenuBtn = element(by.linkText('Charts'));
openCharts() {
return this.chartsMenuBtn.click();
}
}
Also verify that a function actually returns the value its caller expects. A function with no return statement returns undefined:
function chartsButton() {
element(by.linkText('Charts')); // no return
}
const button = chartsButton();
button.click(); // button is undefined
Fix it with return:
function chartsButton() {
return element(by.linkText('Charts'));
}
Use Protractor’s element APIs correctly
Protractor’s element(locator) helper returns an ElementFinder, the wrapper used to locate, inspect, and interact with one page element. A normal declaration is therefore valid:
const chartsMenuBtn = element(by.linkText('Charts'));
await chartsMenuBtn.click();
The locator is evaluated when Protractor performs the operation. If the element is not present, you generally receive a WebDriver “no such element” or wait error—not an undefined JavaScript base. For Angular applications, the project documentation says binding and model locators are unsupported and recommends CSS locators, so prefer a stable selector when those older locator strategies are involved:
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →const chartsMenuBtn = element(by.css('[data-testid="charts-menu"]'));
await chartsMenuBtn.click();
Do not confuse element with element.all. The former represents one ElementFinder; the latter represents a collection that resolves asynchronously.
Resolve collections before indexing them
A common source of this error is treating the result of element.all(...) as a JavaScript array immediately. The collection is promise-like. Indexing the unresolved result can produce an undefined value or otherwise fail before the click:
const rows = element.all(by.css('.row'));
const firstRow = rows[0]; // not a resolved ElementFinder
firstRow.click();
Resolve the collection inside .then() and index the resolved array:
element.all(by.css('.row')).then(function (rows) {
return rows[0].click();
});
In a setup that supports async/await, the equivalent is:
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #4
const rows = await element.all(by.css('.row'));
await rows[0].click();
Guard the empty-collection case so the next failure is explicit:
const rows = await element.all(by.css('.row'));
if (rows.length === 0) {
throw new Error('Expected at least one .row element');
}
await rows[0].click();
This promise/array mistake is one plausible cause, not proof of the cause in any particular test. The exact stack trace and code determine which value is undefined.
Separate an undefined value from a blocked click
Once logging shows a real ElementFinder, a different class of failure may appear. A modal, cookie layer, animation, or other element can receive the pointer instead of the intended target. A historic report involving Chrome 62 and ChromeDriver 2.33 described a modal intercepting the click. That is a browser-state problem, not evidence that the original variable was undefined.
Wait for the intended state
Wait for a condition that represents readiness, such as visibility or clickability, rather than adding an arbitrary long delay:
Best Value
const chartsMenuBtn = element(by.css('[data-testid="charts-menu"]'));
await browser.wait(
protractor.ExpectedConditions.elementToBeClickable(chartsMenuBtn),
10000,
'Charts menu did not become clickable'
);
await chartsMenuBtn.click();
If a dialog should disappear, wait for its invisibility:
const modal = element(by.css('.modal'));
await browser.wait(
protractor.ExpectedConditions.invisibilityOf(modal),
10000,
'Modal remained visible'
);
await chartsMenuBtn.click();
Check that the page has finished its transition and that no consent banner, overlay, or loading mask covers the target. Avoid forcing a JavaScript click merely to hide a real UI defect; it can make a test pass while a user still cannot click.
A practical debugging sequence
- Read the full stack trace and isolate the first test-code expression containing
.click. - Log every intermediate value in a chained expression.
- Verify spelling, capitalization, assignment order, return statements, and function scope.
- Confirm that the locator is constructed with the intended strategy and selector.
- For
element.all, await or callback-resolve the collection before indexing. - Run the test with the smallest reproduction: declare one ElementFinder, log it, and click it.
- If the value is defined, diagnose UI readiness: overlays, animations, visibility, and clickability.
- Check browser and driver versions against the versions actually installed in your environment; historic versions in an old report are not current compatibility advice.
Common symptoms and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
foo.click is undefined immediately |
foo was never assigned, was misspelled, or a function returned nothing |
Log foo; correct declaration, return, or scope |
Failure occurs after element.all |
An unresolved promise was indexed as an array | Use await or index inside .then() |
| “No such element” | The ElementFinder exists, but the locator found no matching DOM element | Verify selector, page state, and navigation; wait for the element when appropriate |
| “Another element would receive the click” | An overlay or animation covers the target | Wait for the overlay to disappear and the target to become clickable |
| Works in one test but not another | Different setup, navigation, scope, or shared-state timing | Log the value and URL in each test; avoid hidden mutable page-object state |
Protractor’s maintenance status
Protractor is archived and read-only as of July 29, 2024. The Angular team’s 2021 project discussion proposed ending Protractor development with Angular 15 at the end of 2022, and described automatic waiting for Angular stability as its remaining differentiating feature compared with selenium-webdriver. For an existing suite, debug the immediate failure in its current environment. For new investment, account for the project’s archived status and evaluate a maintained end-to-end stack separately; the error itself does not identify a migration path.
Or skip the browser setup
If your goal is a clean image of a page rather than an end-to-end interaction test, ScreenshotNeo provides a one-request screenshot API. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks and 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 exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo documentation for request options. Every plan includes its features: full-page and element capture, device and viewport controls, retina scale, PDF settings, custom CSS and JavaScript, waits, request blocking, headers, cookies, user-agent, timezone, geolocation, transparent backgrounds, resizing, selectable caching TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage data, and an OpenAPI specification. The free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Does increasing Protractor’s timeout fix this exception?
No. A timeout can help an element that is still loading, but it cannot make an undefined JavaScript variable exist. First fix assignment, scope, spelling, or promise handling.
Should I replace element(locator) with element.all(locator).first()?
Only when the page can legitimately contain multiple matches and you intend to select the first. Changing APIs without confirming the failing value can conceal a locator or data problem.
Is this error caused by Angular failing to load?
Not necessarily. The message describes JavaScript property access. Angular or page readiness may explain a later missing-element or intercepted-click error, but the stack-trace expression still determines the immediate cause.
Quick Recap
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.

