How do I mix, expect() with non-protractor promises in protractor promise chains?

Viewed 131

I'm trying to understand how (in what order) promises get executed in protractor, specifically for non-protractor promises (ex. native or q promises) mixed in with protractor promises. I'm debugging & fixing some flaky protractor tests at work, and I am by no stretch of the imagination a JavaScript programmer; so when giving your answer, assume your answer will go over my head and take it down a level or two.

After days of no luck at work with a particular flaky test I decided to write some very simple examples to try to understand how promise chains get executed. From what I've read online I thought chaining like this:

a().then(() => {
    x();
    b().then(() => {
        y();
        c().then(() => z());
    });
});

should be the same as:

a().then(() => {
    x();
    b();
}).then(() => {
    y();
    c();
}).then(() => z());

which should also be the same as this (if I was using ES6 at work, which I'm not):

await a();
await x();
await b();
await y();
await c();
await z();

You can see my full code here along with the output I got: https://github.com/cpjust/TypeScriptTest/tree/dev/specs

I got different results for all of those scenarios and also when using native promises vs protractor promises. In second_spec.ts I also tried adding expect() statements inside promises which should fail and I was expecting the promises that were chained after the expect() not to execute since the expect should throw an assert error, but what I saw is that all promises after it execute, but then the test still fails due to the expect() failure. Very strange...

1 Answers

Your one question contains many sub questions but all are good. So It is better to go one by one and i try my best to explain that might help you to solve your problem.

first_spec.ts

 it('promise chain 1', function () {
        /* Prints the following:
        [TRACE] default - Start
        [DEBUG] default - Sleeping for 100 ms...
        [TRACE] default - End
        [INFO] default - --1
        [DEBUG] default - Sleeping for 200 ms...
        [INFO] default - --2
        [DEBUG] default - Sleeping for 300 ms...
        [INFO] default - --3
        [INFO] default - --done
        */
        logger.trace("Start");

        printLater("--1", 100).then(() => {
            printLater("--2", 200).then(() => {
                printLater("--3", 300).thenFinally(() => {
                    logger.info("--done");
                });
            });
        });

        logger.trace("End");
    });
  • This one is very straight forward. printLater returns promise browser.sleep, so it has to wait for given time then make it resolve. It means every printLater has to be completed before going to next one.I hope you don't have doubt for this it block.

    it('promise chain 1.1', function () { /* Prints the following: [TRACE] default - Start [DEBUG] default - Sleeping for 100 ms... [TRACE] default - End [INFO] default - --1 */ logger.trace("Start");

        nativePromise("--1", 100).then(() => {
            nativePromise("--2", 200).then(() => {
                nativePromise("--3", 300).finally(() => {
                    logger.info("--done");
                });
            });
        });
    
        logger.trace("End");
    });
    

In this case nativePromise creates Promise object and calling printLater inside of it. Let me explain how the flow happens. When we called function nativePromise, It directly returns the promise object which is currently in default pending state(As there are three states of any promise object resolve/reject/pending).Now using then on nativePromise expect resolve/reject state but it remains in pending state for indefinite time until test timeout.This is the reason that test struck at one point and doesn't do anything. Last point is that we get some output for first nativePromise because of asynchronous behavior. since there is printLater function which worked asynchronously and logged its outcome.

The easy way to make it same as first it block is make slight change in nativePromise function. You will have to use resolve to make sure that this promise is resolved.

function nativePromise(msg, time) {
        return new Promise((resolve) => {
            resolve(printLater(msg, time));
        });
    }

Note: Its always best practice to resolve/reject promises.

it('await promise 3.1', async function () {
        /* Prints the following:
        [TRACE] default - Start
        [DEBUG] default - Sleeping for 100 ms...
        [INFO] default - --1
        */
        await logger.trace("Start");

        await nativePromise("--1", 100);
        await nativePromise("--2", 200);   // Gets stuck after "--1"
        await nativePromise("--3", 300);
        await logger.info("--done");

        await logger.trace("End");
    });

For preceding code snippet reason is same as i mentioned earlier. await also waits on promise to resolve/reject. If it doesn't return anything then await also get stuck at one point.

We use await to make code cleaner. Internally it also wait for the promise to resolve/reject and it doesn't return promise object. await get the value out from promise object and returns.

Note1 : expect from Jasmine waits for the control flow to empty before performing any further action refer this link from Protractor

NOte2: Protractor Promise which is actually webdriver.Promise is deprecated in Protractor and webdriverJs. So if possible not to use them.

Related