Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I ask a question in a task? #57

Closed
nbarrett opened this issue Jun 5, 2017 · 12 comments
Closed

How do I ask a question in a task? #57

nbarrett opened this issue Jun 5, 2017 · 12 comments

Comments

@nbarrett
Copy link
Contributor

nbarrett commented Jun 5, 2017

Hey @jan-molak - here's something that is stumping me now I am using Serenity in javascript rather than java....

I need to be able ask a question within a task as I need to perform actions based on what I find in the question data (aka: answer). In Java I would normally say <Question>.answeredBy(actor) -> <data>, however in the serenity-js I see we pass a different actor interface into the Task:

performAs(actor: PerformsTasks):

as opposed to the question...

answeredBy(actor: UsesAbilities):

So within my task, I'm getting this problem:

image

I did see in the serenity source this kind of magic performAs(actor: UsesAbilities & AnswersQuestions): PromiseLike<void>, but I was unable to decipher whether this treatment is applicable to the problem above. Any thoughts please?

PS: Thanks for helping us all learn typescript as well as the awesome serenity-js!

@marktigno
Copy link

marktigno commented Jun 5, 2017

Try to use this:

performAs(actor: PerformsTasks): PromiseLike<void> {
    return actor.attemptsTo(
    See.if(ClassWithQuestion.StaticQuestion, SomeLambdaVar => expect(SomeLambdaVar).to.eventually.equal(ExpectedVar))`
    );
}

@nbarrett
Copy link
Contributor Author

nbarrett commented Jun 5, 2017

Cheers for response @marktigno - actually, I don't want to perform an expectation here, so after some experimentation I think I need something like:

performAs(actor: PerformsTasks & AnswersQuestions): PromiseLike<void> {
      return actor.toSee(ClassWithQuestion.StaticQuestion).map(data => {
... do some stuff
        });
    }

but still not entirely sure 😄

@nbarrett
Copy link
Contributor Author

nbarrett commented Jun 5, 2017

Actually, that didn't work... , I ended up used the following approach (return actor.attemptsTo() was just to appease the compiler so I could run it):

performAs(actor: PerformsTasks & AnswersQuestions): PromiseLike<void> {
    actor.toSee(ClassWithQuestion.StaticQuestion).forEach(data => console.log(data));
    return actor.attemptsTo()
}

But I received: TypeError: actor.toSee(...).forEach is not a function. I'm not surprised here as I would have expected .toSee() to have returned a promise but apparently it returns that actual type which doesn't make sense at all as there has to be a promise in there somewhere! 😖

@jan-molak
Copy link
Member

Hey @nbarrett,

The regular Serenity/JS Actor PerformsTasks, UsesAbilities and AnswersQuestions.

Saying performAs(actor: PerformsTasks & AnswersQuestions) means "I need an object that implements both those interfaces", so if you want to ask a question inside a task you can definitely use that.

Now regarding the second part of your question (no pun intended ;-) ).

The below construct won't behave the way you'd like it to. That's because actor.toSee() will just "spawn" a promise, but won't wait for it. Also, toSee returns a promise not a list, so forEarch is not suitable here.

performAs(actor: PerformsTasks & AnswersQuestions): PromiseLike<void> {
    actor.toSee(ClassWithQuestion.StaticQuestion).forEach(data => console.log(data));
    return actor.attemptsTo()
}

What I think you wanted to do is either something along those lines, using the See (there's an example of that in the Journey Planner demo):

performAs(actor: PerformsTasks & AnswersQuestions): PromiseLike<void> {
    return actor.attemptsTo(
        // some tasks
        See.if(ClassWithQuestion.StaticQuestion, someAssertion)
        // some more tasks
    );
}

The above is useful if you need an intra-flow check.

Alternatively, if you want to use the value returned by the question, you can do something like this (here's an example):

performAs(actor: PerformsTasks & AnswersQuestions): PromiseLike<void> {
    return actor.attemptsTo(
        TakeNote.of(ClassWithQuestion.StaticQuestion),
        // ... other tasks
        CompareNotes.toSeeIf(ClassWithQuestion.SomeOtherQuestion, equals, ClassWithQuestion.StaticQuestion)
    );
}

Does this help?

Jan

@nbarrett
Copy link
Contributor Author

nbarrett commented Jun 5, 2017

Hi @jan - thanks for comprehensive response. Actually that's not quite what I want to do as there are no expectations to perform in my use use. To be more specific, I want to do something like this (note use of pseudo-code):

ExtractValue.of(ClassWithQuestionReturningArrayOfMyDomainType.staticConstructor())
.then(domainItem => RunSomeTasksBasedOnContent.of(domainItem))

It might be worth explaining at this point that I'm writing some production code to create some integration with a 'legacy' web-ui which has no API grrrr 😠 , hence the absence of expectation code..

@jan-molak
Copy link
Member

Ah right, screen scraping, huh? 😉

I think you might need a custom task for that as there's no support for logical branching I'm afraid.

You'll probably need a construct like this:

Check.if(/* question<T> */, verification: (value: T) => boolean).andIfSo(/* list of tasks */).otherwise(/* some other tasks */)

For example:

Check.if(Text.of(H1), (text: string) => text === 'About us').andIfSo(task1, task2, task3).otherwise(task4, task5);

@nbarrett
Copy link
Contributor Author

nbarrett commented Jun 5, 2017

Yup no REST for the wicked, so resorting to scrape 🙀 FYI, there is not logical branching required in my use case. I pass the entire domainItem[] array to my Top level task and then I do the necessary with it in there. So my problem is solely to unconditionally extract the value from my question and off I go...

@nbarrett
Copy link
Contributor Author

nbarrett commented Jun 5, 2017

So.... I remain stuck on this one @jan-molak and I have to say a little confused.

In previous comments you said actor.toSee() :

  • will just "spawn" a promise, but won't wait for it.
  • returns a promise not a list, so forEach is not suitable here.

When I look at the interface for AnswersQuestions I see:

toSee<T>(question: Question<T>): T;

Cool, so all I have to do to extract the data is to call .then() on T and I get my data?
It seems not as the type system is adamant that T is already resolved (e.g. it's not thenable) so I'm not sure what to do. In the old days before Typescript, I'd have just slapped .then() on the end and hey presto ... but what do I do now?

@jan-molak
Copy link
Member

Right, maybe I misunderstood what you wanted to accomplish.

Have a look at See. I think you might need something similar, maybe along the lines of:

    performAs(actor: AnswersQuestions): PromiseLike<void> {
        return actor.toSee(ClassWithQuestion.StaticQuestion).then(resultOfTheQuestion => {
            return actor.attemptsTo(
                SomeOtherTask.thatUses(resultOfTheQuestion)
            );
        });
    }

Does that help?

J

@nbarrett
Copy link
Contributor Author

nbarrett commented Jun 6, 2017

erm... no as the result T from toSee is not thenable - see previous comment...

@jan-molak
Copy link
Member

Gotcha, I think this is caused by the Question interface being defined as:

export interface Question<T> {
    answeredBy(actor: UsesAbilities): PromiseLike<T>|T;
}

Which should probably become:

export interface Question<T> {
    answeredBy(actor: UsesAbilities): T;
}

With this change, some questions could be defined as synchronous, so implements Question<string> for example, some could be async implements Question<PromiseLike<string>>. This should resolve the issue you're seeing.

I'll look into this over the coming days.

@nbarrett
Copy link
Contributor Author

Wahoo! thanks @jan-molak - will look into ASAP 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants