Skip to content

Commit

Permalink
[plugin-mobitru] Use dedicated API endpoint to take device when UDID …
Browse files Browse the repository at this point in the history
…is provided (#5134)

Co-authored-by: dubovik-sergey <dubovik.siarhei@gmail.com>
Co-authored-by: Valery Yatsynovich <valery_yatsynovich@epam.com>
  • Loading branch information
3 people authored Jun 19, 2024
1 parent 1f2d7f0 commit 9ee23b5
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ public byte[] findDevices(String platform, Map<String, String> parameters) throw
MobitruDeviceSearchException::new);
}

public byte[] takeDeviceBySerial(String udid) throws MobitruOperationException
{
URIBuilder uriBuilder = new URIBuilder().setPath(DEVICE_PATH).appendPath(udid);
return executeRequest(uriBuilder.toString(), HttpMethod.POST, UnaryOperator.identity(),
HttpStatus.SC_OK, MobitruDeviceTakeException::new);
}

public byte[] takeDevice(String requestedDevice) throws MobitruOperationException
{
return executePost(DEVICE_PATH, requestedDevice, HttpStatus.SC_OK);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.function.FailableFunction;
import org.apache.commons.lang3.function.FailableSupplier;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -49,6 +51,7 @@ public class MobitruFacadeImpl implements MobitruFacade
private static final Logger LOGGER = LoggerFactory.getLogger(MobitruFacadeImpl.class);

private static final String UDID = "udid";
private static final String APPIUM_UDID = "appium:udid";

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Expand All @@ -72,10 +75,25 @@ public String takeDevice(DesiredCapabilities desiredCapabilities) throws Mobitru
List<Device> foundDevices = findDevices(deviceSearchParameters);
return takeDevice(foundDevices);
}

Map<String, Object> capabilities = desiredCapabilities.asMap();
Object deviceUdid = capabilities.getOrDefault(APPIUM_UDID, capabilities.get(UDID));
if (deviceUdid != null)
{
//use different API in case if udid is provided in capabilities
//it's required in some cases like if the device is already taken
LOGGER.info("Trying to take device with udid {}", deviceUdid);
return takeDevice(() -> mobitruClient.takeDeviceBySerial(String.valueOf(deviceUdid)),
() -> "Unable to take device with udid " + deviceUdid, getDefaultDeviceWaiter());
}
Device device = new Device();
device.setDesiredCapabilities(desiredCapabilities.asMap());
Waiter deviceWaiter = new DurationBasedWaiter(new WaitMode(waitForDeviceTimeout, RETRY_TIMES));
return takeDevice(device, deviceWaiter);
return takeDevice(device, getDefaultDeviceWaiter());
}

private Waiter getDefaultDeviceWaiter()
{
return new DurationBasedWaiter(new WaitMode(waitForDeviceTimeout, RETRY_TIMES));
}

@Override
Expand All @@ -95,10 +113,17 @@ private String takeDevice(Device device, Waiter deviceWaiter) throws MobitruOper
{
LOGGER.info("Trying to take device with configuration {}", device);
String capabilities = performMapperOperation(mapper -> mapper.writeValueAsString(device));
return takeDevice(() -> mobitruClient.takeDevice(capabilities),
() -> "Unable to take device with configuration " + capabilities, deviceWaiter);
}

