Skip to content

Commit

Permalink
[plugin-mobile-app] Add step to download the file from the device (#3749
Browse files Browse the repository at this point in the history
)

Co-authored-by: Ivan Baranau <ivan_baranau@epam.com>
  • Loading branch information
IBrun and avinBar authored Mar 28, 2023
1 parent 4d9357c commit c717fb5
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 23 deletions.
42 changes: 23 additions & 19 deletions docs/modules/plugins/pages/plugin-mobile-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -482,25 +482,7 @@ Deletes a file from the device/emulator/simulator.
When I delete file `$filePath` from device
----

[cols="1,1,2,1", options="header"]

|===

|Argument
|Platform
|Description
|Examples

.2+.^| `$filePath`
|iOS
|The path to an existing remote file on the device. This variable can be prefixed with bundle id, so then the file will be deleted from the corresponding application container instead of the default media folder. Use `@<app_bundle_id>:<optional_container_type>/<path_to_the_file_or_folder_inside_container>` format to delete a file or a folder from an application container of the given type. The only supported container type is 'documents'. If the container type is not set explicitly for a bundle id, then the default application container is going to be mounted (aka --container ifuse argument) e.g. If `@com.myapp.bla:documents/111.png` is provided, `On My iPhone/<app name>` in Files app will be mounted to the host machine. `@com.myapp.bla:documents/ means On My iPhone/<app name>.
|`/DCIM/100APPLE/image.png` or `@com.mycompany.myapp:documents/myfile.txt`

|Android
|The full path to the remote file or a file inside an application bundle
|`/sdcard/myfile.txt` or `@my.app.id/path/in/bundle`

|===
include::partial$mobile-remote-file-path.adoc[]

.Delete file
[source,gherkin]
Expand All @@ -521,6 +503,28 @@ accomplish it by deleting following files: */Media/PhotoData/Photos.sqlite*, */M
:web-view-info: (see http://appium.io/docs/en/writing-running-appium/web/hybrid/[Automating hybrid apps] for more information)


=== Download the file from the device

Downloads the file from the device/emulator/simulator and saves its content to a variable.

[source,gherkin]
----
When I download file `$filePath` from device and save its content to $scopes variable `$variableName`
----

include::partial$mobile-remote-file-path.adoc[]

* `$scopes` - xref:commons:variables.adoc#_scopes[The comma-separated set of the variables scopes].
* `$variableName` - The variable name to store the file content.


.Download file
[source,gherkin]
----
When I download file `/sdcard/file.json` from device and save its content to scenario variable `json-file`
----


=== Switch to Web view by name

Switches context to a web view with name that matches the rule {web-view-info}. Also, Appium capabilities for https://github.com/appium/appium-xcuitest-driver#web-context[iOS] and https://github.com/appium/appium-uiautomator2-driver#web-context[Android] are available for the fine-tuning configuration.
Expand Down
26 changes: 26 additions & 0 deletions docs/modules/plugins/partials/mobile-remote-file-path.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[cols="1,1,2,1", options="header"]

|===

|Argument
|Platform
|Description
|Examples

.2+.^| `$filePath`
|iOS
|The path to an existing remote file on the device. This variable can be prefixed with bundle id, so then the file will be downloaded/deleted from
the corresponding application container instead of the default media folder. Use
`@<app_bundle_id>:<optional_container_type>/<path_to_the_file_or_folder_inside_containe>` format to download/delete a file from an application
container of the given type. The only supported container type for real devices is `documents`. Containers available for Simulators: `app`, `data`,
`groups`, `<A specific App Group container>`. If the container type is not set explicitly for a bundle id, then the default application container
is going to be mounted (aka --container ifuse argument) e.g. If `@com.myapp.bla:documents/111.png` is provided, `On My iPhone/<app name>` in Files app
will be mounted to the host machine. `@com.myapp.bla:documents/ means On My iPhone/<app name>.
|`/DCIM/100APPLE/image.png` or `@com.mycompany.myapp:documents/myfile.txt`

|Android
|The full path to the remote file or a specially formatted path, which points to an item inside an app bundle, for example `@my.app.id/my/path`.
It is mandatory for the app bundle to have debugging enabled in order to use the latter remotePath format.
|`/sdcard/myfile.txt` or `@my.app.id/path/in/bundle`

|===
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.vividus.selenium.session.WebDriverSessionAttributes;
import org.vividus.selenium.session.WebDriverSessionInfo;

import io.appium.java_client.PullsFiles;
import io.appium.java_client.PushesFiles;
import io.appium.java_client.remote.SupportsRotation;

