serenity-js: Serenity/JS 3.0 - Test fails immediately and does not await `Wait` timeout if `Text` of a `PageElement` in a `List` can't be retrieved

Based on this test I wrote the following one.

It will fail with the LogicError: Can't retrieve the first item from a list with 0 items: [ ] (as expected).

But it fails immediately, not as expected, after the Wait timeout, in this case 30 seconds:

it(`waits for x seconds if the text of an element is not present before fail`, () =>
    actorCalled('Peggy').attemptsTo(
        Wait.upTo(Duration.ofSeconds(30)).until(
            Text.of(
                parents()
                .where(Text.ofAll(children()), contain('tea'))
                .where(Text.ofAll(children()), contain('coffee'))
                .where(Text.ofAll(children()), contain('juice'))
                // there's no parent container with all the three items
                .first()
            ), includes('tea')
        ))
);

In my case, there are lists, I have to wait until some list get the expected content. As workaround I would have to work with explicit fixed wait timeouts, what is not very convenient.

see https://serenityjs-serenityjs-adtizt5mmv3.ws-eu71.gitpod.io/

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 20 (9 by maintainers)

Commits related to this issue

Most upvoted comments

Solved with merge of #1355 and release of 3.0.0-rc.34. Thanks, @jan-molak

Yes, thanks. I will try to implement it asap and come back with the PR. 👍

Then @jan-molak you should enjoy your holidays, decent internet connections will be back soon enough! 😏 Hope you have a great time.

@manuelfidalgo, to be honest, my example was a bit lame. Should better have said Wait.until(list.count(0), isGreaterThan(0)) instead of equals(3). And yes, you can wrap in into a Task (what I did as well as a workaround).

Still, I’m on my way to fix this special issue. It should really wait (and “retry” to find the list element) until the timeout before failing, not fail immediately.

Anyhow, I don’t wanted to cancel other discussions. Was just not sure, if this is the right issue for this.

Concerning the wdio retry mechanism: As you said, this is a wdio setting that is already in place and ready to use with Serenity/JS. Though, as you said, as well, it targets a special Selenium “situation”, when the request over the webdriver protocol was not answered correctly (maybe due to a connection loss or whatever). I’m not sure, if this retry mechanism will trigger a retry of a request because an element was not found (as this is a valid answer for a request). I’ve never seen, or at least, I’ve never noticed that.

@jan-molak, concerning to our former discussion and the necessary changes in https://github.com/serenity-js/serenity-js/blob/3480ba44d83ff663a6a7c3e51ab1f977776cc9d0/packages/core/src/screenplay/interactions/Wait.ts#L338-L341

I added a pollIfPollingActive() sub-function into waitUntil that encapsulate some functionality. It is called twice. Once at the place where it was before and again when the LogicError occurs, so we have a recursion here, until the TimeoutExpiredError is thrown.

function waitUntil(expectation: () => Promise<boolean> | boolean, pollingInterval: Duration): { start: () => Promise<void>, stop: () => void } {
    let delay: { start: () => Promise<void>, stop: () => void };
    let pollingActive = false;    

    async function pollIfPollingActive(): Promise<void> {
        if (pollingActive) {
            delay = waitFor(pollingInterval);

            await delay.start();

            await poll();
        }

    }
    async function poll(): Promise<void> {
        try {
            const expectationIsMet = await expectation();

            if (expectationIsMet) {
                delay?.stop();
                return;
            }

            await pollIfPollingActive();
        } catch (error) {
            delay?.stop();
            if (error instanceof LogicError) {                       
                await pollIfPollingActive();
            } else {
                throw error;
            }
        }
        
    }

    return {
        async start () {
            pollingActive = true;
            await poll();
        },

        stop () {
            delay?.stop();
            pollingActive = false;
        }
    };
}

If this is still the way to go I could try to implement the ListItemNotFound error, create some valuable tests and create a PR. But yeah, just makes sense for me to invest more time here, if this would find it’s way to the main branch and can be useful for erverybody.