2

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
Amrou
  • 371
  • 1
  • 3
  • 18

1 Answers1

1

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

Sergey Pleshakov
  • 7,964
  • 2
  • 17
  • 40
  • Sure thing @Sergey Peshakov, I'm sorry but the comment won't format as code properly. Here it is: `async isVisible() { await browser.wait(ExpectedConditions.visibilityOf(this.webElement), this._elapsedTime); } async isClickable() { await browser.wait(ExpectedConditions.elementToBeClickable(this.webElement), this._elapsedTime); } async isPresent() { await browser.wait(ExpectedConditions.presenceOf(this.webElement), this._elapsedTime); } ` – Amrou Mar 24 '21 at 14:09
  • webElement is a getter of the _webElement attribute. Here's the getter definition: `public get webElement(): ElementFinder { this._webElement = this.getElements().get(this._index); return this._webElement; }` – Amrou Mar 24 '21 at 14:10
  • and finally, **getElements()** does something like this: ```private getElements(): ElementArrayFinder { switch (this._locatorType) { case (LocatorType.Id): { browser.wait(ExpectedConditions.presenceOf(element(by.id(this._locator))), this._elapsedTime); return element.all(by.id(this._locator)); }``` – Amrou Mar 24 '21 at 14:14
  • That'll help yes. But the most important issue is not solved, it's not showing me the exact line that caused the error. And since I'm using isVisible more than 500 times, it won't do it :s – Amrou Mar 24 '21 at 15:01
  • updating one more time to display which element was not visible. How about now? – Sergey Pleshakov Mar 24 '21 at 15:09
  • First of all, thanks for the effort @Sergey Pleshakov ! Second of all, I had already done that and implemented it. But what happens if it fails in the .click() method and it doesn't show me the proper stack trace? What can I do ? And most importantly, do you have an idea on why it's happening in the first place ? Because I'm refactoring everything using async await, but before when I was using control flow, it would show me exactly where it's failing – Amrou Mar 24 '21 at 15:41
  • I don't know why, but that's the way it is with async. As I said I spent quite some time to solve (increase stack size, filter it, use additional packages), no luck. If you manage to solve properly without workarounds, let me know. But what I noticed the problem is normally with waiting only. when you use click it gives you pretty comprehensive stacks. For everything else, there is one more thing I use. I'll include that to the answer – Sergey Pleshakov Mar 24 '21 at 15:55
  • I just found something that might work too, I changed isIvisible likeWise: `async isVisible() { let s = new Error().stack; await browser.wait(ExpectedConditions.visibilityOf(this.webElement), this._elapsedTime, 'Element ' + this._webElementText +" timed out in isVisible() " + this._elapsedTime + 'ms' + "\n" + s ) }` So now when an error does happen, the stack trace is more detailed Try it and let me know ! – Amrou Mar 24 '21 at 16:17
  • if it works for you - great. Again, I learned how to coexist with current behavior by implementing a set of workarounds. Just define your major problems and try to research what can be done to solve them – Sergey Pleshakov Mar 24 '21 at 16:38
  • Can you share with me those workarounds ? I'd like to have an idea ! – Amrou Mar 24 '21 at 19:01
  • 1. using custom messages in `browser.wait` 2. using `__file` `__function` `__line` variables (did you see the last edit?) 3. Using logging to a file and logging every function and it's parameters (pretty ugly to have so many extra lines of code but does the work). 4. using `expect(…).withContext` in Jasmine 3, and `expect(…).toBe(..., "message")` in jasmine 2. I think that's all – Sergey Pleshakov Mar 24 '21 at 19:11
  • actually no, I haven't. But I did now, thank you very much. I'll let you know if I find a way to deal with this – Amrou Mar 24 '21 at 23:20
  • Hi @Sergey Pleshakov. I was trying to add the "onPrepare" snippet that you gave me. But when I add this: `Failed at ${__file} -> ${__function}() -> line ${__line}`to the "isVisible" method for example. It doesn't recognize what __file, __function, __line are – Amrou Apr 26 '21 at 12:45
  • did you implement evethying as I suggested? Including the part where I define these 3 variables? – Sergey Pleshakov Apr 26 '21 at 13:03
  • And just for people that mught run into this issue, my proposition to use new Error().stack turned down to be useless – Amrou Apr 27 '21 at 11:27
  • Item # 2 of my answer, I'm setting all __variables as global within onPrepare of protractor config, it is working in my case, and I don't see why it wouldnt for you – Sergey Pleshakov Apr 27 '21 at 13:07