Expand Down Expand Up @@ -54,7 +55,7 @@ public void pushFile(String deviceFilePath, byte[] content)
* Deletes a file by <b>deviceFilePath</b> from the device
* @param deviceFilePath the path to an existing remote file on the device. This variable can be predefined
* with bundle id to delete file inside an application bundle. See details for
* <a href="https://github.com/appium/appium-xcuitest-driver#mobile-deletefile">iOS</a>,
* <a href="https://appium.github.io/appium-xcuitest-driver/4.19/execute-methods/#mobile-deletefile">iOS</a>,
* <a href="https://github.com/appium/appium-uiautomator2-driver#mobile-deletefile">Android</a>
*/
public void deleteFile(String deviceFilePath)
Expand All @@ -78,4 +79,19 @@ public void rotate(ScreenOrientation orientation)
webDriverSessionInfo.reset(WebDriverSessionAttributes.SCREEN_SIZE);
}
}

/**
* Pulls a file at <b>deviceFilePath</b> from the device
* @param deviceFilePath The path to an existing remote file on the device. This parameter can be predefined
* with bundle id, then the file will be pulled from the root of the corresponding application container.
* See details for
* <a href="https://appium.github.io/appium-xcuitest-driver/4.19/execute-methods/#mobile-pullfile">iOS</a>,
* <a href="https://github.com/appium/appium-uiautomator2-driver#mobile-pullfile">Android</a>
*
* @return The content of the downloaded file.
*/
public byte[] pullFile(String deviceFilePath)
{
return webDriverProvider.getUnwrapped(PullsFiles.class).pullFile(deviceFilePath);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,32 @@
package org.vividus.mobileapp.steps;

import java.nio.file.Paths;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.jbehave.core.annotations.When;
import org.openqa.selenium.ScreenOrientation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vividus.context.VariableContext;
import org.vividus.mobileapp.action.DeviceActions;
import org.vividus.steps.DataWrapper;
import org.vividus.util.ResourceUtils;
import org.vividus.variable.VariableScope;

public class DeviceSteps
{
private static final Logger LOGGER = LoggerFactory.getLogger(DeviceSteps.class);

private final DeviceActions deviceActions;
private final String folderForFileUpload;
private final VariableContext variableContext;

public DeviceSteps(String folderForFileUpload, DeviceActions deviceActions)
public DeviceSteps(String folderForFileUpload, DeviceActions deviceActions, VariableContext variableContext)
{
this.folderForFileUpload = folderForFileUpload;
this.deviceActions = deviceActions;
this.variableContext = variableContext;
}

/**
Expand Down Expand Up @@ -79,7 +84,7 @@ public void uploadFileToDevice(String fileName, DataWrapper data)
* @param filePath the path to an existing remote file on the device. This variable can be predefined with bundle
* id to delete file inside an application bundle.
* <p>See details for
* <a href="https://github.com/appium/appium-xcuitest-driver#mobile-deletefile">iOS</a>,
* <a href="https://appium.github.io/appium-xcuitest-driver/4.19/execute-methods/#mobile-deletefile">iOS</a>,
* <a href="https://github.com/appium/appium-uiautomator2-driver#mobile-deletefile">Android</a>
*/
@When("I delete file `$filePath` from device")
Expand All @@ -88,6 +93,37 @@ public void deleteFileFromDevice(String filePath)
deviceActions.deleteFile(filePath);
}

/**
* Downloads a file at <b>filePath</b> from the device and save its content to <b>scopes</b> variables with name
* <b>variableName</b>.<br>
* Example:
* <br>
* <code>
* When I download file `$filePath` from device and save its content to $scopes variable `$variableName`
* </code>
*
* @param filePath The path to an existing remote file on the device. This parameter can be predefined
* with bundle id, then the file will be pulled from the root of the corresponding application container.
* See details for
* <a href="https://appium.github.io/appium-xcuitest-driver/4.19/execute-methods/#mobile-pullfile">iOS</a>,
* <a href="https://github.com/appium/appium-uiautomator2-driver#mobile-pullfile">Android</a>
* @param scopes The set (comma separated list of scopes e.g.: STORY, NEXT_BATCHES) of the variable scopes.<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 The variable name to store the file content.
*/
@When("I download file `$filePath` from device and save its content to $scopes variable `$variableName`")
public void downloadFileFromDevice(String filePath, Set<VariableScope> scopes, String variableName)
{
byte[] data = deviceActions.pullFile(filePath);
variableContext.putVariable(scopes, variableName, data);
}

/**
* Changes the device screen orientation to the specified value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.vividus.selenium.session.WebDriverSessionAttributes;
import org.vividus.selenium.session.WebDriverSessionInfo;

import io.appium.java_client.PullsFiles;
import io.appium.java_client.PushesFiles;
import io.appium.java_client.remote.SupportsRotation;

Expand Down Expand Up @@ -85,4 +86,14 @@ void shouldRotate()
verify(supportsRotation).rotate(ScreenOrientation.LANDSCAPE);
verify(webDriverSessionInfo).reset(WebDriverSessionAttributes.SCREEN_SIZE);
}

@Test
void shouldPullFile()
{
var pullFileDriver = mock(PullsFiles.class);
when(webDriverProvider.getUnwrapped(PullsFiles.class)).thenReturn(pullFileDriver);
deviceActions.pullFile(DEVICE_FILE_PATH);
verify(pullFileDriver).pullFile(DEVICE_FILE_PATH);
verifyNoMoreInteractions(webDriverProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import com.github.valfirst.slf4jtest.TestLogger;
Expand All @@ -38,8 +40,10 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.ScreenOrientation;
import org.vividus.context.VariableContext;
import org.vividus.mobileapp.action.DeviceActions;
import org.vividus.steps.DataWrapper;
import org.vividus.variable.VariableScope;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

Expand All @@ -53,14 +57,15 @@ class DeviceStepsTests
private static final String DEVICE_FILE_PATH = Paths.get(DEVICE_FOLDER, FILE_NAME).toString();

@Mock private DeviceActions deviceActions;
@Mock private VariableContext variableContext;
private DeviceSteps deviceSteps;

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

@BeforeEach
void init()
{
deviceSteps = new DeviceSteps(DEVICE_FOLDER, deviceActions);
deviceSteps = new DeviceSteps(DEVICE_FOLDER, deviceActions, variableContext);
}

static Stream<Arguments> dataProvider()
Expand Down Expand Up @@ -103,6 +108,16 @@ void shouldDeleteFileFromDevice()
verify(deviceActions).deleteFile(DEVICE_FILE_PATH);
}

@Test
void shouldDownloadFileFromDevice()
{
var variableScopes = Set.of(VariableScope.SCENARIO);
var variableName = "variableName";
when(deviceActions.pullFile(DEVICE_FILE_PATH)).thenReturn(BINARY_CONTENT);
deviceSteps.downloadFileFromDevice(DEVICE_FILE_PATH, variableScopes, variableName);
verify(variableContext).putVariable(variableScopes, variableName, BINARY_CONTENT);
}

@Test
void shouldChangeDeviceScreenOrientation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,46 @@ When I tap on element located by `xpath((//XCUIElementTypeImage[contains(@name,
Then number of elements found by `xpath(//XCUIElementTypeStaticText[@value='569x407'])` is equal to `1`


Scenario: [Android] Verify step: 'When I download file `$filePath` from device and save its content to $scopes variable `$variableName`'
Meta:
@targetPlatform android
Given I initialize story variable `json` with value `
{
"productId": 1,
"productName": "An ice sculpture",
"price": 12.50,
"tags": [ "cold", "ice" ],
"dimensions": {
"length": 7.0,
"width": 12.0,
"height": 9.5
},
"warehouseLocation": {
"latitude": -78.75,
"longitude": 20.4
}
}
`
When I upload file with name `product.json` and data `${json}` to device
When I download file `/sdcard/Pictures/product.json` from device and save its content to scenario variable `downloaded-json`
Then JSON element from `${json}` by JSON path `$` is equal to `${downloaded-json}`


Scenario: [iOS] Verify step: When I download file `$filePath` from device and save its content to $scopes variable `$variableName`
Meta:
@targetPlatform ios
When I tap on element located by `iosNsPredicate(name == 'selectImage')`
When I wait until element located by `accessibilityId(Photos)` appears
Then number of elements found by `xpath(//XCUIElementTypeImage[contains(@name, "Photo, March 13")])` is equal to `1`
When I tap on element located by `accessibilityId(Cancel)`
When I download file `/Media/DCIM/100APPLE/IMG_0001.JPG` from device and save its content to scenario variable `downloaded-image`
When I upload file with name `downloaded-image.jpg` and data `${downloaded-image}` to device
When I tap on element located by `iosNsPredicate(name == 'selectImage')`
When I wait until element located by `accessibilityId(Photos)` appears
Then number of elements found by `xpath(//XCUIElementTypeImage[contains(@name, "Photo, March 13")])` is equal to `2`
When I tap on element located by `accessibilityId(Cancel)`


Scenario: [Android] Verify step: 'When I delete file `$filePath` from device'
Meta:
@targetPlatform android
Expand Down

0 comments on commit c717fb5

Please sign in to comment.