Protractor: StaleElementReferenceError when interacting with browser

Created on 17 May 2018  路  21Comments  路  Source: angular/protractor

Hey there, thanks for the project, it really simplifies angular UI testing!

I'm very new to using protractor so this very well may be user error, but I've tried everything I've seen online and not found a solution that addresses my issue.

  • Node Version: 8.7.0
  • Protractor Version: 5.3.0
  • Angular Version: 5.2.4
  • Browser(s): chrome
  • Operating System and Version OSX 10.3.4
  • Your protractor configuration file
const { SpecReporter } = require('jasmine-spec-reporter');

const config = {
  allScriptsTimeout: 11000,
  capabilities: {
    browserName: 'chrome'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  useAllAngular2AppRoots: true,

  framework: 'custom',
  frameworkPath: require.resolve('protractor-cucumber-framework'),

  specs: ['./e2e/**/*.feature'],
  cucumberOpts: {
    require: ['./e2e/_shared/setup.ts', './e2e/**/*.steps.ts'],
    tags: [],
    format: ['node_modules/cucumber-pretty'],
    strict: true,
    dryRun: false,
    compiler: []
  },

  beforeLaunch: function () {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });
  },

  // Work around for EPIPE errors that I was running into (serializing execution
  // as recommended by https://github.com/angular/protractor/issues/4294#issuecomment-357941307)
  onPrepare: function () {
    let currentCommand = Promise.resolve();
    const webdriverSchedule = browser.driver.schedule;
    browser.driver.schedule = (command, description) => {
      currentCommand = currentCommand.then(() =>
        webdriverSchedule.call(browser.driver, command, description)
      );
      return currentCommand;
    };
  },
};

exports.config = config;
  • A relevant example test
    I am consistently getting a StaleElementReferenceError when interacting with the ProtractorBrowser. Specifically I'm testing against when I call browser.get('/') or browser.driver.getTitle().
  • Output from running the test
StaleElementReferenceError: stale element reference: element is not attached to the page document
  (Session info: chrome=66.0.3359.181)
  (Driver info: chromedriver=2.38.552518 (183d19265345f54ce39cbb94cf81ba5f15905011),platform=Mac OS X 10.13.4 x86_64)
    at Object.checkLegacyResponse ([...]/node_modules/selenium-webdriver/lib/error.js:546:15)
    at parseHttpResponse ([...]/node_modules/selenium-webdriver/lib/http.js:509:13)
    at doSend.then.response ([...]/node_modules/selenium-webdriver/lib/http.js:441:30)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
From: Task: WebElement.isEnabled()
    at Driver.schedule ([...]/node_modules/selenium-webdriver/lib/webdriver.js:807:17)
    at currentCommand.then ([...]/protractor.conf.js:47:27)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
  • Steps to reproduce the bug
    It seems to only occur along with certain cucumber scenarios that I am running, but I'm really struggling to understand what state could be saved that is triggering the error in the first place.

Some notes on what I've done:

1) I don't interact with any WebElements directly (in fact I wrapped all my ElementFinders in functions just to be sure I was generating a new one every time I interacted with it)
1) I have added try/catch retries around the failing code
1) I've tried adding sleeps before the failing code

Any insight is much appreciated, let me know if you need more information. Thank you!

Most helpful comment

Alright, I've gone ahead and verified that your advice fixed my original application as well @awarecan. Many, many thanks for your help!

Again, for future readers, the gist of my issues were fixed by:
1) Ensuring all instances of selenium promises were converted to regular promises.
e.g. instead of

foo() {
    return browser.get('/person');
}

do

async foo() {
    await browser.get('/person');
}
  1. Dont return promises to cucumber
    e.g. instead of
Given('I am on the dashboard', function() {
    return dashboardPage.navigateTo();
})

do

Given('I am on the dashboard', async function() {
    await dashboardPage.navigateTo();
})
  1. Remove EPIPE workaround
    https://github.com/J-Swift/protractor-repro/blob/bc3c87c757a91461e94b99c333d37551d5a55e03/protractor.conf.js#L28-L38

By doing this, I was able to get rid of the StaleElementReferenceError but I did have a couple issues with EPIPE that I had to work through. It ended up being related to how I was interacting with ElementArrayFinder to parse table data. Once I restructured those patterns, my tests are all green again!

All 21 comments

Hi, @J-Swift ! I faced with same problem recently. It was caused by angular material animation. Such kind of errors appears when element not completely loaded and you're trying to interact with it. Can you provide app example?

Interesting. It is sort of hard to get a minimal repro on this due to it being a non-trivial app with a non-trivial test suite. I'll try swapping in the no-op animations module and then see if I can't find some time next week to narrow it down if that doesn't work.

The most puzzling thing for me is that it triggers when interacting with the browser object, so I'm not sure what element is being retained in that call stack which ends up tripping the exception.

I hope that you will find relevant test case example) I need it for better understanding of your problem, also it would help me to do it faster.

I tried swapping out for NoopAnimationsModule but the problem still occurs. This makes sense, as I was putting sleeps in before and it was broken. I'll continue to investigate and try to get a base repro.

