testingjasmineprotractorautomated-testse2e

Short stack trace with async await in protractor 7


I'm trying to automate some tests with protractor and jasmine and I'm using async/await to resolve the promises.

The issue is, when an error does happen, the stack trace is TOO short and thus, I can't seem to locate the source of the issue.

I did make sure to put SELENIUM_PROMISE_MANAGER to FALSE in the config file.

I'm using protractor 7 along with node 14.16.0

Does anyone know how to solve this ? There are not enough details

Here's a code snippet

const invoicesButton: Button = new Button("Invoices", LocatorType.Href, "#/Invoices");


describe("Kouka test", function () {
    beforeEach(function () {
        jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000000000;
    });

    it("Does a random test", async function () {
        await browser.get("https://appdev.expensya.com/Portal/#/Login?lang=en");
        await loginEmailInput.typeText("amr.refacto@yopmail.com")
        await loginPasswordInput.typeText("a")
        await loginButton.click(true);
        await dashboardPage.invoicesButton.click().catch((e) => {
            e.stackTraceLimit = Infinity;
            throw e;
        });
        await userInvoicesPage.createManualInvoice(invoice).catch((e) => {
            e.stackTraceLimit = Infinity;
            console.error("TEST ERROR ", e);
            throw e;
        });
        await browser.sleep(10000);

    });

});

And here's the definition of the "Button" Class:

import { browser } from "protractor";
import { WebComponent } from "./webComponent";


export class Button extends WebComponent {

/**
 * @param webElementText Text that the web element contains.
 * @param locatorType Locator type of the web element to search for.
 * @param locator Locator of the web element to search for.
 * @param parentElement Parent Web Component if it exists.
 */
constructor(webElementText, locatorType, locator, parentElement: WebComponent = null) {
    super(webElementText, locatorType, locator, parentElement);
}


async click(usingJavaScript = false) {
    if (usingJavaScript) {
        await this.isPresent();
        await browser.executeScript("arguments[0].click();", await this.webElement)
    }
    else {
        await this.isVisible();
        await this.isClickable();
        await this.webElement.click();
    }
}

}

Finally, here's the stack trace

Started
Jasmine started
undefined
F
  Kouka test
    × Does a random test
      - Failed: Wait timed out after 10012ms
          at C:\Users\Amrou Bellalouna\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\promise.js:2201:17
          at runMicrotasks (<anonymous>)
          at processTicksAndRejections (internal/process/task_queues.js:93:5)
      From asynchronous test:
      Error
          at Suite.<anonymous> (C:\Users\Amrou Bellalouna\source\repos\NewE2EArchitecture\NewArchitecture\koukouTest.spec.ts:20:5)
          at Object.<anonymous> (C:\Users\Amrou Bellalouna\source\repos\NewE2EArchitecture\NewArchitecture\koukouTest.spec.ts:15:1)
          at Module._compile (internal/modules/cjs/loader.js:1063:30)
          at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
          at Module.load (internal/modules/cjs/loader.js:928:32)
          at Function.Module._load (internal/modules/cjs/loader.js:769:14)



Failures:
1) Kouka test Does a random test
  Message:
    Failed: Wait timed out after 10012ms
  Stack:
    TimeoutError: Wait timed out after 10012ms
        at C:\Users\Amrou Bellalouna\AppData\Roaming\npm\node_modules\protractor\node_modules\selenium-webdriver\lib\promise.js:2201:17
        at runMicrotasks (<anonymous>)
        at processTicksAndRejections (internal/process/task_queues.js:93:5)
    From asynchronous test:
    Error
        at Suite.<anonymous> (C:\Users\Amrou Bellalouna\source\repos\NewE2EArchitecture\NewArchitecture\koukouTest.spec.ts:20:5)
        at Object.<anonymous> (C:\Users\Amrou Bellalouna\source\repos\NewE2EArchitecture\NewArchitecture\koukouTest.spec.ts:15:1)
        at Module._compile (internal/modules/cjs/loader.js:1063:30)
        at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
        at Module.load (internal/modules/cjs/loader.js:928:32)
        at Function.Module._load (internal/modules/cjs/loader.js:769:14)

1 spec, 1 failure
Finished in 19.461 seconds

Solution

  • I remember trying to solve this a while ago and I couldn't. But I implemented a bunch of workarounds and apparently this was enough

    To begin, can you share what these are doing

    await this.isPresent();
    await this.isVisible();
    await this.isClickable();
    

    1. Having this function
    async isVisible(){
      await browser.wait(
        ExpectedConditions.visibilityOf(this.webElement),
        this._elapsedTime
      )
    }
    

    you can use an advantage of third argument of browser.wait as described here and include an optional message on failure like this

    async isVisible(){
      await browser.wait(
        ExpectedConditions.visibilityOf(this.webElement),
        this._elapsedTime,
        `Element ${this.webElement.locator().toString()} is not present after ${this._elapsedTime}ms`);
      )
    }
    
    1. (I'm giving you all my secret sauce ingredients haha) If you add this to onPrepare in the config
        /**
         *  Set global environment configuration
         */
        Object.defineProperty(global, '__stack', {
            get: function() {
                let orig = Error.prepareStackTrace;
                Error.prepareStackTrace = function(_, stack) {
                    return stack;
                };
                let err = new Error();
                Error.captureStackTrace(err, arguments.callee);
                let stack = err.stack;
                Error.prepareStackTrace = orig;
                return stack;
            },
        });
    
        // returns name of the file from which is called
        Object.defineProperty(global, '__file', {
            get: function() {
                let path = __stack[1].getFileName();
                try {
                    //*nix OSes
                    return path.match(/[^\/]+\/[^\/]+$/)[0];
                } catch (error) {
                    //Windows based OSes
                    return path.match(/[^\\]+\\[^\\]+$/)[0];
                }
            },
        });
        // returns function name from which is called
        Object.defineProperty(global, '__function', {
            get: function() {
                return __stack[1].getFunctionName();
            },
        });
        // returns line number of the position in code when called
        Object.defineProperty(global, '__line', {
            get: function() {
                return __stack[1].getLineNumber();
            },
        });
    

    then you can use it for logging the file name, function name, and the line where it's called

    For example,

    async isVisible(){
      await browser.wait(
        ExpectedConditions.visibilityOf(this.webElement),
        this._elapsedTime,
        `Failed at ${__file} -> ${__function}() -> line ${__line}`
      )
    }
    

    will result in this error

     - Failed: Failed at common/index.js -> isVisible() -> line 82
          Wait timed out after 1032ms
          Wait timed out after 1032ms
    

    So you can accommodate this to your needs

    also I just realized you may want to play around with __stack variable itself