
jp.co.moneyforward.autotest.framework.action.Scene Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of insdog-all Show documentation
Show all versions of insdog-all Show documentation
An action-based testing framework and library
The newest version!
package jp.co.moneyforward.autotest.framework.action;
import com.github.dakusui.actionunit.core.Action;
import com.github.valid8j.pcond.fluent.Statement;
import jp.co.moneyforward.autotest.actions.web.Value;
import jp.co.moneyforward.autotest.framework.internal.InternalUtils;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import static com.github.dakusui.actionunit.core.ActionSupport.sequential;
import static com.github.valid8j.classic.Requires.requireNonNull;
import static java.util.Arrays.asList;
import static jp.co.moneyforward.autotest.framework.action.AutotestSupport.*;
import static jp.co.moneyforward.autotest.framework.internal.InternalUtils.simpleClassNameOf;
///
/// An interface that represents a reusable unit of an action in **insdog**'s programming model.
/// An instance of this object contains 0 or more {@link Act} instances.
///
/// Note that `Scene` uses the same map for both input and output.
///
public interface Scene extends WithOid {
///
/// A default value of default variable name for `Scene.Builder` and `@Export`.
/// This value is currently defined `page` for historical reason.
/// However, **InspektorDog** is designed not only for GUI end-to-end test, but a general purpose testing framework.
/// The value should be changed to a more context neutral keyword, such as `var` or `session`.
///
String DEFAULT_DEFAULT_VARIABLE_NAME = "page";
///
/// A place-holder variable name to which an output from a sink will be assigned
String DUMMY_OUTPUT_VARIABLE_NAME = "__DUMMY__" + UUID.randomUUID();
///
/// // @formatter:off
/// A synonym of `new Scene.Builder()`.
/// Use this in combination with `Scene#end()` so that your code looks like:
///
/// ```java
/// class YourClass {
/// void method() {
/// Scene.begin()
/// .act(yourAct())
/// .assertion(s -> s.then().looksOkay())
/// .end()
/// }
/// }
/// ```
///
/// // @formatter:on
///
/// @return A new `Scene.Builder` object.
///
static Scene.Builder begin() {
return begin(DEFAULT_DEFAULT_VARIABLE_NAME);
}
static Scene.Builder begin(String defaultVariableName) {
return new Builder(defaultVariableName);
}
///
/// Creates a scene by chaining acts.
/// When you need to handle multiple variables, use {@link Scene.Builder} directly.
///
/// @param variableName A variable chained acts read input value from and write output value to.
/// @param acts Acts from which a scene is created.
/// @return Created scene.
///
static Scene create(String variableName, Act, ?>... acts) {
Scene.Builder b = new Builder(variableName);
for (Act, ?> act : acts) {
b.add(act);
}
return b.build();
}
///
/// Creates a scene by chaining acts.
/// This method internally calls {@link Scene#create(String, Act[])} using {@link Scene#DEFAULT_DEFAULT_VARIABLE_NAME} as a `variableName`.
///
/// When you need to handle other variables, use {@link Scene#create(String, Act[])} or {@link Scene.Builder}, instead.
///
/// @param acts Acts from which a scene is created.
/// @return Created scene.
///
static Scene create(Act, ?>... acts) {
return create(Scene.DEFAULT_DEFAULT_VARIABLE_NAME, acts);
}
///
/// Creates a sequential action from the child calls of this object
///
/// @param actionComposer A visitor that builds a sequential action from child calls of this object.
/// @return A sequential action created from child calls
/// @see Scene#children()
///
default Action toSequentialAction(ActionComposer actionComposer) {
return sequential(toActions(actionComposer));
}
///
/// Returns members of this scene object, which are executed as "children".
///
/// @return members of this scene object.
///
List children();
///
/// Returns an object identifier of this object.
///
/// @return An object identifier of this object.
///
String oid();
///
/// Returns a name of this object.
/// The returned string will appear in an action tree printed during the action execution.
///
/// @return A name of this object.
///
default String name() {
return simpleClassNameOf(this.getClass());
}
///
/// Returns a list of variables that are assigned by child scenes of this object.
///
/// @return A list of variables that are assigned by child scenes of this object.
///
default List outputVariableNames() {
return this.children()
.stream()
.flatMap(Scene::outputVariableNamesOf)
.distinct()
.toList();
}
///
/// Returns a list of variables that are accessed by child scenes of this object.
///
/// @return A list of variables that are accessed by child scenes of this object.
///
default List inputVariableNames() {
return this.children()
.stream()
.flatMap(Scene::inputVariableNamesOf)
.distinct()
.toList();
}
private static Stream outputVariableNamesOf(Call call) {
return switch (call) {
case SceneCall sceneCall -> sceneCall.targetScene().outputVariableNames().stream();
case ActCall, ?> actCall -> Stream.of(actCall.outputVariableName());
case EnsuredCall ensuredCall -> outputVariableNamesOf(ensuredCall);
case CallDecorator> callDecorator -> outputVariableNamesOf(callDecorator.targetCall());
};
}
private static Stream outputVariableNamesOf(EnsuredCall ensuredCall) {
return Stream.concat(outputVariableNamesOf(ensuredCall.targetCall()),
ensuredCall.ensurers()
.stream()
.flatMap(Scene::outputVariableNamesOf)).distinct();
}
private static Stream inputVariableNamesOf(Call call) {
return switch (call) {
case SceneCall sceneCall -> sceneCall.targetScene().inputVariableNames().stream();
case ActCall, ?> actCall -> Stream.of(actCall.inputVariableName());
case EnsuredCall ensuredCall -> inputVariableNamesOf(ensuredCall);
case CallDecorator> callDecorator -> inputVariableNamesOf(callDecorator.targetCall());
};
}
private static Stream inputVariableNamesOf(EnsuredCall ensuredCall) {
return Stream.concat(inputVariableNamesOf(ensuredCall.targetCall()),
ensuredCall.ensurers()
.stream()
.flatMap(Scene::inputVariableNamesOf)).distinct();
}
private List toActions(ActionComposer actionComposer) {
return children().stream()
.map((Call each) -> each.toAction(actionComposer))
.flatMap(InternalUtils::flattenIfSequential)
.toList();
}
///
/// A builder for `Scene` class.
///
/// @see Scene
///
class Builder implements WithOid {
final String defaultVariableName;
private final List children = new LinkedList<>();
private String name = null;
///
/// Creates an instance of this class.
///
/// Note that `defaultVariableName` is only used by this `Builder`, not directly by the `Scene` built by this object.
///
/// @param defaultVariableName A name of field used when use `add` methods without explicit input/output target field names.
///
public Builder(String defaultVariableName) {
this.defaultVariableName = defaultVariableName;
}
///
/// Creates an instance of this class.
/// If you add an act to this object without explicitly specifying variable name with which the act interacts,
/// a `NullPointerException` will be thrown.
///
public Builder() {
this(DEFAULT_DEFAULT_VARIABLE_NAME);
}
///
/// Sets a name of a scene built by this builder object.
///
/// @param name A name of a scene.
/// @return This object.
///
public Builder name(String name) {
this.name = requireNonNull(name);
return this;
}
///
/// // @formatter:off
/// A "syntax-sugar" method to group a sequence of method calls to this `Builder` object.
///
/// That is, you can do:
///
/// ```java
/// new SceneBuilder.with(b -> b.add(...)
/// .add(...)
/// .add(...))
/// .build();
/// ```
///
/// Note that the operator `op` is supposed to return `this` object.
///
/// // @formatter:on
/// @param op A unary operator that groups operator on this object.
/// @return This object returned by `op`.
///
public final Builder with(UnaryOperator op) {
return op.apply(this);
}
///
/// Adds an `Act` to this builder.
/// `defaultFieldName` is used for both input and output.
/// Note that in case `T` and `R` are different, the field will have a different type after `leafAct` execution from the value before it is executed.
///
/// @param act An act object to be added to this builder.
/// @param Type of input parameter field.
/// @param Type of output parameter field.
/// @return This object.
///
public final Builder add(Act act) {
return this.act(act);
}
///
/// Synonym of {@link Builder#add(Act)}.
///
/// Prefer this over `Builder#add(Act)` as usage of this method results in more readable code in general.
/// This method is also useful for languages that run on JVM, but doesn't have method overloading.
///
/// @param act An act to be added.
/// @return This object
///
public final Builder act(Act act) {
return this.add(outputVariableNameFor(act), act, defaultVariableName());
}
///
/// Adds an act to this builder.
/// The output of an act goes to a variable specified by `outputVariableName` in the scene's variable store.
///
/// @param outputVariableName A variable name `act`'s output goes to.
/// @param act An `Act` to be added.
/// @param Input type of `act`.
/// @param Output type of `act`.
/// @return This object
///
public final Builder add(String outputVariableName, Act act) {
return this.add(outputVariableName, act, defaultVariableName());
}
///
/// Adds an `act` to this object so that it takes input from `inputVariableName` and writes output to `outputVariableName`.
///
/// @param outputVariableName A string to specify an output variable.
/// @param act An `Act` to be added to this object.
/// @param inputVariableName A string to specify an input variable.
/// @param An input value type
/// @param An output value type
/// @return This object
///
public final Builder add(String outputVariableName, Act act, String inputVariableName) {
return this.addCall(actCall(outputVariableName, act, inputVariableName));
}
///
/// Adds a function `assertion` to this builder object.
///
/// @param assertion An assertion to be added to this object.
/// @param Type of value to be checked by `assertion`.
/// @return This object.
///
@SuppressWarnings("unchecked")
public final Builder assertion(Function> assertion) {
return this.assertions(assertion);
}
///
/// Adds `assertions` to this builder object.
///
/// @param assertions Functions that generates statements to be added.
/// @param Type of the value to be verified.
/// @return This object.
///
@SuppressWarnings("unchecked")
public final Builder assertions(Function>... assertions) {
return this.assertions(defaultVariableName(), assertions);
}
///
/// Returns an `AssertionAct` object that verifies a variable in a currently ongoing scene call's variable store.
/// The variable in the store is specified by `inputFieldName`.
/// This method is implemented as:
///
/// `this.addCall(assertionCall(variableName, new Value<>(), singletonList(assertionAct), variableName))`,
/// where `Value` is a trivial act which just copies its input variable to an output variable.
///
/// @param Type of the variable specified by `inputVariableName`.
/// @param variableName A name of an input variable to be verified.
/// @param assertions An assertion function
/// @return This object
///
@SuppressWarnings("unchecked")
public final Builder assertions(String variableName, Function>... assertions) {
return this.addCall(assertionCall(variableName, new Value<>(), asList(assertions), variableName));
}
///
/// Adds a given `scene` to this builder object.
/// A call to the `scene` will be created (a `SceneCall` object), and it will be a child of the scene that this builder builds.
///
/// The child call will use the working variable store of the parent scene (i.e., a scene built by this builder) as its input variable store.
/// With this mechanism, the child can reference the values of the
///
/// @param scene A scene to be added.
/// @return This object,
///
public final Builder add(Scene scene) {
return this.addCall(toSceneCall(scene));
}
///
/// Synonym of {@link Builder#add(Scene)}.
///
/// Prefer this over `Builder#add(Scene)` as usage of this method results in more readable code in general.
/// This method is also useful for languages that run on JVM, but doesn't have method overloading.
///
/// @param scene A scene to be added.
/// @return This object
///
public final Builder scene(Scene scene) {
return this.add(scene);
}
///
/// Adds a call that retries a given `call`.
/// The call retries given `times` on a failure designated by a class `onException`.
/// An interval between tries will be `interval` seconds.
///
/// Note that `times` means number of "RE"-tries.
/// If you give 1, it will be retried once after `interval` seconds, if the first try fails.
///
/// @param call A call to be retried
/// @param times Number of retries at maximum.
/// @param onException An exception class on which retries should be attempted.
/// @param interval Interval between tries.
/// @return A call that retries a given `call`.
///
public final Builder retry(Call call, int times, Class extends Throwable> onException, int interval) {
return this.addCall(AutotestSupport.retryCall(call, times, onException, interval));
}
///
/// This method is implemented as a shorthand for `this.retry(call, times, onException, 5)`.
///
/// @param call A call to be retried
/// @param times How many times `call` should be retried until it succeeds.
/// @return This object
///
public final Builder retry(Call call, int times, Class extends Throwable> onException) {
return retry(call, times, onException, 5);
}
///
/// This method is implemented as a shorthand for `this.retry(call, times, AssertionFailedError.class)`.
///
/// @param call A call to be retried
/// @param times How many times `call` should be retried until it succeeds.
/// @return This object
///
public final Builder retry(Call call, int times) {
return retry(call, times, Throwable.class);
}
///
/// Adds a scene that retries a given `scene` twice.
/// Note that, `scene` is attempted three times in total.
///
/// @param scene A scene to be added and retried on a failure.
/// @return This object
///
public final Builder retry(Scene scene) {
return retry(toSceneCall(scene));
}
///
/// This method is implemented as a shorthand for `this.retry(call, 2)`.
///
/// @param call A call object to be added.
/// @return This object.
///
public final Builder retry(Call call) {
return retry(call, 2);
}
///
/// Adds a call to this object as a child.
/// You need to ensure that requirements of `call` are satisfied in the context it will be run by yourself.
///
/// For instance, if the call is a `SceneCall`, the variable store from which it reads needs to be prepared beforehand by one of preceding calls.
///
/// @param call A `Call` object to be added.
/// @return This object.
///
public Builder addCall(Call call) {
this.children.add(call);
return this;
}
///
/// Builds a `Scene` object.
///
/// @return A `Scene` object.
///
public Scene build() {
return new Scene() {
private final List children = Builder.this.children;
private final String oid = Builder.this.oid();
@Override
public List children() {
return children;
}
@Override
public String oid() {
return oid;
}
@Override
public String toString() {
return name() + "@" + oid();
}
@Override
public String name() {
return name != null ? name
: Scene.super.name();
}
};
}
///
/// A synonym of {@link Builder#build()}.
/// Use this with {@link Scene#begin()}.
///
/// @return A new scene object
/// @see Builder#build()
/// @see Scene#begin()
///
public Scene end() {
return build();
}
///
/// Returns an object identifier of this object.
///
/// @return An object identifier of this object.
///
@Override
public String oid() {
return "id-" + System.identityHashCode(this);
}
private String outputVariableNameFor(Act, ?> act) {
if (act instanceof Act.Sink>) {
return DUMMY_OUTPUT_VARIABLE_NAME;
}
return defaultVariableName();
}
private String defaultVariableName() {
return requireNonNull(this.defaultVariableName);
}
private SceneCall toSceneCall(Scene scene) {
return sceneToSceneCall(scene, this.workingVariableStoreName());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy