Skip to content

Commit

Permalink
Use fastlane and UI tests to generate App Store screenshots.
Browse files Browse the repository at this point in the history
  • Loading branch information
Staacks committed Jun 10, 2019
1 parent 424b5bd commit 7c291e6
Show file tree
Hide file tree
Showing 45 changed files with 696 additions and 8 deletions.
3 changes: 3 additions & 0 deletions phyphox-iOS/Gemfile
@@ -0,0 +1,3 @@
source "https://rubygems.org"

gem "fastlane"
6 changes: 6 additions & 0 deletions phyphox-iOS/fastlane/Appfile
@@ -0,0 +1,6 @@
app_identifier("de.rwth-aachen.physics.phyphox") # The bundle identifier of your app
# apple_id("[[APPLE_ID]]") # Your Apple email address


# For more information about the Appfile, see:
# https://docs.fastlane.tools/advanced/#appfile
23 changes: 23 additions & 0 deletions phyphox-iOS/fastlane/Fastfile
@@ -0,0 +1,23 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#

# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane

default_platform(:ios)

platform :ios do
desc "Generate new localized screenshots"
lane :screenshots do
capture_screenshots(scheme: "phyphox")
end
end
43 changes: 43 additions & 0 deletions phyphox-iOS/fastlane/Snapfile
@@ -0,0 +1,43 @@
# Uncomment the lines below you want to change by removing the # in the beginning

# A list of devices you want to take the screenshots from
devices([
"iPhone 8",
"iPhone 8 Plus",
"iPhone X",
"iPhone Xs Max",
"iPad Pro (12.9-inch) (2nd generation)",
"iPad Pro (12.9-inch) (3rd generation)"
])

languages([
"cs",
"de-DE",
"el",
"en-US",
"fr-FR",
"it",
"ja",
"nl-NL",
"pl",
"pt-BR",
"ru",
"vi",
"zh-Hant",
"zh-Hans"
])

# The name of the scheme which contains the UI Tests
# scheme("SchemeName")

# Where should the resulting screenshots be stored?
# output_directory("./screenshots")

# remove the '#' to clear all previously generated screenshots before creating new ones
# clear_previous_screenshots(true)

# Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
# launch_arguments(["-favColor red"])

# For more information about all available options run
# fastlane action snapshot
298 changes: 298 additions & 0 deletions phyphox-iOS/fastlane/SnapshotHelper.swift
@@ -0,0 +1,298 @@
//
// SnapshotHelper.swift
// Example
//
// Created by Felix Krause on 10/8/15.
//

// -----------------------------------------------------
// IMPORTANT: When modifying this file, make sure to
// increment the version number at the very
// bottom of the file to notify users about
// the new SnapshotHelper.swift
// -----------------------------------------------------

import Foundation
import XCTest

var deviceLanguage = ""
var locale = ""

func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) {
Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations)
}

func snapshot(_ name: String, waitForLoadingIndicator: Bool) {
if waitForLoadingIndicator {
Snapshot.snapshot(name)
} else {
Snapshot.snapshot(name, timeWaitingForIdle: 0)
}
}

/// - Parameters:
/// - name: The name of the snapshot
/// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
Snapshot.snapshot(name, timeWaitingForIdle: timeout)
}

enum SnapshotError: Error, CustomDebugStringConvertible {
case cannotDetectUser
case cannotFindHomeDirectory
case cannotFindSimulatorHomeDirectory
case cannotAccessSimulatorHomeDirectory(String)
case cannotRunOnPhysicalDevice

var debugDescription: String {
switch self {
case .cannotDetectUser:
return "Couldn't find Snapshot configuration files - can't detect current user "
case .cannotFindHomeDirectory:
return "Couldn't find Snapshot configuration files - can't detect `Users` dir"
case .cannotFindSimulatorHomeDirectory:
return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
case .cannotAccessSimulatorHomeDirectory(let simulatorHostHome):
return "Can't prepare environment. Simulator home location is inaccessible. Does \(simulatorHostHome) exist?"
case .cannotRunOnPhysicalDevice:
return "Can't use Snapshot on a physical device."
}
}
}

@objcMembers
open class Snapshot: NSObject {
static var app: XCUIApplication?
static var waitForAnimations = true
static var cacheDirectory: URL?
static var screenshotsDirectory: URL? {
return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true)
}

open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) {

Snapshot.app = app
Snapshot.waitForAnimations = waitForAnimations

do {
let cacheDir = try pathPrefix()
Snapshot.cacheDirectory = cacheDir
setLanguage(app)
setLocale(app)
setLaunchArguments(app)
} catch let error {
NSLog(error.localizedDescription)
}
}

