Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[plugin-mobile-app] Add steps to interact with sliders #2377

Merged
merged 1 commit into from
Jan 19, 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
73 changes: 73 additions & 0 deletions docs/modules/plugins/pages/plugin-mobile-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,79 @@ When I stop recording `Time Profiler` metrics and save results to file `${output
----


=== Set slider value

==== iOS

Sets the slider value in percents.

[WARNING]
* The accuracy of setting the slider value is not guaranteed and may vary depending on the device screen resolution.
* The target element must be of type `XCUIElementTypeSlider`.
* The percent number must be between 0 and 100 inclusively.

[source,gherkin]
----
When I set value of iOS slider located `$locator` to `$percent` percents
----

* `$locator` - The locator to find `XCUIElementTypeSlider` element.
* `$percent` - The target value of the slider in percents.

.Set value of iOS slider (with range 0:50) to 45
[source,gherkin]
----
When I set value of iOS slider located `xpath(//XCUIElementTypeSlider)` to `90` percents
----

.Set value of iOS slider (with range -50:50) to 20
[source,gherkin]
----
When I set value of iOS slider located `xpath(//XCUIElementTypeSlider)` to `70` percents
----

.Set value of iOS slider (with range 18:65) to 55
[source,gherkin]
----
When I set value of iOS slider located `xpath(//XCUIElementTypeSlider)` to `80` percents
----

==== Android

Sets the slider value to the number relatively to the slider leftmost side of its range.

[WARNING]
* The step is expected to set accurate value without deviations.
* The target element must be of type `android.widget.SeekBar`.
* The number must be greater than or equal to 0.
* Make sure the passed number does not exceed the right limit of the slider, this may lead to unexpected failure.

[source,gherkin]
----
When I set value of Android slider located `$locator` to `$number`
----

* `$locator` - The locator to find `android.widget.SeekBar` element.
* `$number` - The number to set on the slider

.Set value of Android slider (with range 0:50) to 13
[source,gherkin]
----
When I set value of Android slider located `xpath(//android.widget.SeekBar)` to `13`
----

.Set value of Android slider (with range -50:50) to -25
[source,gherkin]
----
When I set value of Android slider located `xpath(//android.widget.SeekBar)` to `25`
----

.Set value of Android slider (with range 18:65) to 20
[source,gherkin]
----
When I set value of Android slider located `xpath(//android.widget.SeekBar)` to `2`
----

include::partial$generic-ui-steps.adoc[]

include::partial$proxy-steps.adoc[]
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 All @@ -22,25 +22,33 @@

import org.jbehave.core.annotations.When;
import org.openqa.selenium.remote.RemoteWebElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vividus.monitor.TakeScreenshotOnFailure;
import org.vividus.selenium.WebDriverUtil;
import org.vividus.selenium.manager.IGenericWebDriverManager;
import org.vividus.steps.ui.validation.IBaseValidations;
import org.vividus.ui.action.ElementActions;
import org.vividus.ui.action.JavascriptActions;
import org.vividus.ui.action.search.Locator;

@TakeScreenshotOnFailure
public class ElementSteps
{
private static final Logger LOGGER = LoggerFactory.getLogger(ElementSteps.class);
private static final double PERCENT = 100.0;

private final IGenericWebDriverManager genericWebDriverManager;
private final JavascriptActions javascriptActions;
private final ElementActions elementActions;
private final IBaseValidations baseValidations;

public ElementSteps(JavascriptActions javascriptActions, IGenericWebDriverManager genericWebDriverManager,
IBaseValidations baseValidations)
ElementActions elementActions, IBaseValidations baseValidations)
{
this.javascriptActions = javascriptActions;
this.genericWebDriverManager = genericWebDriverManager;
this.elementActions = elementActions;
this.baseValidations = baseValidations;
}

Expand Down Expand Up @@ -71,6 +79,52 @@ public void selectPickerWheelValue(PickerWheelDirection direction, double offset
"offset", offset)));
}

