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

Add experimental support for testing on real devices #8

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions Docs/run_this_from_post_action.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Add the following script as Post-action to your Target's scheme in Test section

```sh
# Log file path where script actions will be saved for debug purposes,
# should be added in test targets' Build Settings > User-Defined
if [[ ! -z "$LOG_FILE_PATH" ]]; then
LOG_FILE_PATH="$PROJECT_DIR/TestPostActions.log"
fi

rm "$LOG_FILE_PATH"

# remote path - this path is valid if you install SUITCase as a remote package
SWIFT_PACKAGES_PATH="${BUILD_DIR%Build/*}SourcePackages/checkouts"
SCRIPT_PATH="$SWIFT_PACKAGES_PATH/suitcase/Scripts/get_device_screenshots.sh"

if test -f "$SCRIPT_PATH"; then
echo "Script found at '$SCRIPT_PATH'" >> "$LOG_FILE_PATH"
else
echo "Script not found at '$SCRIPT_PATH'" >> "$LOG_FILE_PATH"

# Try local path if it is set, can be used if you install SUITCase as a local package
# should be added in test targets' Build Settings > User-Defined as absolute path
if [[ -z $LOCAL_SCRIPT_PATH ]]; then
echo "Local path not set too, exiting" >> "$LOG_FILE_PATH"
exit
fi

SCRIPT_PATH=$LOCAL_SCRIPT_PATH

if test -f "$SCRIPT_PATH"; then
echo "Script found at '$SCRIPT_PATH'" >> "$LOG_FILE_PATH"
else
echo "Script not found at '$SCRIPT_PATH', exiting" >> "$LOG_FILE_PATH"
exit
fi
fi

# Path where to save test images retrieved from device,
# should be added in test targets' Build Settings > User-Defined
if [[ -z "$IMAGES_DIR" ]]; then
echo "IMAGES_DIR environment variable not set, exiting" >> "$LOG_FILE_PATH"
exit
fi

# Images relative path inside application container without leading slash,
# should be added in test targets' Build Settings > User-Defined
# for instance FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("TestImages").path
if [[ -z "$IMAGES_CONTAINER_PATH" ]]; then
echo "IMAGES_CONTAINER_PATH environment variable not set, exiting" >> "$LOG_FILE_PATH"
exit
fi

# Tests target bundle id. This will be a runner app where images will be saved
TESTS_TARGET_BUNDLE_ID="$PRODUCT_BUNDLE_IDENTIFIER"

# run configured script
"$SCRIPT_PATH" "$TESTS_TARGET_BUNDLE_ID" "$IMAGES_CONTAINER_PATH" "$IMAGES_DIR" "$LOG_FILE_PATH"

```
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,43 @@ Compares the average colors of screenshots.
* You can also verify the average color without the reference screenshot by using `averageColorIs(_ uiColor: UIColor, tolerance: Double = 0.1)` \
`XCTAssert(app.buttons["Red Button"].averageColorIs(.red))`

## Experimental support for testing on real devices using `ifuse` library
Currently SUITCase is intended to be used mainly with iOS Simulator, because the latter allows seamless access to macOS filesystem (test screenshots can be saved directly to your Mac). This is not as on real devices, because tests are being run on device filesystem which has no direct access to macOS filesystem.

However we can opt-in saving all screenshots during tests on device, mount xctrunner application container in macOS and this way copy screenshots from the testable device to Mac. This needs additional setup as follows
schmidt9 marked this conversation as resolved.
Show resolved Hide resolved

### Setup

