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

[vividus] Add generic while-like step with iteration delays #1268

Merged
merged 1 commit into from
Dec 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 51 additions & 20 deletions docs/modules/commons/pages/vividus-steps.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,78 @@

Here one could find description of the steps that are delivered with Vividus itself without any plugins required.

=== While-like steps executor
=== Execute while-like loop

==== *_Info_*

Executes steps until condition or iteration limit reached.
Executes the steps while variable matches the comparison rule or until the maximum number of iterations is reached.

[IMPORTANT]
If iteration limit is reached no failure or exception will occur.

==== *_Wording_*

Main:
If the maximum number of iterations is reached no failure or exception will occur.

Syntax:
[source,gherkin]
----
When I execute steps at most $max times while variable `$variableName` is $comparisonRule `$expectedValue`:$stepsToExecute
----

Alias:

[source,gherkin]
----
When I execute steps at most $max times while variable '$variableName' is $comparisonRule '$expectedValue':$stepsToExecute
----

==== *_Parameters_*
* `$max` - The maximum number of iterations
* `$variableName` - The name of the variable to check
* `$comparisonRule` - xref:parameters:comparison-rule.adoc[The comparison rule]
* `$expectedValue` - The expected value of the variable
* `$stepsToExecute` - The ExamplesTable with a single column containing the steps to execute

==== *_Usage_*

.Click button 5 times
[source,gherkin]
----
When I execute steps at most 5 times while variable `var` is less than `3`:
|step |
|When I click on element located `id(counter)` |
|When I find <= `1` elements by `xpath(//div[@id='clickResult' and (text()='3' or text()='4')])` and for each element do|
|{headerSeparator=!,valueSeparator=!} |
|!step! |
|!When I set the text found in search context to the 'scenario' variable 'var'! |
Then `${var}` is = `3`
----

=== Execute while-like loop with delays

Executes the steps while variable matches the comparison rule or until the maximum number of iterations is reached. The delay is used to define the amount of time to wait between iterations.

[IMPORTANT]
If the maximum number of iterations is reached no failure or exception will occur.

Syntax:
[source,gherkin]
----
When I execute steps with delay `$delay` at most $max times while variable variable `$variableName` is $comparisonRule `$expectedValue`:$stepsToExecute
----

Alias:
[source,gherkin]
----
When I execute steps with delay '$delay' at most $max times while variable '$variableName' is $comparisonRule '$expectedValue':$stepsToExecute
----

. `$max` - defines how many iteration could be executed
. `$variableName` - a name of the variable to check
. `$comparisonRule` - xref:parameters:comparison-rule.adoc[Comparison rule]
. `$expectedValue` - expected value of the variable
. `$stepsToExecute` - examples table with a single column containing the steps
* `$delay` - The delay between iterations
* `$max` - The maximum number of iterations
* `$variableName` - The name of the variable to check
* `$comparisonRule` - xref:parameters:comparison-rule.adoc[The comparison rule]
* `$expectedValue` - The expected value of the variable
* `$stepsToExecute` - The ExamplesTable with a single column containing the steps to execute

==== *_Usage_*

.ExecuteWhile.story
.Click button 5 times with 1 second delay
[source,gherkin]
----
Scenario: Click button 5 times
Given I am on a page with the URL 'https://vividus-test-site.herokuapp.com/mouseEvents.html'
When I execute steps at most 5 times while variable `var` is < `3`:
When I execute steps with delay `PT1S` at most 5 times while variable `var` is less than `3`:
|step |
|When I click on element located `id(counter)` |
|When I find <= `1` elements by `xpath(//div[@id='clickResult' and (text()='3' or text()='4')])` and for each element do|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,12 @@ When I execute steps at most 5 times while variable `var` is < `10`:
|step |
|When I initialize the scenario variable `var` with value `#{eval(${var} + 1)}`|
Then `${var}` is = `10`

