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

[plugin-mobile-app] Make JavaScript executing steps available for mob… #3128

Merged
merged 2 commits into from
Sep 12, 2022
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
19 changes: 0 additions & 19 deletions docs/modules/plugins/pages/plugin-web-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1219,25 +1219,6 @@ Given I am on the main application page
----

=== Code steps
==== Execute JavaScript and save result.

https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html[Executes JavaScript] code and saves result into variable.

[source,gherkin]
----
When I execute javascript `$jsCode` and save result to $scopes variable `$variableName`
----
* `$jsCode` - Any JavaScript code. In order to save a result https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return[return] statement should be used.
* `$scopes` - xref:commons:variables.adoc#_scopes[The comma-separated set of the variables scopes].
* `$variableName` - The variable name to save the cookie value.

.Validate timings
[source,gherkin]
----
Given I am on a page with the URL 'http://vividus-test-site.herokuapp.com/mouseEvents.html'
When I execute javascript `return JSON.stringify(window.performance.timing)` and save result to scenario variable `timings`
Then number of JSON elements from `${timings}` by JSON path `$.connectStart` is = 1
----

==== Execute async JavaScript and save result.

Expand Down
55 changes: 55 additions & 0 deletions docs/modules/plugins/partials/generic-ui-steps.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -405,3 +405,58 @@ When I find >= `0` elements `xpath(//*[@class='menu enabled'])` and while they e
|step |
|When I click on element located `id(disable)`|
----

=== Execute JavaScript with arguments

Executes JavaScript via https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html[JavascriptExecutor]. Step executes the script either without any arguments or with `String` or `Object` argument types.

[source,gherkin]
----
When I execute javascript `$script` with arguments:$arguments
----

* `$script` - The JavaScript code to execute.
* `$arguments` - The examples table with set of arguments and their type:
** [subs=+quotes]`*value*` - The value of the argument.
** [subs=+quotes]`*type*` - The type of the argument. Either `string` or `object`.

.Remove noisy assistant
[source,gherkin]
----
When I execute javascript `document.querySelector('#assistant').remove()` with arguments:
----

.Inject image
[source,gherkin]
----
When I execute javascript `sauce:inject-image` with arguments:
|value |type |
|iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=|string|
ikalinin1 marked this conversation as resolved.
Show resolved Hide resolved

.Throttle CPU
[source,gherkin]
----
When I execute javascript `sauce:throttleCPU` with arguments:
|value |type |
|{"rate" : 4}|object|
----

==== Execute JavaScript and save result.

https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html[Executes JavaScript] code and saves result into variable.

[source,gherkin]
----
When I execute javascript `$jsCode` and save result to $scopes variable `$variableName`
----
* `$jsCode` - Any JavaScript code. In order to save a result https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/return[return] statement should be used.
* `$scopes` - xref:commons:variables.adoc#_scopes[The comma-separated set of the variables scopes].
* `$variableName` - The variable name to script execution result.

.Validate timings
[source,gherkin]
----
Given I am on a page with the URL 'http://vividus-test-site.herokuapp.com/mouseEvents.html'
When I execute javascript `return JSON.stringify(window.performance.timing)` and save result to scenario variable `timings`
Then number of JSON elements from `${timings}` by JSON path `$.connectStart` is = 1
----
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2019-2022 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.steps.ui;

import java.util.Set;
import java.util.function.Supplier;

import org.vividus.context.VariableContext;
import org.vividus.softassert.ISoftAssert;
import org.vividus.variable.VariableScope;

