Skip to content

Commit

Permalink
[plugin-mobile-app] Add steps to interact with sliders
Browse files Browse the repository at this point in the history
  • Loading branch information
uarlouski committed Jan 19, 2022
1 parent e5e0fce commit 1bf906d
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 4 deletions.
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

0 comments on commit 1bf906d

Please # to comment.