1. Install [ifuse](https://github.com/libimobiledevice/ifuse) using Homebrew (based on [this article](https://habr.com/ru/post/459888/))
* Install `osxfuse`
```
brew install osxfuse
```
* Install dependencies
```
brew uninstall --ignore-dependencies libimobiledevice
brew uninstall --ignore-dependencies usbmuxd
#If you never installed libimobiledevice and usbmuxd before
#skip above commands
brew install --HEAD usbmuxd
brew unlink usbmuxd
brew link usbmuxd
brew install --HEAD libimobiledevice
```
**Important**: If you've already installed stable `libimobiledevice` and `usbmuxd` versions remove them and install `dev` versions with `--HEAD` instead to avoid connection issues with iOS 12
* Install `ifuse`
```
brew install ifuse
```

2. Add [this script](Docs/run_this_from_post_action.md) as Post-action to your Target's scheme in Test section and configure it properly

3. Make sure you selected a real device for testing and use the following code snippet to enable the feature
```
let imagesFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("TestImages").path
SUITCase.screenshotComparisonImagesFolder = imagesFolder
deviceTestingEnabled = true
```

## License
SUITCase is the open-source software under [the MPL 2.0 license.](LICENSE)
43 changes: 43 additions & 0 deletions Scripts/get_device_screenshots.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash

#
# See Docs/run_this_from_post_action.md for configuration script
#

LOG_FILE_PATH="$4"

MOUNT_DIR="$HOME/fuse_mount_point"
echo "Mount point $MOUNT_DIR" >> "$LOG_FILE_PATH"

mkdir "$MOUNT_DIR"

# get input

TESTS_TARGET_BUNDLE_ID="$1.xctrunner"
TEST_IMAGES_SOURCE_PATH="$MOUNT_DIR/$2"
TEST_IMAGES_DESTINATION_PATH="$3"

echo "Got following data:" >> "$LOG_FILE_PATH"
echo "-- Bundle identifier '$TESTS_TARGET_BUNDLE_ID'" >> "$LOG_FILE_PATH"
echo "-- Images source path '$TEST_IMAGES_SOURCE_PATH'" >> "$LOG_FILE_PATH"
echo "-- Images destination path '$TEST_IMAGES_DESTINATION_PATH'" >> "$LOG_FILE_PATH"

# mount app container

echo "(Re)mounting '$TESTS_TARGET_BUNDLE_ID'" >> "$LOG_FILE_PATH"
# unmount container (if already mounted) to avoid copying error,
# make it forcibly (with -f flag) to get updated mounted container
umount -f -v "$MOUNT_DIR" >> "$LOG_FILE_PATH" 2>&1
# mount container
ifuse --debug --container $TESTS_TARGET_BUNDLE_ID "$MOUNT_DIR" >> "$LOG_FILE_PATH" 2>&1

# sync images

echo "Syncing images between $TEST_IMAGES_SOURCE_PATH and $TEST_IMAGES_DESTINATION_PATH" >> "$LOG_FILE_PATH"
echo "-- source -> target" >> "$LOG_FILE_PATH"
rsync -rtuv "$TEST_IMAGES_SOURCE_PATH/" "$TEST_IMAGES_DESTINATION_PATH" >> "$LOG_FILE_PATH" 2>&1
echo "-- target -> source" >> "$LOG_FILE_PATH"
rsync -rtuv "$TEST_IMAGES_DESTINATION_PATH/" "$TEST_IMAGES_SOURCE_PATH" >> "$LOG_FILE_PATH" 2>&1



2 changes: 1 addition & 1 deletion Sources/SUITCase/SUITCase+getImagePaths.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import XCTest
@available(iOS 12.0, *)
@available(tvOS 10.0, *)
extension SUITCase {
static var screenshotComparisonImagesFolder = ProcessInfo.processInfo.environment["IMAGES_DIR"]
public static var screenshotComparisonImagesFolder = ProcessInfo.processInfo.environment["IMAGES_DIR"]

/// The enumeration of possible reference screenshots naming strategies.
public enum ScreenshotComparisonNamingStrategies {
Expand Down
5 changes: 3 additions & 2 deletions Sources/SUITCase/SUITCase+verifyScreenshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@ extension SUITCase {
withThreshold customThreshold: Double? = nil,
withMethod method: SUITCaseMethod = SUITCaseMethodWithTolerance(),
withLabel label: String? = nil) throws {
guard UIDevice.isSimulator else {
schmidt9 marked this conversation as resolved.
Show resolved Hide resolved

guard UIDevice.isSimulator || deviceTestingEnabled else {
throw VerifyScreenshotError.notSimulator
}

schmidt9 marked this conversation as resolved.
Show resolved Hide resolved
let actualImage = try collectScreenshot(ofElement: element,
withoutElement: ignoredElement,
withoutQuery: ignoredQuery,
Expand Down
2 changes: 2 additions & 0 deletions Sources/SUITCase/SUITCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ open class SUITCase: XCTestCase {
public var screenshotComparisonGlobalThreshold = 0.01
/// Changes current reference images naming strategy.
public var screenshotComparisonNamingStrategy = ScreenshotComparisonNamingStrategies.imageSize
/// Enables testing on real devices, is ignored when run on Simulator.
public var deviceTestingEnabled = false
}
4 changes: 2 additions & 2 deletions Sources/SUITCase/UIDevice+modelName.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ extension UIDevice {
}()

#if targetEnvironment(simulator)
static let isSimulator = true
public static let isSimulator = true
#else
static let isSimulator = false
public static let isSimulator = false
#endif
}