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

Comments

3 participants
@nbarrett
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

This comment has been minimized.

Show comment
Hide comment
@marktigno

marktigno 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))`
    );
}

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

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 5, 2017

Contributor

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 😄

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 5, 2017

Contributor

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! 😖

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@jan-molak

jan-molak Jun 5, 2017

Owner

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

Owner

jan-molak commented Jun 5, 2017

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

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 5, 2017

Contributor

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..

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@jan-molak

jan-molak Jun 5, 2017

Owner

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);
Owner

jan-molak commented Jun 5, 2017

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

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 5, 2017

Contributor

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...

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 5, 2017

Contributor

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?

Contributor

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

This comment has been minimized.

Show comment
Hide comment
@jan-molak

jan-molak Jun 6, 2017

Owner

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

Owner

jan-molak commented Jun 6, 2017

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

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 6, 2017

Contributor

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

Contributor

nbarrett commented Jun 6, 2017

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

@jan-molak

This comment has been minimized.

Show comment
Hide comment
@jan-molak

jan-molak Jun 6, 2017

Owner

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.

Owner

jan-molak commented Jun 6, 2017

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.

@jan-molak jan-molak added ready in progress and removed ready labels Jun 9, 2017

@jan-molak jan-molak closed this in 58ed941 Jun 11, 2017

@jan-molak jan-molak removed the in progress label Jun 11, 2017

@nbarrett

This comment has been minimized.

Show comment
Hide comment
@nbarrett

nbarrett Jun 11, 2017

Contributor

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

Contributor

nbarrett commented Jun 11, 2017

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