/**
* Sets the value of the slider to the number. The number must be greater than or equal to 0. Make
* sure the passed number does not exceed the right limit of the slider, this may lead to unexpected
* failure.
* <br>
* @param locator locator to find a <i>android.widget.SeekBar</i> element
* @param number the number to set on the slider (greater than or equal to 0)
*/
@When("I set value of Android slider located `$locator` to `$number`")
public void setAndroidSliderValue(Locator locator, double number)
{
isTrue(genericWebDriverManager.isAndroidNativeApp(), "The step is supported only for Android platform");
isTrue(number >= 0, "The target slider number must be greater than or equal to 0, but got %s", number);
setSliderValue(locator, number);
}

/**
* Sets the value of the slider to the percents. The percent value must be between 0 and 100.
* <br>
* <br>
* <b>The accuracy of setting the slider value is not guaranteed and may vary depending on the device screen
* resolution.</b>
* <br>
* @param locator locator to find a <i>XCUIElementTypeSlider</i> element
* @param percent the percent number to set on the slider (0:100)
*/
@SuppressWarnings("MagicNumber")
@When("I set value of iOS slider located `$locator` to `$percent` percents")
public void setIOSSliderValue(Locator locator, int percent)
{
isTrue(genericWebDriverManager.isIOSNativeApp(), "The step is supported only for iOS platform");
isTrue(percent >= 0 && percent <= 100,
"The target slider percent value must be between 0 and 100 inclusively, but got %s", percent);
setSliderValue(locator, percent / PERCENT);
}

private void setSliderValue(Locator locator, double value)
{
baseValidations.assertElementExists("The slider", locator).ifPresent(slider ->
{
slider.sendKeys(Double.toString(value));
LOGGER.atInfo().addArgument(() -> elementActions.getElementText(slider))
.log("The slider value is set to {}");
});
}

public enum PickerWheelDirection
{
NEXT, PREVIOUS;
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 All @@ -16,39 +16,59 @@

package org.vividus.mobileapp.steps;

import static com.github.valfirst.slf4jtest.LoggingEvent.info;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.github.valfirst.slf4jtest.TestLogger;
import com.github.valfirst.slf4jtest.TestLoggerFactory;
import com.github.valfirst.slf4jtest.TestLoggerFactoryExtension;

import org.junit.jupiter.api.Nested;
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.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.vividus.mobileapp.steps.ElementSteps.PickerWheelDirection;
import org.vividus.selenium.manager.IGenericWebDriverManager;
import org.vividus.steps.ui.validation.IBaseValidations;
import org.vividus.ui.action.ElementActions;
import org.vividus.ui.action.JavascriptActions;
import org.vividus.ui.action.search.Locator;

@ExtendWith(MockitoExtension.class)
@ExtendWith({ TestLoggerFactoryExtension.class, MockitoExtension.class })
class ElementStepsTests
{
private static final String PICKER_WHEEL = "Picker wheel";
private static final String SLIDER = "The slider";
private static final String SLIDER_VALUE_MESSAGE = "The slider value is set to {}";
private static final double OFFSET = 0.1;

@Mock private IGenericWebDriverManager genericWebDriverManager;
@Mock private JavascriptActions javascriptActions;
@Mock private ElementActions elementActions;
@Mock private IBaseValidations baseValidations;
@Mock private Locator locator;
@InjectMocks private ElementSteps elementSteps;

private final TestLogger logger = TestLoggerFactory.getTestLogger(ElementSteps.class);

@Test
void shouldSelectPickerWheelValue()
{
Expand Down Expand Up @@ -89,4 +109,109 @@ void shouldNotSelectPickerWheelValueOnAndroid()
() -> elementSteps.selectPickerWheelValue(PickerWheelDirection.NEXT, OFFSET, locator));
assertEquals("Picker wheel selection is supported only for iOS platform", exception.getMessage());
}

@Nested
class IOSSliderTests
{
@Test
void shouldSetValue()
{
when(genericWebDriverManager.isIOSNativeApp()).thenReturn(true);
WebElement slider = mock(WebElement.class);
when(baseValidations.assertElementExists(SLIDER, locator)).thenReturn(Optional.of(slider));
Integer number = 50;
when(elementActions.getElementText(slider)).thenReturn(number.toString());

elementSteps.setIOSSliderValue(locator, number);

verify(slider).sendKeys("0.5");
assertThat(logger.getLoggingEvents(), is(List.of(info(SLIDER_VALUE_MESSAGE, number.toString()))));
}

@Test
void shouldNotSetValueIfSliderIsMissing()
{
when(genericWebDriverManager.isIOSNativeApp()).thenReturn(true);
when(baseValidations.assertElementExists(SLIDER, locator)).thenReturn(Optional.empty());

elementSteps.setIOSSliderValue(locator, 1);

verifyNoInteractions(elementActions);
assertThat(logger.getLoggingEvents(), is(empty()));
}

@Test
void shouldFailToSetValueIfNotIOSPlatform()
{
when(genericWebDriverManager.isIOSNativeApp()).thenReturn(false);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> elementSteps.setIOSSliderValue(locator, 1));
assertEquals("The step is supported only for iOS platform", thrown.getMessage());
assertThat(logger.getLoggingEvents(), is(empty()));
}

@ParameterizedTest
@ValueSource(ints = { -1, 101 })
void shouldFailToSetValueIfItsBetweenZeroAndHundred(int value)
{
when(genericWebDriverManager.isIOSNativeApp()).thenReturn(true);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> elementSteps.setIOSSliderValue(locator, value));
assertEquals("The target slider percent value must be between 0 and 100 inclusively, but got " + value,
thrown.getMessage());
assertThat(logger.getLoggingEvents(), is(empty()));
}
}