class func setLanguage(_ app: XCUIApplication) {
guard let cacheDirectory = self.cacheDirectory else {
NSLog("CacheDirectory is not set - probably running on a physical device?")
return
}

let path = cacheDirectory.appendingPathComponent("language.txt")

do {
let trimCharacterSet = CharacterSet.whitespacesAndNewlines
deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"]
} catch {
NSLog("Couldn't detect/set language...")
}
}

class func setLocale(_ app: XCUIApplication) {
guard let cacheDirectory = self.cacheDirectory else {
NSLog("CacheDirectory is not set - probably running on a physical device?")
return
}

let path = cacheDirectory.appendingPathComponent("locale.txt")

do {
let trimCharacterSet = CharacterSet.whitespacesAndNewlines
locale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
} catch {
NSLog("Couldn't detect/set locale...")
}

if locale.isEmpty && !deviceLanguage.isEmpty {
locale = Locale(identifier: deviceLanguage).identifier
}

if !locale.isEmpty {
app.launchArguments += ["-AppleLocale", "\"\(locale)\""]
}
}

class func setLaunchArguments(_ app: XCUIApplication) {
guard let cacheDirectory = self.cacheDirectory else {
NSLog("CacheDirectory is not set - probably running on a physical device?")
return
}

let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]

do {
let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8)
let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: [])
let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count))
let results = matches.map { result -> String in
(launchArguments as NSString).substring(with: result.range)
}
app.launchArguments += results
} catch {
NSLog("Couldn't detect/set launch_arguments...")
}
}

open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
if timeout > 0 {
waitForLoadingIndicatorToDisappear(within: timeout)
}

NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work

if Snapshot.waitForAnimations {
sleep(1) // Waiting for the animation to be finished (kind of)
}

#if os(OSX)
guard let app = self.app else {
NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
return
}

app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
#else

guard let app = self.app else {
NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
return
}

let window = app.windows.firstMatch
let screenshot = window.screenshot()
guard let simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
do {
try screenshot.pngRepresentation.write(to: path)
} catch let error {
NSLog("Problem writing screenshot: \(name) to \(path)")
NSLog(error.localizedDescription)
}
#endif
}

class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) {
#if os(tvOS)
return
#endif

guard let app = self.app else {
NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
return
}

let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element
let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator)
_ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
}

class func pathPrefix() throws -> URL? {
let homeDir: URL
// on OSX config is stored in /Users/<username>/Library
// and on iOS/tvOS/WatchOS it's in simulator's home dir
#if os(OSX)
guard let user = ProcessInfo().environment["USER"] else {
throw SnapshotError.cannotDetectUser
}

guard let usersDir = FileManager.default.urls(for: .userDirectory, in: .localDomainMask).first else {
throw SnapshotError.cannotFindHomeDirectory
}

homeDir = usersDir.appendingPathComponent(user)
#else
#if arch(i386) || arch(x86_64)
guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
throw SnapshotError.cannotFindSimulatorHomeDirectory
}
guard let homeDirUrl = URL(string: simulatorHostHome) else {
throw SnapshotError.cannotAccessSimulatorHomeDirectory(simulatorHostHome)
}
homeDir = URL(fileURLWithPath: homeDirUrl.path)
#else
throw SnapshotError.cannotRunOnPhysicalDevice
#endif
#endif
return homeDir.appendingPathComponent("Library/Caches/tools.fastlane")
}
}

private extension XCUIElementAttributes {
var isNetworkLoadingIndicator: Bool {
if hasWhiteListedIdentifier { return false }

let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)

return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
}

var hasWhiteListedIdentifier: Bool {
let whiteListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]

return whiteListedIdentifiers.contains(identifier)
}

func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
if elementType == .statusBar { return true }
guard frame.origin == .zero else { return false }

let oldStatusBarSize = CGSize(width: deviceWidth, height: 20)
let newStatusBarSize = CGSize(width: deviceWidth, height: 44)

return [oldStatusBarSize, newStatusBarSize].contains(frame.size)
}
}

private extension XCUIElementQuery {
var networkLoadingIndicators: XCUIElementQuery {
let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in
guard let element = evaluatedObject as? XCUIElementAttributes else { return false }

return element.isNetworkLoadingIndicator
}

return self.containing(isNetworkLoadingIndicator)
}

var deviceStatusBars: XCUIElementQuery {
guard let app = Snapshot.app else {
fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
}

let deviceWidth = app.windows.firstMatch.frame.width

let isStatusBar = NSPredicate { (evaluatedObject, _) in
guard let element = evaluatedObject as? XCUIElementAttributes else { return false }

return element.isStatusBar(deviceWidth)
}

return self.containing(isStatusBar)
}
}

private extension CGFloat {
func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool {
return numberA...numberB ~= self
}
}

// Please don't remove the lines below
// They are used to detect outdated configuration files
// SnapshotHelperVersion [1.16]
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7c291e6

Please sign in to comment.