Using `await` on Protractor `element().getAttribute()` gives error: "Type of 'await' operand must either be a valid promise..."

Viewed 1541

I'm writing behavioral tests for an Angular2 app using Protractor, and I'm new to async/await syntax. This problem has brought me to a halt. Can someone explain why I get the Typescript error Type of 'await' operand must either be a valid promise or must not contain a callable 'then' member. in the isLogoFooterPresent() method, and how to fix it?

I have the following simple Protractor spec that tries to verify that an image is displayed by checking the image's width.

Here's an excerpt from my e2e-spec.ts file:

import { LoginPage, Layout } from '../page_objects';

describe('Login page', () => {
  it('Should have the logo in the footer', async () => {
    await LoginPage.navigateTo();
    await expect(Layout.isFooterLogoPresent).toBe(true);
  })
});

And here's a snippet from the my Layout page object that has visibility to the logo image in question. The line with the await element(...).getAttribute(...) chain that causes the error.

import { browser, element, by } from 'protractor/globals';

export class Layout {
  static get isFooterLogoPresent() {
    return ( async () => { // <-- this wrapping is necessary to use async/await in getter method
      return await element(by.css('.content-footer img')).getAttribute('naturalWidth') > 0; // <-- PROBLEM HERE
    })
  }
}

The following promise-based syntax (which works) is what I'm trying to replace:

  static get isFooterLogoPresent() {
    return element(by.css('.content-footer img')).getAttribute('naturalWidth') // naturalWidth attr is 0 if image is missing
      .then( imgWidth => imgWidth > 0 );
  }

BTW, I am using Typscript 2.3.2 and Protractor 5.2.0. I appreciate any wisdom you've got. Thanks!

2 Answers

I'm coming to the conclusion that Protractor 5.2 is not ready for async/await syntax yet. If anybody gets it working robustly, I'd love to know how and what version. At the moment, if you dig down to the getAttribute() source, it returns a ElementFinder object, not a Promise. This object has a .then() method on it, so you can use the result like a promise by chaining the .then(), but TypeScript (rightfully) won't recognize it as a proper promise, which is what await expects.

For the moment, I am giving up on async/await for this Protractor project. I am falling back to handling asynchronous behavior entirely with promises instead.

Just add parentheses for your await -

  import { browser, element, by } from 'protractor/globals';

  export class Layout {
    static get isFooterLogoPresent() {
      return ( async () => {
        return (await $('.content-footer img').getAttribute('naturalWidth')) > 0;
      })
    }
  }

This will apply > 0 to resolved value, not to comparison expression

BUT! You have error over here:

await expect(Layout.isFooterLogoPresent).toBe(true);

function isFooterLogoPresent is still returns a promise, so you asserting promise object to be true, it will be always true according to how object comparison in JS works. Change to this to fix false positive test:

await expect(await Layout.isFooterLogoPresent).toBe(true);
Related