@SuppressWarnings("PMD.AbstractClassWithoutAbstractMethod")
public abstract class AbstractExecuteScriptSteps
{
private final ISoftAssert softAssert;
private final VariableContext variableContext;

protected AbstractExecuteScriptSteps(ISoftAssert softAssert, VariableContext variableContext)
{
this.softAssert = softAssert;
this.variableContext = variableContext;
}

protected void assertAndSaveResult(Supplier<Object> resultProvider, Set<VariableScope> scopes, String variableName)
{
Object result = resultProvider.get();
if (softAssert.assertNotNull("Returned result is not null", result))
{
variableContext.putVariable(scopes, variableName, result);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2019-2022 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.steps.ui;

import java.util.List;
import java.util.Set;

import org.jbehave.core.annotations.When;
import org.vividus.context.VariableContext;
import org.vividus.softassert.ISoftAssert;
import org.vividus.steps.ui.web.model.JsArgument;
import org.vividus.steps.ui.web.model.JsArgumentType;
import org.vividus.ui.action.JavascriptActions;
import org.vividus.variable.VariableScope;

public class ExecuteScriptSteps extends AbstractExecuteScriptSteps
{
private final JavascriptActions javascriptActions;

public ExecuteScriptSteps(JavascriptActions javascriptActions, VariableContext variableContext,
ISoftAssert softAssert)
{
super(softAssert, variableContext);
this.javascriptActions = javascriptActions;
}

/**
* Executes passed JavaScript code on the opened page
* and saves returned value into the <b>variable</b>
*
* @param scopes The set (comma separated list of scopes e.g.: STORY, NEXT_BATCHES) of variable's scope<br>
* <i>Available scopes:</i>
* <ul>
* <li><b>STEP</b> - the variable will be available only within the step,
* <li><b>SCENARIO</b> - the variable will be available only within the scenario,
* <li><b>STORY</b> - the variable will be available within the whole story,
* <li><b>NEXT_BATCHES</b> - the variable will be available starting from next batch
* </ul>
* @param variableName A name under which the value should be saved
* @param jsCode Code in javascript that returns some value as result
* (e.g. var a=1; return a;)
*/
@When("I execute javascript `$jsCode` and save result to $scopes variable `$variableName`")
public void saveValueFromJS(String jsCode, Set<VariableScope> scopes, String variableName)
{
assertAndSaveResult(() -> javascriptActions.executeScript(jsCode), scopes, variableName);
}

/**
* Executes JavaScript code with specified arguments
*
* @param jsCode JavaScript code
* @param args A table containing JS command argument value and type (one of <i>STRING</i> or <i>OBJECT</i>).
* The parameter is optional and could be omitted.<br>
*/
@When("I execute javascript `$jsCode` with arguments:$args")
public void executeJavascriptWithArguments(String jsCode, List<JsArgument> args)
{
javascriptActions.executeScript(jsCode, args.stream().map(this::convertRowToArgument).toArray());
}

private Object convertRowToArgument(JsArgument arg)
{
JsArgumentType type = arg.getType();
String value = arg.getValue();
if (type == null || value == null)
{
throw new IllegalArgumentException("Please, specify command argument values and types");
}
return type.convert(value);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2022 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@
<property name="debugScreenshotsLocation" value="${ui.screenshot.debug-directory}" />
</bean>

<bean id="executeScriptSteps" class="org.vividus.steps.ui.ExecuteScriptSteps" />

<util:list id="stepBeanNames-UI" value-type="java.lang.String">
<idref bean="executeScriptSteps" />
<idref bean="genericElementSteps" />
<idref bean="genericWaitSteps" />
<idref bean="genericSetVariableSteps" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2019-2022 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.steps.ui;

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

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.vividus.context.VariableContext;
import org.vividus.softassert.ISoftAssert;
import org.vividus.steps.ui.web.model.JsArgument;
import org.vividus.steps.ui.web.model.JsArgumentType;
import org.vividus.ui.action.JavascriptActions;
import org.vividus.variable.VariableScope;

@ExtendWith(MockitoExtension.class)
class ExecuteScriptStepsTests
{
private static final String JS_RESULT_ASSERTION_MESSAGE = "Returned result is not null";
private static final String JS_CODE = "return 'value'";
private static final String JS_ARGUMENT_ERROR_MESSAGE = "Please, specify command argument values and types";
private static final String VALUE = "value";
private static final String BODY = "body";
private static final Set<VariableScope> VARIABLE_SCOPE = Set.of(VariableScope.SCENARIO);
private static final String VARIABLE_NAME = "variableName";

@Mock
private JavascriptActions javascriptActions;

@Mock
private ISoftAssert softAssert;

@Mock
private VariableContext variableContext;

@InjectMocks
private ExecuteScriptSteps executeScriptSteps;

static Stream<Arguments> executeJavascriptWithArguments()
{
// CHECKSTYLE:OFF
return Stream.of(
Arguments.of("document.querySelector(arguments[0])", createJsArgument(JsArgumentType.STRING, BODY), BODY ),
Arguments.of("remote:throttle", createJsArgument(JsArgumentType.OBJECT, "{\"condition\": \"Wifi\"}"), Map.of("condition", "Wifi"))
);
// CHECKSTYLE:ON
}

@ParameterizedTest
@MethodSource("executeJavascriptWithArguments")
void testExecuteJavascriptWithStringArguments(String jsCode, JsArgument argument, Object arg)
{
executeScriptSteps.executeJavascriptWithArguments(jsCode, Collections.singletonList(argument));
verify(javascriptActions).executeScript(jsCode, arg);
}

@Test
void testExecuteJavascriptWithEmptyArguments()
{
String jsCode = "document.readyState";
executeScriptSteps.executeJavascriptWithArguments(jsCode, List.of());
verify(javascriptActions).executeScript(jsCode);
}

@ParameterizedTest
@CsvSource({
", body",
"OBJECT, "
})
void testExecuteJavascriptWithArgumentsNoType(JsArgumentType type, String value)
{
List<JsArgument> arguments = List.of(createJsArgument(type, value));
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class,
() -> executeScriptSteps.executeJavascriptWithArguments("document.querySelector(arguments[0])",
arguments));
assertEquals(JS_ARGUMENT_ERROR_MESSAGE, exception.getMessage());
}

@Test
void testGettingValueFromJS()
{
when(javascriptActions.executeScript(JS_CODE)).thenReturn(VALUE);
when(softAssert.assertNotNull(JS_RESULT_ASSERTION_MESSAGE, VALUE)).thenReturn(true);
executeScriptSteps.saveValueFromJS(JS_CODE, VARIABLE_SCOPE, VARIABLE_NAME);
verify(variableContext).putVariable(VARIABLE_SCOPE, VARIABLE_NAME, VALUE);
}

@Test
void testGettingValueFromJSNullIsReturned()
{
executeScriptSteps.saveValueFromJS(JS_CODE, VARIABLE_SCOPE, VARIABLE_NAME);
verify(softAssert).assertNotNull(JS_RESULT_ASSERTION_MESSAGE, null);
verifyNoInteractions(variableContext);
}

private static JsArgument createJsArgument(JsArgumentType type, String value)
{
JsArgument argument = new JsArgument();
argument.setType(type);
argument.setValue(value);
return argument;
}
}
Loading