private String takeDevice(FailableSupplier<byte[], MobitruOperationException> takeDeviceActions,
Supplier<String> unableToTakeDeviceErrorMessage, Waiter deviceWaiter) throws MobitruOperationException
{
byte[] receivedDevice = deviceWaiter.wait(() -> {
try
{
return mobitruClient.takeDevice(capabilities);
return takeDeviceActions.get();
}
catch (MobitruDeviceTakeException e)
{
Expand All @@ -108,7 +133,7 @@ private String takeDevice(Device device, Waiter deviceWaiter) throws MobitruOper
}, Objects::nonNull);
if (null == receivedDevice)
{
throw new MobitruDeviceTakeException(String.format("Unable to take device with configuration %s", device));
throw new MobitruDeviceTakeException(unableToTakeDeviceErrorMessage.get());
}
Device takenDevice = performMapperOperation(mapper -> mapper.readValue(receivedDevice, Device.class));
LOGGER.info("Device with configuration {} is taken", takenDevice);
Expand Down Expand Up @@ -195,7 +220,7 @@ private boolean isSearchForDevice(DesiredCapabilities desiredCapabilities)
.anyMatch(key -> key.startsWith("mobitru-device-search:"));
if (containsSearchCapabilities)
{
Optional<String> conflictingCapability = Stream.of(UDID, "appium:udid", "deviceName", "appium:deviceName")
Optional<String> conflictingCapability = Stream.of(UDID, APPIUM_UDID, "deviceName", "appium:deviceName")
.filter(capabilities::containsKey).findFirst();
Validate.isTrue(conflictingCapability.isEmpty(),
"Conflicting capabilities are found. `%s` capability can not be specified along with "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class MobitruClientTests
private static final String DEVICE_ENDPOINT = "/billing/unit/vividus/automation/api/device/deviceid";
private static final String DEVICE_ID = "deviceid";
private static final String TAKE_DEVICE_ENDPOINT = "/billing/unit/vividus/automation/api/device";
private static final String UDID = "Z3CT103D2DZ";

@Mock private IHttpClient httpClient;
@Mock private HttpResponse httpResponse;
Expand Down Expand Up @@ -109,6 +110,25 @@ void shouldTakeDevice() throws IOException, MobitruOperationException
}
}

@Test
void shouldTakeDeviceBySerial() throws IOException, MobitruOperationException
{
var builder = mock(HttpRequestBuilder.class);
ClassicHttpRequest httpRequest = mock();
try (MockedStatic<HttpRequestBuilder> builderMock = Mockito.mockStatic(HttpRequestBuilder.class))
{
builderMock.when(HttpRequestBuilder::create).thenReturn(builder);
when(builder.withEndpoint(ENDPOINT)).thenReturn(builder);
when(builder.withHttpMethod(HttpMethod.POST)).thenReturn(builder);
when(builder.withRelativeUrl(TAKE_DEVICE_ENDPOINT.concat("/").concat(UDID))).thenReturn(builder);
when(builder.build()).thenReturn(httpRequest);
when(httpClient.execute(httpRequest)).thenReturn(httpResponse);
when(httpResponse.getResponseBody()).thenReturn(RESPONSE);
when(httpResponse.getStatusCode()).thenReturn(HttpStatus.SC_OK);
assertArrayEquals(RESPONSE, mobitruClient.takeDeviceBySerial(UDID));
}
}

@Test
void shouldThrowDeviceExceptionIfInvalidStatusCodeReturned() throws IOException
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.valfirst.slf4jtest.LoggingEvent;
import com.github.valfirst.slf4jtest.TestLogger;
import com.github.valfirst.slf4jtest.TestLoggerFactory;
Expand All @@ -56,28 +59,38 @@ class MobitruFacadeImplTests
{
private static final String DEVICE_TAKEN_MESSAGE = "Device with configuration {} is taken";
private static final String TRYING_TO_TAKE_DEVICE_MESSAGE = "Trying to take device with configuration {}";
private static final String TRYING_TO_TAKE_DEVICE_UDID_MESSAGE = "Trying to take device with udid {}";
private static final String RETRY_TO_TAKE_DEVICE_MESSAGE = "Unable to take device, retrying attempt.";
private static final String UNABLE_TO_TAKE_DEVICE_ERROR_FORMAT = "Unable to take device with configuration %s";
private static final String UNABLE_TO_TAKE_DEVICE_WITH_CONFIGURATION_ERROR_FORMAT =
"Unable to take device with configuration %s";
private static final String UNABLE_TO_TAKE_DEVICE_WITH_UDID_ERROR_FORMAT = "Unable to take device with udid %s";
private static final String UDID = "Z3CT103D2DZ";
private static final String DEVICE_TYPE_CAPABILITY_NAME = "mobitru-device-search:type";
private static final String PHONE = "phone";
private static final String PLATFORM_NAME = "platformName";
private static final String UDID_CAP = "udid";
private static final String IOS = "ios";
private static final String ANDROID = "android";
private static final String NO_DEVICE = "no device";
private static final String CAPABILITIES_JSON_PREFIX = "{\"desiredCapabilities\":{\"platformName\":\"Android\",";
private static final String CAPABILITIES_JSON = CAPABILITIES_JSON_PREFIX
private static final String CAPABILITIES_IOS_PLATFORM = "{\"desiredCapabilities\":{\"platformName\":\"IOS\"}}";
private static final String CAPABILITIES_WITHOUT_UDID_JSON = CAPABILITIES_JSON_PREFIX
+ "\"platformVersion\":\"12\",\"deviceName\":\"SAMSUNG SM-G998B\"}}";
private static final String CAPABILITIES_WITH_UDID_JSON = CAPABILITIES_JSON_PREFIX
+ "\"platformVersion\":\"12\",\"deviceName\":\"SAMSUNG SM-G998B\",\"udid\":\"Z3CT103D2DZ\"}}";
private static final Map<String, String> DEVICE_SEARCH_PARAMETERS = Map.of("type", PHONE);

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

private static final TestLogger LOGGER = TestLoggerFactory.getTestLogger(MobitruFacadeImpl.class);

@Mock private MobitruClient mobitruClient;

@InjectMocks private MobitruFacadeImpl mobitruFacadeImpl;

@Test
void shouldFindDeviceThenTakeItAndProvideItsUdid() throws MobitruOperationException
void shouldFindDeviceThenTakeItAndProvideItsUdid() throws MobitruOperationException, JsonProcessingException
{
mobitruFacadeImpl.setWaitForDeviceTimeout(Duration.ofSeconds(20));
var deviceSearchException = new MobitruDeviceSearchException(NO_DEVICE);
Expand All @@ -87,11 +100,11 @@ void shouldFindDeviceThenTakeItAndProvideItsUdid() throws MobitruOperationExcept
var desiredCapabilities = new DesiredCapabilities(
Map.of(PLATFORM_NAME, ANDROID, DEVICE_TYPE_CAPABILITY_NAME, PHONE));
when(mobitruClient.findDevices(ANDROID, DEVICE_SEARCH_PARAMETERS)).thenThrow(deviceSearchException)
.thenReturn(('[' + failedToTakeDeviceCapabilitiesJson + "," + CAPABILITIES_JSON + ']')
.thenReturn(('[' + failedToTakeDeviceCapabilitiesJson + "," + CAPABILITIES_WITH_UDID_JSON + ']')
.getBytes(StandardCharsets.UTF_8));
when(mobitruClient.takeDevice(failedToTakeDeviceCapabilitiesJson)).thenThrow(deviceTakeException);
when(mobitruClient.takeDevice(CAPABILITIES_JSON))
.thenReturn(CAPABILITIES_JSON.getBytes(StandardCharsets.UTF_8));
when(mobitruClient.takeDevice(CAPABILITIES_WITH_UDID_JSON))
.thenReturn(CAPABILITIES_WITH_UDID_JSON.getBytes(StandardCharsets.UTF_8));
assertEquals(UDID, mobitruFacadeImpl.takeDevice(desiredCapabilities));
var failedTakeDevice = createDevice("13", "GOOGLE PIXEL 6", "777");
var takenDevice = getTestDevice();
Expand All @@ -102,11 +115,29 @@ void shouldFindDeviceThenTakeItAndProvideItsUdid() throws MobitruOperationExcept
+ System.lineSeparator() + "Device 2: " + takenDevice),
LoggingEvent.info(TRYING_TO_TAKE_DEVICE_MESSAGE, failedTakeDevice),
LoggingEvent.warn(deviceTakeException, RETRY_TO_TAKE_DEVICE_MESSAGE),
LoggingEvent.warn(String.format(UNABLE_TO_TAKE_DEVICE_ERROR_FORMAT, failedTakeDevice)),
LoggingEvent.warn(UNABLE_TO_TAKE_DEVICE_WITH_CONFIGURATION_ERROR_FORMAT.formatted(
OBJECT_MAPPER.writeValueAsString(failedTakeDevice))),
LoggingEvent.info(TRYING_TO_TAKE_DEVICE_MESSAGE, takenDevice),
LoggingEvent.info(DEVICE_TAKEN_MESSAGE, takenDevice)), LOGGER.getLoggingEvents());
}

@ParameterizedTest
@ValueSource(strings = { "appium:udid", UDID_CAP})
void shouldTakeDeviceByUdidAndProvideItsUdid(String capName) throws MobitruOperationException
{
mobitruFacadeImpl.setWaitForDeviceTimeout(Duration.ofSeconds(20));
var deviceTakeException = new MobitruDeviceTakeException(NO_DEVICE);
var desiredCapabilities = new DesiredCapabilities(Map.of(capName, UDID));
when(mobitruClient.takeDeviceBySerial(UDID)).thenThrow(deviceTakeException).
thenReturn(CAPABILITIES_WITH_UDID_JSON.getBytes(StandardCharsets.UTF_8));
assertEquals(UDID, mobitruFacadeImpl.takeDevice(desiredCapabilities));
var takenDevice = getTestDevice();
assertEquals(List.of(
LoggingEvent.info(TRYING_TO_TAKE_DEVICE_UDID_MESSAGE, UDID),
LoggingEvent.warn(deviceTakeException, RETRY_TO_TAKE_DEVICE_MESSAGE),
LoggingEvent.info(DEVICE_TAKEN_MESSAGE, takenDevice)), LOGGER.getLoggingEvents());
}

@Test
void shouldTakeDeviceInUseAndProvideItsUdId() throws MobitruOperationException
{
Expand All @@ -117,7 +148,7 @@ void shouldTakeDeviceInUseAndProvideItsUdId() throws MobitruOperationException
requestedDevice.setDesiredCapabilities(desiredCapabilities.asMap());
when(mobitruClient.takeDevice("{\"desiredCapabilities\":{\"platformName\":\"ANDROID\"}}"))
.thenThrow(exception)
.thenReturn(CAPABILITIES_JSON.getBytes(StandardCharsets.UTF_8));
.thenReturn(CAPABILITIES_WITH_UDID_JSON.getBytes(StandardCharsets.UTF_8));
assertEquals(UDID,
mobitruFacadeImpl.takeDevice(desiredCapabilities));
assertEquals(List.of(LoggingEvent.info(TRYING_TO_TAKE_DEVICE_MESSAGE, requestedDevice),
Expand All @@ -126,7 +157,7 @@ void shouldTakeDeviceInUseAndProvideItsUdId() throws MobitruOperationException
}

@ParameterizedTest
@ValueSource(strings = { "appium:udid", "udid", "deviceName", "appium:deviceName" })
@ValueSource(strings = { "appium:udid", UDID_CAP, "deviceName", "appium:deviceName" })
void shouldThrowExceptionIfConflictingCapabilities(String capabilityName)
{
mobitruFacadeImpl.setWaitForDeviceTimeout(Duration.ofSeconds(20));
Expand All @@ -153,25 +184,37 @@ void shouldThrowExceptionIfFoundDeviceNotTaken() throws MobitruOperationExceptio
{
mobitruFacadeImpl.setWaitForDeviceTimeout(Duration.ofSeconds(1));
when(mobitruClient.findDevices(ANDROID, DEVICE_SEARCH_PARAMETERS))
.thenReturn(('[' + CAPABILITIES_JSON + ']').getBytes(StandardCharsets.UTF_8));
when(mobitruClient.takeDevice(CAPABILITIES_JSON))
.thenReturn(('[' + CAPABILITIES_WITHOUT_UDID_JSON + ']').getBytes(StandardCharsets.UTF_8));
when(mobitruClient.takeDevice(CAPABILITIES_WITHOUT_UDID_JSON))
.thenThrow(new MobitruDeviceTakeException(NO_DEVICE));
var capabilities = new DesiredCapabilities(Map.of(PLATFORM_NAME, ANDROID, DEVICE_TYPE_CAPABILITY_NAME, PHONE));
var exception = assertThrows(MobitruDeviceTakeException.class,
() -> mobitruFacadeImpl.takeDevice(capabilities));
assertEquals(String.format(UNABLE_TO_TAKE_DEVICE_ERROR_FORMAT, getTestDevice()), exception.getMessage());
assertEquals(UNABLE_TO_TAKE_DEVICE_WITH_CONFIGURATION_ERROR_FORMAT.formatted(CAPABILITIES_WITHOUT_UDID_JSON),
exception.getMessage());
}

@Test
void shouldThrowExceptionIfNoDeviceWithUdid() throws MobitruOperationException
{
mobitruFacadeImpl.setWaitForDeviceTimeout(Duration.ofSeconds(1));
var desiredCapabilities = new DesiredCapabilities(Map.of(UDID_CAP, UDID));
when(mobitruClient.takeDeviceBySerial(UDID)).thenThrow(new MobitruDeviceTakeException(NO_DEVICE));
var exception = assertThrows(MobitruDeviceTakeException.class,
() -> mobitruFacadeImpl.takeDevice(desiredCapabilities));
assertEquals(UNABLE_TO_TAKE_DEVICE_WITH_UDID_ERROR_FORMAT.formatted(UDID), exception.getMessage());
}

@Test
void shouldThrowExceptionIfNoDeviceTaken() throws MobitruOperationException
{
mobitruFacadeImpl.setWaitForDeviceTimeout(Duration.ofSeconds(1));
when(mobitruClient.takeDevice("{\"desiredCapabilities\":{\"platformName\":\"IOS\"}}"))
when(mobitruClient.takeDevice(CAPABILITIES_IOS_PLATFORM))
.thenThrow(new MobitruDeviceTakeException(NO_DEVICE));
var capabilities = new DesiredCapabilities(Map.of(PLATFORM_NAME, IOS));
var exception = assertThrows(MobitruDeviceTakeException.class,
() -> mobitruFacadeImpl.takeDevice(capabilities));
assertEquals("Unable to take device with configuration {desiredCapabilities={platformName=IOS}}",
assertEquals(UNABLE_TO_TAKE_DEVICE_WITH_CONFIGURATION_ERROR_FORMAT.formatted(CAPABILITIES_IOS_PLATFORM),
exception.getMessage());
}

Expand Down Expand Up @@ -227,7 +270,7 @@ private Device createDevice(String platformVersion, String deviceName, String ud
desiredCapabilities.put(PLATFORM_NAME, "Android");
desiredCapabilities.put("platformVersion", platformVersion);
desiredCapabilities.put("deviceName", deviceName);
desiredCapabilities.put("udid", udid);
desiredCapabilities.put(UDID_CAP, udid);
Device device = new Device();
device.setDesiredCapabilities(desiredCapabilities);
return device;
Expand Down

0 comments on commit 9ee23b5

Please sign in to comment.