diff --git a/.DS_Store b/.DS_Store index 73a62ce6..976d6ebd 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/GME Remit.xcodeproj/project.pbxproj b/GME Remit.xcodeproj/project.pbxproj index 5104f680..7187f4b3 100644 --- a/GME Remit.xcodeproj/project.pbxproj +++ b/GME Remit.xcodeproj/project.pbxproj @@ -312,6 +312,8 @@ 739A7789228BB2B40018A1A9 /* DetailOrderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739A777E228BB2B40018A1A9 /* DetailOrderViewController.swift */; }; 739A778A228BB2B40018A1A9 /* DetailOrderViewInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739A777F228BB2B40018A1A9 /* DetailOrderViewInterface.swift */; }; 739A778B228BB2B40018A1A9 /* DetailOrder.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 739A7780228BB2B40018A1A9 /* DetailOrder.storyboard */; }; + 739F817D234ADA75003ABCF3 /* SnapshotUITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739F817C234ADA75003ABCF3 /* SnapshotUITest.swift */; }; + 739F8185234ADA84003ABCF3 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 739F8184234ADA83003ABCF3 /* SnapshotHelper.swift */; }; 73A2238F23399A9F00FD75C9 /* UIScrollView+Ext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A2238E23399A9F00FD75C9 /* UIScrollView+Ext.swift */; }; 73A621382330742C000FFB5B /* DomesticRemitModuleInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A621232330742C000FFB5B /* DomesticRemitModuleInterface.swift */; }; 73A621392330742C000FFB5B /* DomesticRemitService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A621272330742C000FFB5B /* DomesticRemitService.swift */; }; @@ -2256,6 +2258,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 739F817F234ADA75003ABCF3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 9FD260EC1FD00458007A511D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 9FD260F31FD00458007A511D; + remoteInfo = "GME Remit"; + }; 9FD261091FD00458007A511D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 9FD260EC1FD00458007A511D /* Project object */; @@ -2584,6 +2593,10 @@ 739A777E228BB2B40018A1A9 /* DetailOrderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailOrderViewController.swift; sourceTree = ""; }; 739A777F228BB2B40018A1A9 /* DetailOrderViewInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailOrderViewInterface.swift; sourceTree = ""; }; 739A7780228BB2B40018A1A9 /* DetailOrder.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = DetailOrder.storyboard; sourceTree = ""; }; + 739F817A234ADA75003ABCF3 /* SnapshotUITest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SnapshotUITest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 739F817C234ADA75003ABCF3 /* SnapshotUITest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnapshotUITest.swift; sourceTree = ""; }; + 739F817E234ADA75003ABCF3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 739F8184234ADA83003ABCF3 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 73A2238E23399A9F00FD75C9 /* UIScrollView+Ext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Ext.swift"; sourceTree = ""; }; 73A621232330742C000FFB5B /* DomesticRemitModuleInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomesticRemitModuleInterface.swift; sourceTree = ""; }; 73A621272330742C000FFB5B /* DomesticRemitService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomesticRemitService.swift; sourceTree = ""; }; @@ -4560,6 +4573,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 739F8177234ADA75003ABCF3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FD260F11FD00458007A511D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -6551,6 +6571,16 @@ path = View; sourceTree = ""; }; + 739F817B234ADA75003ABCF3 /* SnapshotUITest */ = { + isa = PBXGroup; + children = ( + 739F8184234ADA83003ABCF3 /* SnapshotHelper.swift */, + 739F817C234ADA75003ABCF3 /* SnapshotUITest.swift */, + 739F817E234ADA75003ABCF3 /* Info.plist */, + ); + path = SnapshotUITest; + sourceTree = ""; + }; 73A6211E233073D2000FFB5B /* RemittanceModules */ = { isa = PBXGroup; children = ( @@ -7220,6 +7250,7 @@ 9FD260F61FD00458007A511D /* GME Remit */, 9FD2610B1FD00458007A511D /* GME RemitTests */, 9FD261161FD00458007A511D /* GME RemitUITests */, + 739F817B234ADA75003ABCF3 /* SnapshotUITest */, 9FD260F51FD00458007A511D /* Products */, 21B1C05AB12FED421AACA473 /* Pods */, 3DE940CE478E7407EB7DE7CE /* Frameworks */, @@ -7232,6 +7263,7 @@ 9FD260F41FD00458007A511D /* GME Remit.app */, 9FD261081FD00458007A511D /* GMERemittanceTests.xctest */, 9FD261131FD00458007A511D /* GMERemittanceUITests.xctest */, + 739F817A234ADA75003ABCF3 /* SnapshotUITest.xctest */, ); name = Products; sourceTree = ""; @@ -12223,6 +12255,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 739F8179234ADA75003ABCF3 /* SnapshotUITest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 739F8181234ADA75003ABCF3 /* Build configuration list for PBXNativeTarget "SnapshotUITest" */; + buildPhases = ( + 739F8176234ADA75003ABCF3 /* Sources */, + 739F8177234ADA75003ABCF3 /* Frameworks */, + 739F8178234ADA75003ABCF3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 739F8180234ADA75003ABCF3 /* PBXTargetDependency */, + ); + name = SnapshotUITest; + productName = SnapshotUITest; + productReference = 739F817A234ADA75003ABCF3 /* SnapshotUITest.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 9FD260F31FD00458007A511D /* GME Remit */ = { isa = PBXNativeTarget; buildConfigurationList = 9FD2611C1FD00458007A511D /* Build configuration list for PBXNativeTarget "GME Remit" */; @@ -12289,10 +12339,15 @@ 9FD260EC1FD00458007A511D /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 0910; + LastSwiftUpdateCheck = 1100; LastUpgradeCheck = 0910; ORGANIZATIONNAME = "Gobal Money Express Co. Ltd"; TargetAttributes = { + 739F8179234ADA75003ABCF3 = { + CreatedOnToolsVersion = 11.0; + ProvisioningStyle = Automatic; + TestTargetID = 9FD260F31FD00458007A511D; + }; 9FD260F31FD00458007A511D = { CreatedOnToolsVersion = 9.1; LastSwiftMigration = 1020; @@ -12348,11 +12403,19 @@ 9FD260F31FD00458007A511D /* GME Remit */, 9FD261071FD00458007A511D /* GMERemittanceTests */, 9FD261121FD00458007A511D /* GMERemittanceUITests */, + 739F8179234ADA75003ABCF3 /* SnapshotUITest */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 739F8178234ADA75003ABCF3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FD260F21FD00458007A511D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -14354,6 +14417,15 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 739F8176234ADA75003ABCF3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 739F817D234ADA75003ABCF3 /* SnapshotUITest.swift in Sources */, + 739F8185234ADA84003ABCF3 /* SnapshotHelper.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 9FD260F01FD00458007A511D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -15024,6 +15096,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 739F8180234ADA75003ABCF3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 9FD260F31FD00458007A511D /* GME Remit */; + targetProxy = 739F817F234ADA75003ABCF3 /* PBXContainerItemProxy */; + }; 9FD2610A1FD00458007A511D /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 9FD260F31FD00458007A511D /* GME Remit */; @@ -15085,6 +15162,48 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 739F8182234ADA75003ABCF3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ZF8NN25XW6; + INFOPLIST_FILE = SnapshotUITest/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.devik.SnapshotUITest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "GME Remit"; + }; + name = Debug; + }; + 739F8183234ADA75003ABCF3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ZF8NN25XW6; + INFOPLIST_FILE = SnapshotUITest/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.devik.SnapshotUITest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "GME Remit"; + }; + name = Release; + }; 9FD2611A1FD00458007A511D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -15347,6 +15466,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 739F8181234ADA75003ABCF3 /* Build configuration list for PBXNativeTarget "SnapshotUITest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 739F8182234ADA75003ABCF3 /* Debug */, + 739F8183234ADA75003ABCF3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 9FD260EF1FD00458007A511D /* Build configuration list for PBXProject "GME Remit" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/GME Remit.xcodeproj/xcshareddata/xcschemes/GME Remit.xcscheme b/GME Remit.xcodeproj/xcshareddata/xcschemes/GME Remit.xcscheme index 57d7d6db..a4914c02 100644 --- a/GME Remit.xcodeproj/xcshareddata/xcschemes/GME Remit.xcscheme +++ b/GME Remit.xcodeproj/xcshareddata/xcschemes/GME Remit.xcscheme @@ -47,6 +47,16 @@ ReferencedContainer = "container:GME Remit.xcodeproj"> + + + + - + - + - + - + diff --git a/GME Remit/Modules/Login/User Interface/View/LoginViewController.swift b/GME Remit/Modules/Login/User Interface/View/LoginViewController.swift index 2aa41de8..bd4f82e4 100644 --- a/GME Remit/Modules/Login/User Interface/View/LoginViewController.swift +++ b/GME Remit/Modules/Login/User Interface/View/LoginViewController.swift @@ -87,6 +87,14 @@ class LoginViewController: UIViewController { self.navigationItem.title = "" } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + #if DEBUG + showTestAccount() + #endif + } + // MARK: IBActions @IBAction func forgotPassword(_ sender: UIButton) { self.presenter?.forgotPassword() @@ -100,7 +108,42 @@ class LoginViewController: UIViewController { let username = self.userNameTextField.text! self.presenter?.login(userName: username, encryptedPassword: self.encryptedPassword ?? "") } + // MARK: Other Functions + func showTestAccount() { + let userName = GMEDB.shared.testAccountID() + let userPassword = GMEDB.shared.testAccountPW() + + let alertcontroller = UIAlertController(title: "Test Account", message: nil, preferredStyle: .actionSheet) + let testAction = UIAlertAction(title: userName, style: .default, handler: { _ in + self.userNameTextField.text = userName + self.encryptedPassword = userPassword + }) + + let otherAction = UIAlertAction(title: "Other", style: .default, handler: nil) + + alertcontroller.addAction(testAction) + alertcontroller.addAction(otherAction) + + + if UIDevice.current.userInterfaceIdiom == .pad { + if let popoverController = alertcontroller.popoverPresentationController { + popoverController.sourceView = self.view + popoverController.sourceRect = CGRect( + x: self.view.bounds.midX, + y: self.view.bounds.midY, + width: 0, + height: 0 + ) + + popoverController.permittedArrowDirections = [] + self.present(alertcontroller, animated: true, completion: nil) + + } + } else { + self.present(alertcontroller, animated: true, completion: nil) + } + } private func setup() { // all setup should be done here diff --git a/GME Remit/Modules/RecipientModules/Recipients/User Interface/View/Recipients.storyboard b/GME Remit/Modules/RecipientModules/Recipients/User Interface/View/Recipients.storyboard index 58f7ab71..ffbdc38b 100644 --- a/GME Remit/Modules/RecipientModules/Recipients/User Interface/View/Recipients.storyboard +++ b/GME Remit/Modules/RecipientModules/Recipients/User Interface/View/Recipients.storyboard @@ -170,6 +170,9 @@ + + + @@ -268,7 +271,7 @@ - + diff --git a/GME Remit/Modules/RemittanceModules/OverseasModules/SelectPayment/User Interface/View/SelectPayment.storyboard b/GME Remit/Modules/RemittanceModules/OverseasModules/SelectPayment/User Interface/View/SelectPayment.storyboard index 3a6c9d1b..30ca80d0 100644 --- a/GME Remit/Modules/RemittanceModules/OverseasModules/SelectPayment/User Interface/View/SelectPayment.storyboard +++ b/GME Remit/Modules/RemittanceModules/OverseasModules/SelectPayment/User Interface/View/SelectPayment.storyboard @@ -45,6 +45,9 @@ + + + @@ -107,13 +110,13 @@ - + - + diff --git a/GME Remit/Utilities/Database/GMEDB.swift b/GME Remit/Utilities/Database/GMEDB.swift index f6c5b741..2233a171 100644 --- a/GME Remit/Utilities/Database/GMEDB.swift +++ b/GME Remit/Utilities/Database/GMEDB.swift @@ -142,3 +142,24 @@ extension GMEDB { return self.user } } + +extension GMEDB { + @discardableResult + func testAccountID() -> String { + switch server { + case .live: + return "demo.gme@gmeremit.com" + default: + return "maxkim@gmeremit.com" + } + } + + func testAccountPW() -> String { + switch server { + case .live: + return "4D544B31316F6E65656238383165626E6F386E72773D8130C548A4605ADE3CF5457E365476DE37B9DA2A9004AFA1D827BC650CAB580D9B8B93B176DAC5F91CFED8A6DE53C0BBF01D209ADFB3E41B0266E7FE61023F2A078EDAB0C42CEFE9BE99D669AC62EB00AAC64E7E2531E7EBE8ED3BE21927FAC9A6B5BF0DA009A4652FE4A35E0560" + default: + return "4D544B3154546E386E65696C614B69656154696F291C7DA9B5DEBA7E0CBA2C6DABE461B40FFF47EEED3448FA85530F5238D2DBCBF03D6764191913A4272A7D8E4D5F650A" + } + } +} diff --git a/GME RemitTests/APITest.swift b/GME RemitTests/APITest.swift index 897196e9..8493432b 100644 --- a/GME RemitTests/APITest.swift +++ b/GME RemitTests/APITest.swift @@ -244,6 +244,7 @@ class APITest: XCTestCase { let service = DomesticRemitService() service.fetchBalance( + type: "", fintechUseNumber: "", success: { print($0) diff --git a/SnapshotUITest/Info.plist b/SnapshotUITest/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/SnapshotUITest/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/SnapshotUITest/SnapshotUITest.swift b/SnapshotUITest/SnapshotUITest.swift new file mode 100644 index 00000000..b9f369a2 --- /dev/null +++ b/SnapshotUITest/SnapshotUITest.swift @@ -0,0 +1,51 @@ +// +// SnapshotUITest.swift +// SnapshotUITest +// +// Created by InKwon James Kim on 07/10/2019. +// Copyright © 2019 Gobal Money Express Co. Ltd. All rights reserved. +// + +import XCTest + +class SnapshotUITest: XCTestCase { + + let app = XCUIApplication() + + override func setUp() { + continueAfterFailure = false + setupSnapshot(app) + app.launch() + } + + func testLogin1() { + snapshot("splash_screen") + app.buttons["Login"].tap() + app.sheets["Test Account"].scrollViews.otherElements.buttons["demo.gme@gmeremit.com"].tap() + app.staticTexts["Continue with the password only"].tap() + snapshot("dashboard") + } + + func testSendMoney() { + let tablesQuery = app.tables + tablesQuery.staticTexts["Send Money"].tap() + snapshot("recipients") + + tablesQuery.cells.children(matching: .image).element.tap() + tablesQuery.images["ic_menu_wallet"].tap() + + app.keys["1"].tap() + app.keys["2"].tap() + app.keys["3"].tap() + app.keys["4"].tap() + app.keys["5"].tap() + + app.toolbars["Toolbar"].buttons["Done"].tap() + + snapshot("exchange_rate") + app.scrollViews.otherElements.staticTexts["Continue"].tap() + + snapshot("verification") + + } +} diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 00000000..99b6dd4d --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,29 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew cask install fastlane` + +# Available Actions +## iOS +### ios beta +``` +fastlane ios beta +``` +Push a new beta build to TestFlight + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/fastlane/Snapfile b/fastlane/Snapfile new file mode 100644 index 00000000..7970fc88 --- /dev/null +++ b/fastlane/Snapfile @@ -0,0 +1,26 @@ +# 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 Plus", + #"iPhone Xs Max" +]) + +languages([ + "en-US" +]) + +# The name of the scheme which contains the UI Tests +scheme("SnapshotUITest") + +# 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 diff --git a/fastlane/SnapshotHelper.swift b/fastlane/SnapshotHelper.swift new file mode 100644 index 00000000..aaa2a9a9 --- /dev/null +++ b/fastlane/SnapshotHelper.swift @@ -0,0 +1,303 @@ +// +// 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 self.app != nil else { + NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().") + return + } + + let screenshot = XCUIScreen.main.screenshot() + guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return } + + do { + // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices + let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ") + let range = NSRange(location: 0, length: simulator.count) + simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "") + + let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png") + try screenshot.pngRepresentation.write(to: path) + } catch let error { + NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png") + 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//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.21]