Good luck and waiting for base repo to help you with this issue)

Been investigating a bit more and it seems to be something about how angular-material and selenium interact.

It turns out that the browser stuff above was a red herring, apparently the StaleElementReferenceError is a delayed exception being thrown from another part of the code (that was executed prior to this). I think I've narrowed it down to this:

async function closeCurrentlyOpenOptionDropdown() {
        const matDropdownClickOverlay = element(by.className('cdk-overlay-backdrop'));

        const needsClosing = await matDropdownClickOverlay.isPresent();
        if (needsClosing) {
            await browser.actions().mouseMove(matDropdownClickOverlay, { x: 0, y: 0 }).perform();
            await matDropdownClickOverlay.click();
            await browser.wait(ExpectedConditions.stalenessOf(matDropdownClickOverlay), 5000);
        }
    }

The check works correctly in the staleness check, but then afterwards calls to browser throw the StaleElementReferenceError. I'm not sure why this would be the case... any ideas?

Can you provide simple tests for angular-material dropDown component? It'd be very helpful for me. As I realized, after closeCurrentlyOpenPptionDropdown you're trying to interact with browser somehow and get this error, am I right?

OK, my base repro is available here:

https://github.com/J-Swift/protractor-repro

I modified the mechanism used to close the dropdown (escape key instead of click) but the result is the same. In case it matters: I typically have been running ng serve in one process, then running protractor separately via protractor --serve=false.

Your help is greatly appreciated, thanks for taking the time to look into this.

Hi, @J-Swift ! Sorry for late response, I was too busy) Where I can find flacky test (that returns StaleElementRef error) in your repository?

No worries @IgorSasovets - you can find the tests in the e2e folder. They are written for cucumber:

https://github.com/J-Swift/protractor-repro/tree/bc3c87c757a91461e94b99c333d37551d5a55e03/e2e/person

Thank you for quick response) I'll try it and provide gathered results!

@IgorSasovets

@J-Swift I quickly scanned your code, seems you didn't handled async/await very well. For example

    navigateTo() {
        return browser.get('/person');
    }

should change to

    async navigateTo() {
        return await browser.get('/person');
    }

async fillInSettings(settings: SettingsLike) should be called likes await fillInSettings(...)

And as others commented in other issue, EPIPE may be caused by incorrect async/await, patching scheduler just masks your root cause.

Hope it helps

Hi @awarecan thanks for taking a look.

There are a mix of async/await and promise-based uses in the repo. As far as I'm aware, it doesn't make a difference which is used, as long as the consumer side is using them correctly. I didn't see any instances where the consumer side was awaiting a non-promise, or was failing to await a promise. If you see any please do let me know though!

https://github.com/J-Swift/protractor-repro/blob/bc3c87c757a91461e94b99c333d37551d5a55e03/e2e/person/person.page.ts#L17-L24

https://github.com/J-Swift/protractor-repro/blob/bc3c87c757a91461e94b99c333d37551d5a55e03/e2e/person/person.steps.ts#L17-L19

Moreover, you are not mixing async/await and promise (they are compatible). You are mixing selenium promise manager and TypeScript async/await, that is a problematic usage.

At least, you should target ES6 to get native async/await support
https://github.com/J-Swift/protractor-repro/blob/bc3c87c757a91461e94b99c333d37551d5a55e03/e2e/tsconfig.e2e.json#L7

Ok, thank you @awarecan, I was not familiar with the selenium promise vs normal promise. I think you are right that treating these the same are where I'm having issues.

I went through the repro app and replaced all returns of wdPromise with a normal promise, then removed the EPIPE workaround, and it looks like that fixes my issue! I'm going to go ahead and do the same for the real application.

I've pushed the updated app in case it is helpful for someone in the future working through the same issues:
https://github.com/J-Swift/protractor-repro

Alright, I've gone ahead and verified that your advice fixed my original application as well @awarecan. Many, many thanks for your help!

Again, for future readers, the gist of my issues were fixed by:
1) Ensuring all instances of selenium promises were converted to regular promises.
e.g. instead of

foo() {
    return browser.get('/person');
}

do

async foo() {
    await browser.get('/person');
}
  1. Dont return promises to cucumber
    e.g. instead of
Given('I am on the dashboard', function() {
    return dashboardPage.navigateTo();
})

do

Given('I am on the dashboard', async function() {
    await dashboardPage.navigateTo();
})
  1. Remove EPIPE workaround
    https://github.com/J-Swift/protractor-repro/blob/bc3c87c757a91461e94b99c333d37551d5a55e03/protractor.conf.js#L28-L38

By doing this, I was able to get rid of the StaleElementReferenceError but I did have a couple issues with EPIPE that I had to work through. It ended up being related to how I was interacting with ElementArrayFinder to parse table data. Once I restructured those patterns, my tests are all green again!

@J-Swift, thanks for documenting for posterity!

@J-Swift the StaleElementReferenceError stops the test execution inside the docker container and exiting with error code 1, any guess?