Scenario: Verify step: When I execute steps with delay `$delay` at most $max times while variable `$variableName` is $comparisonRule `$expectedValue`:$stepsToExeute
Meta:
@requirementId 1235
When I initialize the scenario variable `var` with value `5`
When I execute steps with delay `PT0.5S` at most 2 times while variable `var` is < `7`:
|step |
|When I initialize the scenario variable `var` with value `#{eval(${var} + 1)}`|
Then `${var}` is = `7`
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ When I execute query `
SET BandMembers =set_add(BandMembers, <<'Christoffer Lundquist'>>)
WHERE Artist='Roxette' and SongTitle='The Look'
` against DynamoDB
When I execute query `SELECT * FROM Music` against DynamoDB and save result as JSON to scenario variable `song`
When I execute query `SELECT * FROM Music` against DynamoDB and save result as JSON to story variable `song`
Then JSON element from `${song}` by JSON path `$` is equal to `
[
{
Expand All @@ -79,3 +79,20 @@ Then JSON element from `${song}` by JSON path `$` is equal to `
"SongTitle": "Real Sugar"
}
]` ignoring array order

Scenario: Wait for the data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ticker id missing?

Meta:
@requirementId 1235
When I save JSON element from `${song}` by JSON path `$[?(@.SongTitle == "The Look")].BandMembers` to scenario variable `bandMembers`
When I save JSON element from `${song}` by JSON path `$[0].length()` to scenario variable `numberOfBandMembers`
When I execute steps with delay `PT1S` at most 1 times while variable `numberOfBandMembers` is greater than `2`:
|step |
|When I execute query ` |
UPDATE Music |
SET BandMembers =set_delete(BandMembers, <<'Christoffer Lundquist'>>) |
WHERE Artist='Roxette' and SongTitle='The Look' |
| ` against DynamoDB |
|When I execute query `SELECT * FROM Music` against DynamoDB and save result as JSON to scenario variable `song` |
|When I save JSON element from `${song}` by JSON path `$[?(@.SongTitle == "The Look")].BandMembers` to scenario variable `bandMembers`|
|When I save JSON element from `${song}` by JSON path `$[0].length()` to scenario variable `numberOfBandMembers` |
Then `${numberOfBandMembers}` is equal to `2`
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus.util.wait;

import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;

import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.function.FailableRunnable;
import org.apache.commons.lang3.function.FailableSupplier;
import org.vividus.util.Sleeper;

public final class MaxTimesBasedWaiter extends Waiter
{
private final int maxIterations;

public MaxTimesBasedWaiter(Duration delay, int maxIterations)
{
super(delay.toMillis());
this.maxIterations = maxIterations;
}

@Override
public <T, E extends Exception> T wait(FailableSupplier<T, E> valueProvider, Predicate<T> stopCondition) throws E
{
throw new NotImplementedException();
}

@Override
public <E extends Exception> void wait(FailableRunnable<E> runnable, BooleanSupplier stopCondition) throws E
{
int counter = 0;
while (counter < maxIterations && !stopCondition.getAsBoolean())
{
runnable.run();
Sleeper.sleep(getPollingTimeoutMillis(), TimeUnit.MILLISECONDS);
counter++;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus.util.wait;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.time.Duration;
import java.util.function.BooleanSupplier;

import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.function.FailableRunnable;
import org.apache.commons.lang3.function.FailableSupplier;
import org.junit.jupiter.api.Test;

class MaxTimesBasedWaiterTests
{
@Test
void shouldNotAllowToWaitForValue()
{
FailableSupplier<Boolean, IOException> valueProvider = mock(FailableSupplier.class);
MaxTimesBasedWaiter waiter = new MaxTimesBasedWaiter(Duration.ofMillis(500), 2);
assertThrows(NotImplementedException.class, () -> waiter.wait(valueProvider, Boolean::booleanValue));
}

@Test
void shouldStopAfterReachingMaxIterations() throws IOException
{
FailableRunnable<IOException> failableRunnable = mock(FailableRunnable.class);
new MaxTimesBasedWaiter(Duration.ofMillis(200), 3).wait(failableRunnable, Boolean.FALSE::booleanValue);
verify(failableRunnable, times(3)).run();
}

@Test
void shouldStopAfterReachingStopCondition() throws IOException
{
FailableRunnable<IOException> failableRunnable = mock(FailableRunnable.class);
BooleanSupplier stopCondition = mock(BooleanSupplier.class);
when(stopCondition.getAsBoolean()).thenReturn(false).thenReturn(false).thenReturn(true);
new MaxTimesBasedWaiter(Duration.ofMillis(200), 3).wait(failableRunnable, stopCondition);
verify(failableRunnable, times(2)).run();
}

@Test
void shouldNotInvokeRunnableIfStopConditionIsReachedImmediately() throws IOException
{
FailableRunnable<IOException> failableRunnable = mock(FailableRunnable.class);
new MaxTimesBasedWaiter(Duration.ofMillis(100), 1).wait(failableRunnable, Boolean.TRUE::booleanValue);
verifyNoInteractions(failableRunnable);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.apache.commons.lang3.Validate.inclusiveBetween;
import static org.apache.commons.lang3.Validate.isTrue;

import java.time.Duration;
import java.util.Optional;
import java.util.function.IntConsumer;
import java.util.stream.IntStream;
Expand All @@ -29,6 +30,7 @@
import org.jbehave.core.annotations.When;
import org.vividus.bdd.context.IBddVariableContext;
import org.vividus.bdd.variable.VariableScope;
import org.vividus.util.wait.MaxTimesBasedWaiter;

public class ExecutableSteps
{
Expand Down Expand Up @@ -94,22 +96,28 @@ public void ifVariableNotSetPerformSteps(String name, SubSteps stepsToExecute)
}

/**
* Executes the steps while variable doesn't match to the coparison rule or until the maximum number of iterations
* is reached.<br>
* Executes the steps while variable matches the comparison rule or until the maximum number of iterations is
* reached.<br>
* <b>Example:</b><br>
* <code>
* When I execute steps at most 5 times while `var` is &lt; `3`:<br>
* |step |<br>
* |When I click on element located `id(counter)` |<br>
* |When I set the text found in search context to the 'scenario' variable 'var'|<br>
* </code>
* @param max Maximum number of iterations
* @param name Variable name to check
* @param comparisonRule The rule to compare values
* (&lt;i&gt;Possible values:&lt;b&gt; LESS_THAN, LESS_THAN_OR_EQUAL_TO, GREATER_THAN, GREATER_THAN_OR_EQUAL_TO,
* EQUAL_TO&lt;/b&gt;&lt;/i&gt;)
* @param expectedValue The expected value of the variable
* @param stepsToExecute The examples table with the steps to execute
*
* @param max The maximum number of iterations
* @param name The name of the variable to check
* @param comparisonRule The rule to match the variable value. Allowed options:
* <ul>
* <li>less than (&lt;)</li>
* <li>less than or equal to (&lt;=)</li>
* <li>greater than(&gt;)</li>
* <li>greater than or equal to(&gt;=)</li>
* <li>equal to(=))</li>
* </ul>
* @param expectedValue The expected value of the variable
* @param stepsToExecute The ExamplesTable with a single column containing the steps to execute
*/
@When("I execute steps at most $max times while variable "
+ "`$variableName` is $comparisonRule `$expectedValue`:$stepsToExecute")
Expand All @@ -126,6 +134,44 @@ public void executeStepsWhile(int max, String name, ComparisonRule comparisonRul
}
}

/**
* Executes the steps while variable matches the comparison rule or until the maximum number of iterations is
* reached. The delay is used to define the amount of time to wait between iterations.<br>
* <b>Example:</b><br>
* <code>
* When I execute steps with delay `PT10S` at most 5 times while `var` is &lt; `3`:<br>
* |step |<br>
* |When I click on element located `id(counter)` |<br>
* |When I set the text found in search context to the 'scenario' variable 'var'|<br>
* </code>
*
* @param delay The delay between iterations
* @param max The maximum number of iterations
* @param name The name of the variable to check
* @param comparisonRule The rule to match the variable value. Allowed options:
* <ul>
* <li>less than (&lt;)</li>
* <li>less than or equal to (&lt;=)</li>
* <li>greater than(&gt;)</li>
* <li>greater than or equal to(&gt;=)</li>
* <li>equal to(=))</li>
* </ul>
* @param expectedValue The expected value of the variable
* @param stepsToExecute The ExamplesTable with a single column containing the steps to execute
*/
@When("I execute steps with delay `$delay` at most $max times while variable "
+ "`$variableName` is $comparisonRule `$expectedValue`:$stepsToExecute")
@Alias("I execute steps with delay '$delay' at most $max times while variable "
+ "'$variableName' is $comparisonRule '$expectedValue':$stepsToExecute")
public void executeStepsWithPollingInterval(Duration delay, int max, String name,
ComparisonRule comparisonRule, Object expectedValue, SubSteps stepsToExecute)
{
new MaxTimesBasedWaiter(delay, max).wait(
() -> stepsToExecute.execute(Optional.empty()),
() -> !doesVariableValueMatch(name, comparisonRule, expectedValue)
);
}

private boolean doesVariableValueMatch(String name, ComparisonRule comparisonRule, Object expectedValue)
{
Object variable = bddVariableContext.getVariable(name);
Expand Down
Loading