@Nested
class AndroidSliderTests
{
@Test
void shouldSetValue()
{
when(genericWebDriverManager.isAndroidNativeApp()).thenReturn(true);
WebElement slider = mock(WebElement.class);
when(baseValidations.assertElementExists(SLIDER, locator)).thenReturn(Optional.of(slider));
Double number = 50.0;
when(elementActions.getElementText(slider)).thenReturn(number.toString());

elementSteps.setAndroidSliderValue(locator, number);

verify(slider).sendKeys(number.toString());
assertThat(logger.getLoggingEvents(), is(List.of(info(SLIDER_VALUE_MESSAGE, number.toString()))));
}

@Test
void shouldNotSetValueIfSliderIsMissing()
{
when(genericWebDriverManager.isAndroidNativeApp()).thenReturn(true);
when(baseValidations.assertElementExists(SLIDER, locator)).thenReturn(Optional.empty());

elementSteps.setAndroidSliderValue(locator, 1);

verifyNoInteractions(elementActions);
assertThat(logger.getLoggingEvents(), is(empty()));
}

@Test
void shouldFailToSetValueIfNotAndroidPlatform()
{
when(genericWebDriverManager.isAndroidNativeApp()).thenReturn(false);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> elementSteps.setAndroidSliderValue(locator, 1));
assertEquals("The step is supported only for Android platform", thrown.getMessage());
assertThat(logger.getLoggingEvents(), is(empty()));
}

@Test
void shouldFailToSetValueIfItsNegative()
{
when(genericWebDriverManager.isAndroidNativeApp()).thenReturn(true);
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> elementSteps.setAndroidSliderValue(locator, -1));
assertEquals("The target slider number must be greater than or equal to 0, but got -1.0",
thrown.getMessage());
assertThat(logger.getLoggingEvents(), is(empty()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|menuQrCodeXpath |//android.widget.TextView[@text='QR Code'] |
|menuWaitXpath |//android.widget.TextView[@text='Wait'] |
|menuScrollViewXpath |//android.widget.TextView[@text='Scroll View'] |
|menuSliderXpath |//android.widget.TextView[@text='Slider'] |
|nameInputXpath |//android.widget.EditText[@text='Enter your name...']|
|action |COMPARE_AGAINST |
|scrollViewXpath |//android.widget.ScrollView |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
|menuQrCodeXpath |//XCUIElementTypeButton[@name="QR Code"] |
|menuWaitXpath |//XCUIElementTypeButton[@name="Wait"] |
|menuScrollViewXpath |//XCUIElementTypeButton[@name="Scroll View"] |
|menuSliderXpath |//XCUIElementTypeButton[@name="Slider"] |
|nameInputXpath |//XCUIElementTypeTextField[@value='Enter your name...']|
|action |COMPARE_AGAINST |
|scrollViewXpath |//XCUIElementTypeScrollView |
Expand Down
Loading