nodeSetup_1  | [e2e] [16:59:15] E/launcher - The element reference of <button class="sc-dxgOiQ bYGPLn" type="button"> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed
nodeSetup_1  | [e2e] [16:59:15] E/launcher - StaleElementReferenceError: The element reference of <button class="sc-dxgOiQ bYGPLn" type="button"> is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed
nodeSetup_1  | [e2e]     at Object.throwDecodedError (/usr/src/script/node_modules/selenium-webdriver/lib/error.js:514:15)
nodeSetup_1  | [e2e]     at parseHttpResponse (/usr/src/script/node_modules/selenium-webdriver/lib/http.js:519:13)
nodeSetup_1  | [e2e]     at doSend.then.response (/usr/src/script/node_modules/selenium-webdriver/lib/http.js:441:30)
nodeSetup_1  | [e2e]     at process._tickCallback (internal/process/next_tick.js:68:7)
nodeSetup_1  | [e2e] From: Task: WebElement.isEnabled()
nodeSetup_1  | [e2e]     at thenableWebDriverProxy.schedule (/usr/src/script/node_modules/selenium-webdriver/lib/webdriver.js:807:17)
nodeSetup_1  | [e2e]     at WebElement.schedule_ (/usr/src/script/node_modules/selenium-webdriver/lib/webdriver.js:2010:25)
nodeSetup_1  | [e2e]     at WebElement.isEnabled (/usr/src/script/node_modules/selenium-webdriver/lib/webdriver.js:2314:17)
nodeSetup_1  | [e2e]     at actionFn (/usr/src/script/node_modules/protractor/built/element.js:89:44)
nodeSetup_1  | [e2e]     at Array.map (<anonymous>)
nodeSetup_1  | [e2e]     at actionResults.getWebElements.then (/usr/src/script/node_modules/protractor/built/element.js:461:65)
nodeSetup_1  | [e2e]     at ManagedPromise.invokeCallback_ (/usr/src/script/node_modules/selenium-webdriver/lib/promise.js:1376:14)
nodeSetup_1  | [e2e]     at TaskQueue.execute_ (/usr/src/script/node_modules/selenium-webdriver/lib/promise.js:3084:14)
nodeSetup_1  | [e2e]     at TaskQueue.executeNext_ (/usr/src/script/node_modules/selenium-webdriver/lib/promise.js:3067:27)
nodeSetup_1  | [e2e]     at asyncRun (/usr/src/script/node_modules/selenium-webdriver/lib/promise.js:2927:27)Error
nodeSetup_1  | [e2e]     at ElementArrayFinder.applyAction_ (/usr/src/script/node_modules/protractor/built/element.js:459:27)
nodeSetup_1  | [e2e]     at ElementArrayFinder.(anonymous function).args [as isEnabled] (/usr/src/script/node_modules/protractor/built/element.js:91:29)
nodeSetup_1  | [e2e]     at ElementFinder.(anonymous function).args [as isEnabled] (/usr/src/script/node_modules/protractor/built/element.js:831:22)
nodeSetup_1  | [e2e]     at citationPage.po.goToNextStep (/usr/src/script/src/pages/citation.page.js:127:55)
nodeSetup_1  | [e2e] [16:59:15] E/launcher - Process exited with error code 199
nodeSetup_1  | [e2e] npm ERR! code ELIFECYCLE
nodeSetup_1  | [e2e] npm ERR! errno 199
nodeSetup_1  | [e2e] npm ERR! [email protected] e2e: `protractor ./protractor.conf.js`
nodeSetup_1  | [e2e] npm ERR! Exit status 199
nodeSetup_1  | [e2e] npm ERR!
nodeSetup_1  | [e2e] npm ERR! Failed at the [email protected] e2e script.
nodeSetup_1  | [e2e] npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
nodeSetup_1  | [e2e]
nodeSetup_1  | [e2e] npm ERR! A complete log of this run can be found in:
nodeSetup_1  | [e2e] npm ERR!     /root/.npm/_logs/2019-03-11T16_59_15_250Z-debug.log
nodeSetup_1  | [failsafe] Script 'e2e' exited with code 199
nodeSetup_1  | [report]

@rakeshnambiar Its been a long time, but I think I had to use the raw browser API to iterate over the DOM elements directly, rather than using the ElementArrayFinder. I'll have to look up the old code later to see exactly what API I was leveraging.

@J-Swift thanks for your quick reply. I solved it yesterday with the below listed approach.

Changed from:

po.Next_Button = by.buttonText('Next');
......
await browser.element(po.Next_Button).click();

Changed to:

po.Get_All_Buttons = by.xpath('//button');
......
            let buttons = await browser.element.all(po.All_Buttons);
            let clicked = false;
            for (let i = 0; i < buttons.length; ++i) {
                await buttons[i].getText().then(async (text) => {
                    if (text.toLowerCase().includes('next')) {
                        await buttons[i].click();
                        await browser.driver.sleep(1000);
                        clicked = true;
                    }
                });
                if(clicked){
                    break;
                }
            }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

amedvedjev picture amedvedjev  路  3Comments

koshkarov picture koshkarov  路  3Comments

davidkarlsen picture davidkarlsen  路  3Comments

luakri picture luakri  路  3Comments

vishalshivnath picture vishalshivnath  路  3Comments