Closed susnigdha1 closed 1 year ago
In the process await() creates a singleton bean and it cannot access any pageObjects that are scenario scoped.
Assuming Awaitility waits on a different thread that sounds plausible. The CucumberTestContext
is also bound to a thread. So creating a bean on a different thread could result in problems.
How did you establish a bean is being created only once objObject
is referenced (i.e when objObject.isDisplayed()
is called)?
How did you establish a bean is being created only once objObject is referenced (i.e when objObject.isDisplayed() is called)? To answer it, in this case await() creates a singleton bean, and from within, it is trying to access objObject which is a ScenarioScope bean, as it is scenario scope the application context is having only 1 instance of the bean, considering tests are not running in parallel. My curiosity is cucumber-spring 4.8.1, we were using @scope(SCOPE_CUCUMBER_GLUE), and await() was working perfectly. So can you explain why cucumber-spring 6.x.x and above does not share state with another standard bean?
To answer it, in this case await() creates a singleton bean, and from within, it is trying to access objObject which is a ScenarioScope bean
But how did you establish this is in fact what happens? Can you explain what observations you made and how you reached your conclusion?
And were you able to see that this happened on a different thread?
Please bear in mind that you are trying to explain your problem to a complete stranger with a cell phone and some spare time while the food is in the oven. 😄
To reproduce the problem use:
public class ExampleSteps {
@Autowired
private ScenarioScopedContainer container;
@Given("some step")
public void some_step() {
UUID s = container.getId();
Awaitility.await().until(() -> {
assertEquals(s, container.getId());
return true;
});
}
}
The assertion will fail when used in combination with:
@ScenarioScope
@Component
public class ScenarioScopedContainer {
private final UUID id = UUID.randomUUID();
public UUID getId() {
return id;
}
}
However it will pass when using @Scope("cucumber-glue")
.
The difference between @ScenarioScope
and @Scope("cucumber-glue")
can be found in the source:
public @interface ScenarioScope {
@AliasFor(
annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
Because the ScopedProxyMode.TARGET_CLASS
is used, each time a method on the ScenarioScopedContainer
is used, a scenario scoped instance of it will be retrieved from the CucumberScenarioScope
.
Combined with:
Awaitility.await()
does it's polling on a separate thread,CucumberScenarioScope
depending on a threadlocal to keep track of the active scopeWe end up attempting to create a new scope for awaitilities thread.
The motivation for using ScopedProxyMode.TARGET_CLASS
can be found in https://github.com/cucumber/cucumber-jvm/pull/1974.
And this is also the solution to your problem. If your scenario scoped objects are only reachable through step definition classes and are not leaked into the application context you can either use @ScenarioScope(proxyMode = ScopedProxyMode.NO)
or @Scope(CucumberTestContext.SCOPE_CUCUMBER_GLUE)
.
Hi @mpkorstanje, highly appreciate your time in analysing the problem in simple terms and recommending the solution with an example. However, there are some more challenges attached to it. Consider below given points first:
Surefire
plugin to run tests in parallel, using Junit4 and WebDriver. If they use Factory Design Pattern in Spring, then they need to annotate the Bean responsible for creating WebDriver with @ScenarioScope
, to instantiate a new browser instance for the scenarios coming from different feature files for parallel runWebDriver
PageFactory design pattern in their PageObject classes (annotated with @Component
and @ScenarioScope
), and initialize the page factory using constructor, supplying the reference to the WebDriver bean created. And the web elements are declared with @FindBy
.
Now, look into the below issues:@ScenarioScope
proxy mode as per your recommendation in no 1 (above), then Cucumber will leak thread state and parallel run using Surefire will malfunctionawait()
on any PageObject
' object from StepDefinition/PageActions class, it will still throw ScopeNotActive
exception. Reason: The PageObject
class is @ScenarioScoped and WebDriver PageFactory may create another thread to lazily retrieve the WebElement
. if we change its ProxyMode
or use the @Scope(CucumberTestContext.SCOPE_CUCUMBER_GLUE)
then it will leak state and catastrophic result is a certainty.
So, for large automation project, whose existing code uses a lot of await() and with(), migration to cucumber 6.10.2 and above going to involve huge effort in code refactoring. As, it is neither feasible, nor recommended, to create new classes with all objects that will be accessed only within Await()
.
FYI... if we create a local variable and store the proxy object into it within the same class file, and pass it within Await()
, that also works.It's a feature of Spring to reuse the Application context between tests. Not just for Cucumber, but also for example for JUnit. Until https://github.com/cucumber/cucumber-jvm/issues/1846#ref-pullrequest-541103690 Cucumber would override some of Springs internals to prevent this. But ultimately that was the wrong choice.
You may want to reconsider if Spring is the right dependency injection framework for you. If you aren't testing a Spring application cucumber-pico
or cucumber-guice
may be more suitable. Unlike Spring, with these frameworks each scenario has it's own.
if we change @ScenarioScope proxy mode as per your recommendation in no 1 (above), then Cucumber will leak thread state and parallel run using Surefire will malfunction
Could you provide a concrete example of this? Since these objects are only accessible through the already Scenario scoped step definitions it is not immediately apparent to me how any state would be leaked.
Hi, thanks a lot for your guidance and support. Sorry for delay in my response, my framework structure is almost same to this one. Differences are: cucumber version we are using is 6.10.2, and we are creating webdriver bean similarly, with @ScenarioScope. https://github.com/soraiareis/demo-spring-selenium
On Mon, 28 Nov 2022, 00:41 M.P. Korstanje, @.***> wrote:
It's a feature of Spring to reuse the Application context between tests. Not just for Cucumber, but also for example for JUnit. Until #1846 (reference) https://github.com/cucumber/cucumber-jvm/issues/1846#ref-pullrequest-541103690 Cucumber would override some of Springs internals to prevent this. But ultimately that was the wrong choice.
You may want to reconsider if Spring is the right dependency injection framework for you. If you aren't testing a Spring application cucumber-pico or cucumber-guice may be more suitable. Unlike Spring, with these frameworks each scenario has it's own.
if we change @ScenarioScope proxy mode as per your recommendation in no 1 (above), then Cucumber will leak thread state and parallel run using Surefire will malfunction
Could you provide a concrete example of this? Since these objects are only accessible through the already Scenario scoped step definitions it is not immediately apparent to me how any state would be leaked.
— Reply to this email directly, view it on GitHub https://github.com/cucumber/cucumber-jvm/issues/2647#issuecomment-1328388601, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFSLLM7LR2XQ7T4DYUMLJEDWKP5TTANCNFSM6AAAAAASLNDCUM . You are receiving this because you authored the thread.Message ID: @.***>
Hi, I am working on migrating our framework from cucumber 4.8.1 to 6.10.2, and we use Spring, Java, Junit, Webdriver. As part of this work, I have added @ScenarioScope annotation across our java classes (pageObjects and pageActions) which were earlier annotated with @Scope(SCOPE_CUCUMBER_GLUE). I am able to run the automation scripts, but the run fails as soon as code founds any await() method and the execution control enters into it. In the process await() creates a singleton bean and it cannot access any pageObjects that are scenario scoped. The error message says, ScopeNotActiveException. Given below sample code:
Did anyone used Awaitability.await() in cucumber-spring v6.10.2 and above? Could you kindly guide me? We have been using Awaitability.await() with cucumber-spring 4.8.1 and that works.